/**
* Copyright 2009 Marc Stogaitis and Mimi Sun
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gmote.client.android;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.gmote.common.DataReceiverIF;
import org.gmote.common.MulticastClient;
import org.gmote.common.ServerInfo;
import org.gmote.common.ServerOutOfDateException;
import org.gmote.common.TcpConnection;
import org.gmote.common.MulticastClient.ServerFoundHandler;
import org.gmote.common.packet.AbstractPacket;
import org.gmote.common.security.AuthenticationException;
import org.gmote.common.security.AuthenticationHandler;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* Wrapper class responsible for finding and communicating with the server.
*
* @author Mimi
*
*/
public class Remote implements DataReceiverIF {
// Current version of the Gmote Client. We don't use the value that is in the
// manifest since its possible that we don't have access to this value (for
// example, when the program crashes and gets restarted by android)
public static final String GMOTE_CLIENT_VERSION = "2.0.2";
public static final String MINIMUM_SERVER_VERSION = "2.0.0";
// Response codes
public static final int NORMAL= 0;
public static final int CONNECTION_FAILURE = 1;
public static final int CONNECTING = 2;
public static final int CONNECTED = 6;
public static final int SEARCHING = 3;
public static final int AUTHENTICATION_FAILURE = 4;
public static final int SERVER_LIST_ADD_SERVER = 5;
public static final int SERVER_LIST_DONE = 6;
public static final int SERVER_OUT_OF_DATE = 7;
// Timing constants
public static final int MAX_ATTEMPTS = 3; // number of connection attempts before report giving up
public static final int TIMEOUT = 3000; // milliseconds server connection timeout
//public static final int FINDSERVERS_LIMIT = 4;
public static final int FINDSERVERS_TIMEOUT = 6500; // milliseconds
private static final String DEBUG_TAG = "Gmote";
private ServerInfo server = null;
private String password = "";
private static Remote remote = new Remote();
private Handler callback;
private TcpConnection con = null;
private Thread worker = null;
private BlockingQueue<AbstractPacket> packetQueue = new LinkedBlockingQueue<AbstractPacket>();
InetAddress serverInetAddress = null;
//private WifiLock wifiLock;
private Remote() {
// Start a new thread that will send packets for us.
worker = new Thread(new PacketSender());
worker.start();
}
private void setCallback(Handler callback) {
this.callback = callback;
}
public static synchronized Remote getInstance(Handler handler) {
remote.setCallback(handler);
return remote;
}
public static Remote getInstance() {
return remote;
}
public synchronized void setServer(ServerInfo serverInfo) {
server = serverInfo;
Log.d(DEBUG_TAG, "Gmote# set server to: " + server.getServer() + ":" + server.getPort());
try {
if (serverInfo == null || serverInfo.getIp() == null) {
serverInetAddress = null;
} else {
serverInetAddress = InetAddress.getByName(serverInfo.getIp());
}
} catch (UnknownHostException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
serverInetAddress = null;
}
disconnect();
}
public InetAddress getServerInetAddress() {
return serverInetAddress;
}
protected synchronized void disconnect() {
if (con != null) {
con.closeConnection();
con = null;
}
packetQueue.clear();
}
public synchronized void setPassword(String newPassword) {
password = newPassword;
Log.d(DEBUG_TAG, "Remote# set password");
}
public void detach() {
callback = null;
}
public String getServerString() {
if (server != null)
return server.toString();
return "";
}
public String getServerIp() {
if (server != null)
return server.getIp();
return "";
}
public int getServerPort() {
if (server != null) {
return server.getPort();
}
return 8889;
}
public int getServerUdpPort() {
if (server != null) {
return server.getUdpPort();
}
return ServerInfo.DEFAULT_UDP_PORT;
}
public synchronized boolean isConnected() {
return con == null? false : con.isConnected();
}
protected synchronized boolean connect(boolean ignoreErrors) {
if (callback == null) {
Log.w(DEBUG_TAG, "Callback is null in connect()");
return false;
}
callback.sendEmptyMessage(CONNECTING);
if (server == null) {
Log.w(DEBUG_TAG, "Server was null in connect");
disconnect();
if (!ignoreErrors) {
callback.sendEmptyMessage(CONNECTION_FAILURE);
}
return false;
}
for (int i = 0; i < MAX_ATTEMPTS && callback != null; i++) {
try {
connectToServer();
if (callback != null) {
callback.sendEmptyMessage(CONNECTED);
}
return true;
} catch (IOException e) {
Log.e(DEBUG_TAG, "Connection attempt " + i + " failed: " + e.getMessage(), e);
} catch (AuthenticationException e) {
Log.e(DEBUG_TAG, "Authentication failure: " + e.getMessage(), e);
disconnect();
if (callback != null) {
callback.sendEmptyMessage(AUTHENTICATION_FAILURE);
} else {
Log.w(DEBUG_TAG, "Authentication failure with callback = null. We won't be able to notify anyone");
}
return false;
} catch (ServerOutOfDateException e) {
Log.e(DEBUG_TAG, "Server out of date error: " + e.getMessage(), e);
if (callback != null) {
callback.sendMessage(Message.obtain(callback, SERVER_OUT_OF_DATE, e.getServerVersion()));
return true;
} else {
Log.e(DEBUG_TAG,"The server is out of date, but no callback was found. This means we won't be able to notify the user of the current error.");
disconnect();
return false;
}
}
}
Log.w(DEBUG_TAG, "Failed to connect after " + MAX_ATTEMPTS + " attempts. Aborting.");
if (callback != null) {
if (!ignoreErrors) {
callback.sendEmptyMessage(CONNECTION_FAILURE);
}
} else {
Log.w(DEBUG_TAG, "Connection failure, and call back is null");
}
disconnect();
return false;
}
private synchronized void connectToServer() throws IOException, AuthenticationException, ServerOutOfDateException {
con = new TcpConnection(new AuthenticationHandler(GMOTE_CLIENT_VERSION, MINIMUM_SERVER_VERSION));
Log.i(DEBUG_TAG, "Connecting to server: " + server.getIp() + ":" + server.getPort());
con.connectToServerAsync(server.getPort(), server.getIp(), (DataReceiverIF) Remote.this, TIMEOUT, password);
}
protected synchronized void queuePacket(AbstractPacket packet) {
try {
packetQueue.put(packet);
} catch (InterruptedException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
}
}
public void handleReceiveData(AbstractPacket reply, TcpConnection connection) {
if (callback != null) {
System.out.println("handleRecieveData1()");
callback.sendMessage(Message.obtain(callback, -1, reply));
} else {
System.out.println("handleRecieveData2()");
Log.w(DEBUG_TAG, "Received a packet, but call back is null, so I won't be able to deliver it to anyone.");
}
}
public void getServerList(Handler findServerCallback) {
Thread serverFinder = new Thread(new ServerFinder(findServerCallback));
serverFinder.start();
}
protected class ServerFinder implements Runnable {
private Handler findServerCallback;
public ServerFinder(Handler findServerCallback) {
this.findServerCallback = findServerCallback;
}
public void run() {
Log.e(DEBUG_TAG, "Creating MC");
MulticastClient mc = new MulticastClient();
ServerFoundHandler serverFoundHandler = new ServerFoundHandler() {
public void onServerFound(ServerInfo server) {
if (findServerCallback != null) {
findServerCallback.sendMessage(Message.obtain(findServerCallback, SERVER_LIST_ADD_SERVER, server));
} else {
Log.w(DEBUG_TAG, "Find Server callback was null. We can't notify anyone that we found a new server.");
}
}};
mc.findServers(FINDSERVERS_TIMEOUT, serverFoundHandler);
Log.e(DEBUG_TAG, "Got Servers");
if (findServerCallback != null) {
findServerCallback.sendMessage(Message.obtain(findServerCallback, SERVER_LIST_DONE));
} else {
Log.w(DEBUG_TAG, "Find Server callback was null. We can't notify anyone that find server has finished.");
}
}
}
class PacketSender implements Runnable {
public void run() {
AbstractPacket packet;
while (true) {
// Get the packet that is at the head of the queue, waiting if
// necessary.
try {
packet = packetQueue.take();
} catch (InterruptedException e) {
Log.w(DEBUG_TAG, e.getMessage(), e);
packet = null;
} catch (Exception e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
packet = null;
createNewQueue();
}
if (packet != null) {
try {
sendPacketToServer(packet);
} catch (Exception e) {
Log.d(DEBUG_TAG, "Send packet failed. " + e.getMessage(), e);
disconnect();
}
}
}
}
private synchronized void createNewQueue() {
packetQueue = new LinkedBlockingQueue<AbstractPacket>(); // recreate the queue if problem occured
}
private synchronized void sendPacketToServer(AbstractPacket packet) throws IOException {
// Try to connect.
// We'll try this twice since the connection may be down but we don't know about it.
boolean tryAgain = false;
do {
if (con != null || connect(false)) {
try {
con.sendPacket(packet);
tryAgain = false;
} catch (IOException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
disconnect();
tryAgain = (tryAgain == false);
if (!tryAgain) {
if (callback != null) {
callback.sendEmptyMessage(CONNECTION_FAILURE);
} else {
Log.e(DEBUG_TAG, "Unable to notify client of io error in send packet since callback is null");
}
}
}
} else {
tryAgain = false;
}
} while (tryAgain);
}
}
public String getSessionId() {
if (con == null) {
return null;
}
return con.getSessionId();
}
public ServerInfo getServer() {
return server;
}
}