package lowijs.XfireClient;
import static lowijs.XfDroid.XfDroidConstants.EMPTY_STRING;
import static lowijs.XfDroid.XfDroidConstants.PREF_AUTO_STATUS;
import static lowijs.XfDroid.XfDroidConstants.PREF_KEEP_ALIVE;
import static lowijs.XfDroid.XfDroidConstants.PREF_ONLINE_STATUS;
import static lowijs.XfDroid.XfDroidConstants.PREF_ONLINE_STATUS_OLD;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.List;
import lowijs.XfDroid.FriendDBAdapter;
import lowijs.XfDroid.Runner;
import lowijs.XfDroid.XfDroidConstants;
import lowijs.XfDroid.XfDroidService;
import lowijs.XfireClient.event.DatalessEvent;
import lowijs.XfireClient.event.EventManager;
import lowijs.XfireClient.event.XfireEvent;
import lowijs.XfireClient.packet.AckImPacket;
import lowijs.XfireClient.packet.ChangeNickPacket;
import lowijs.XfireClient.packet.ChangeStatusTextPacket;
import lowijs.XfireClient.packet.FriendGamePacket;
import lowijs.XfireClient.packet.FriendStatusPacket;
import lowijs.XfireClient.packet.FriendStatusTextPacket;
import lowijs.XfireClient.packet.FriendslistPacket;
import lowijs.XfireClient.packet.KeepalivePacket;
import lowijs.XfireClient.packet.LoginPacket;
import lowijs.XfireClient.packet.LoginReplyPacket;
import lowijs.XfireClient.packet.NewVersionPacket;
import lowijs.XfireClient.packet.Packet;
import lowijs.XfireClient.packet.ReceiveMessagePacket;
import lowijs.XfireClient.packet.SaltPacket;
import lowijs.XfireClient.packet.ScreenshotsPacket;
import lowijs.XfireClient.packet.VersionPacket;
import lowijs.XfireClient.util.Util;
import lowijs.util.logging.Logging;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import android.util.Log;
import com.googlecode.xfdroid.R;
public class XfireClient implements Runner {
/**
* How often the Pinger class should send keep alive packet.
*/
private static final int DEF_KEEPALIVE_EVERY = 60000;
private DataInputStream in = null;
private DataOutputStream out = null;
private String username, password;
private String statustext;
private String onlinetext;
private XfireStatus status;
private long lastConnection;
private Pinger pinger;
private long lastWrite;
private long when;
private XfDroidService service;
private SharedPreferences prefs;
private int attempt = 0;
public XfireClient(XfDroidService service) {
Logging.getLogging().clientCreated(this);
this.service = service;
statustext = service.getString(R.string.status_offline);
setStatus(XfireStatus.DISCONNECTED);
}
/**
* This class tries to keep the connection alive by periodically re-setting
* the status.
* @author sgaines
*/
class Pinger implements Runnable {
private boolean running;
public void run() {
running = true;
Logging logging = Logging.getLogging();
try {
ActivityManager activity = (ActivityManager)service.getSystemService(Context.ACTIVITY_SERVICE);
//PackageManager mngr = (PackageManager)service.getPackageManager();
lastWrite = System.currentTimeMillis();
{
List<RunningAppProcessInfo> procs = activity.getRunningAppProcesses();
logging.clientProcess(procs);
}
try {
List<RunningTaskInfo> tasks = activity.getRunningTasks(100);
logging.clientTasks(tasks);
} catch (Exception e) {
}
String afk = service.getString(R.string.status_afk);
PackageManager pm = service.getPackageManager();
long lastPing = System.currentTimeMillis() - 1000000;
String keepAliveCode = prefs.getString(PREF_KEEP_ALIVE, Integer.toString(DEF_KEEPALIVE_EVERY));
int keepAlive = Integer.parseInt(keepAliveCode);
while (isConnected()) {
if (service.isScreenOff() || service.isDocked()) {
changeStatus(afk);
} else {
List<RunningAppProcessInfo> procs = activity.getRunningAppProcesses();
if (prefs.getBoolean(PREF_AUTO_STATUS, false) && procs != null && !procs.isEmpty()) {
RunningAppProcessInfo curr = null;
for (RunningAppProcessInfo proc : procs) {
if (!proc.processName.equals("system") && proc.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
curr = proc;
}
}
String name = curr.processName;
if (name.equals("lowijs.XfDroid") || name.equals("system") || name.equals("android.process.acore")) {
changeStatus(onlinetext);
} else {
try {
ApplicationInfo app = pm.getApplicationInfo(curr.processName, 0);
if (app != null) {
CharSequence label = pm.getApplicationLabel(app);
if (label != null) {
if (label instanceof String) {
name = (String)label;
} else {
name = label.toString();
}
} else if (app.name != null) {
name = app.name;
}
}
} catch (Exception e) {
}
changeStatus(name);
}
} else {
changeStatus(onlinetext);
}
}
String newCode = prefs.getString(PREF_KEEP_ALIVE, keepAliveCode);
if (!newCode.equals(keepAliveCode)) {
keepAliveCode = newCode;
keepAlive = Integer.parseInt(newCode);
}
long next = lastPing + keepAlive;
long now = System.currentTimeMillis();
if (next <= now) {
write(KeepalivePacket.KEEPALIVE_PACKET);
lastPing = now;
next = lastPing + keepAlive;
}
long dif = next - now;
if (dif > 0) {
try {
Thread.sleep(Math.min(dif, 10000));
} catch (Exception e) {
}
}
}
logging.pingFinished();
} catch (Exception e) {
logging.pingFailed(e);
} finally {
running = false;
logging.pingStopped();
}
}
public void start() {
if (!running) {
new Thread(this).start();
}
}
}
public long getLastConnection() {
return lastConnection;
}
/**
* Connect to Xfire using the username and password provided in the constructor.
*/
public void connect(String username, String password) {
this.username = username;
this.password = password;
if (status == XfireStatus.CONNECTING || status == XfireStatus.CONNECTED) {
return;
}
setStatus(XfireStatus.CONNECTING);
service.clientStarting();
}
public void disconnect(boolean temporary) {
Logging.getLogging().clientDisconnect();
if (close()) {
service.disconnected(temporary);
}
}
public boolean close() {
try {
Logging.getLogging().clientClose();
if (status == XfireStatus.DISCONNECTED) {
// If already disconnected, don't process request
return false;
}
setStatus(XfireStatus.DISCONNECTED);
if (in == null) {
return true;
}
InputStream i = in;
try {
new Thread(new Runnable() {
public void run() {
// sabotage the stream
Log.d(XfDroidConstants.LOG_TAG, "close(), run(), sabotage stream");
write(new byte[] { 0, 0, 0, 0 });
Log.d(XfDroidConstants.LOG_TAG, "close(), run(), stream has been sabotaged");
}
}).start();
try {
Log.d(XfDroidConstants.LOG_TAG, "close(), giving sabotage a chance to work (half second)");
Thread.sleep(500);
} catch (Exception e) {
}
} finally {
Log.d(XfDroidConstants.LOG_TAG, "close(), closing streams");
try {in.close();} catch (Exception e) {}
try {out.close();} catch (Exception e) {}
synchronized (i) {
Log.d(XfDroidConstants.LOG_TAG, "close(), nil streams");
out = null;
in = null;
}
}
} catch (Exception e) {
Logging.getLogging().clientException("Problem cloasing", e);
}
return true;
}
public void run() {
Logging logging = Logging.getLogging();
logging.clientEnterThread();
boolean connect = true;
int event = XfireEvent.XF_OFFLINE;
int reason = -1;
SharedPreferences preferences = getPrefs();
onlinetext = preferences.getString(PREF_ONLINE_STATUS, null);
if (onlinetext == null) {
onlinetext = preferences.getString(PREF_ONLINE_STATUS_OLD, null);
}
if (onlinetext == null) {
onlinetext = service.getString(R.string.status_online);
}
logging.startConnection();
while (connect) {
try {
logging.connectToXfire();
Socket s = new Socket("cs.xfire.com", 25999);
s.setKeepAlive(true);
//logging.clientGetStreams();
in = new DataInputStream(s.getInputStream());
out = new DataOutputStream(s.getOutputStream());
} catch (Exception e) {
Logging.getLogging().clientException("Connection to server failed", e);
event = XfireEvent.XF_CONNECTIONFAIL;
reason = R.string.connection_failed;
break;
}
logging.performLogin();
login();
if (pinger == null) {
pinger = new Pinger();
}
pinger.start();
try {
lastConnection = System.currentTimeMillis();
service.setOnline(true);
setStatus(XfireStatus.CONNECTED);
if (handleCommunication()) {
if (attempt < 10 && waitForReconnect()) {
attempt++;
connect = true;
} else {
connect = false;
}
} else {
connect = false;
}
} catch (Exception e) {
connect = false;
logging.clientException("Problem durring communication, closing conneciton", e);
event = XfireEvent.XF_CONNECTIONFAIL;
reason = R.string.connection_exception;
}
}
setStatus(XfireStatus.DISCONNECTED);
stopping(event, reason);
}
private boolean waitForReconnect() {
setStatus(XfireStatus.RECONNECTING);
when = System.currentTimeMillis() + 30000;
while (status == XfireStatus.RECONNECTING) {
long time = when - System.currentTimeMillis();
if (time <= 0) {
// reconnect
return true;
} else {
try {
Thread.sleep(time);
} catch (Exception e) {
}
}
}
// TODO Auto-generated method stub
return false;
}
private boolean handleCommunication() {
Logging logging = Logging.getLogging();
try {
SharedPreferences preferences = getPrefs();
while (true) {
byte[] buffer = readBytes();
debug(buffer);
if (buffer == null) {
// Disconnected!
return true;
}
// This should prevent log out crash
if (status != XfireStatus.CONNECTED) break;
int type = ((buffer[0] & 0xFF) | ((buffer[1] & 0xFF) << 8)) & 0xFFFF;
logging.clientRecievedPacket(type);
try {
switch(type) {
case 0x80: // salt
SaltPacket sp = new SaltPacket(buffer);
LoginPacket lp = new LoginPacket(username, password, sp.getSalt());
write(lp.getBytes());
break;
case 0x81: // auth failed
service.loginFailed();
logging.clientLoginFailed();
return false;
case 0x82: // loginreply
new LoginReplyPacket(buffer, username);
changeStatus(EMPTY_STRING);
changeStatus(onlinetext);
EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_ONLINE));
break;
case 0x83: // friendslist
new FriendslistPacket(buffer);
attempt = 0;
break;
case 0x84: // friend online
String[] results = FriendStatusPacket.invoke(buffer);
service.notify(results);
break;
case 0x85: // receive message
ReceiveMessagePacket rmp = new ReceiveMessagePacket(buffer);
if (rmp.getMessageType() == ReceiveMessagePacket.MSGTYPE_IM) {
AckImPacket amp = new AckImPacket(rmp.getSid(), rmp.getImIndex());
write(amp.getBytes());
}
break;
case 0x86: // new version
int[] versionArray = NewVersionPacket.getVersions(buffer);
int version = versionArray[0];
for (int v : versionArray) {
version = Math.max(v, version);
}
int v = preferences.getInt(VersionPacket.PREF_VERSION, 0);
//if (v < version) {
logging.clientNewVersion(version, v);
SharedPreferences.Editor editor = preferences.edit();
editor.putInt(VersionPacket.PREF_VERSION, version);
editor.commit();
if (reconnect()) {
//XfireEvent.XF_FLRECV;
}
//} else {
// logging.clientBadVersion();
// disconnect(false);
// return false;
//}
break;
case 0x87: // friend in game
new FriendGamePacket(buffer);
break;
case 0x91: // disconnected with reason
logging.clientBooted();
disconnect(false);
return false;
case 0x9a: // friend status text
new FriendStatusTextPacket(buffer);
break;
case 0xac:
new ScreenshotsPacket(buffer);
break;
case 0x0164:
Log.d(XfDroidConstants.LOG_TAG, "Buffer Size: " + buffer.length);
for (String line : Util.toHex(buffer, "\n", 40).split(",")) {
Log.d(XfDroidConstants.LOG_TAG, line);
}
break;
case 0x0190: // DID - Unknown how to process!
break;
default:
logging.clientUnknownCode(type);
//disconnect();
//EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_DCREASON));
//return;
}
} catch (Exception e) {
logging.clientException("Problem handling packet from server", new PacketException("Problem handling packet", e, buffer));
stopping(XfireEvent.XF_CONNECTIONFAIL, R.string.packet_exception);
return true;
}
}
} catch (Exception e) {
logging.clientException("Error!", e);
} finally {
logging.clientFinished();
}
return false;
}
private void stopping(int event, int id) {
Logging logging = Logging.getLogging();
setStatus(XfireStatus.DISCONNECTED);
service.setOnline(false);
logging.clientExitThread();
disconnect(false);
service.clientClosed(event, id);
}
private void login() {
Logging logging = Logging.getLogging();
logging.clientLogin();
// initialize connection with the 'UA01' packet
write("UA01".getBytes());
// send the version packet
VersionPacket vp = new VersionPacket(getPrefs());
write(vp.getBytes());
}
private SharedPreferences getPrefs() {
if (prefs == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(service);
}
return prefs;
}
/**
* Sets a new nickname for this Xfire user.
* @param nick the nickname to set.
*/
public void setNickname(String nickname) {
ChangeNickPacket cnp = new ChangeNickPacket(nickname);
write(cnp.getBytes());
FriendDBAdapter.getInstance(service.getApplicationContext()).nickname(nickname);
}
public String getStatusText() {
return statustext;
}
private void changeStatus(String statustext) {
if (statustext == null || statustext.trim().length() == 0) {
statustext = onlinetext;
}
if (!statustext.equals(this.statustext)) {
this.statustext = statustext;
ChangeStatusTextPacket cstp = new ChangeStatusTextPacket(statustext);
write(cstp.getBytes());
}
}
public void setStatusText(String statustext) {
this.onlinetext = statustext;
changeStatus(statustext);
Editor editor = getPrefs().edit();
editor.putString(PREF_ONLINE_STATUS, statustext);
editor.commit();
}
public String getUsername() {
return username;
}
public void send(Packet packet) {
write(packet.getBytes());
}
/*
* I/O and debugging methods
*/
private byte[] readBytes() {
byte[] buffer = null;
try {
if (in != null) {
synchronized (in) {
int low = in.read();
int hgh = in.read();
int len = (0x00 | low | (hgh << 8)) - 2;
if (len <= 0) {
buffer = new byte[] { 0 };
} else {
buffer = new byte[len];
in.readFully(buffer, 0, len);
}
}
}
} catch (IOException ioe) {
if (!ioe.getMessage().equals("The connection was reset")) {
Logging logging = Logging.getLogging();
logging.clientException("Durring read", ioe);
}
disconnect(true);
buffer = null;
}
return buffer;
}
public synchronized boolean write(byte[] bs) {
if (out == null) {
return false;
}
try {
lastWrite = System.currentTimeMillis();
out.write(bs);
return true;
} catch (IOException ioe) {
if (
!ioe.getMessage().equals("The socket argument is not a valid file descriptor") &&
!ioe.getMessage().equals("The socket argument is not a socket")
) {
Logging logging = Logging.getLogging();
logging.clientException("Durring write", ioe);
}
disconnect(true);
return false;
}
}
private void debug(byte[] bs) {
}
public boolean reconnect() {
close();
try {Thread.sleep(100);} catch (Exception e) {}
connect(username, password);
while (status == XfireStatus.CONNECTING) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
if (status == XfireStatus.CONNECTED) {
Logging.getLogging().clientReconnected(true);
return true;
} else {
Logging.getLogging().clientReconnected(false);
}
return false;
}
public boolean isConnected() {
if (status == XfireStatus.DISCONNECTED) {
Logging.getLogging().clientDisconnected();
return false;
}
return true;
}
public int getReconnectTime() {
if (status == XfireStatus.RECONNECTING) {
return (int)((when - System.currentTimeMillis()) / 1000);
} else {
return -1;
}
}
public void stopReconnect() {
if (status == XfireStatus.RECONNECTING) {
setStatus(XfireStatus.DISCONNECTED);
}
}
public boolean isReconnecting() {
return status == XfireStatus.RECONNECTING;
}
private void setStatus(XfireStatus status) {
this.status = status;
Logging.getLogging().clientSatusChanged(status);
}
}
|