XfireClient.java :  » Client » xfdroid » lowijs » XfireClient » Android Open Source

Android Open Source » Client » xfdroid 
xfdroid » lowijs » XfireClient » XfireClient.java
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);
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.