CommunicationThread.java :  » Client » android-gadu » pl » szpadel » android » gadu » Android Open Source

Android Open Source » Client » android gadu 
android gadu » pl » szpadel » android » gadu » CommunicationThread.java
package pl.szpadel.android.gadu;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import pl.szpadel.android.gadu.packets.BasePacket;
import pl.szpadel.android.gadu.packets.ReceivedPacket;
import pl.szpadel.android.gadu.packets.SendPacket;
import pl.szpadel.android.gadu.packets.ZeroLenghtSendPacket;

/// Connection thread. Responsible for connection management, network communication,
/// receiving messages from UI thread and sending received packets and connection status to UI thread
public class CommunicationThread extends Thread {

  /////////////////////////////////////////////////////////
  // data
  
  /// Parent service
  CommunicationService mService;
  
  /// Connector
  private Connector mConnector;
  
  /// flag - if reconnection should be attempted
  private boolean mReconnect = true;
  
  // flag - stop working.
  //private boolean mQuit = false;
  
  /// comm channel
  //SocketChannel mChannel;
  
  /// Inter-thread message queue
  /// connection thread is the receiver, other threads are feeding into the queue
  private BlockingQueue<InterThreadMessage> mInterthreadQueue = new LinkedBlockingQueue<InterThreadMessage>();
  
  //////////////////////////////////////////////////////////
  // types
  
  /// Thread message type. USed for inter-thread communication
  private static class InterThreadMessage {
    /// message type
    public int type;
    
    // known types
    
    // commands from ui thread
    public static final int TYPE_RECONECT_NOW = 0; // command - reconnect now
    public static final int TYPE_DISCONNECT = 1; // command - disconnect
    public static final int TYPE_SEND_PACKET = 2; // command - send packet
    public static final int TYPE_QUIT = 3; // stops the comm thread
    
    // info from reading thread
    public static final int TYPE_DISCONNECTED = 100; // info - connection broken
    
    /// general purpose fields
    public SendPacket sendPacket;
    
    public InterThreadMessage(int t) {
      type = t;
    }
  }
  
  ////////////////////////////////
  // Receiving thread type
  private class ReceivingThread extends Thread {
    
    /// channel to read from
    private ReadableByteChannel mChannel;
    
    /// c-tor
    ReceivingThread(ReadableByteChannel channel) {
      super();
      assert(channel != null);
      mChannel = channel;
    }
    
    /// the routine
    public void run() {
      try {
        while(true) {
          AGLog.d(CommunicationService.TAG, "rth: receiving packet");
          ReceivedPacket packet = ReceivedPacket.createFromChannel(mChannel);
          AGLog.i(CommunicationService.TAG, "rth: received packet: " + packet); 
          handlePacket(packet);
        }
      } catch (ClosedChannelException e) {
        AGLog.i(CommunicationService.TAG, "rth:channel closed, ending");
      } catch(Exception e) {
        String msg =  "rth:comm error, sending itm and ending: exception:" + e.toString() + ", \nmsg:";
        for (StackTraceElement st : e.getStackTrace()) {
          msg += "\n    > " + st;
        }
        
        AGLog.i(CommunicationService.TAG, msg);
        
        mInterthreadQueue.offer(new InterThreadMessage(InterThreadMessage.TYPE_DISCONNECTED));
      }
    }
  }
  
  /////////////////////////////////////////////////
  // intereface
  
  /// C-tor
  public CommunicationThread(CommunicationService service) {
    super("connection thread");
    mService = service;
    mConnector = new Connector(mService);
  }

  /// Routine
  public void run() {
    // try connecting
    mReconnect = false; // wait for command
    SocketChannel channel = null;
    ReceivingThread receivingThread = null;
    long lastPingTime = 0;
    boolean quit = false;
    try {
    do {
      AGLog.v(CommunicationService.TAG, "th:main loop. channel is null:" + (channel==null) + ", reconnect=" + mReconnect);
      if (mReconnect && channel == null) {
        channel = tryConnecting();
        receivingThread = null;
        if (channel != null) {
          try {
            AGLog.i(CommunicationService.TAG, "th: connected, starting receiving channel");
            receivingThread = new ReceivingThread(channel);
            receivingThread.start();
            
            // connected now
            sendConnectionState(ConnectionState.Connected());
            lastPingTime = System.currentTimeMillis();
          } catch (Exception e) {
            AGLog.e(CommunicationService.TAG, "th: failed to start rerading thread:"+e.getMessage());
            channel = null; // will try again...
          }
        } else {
          AGLog.e(CommunicationService.TAG, "failed to connect");
        }
      }
      // wait for events
      AGLog.v(CommunicationService.TAG, "waiting for itm");
      
      try {
        // exit after 10s to i.e. to retry reconnecting
        InterThreadMessage itm = mInterthreadQueue.poll(60, TimeUnit.SECONDS);
        
        // any itms?
        if (itm != null){
          AGLog.v(CommunicationService.TAG, "handling itm of type=" + itm.type);
          switch(itm.type) {
          case InterThreadMessage.TYPE_QUIT:
            quit = true;
            break;
          case InterThreadMessage.TYPE_RECONECT_NOW:
            closeChannelAndJoin(channel, receivingThread);
            sendConnectionState(ConnectionState.Connecting());
            channel = null;
            mReconnect = true;
            break;
          case InterThreadMessage.TYPE_DISCONNECT:
            closeChannelAndJoin(channel, receivingThread);
            sendConnectionState(ConnectionState.Disconnected(ConnectionState.REASON_DISCONNECTED_BY_USER));
            channel = null;
            mReconnect = false;
            break;
          case InterThreadMessage.TYPE_DISCONNECTED:
            closeChannelAndJoin(channel, receivingThread);
            channel = null;
            mReconnect = true;
            break;
          case InterThreadMessage.TYPE_SEND_PACKET:
            // send only if channel connected
            try {
              if (channel != null) {
                assert(itm.sendPacket != null);
                itm.sendPacket.toChannel(channel);
                
                AGLog.i(CommunicationService.TAG, "th: Sending packet: " + itm.sendPacket);
              }
            } catch (Exception e ) {
              AGLog.e(CommunicationService.TAG, "th: failed to send packet " + itm.sendPacket);
              // actual error will be handled in receiving thread
            }
          default:
            ; // do nothing
          } // switch
        } // if itm
        
        // ping time?
        if (channel != null) {
          long timeFromLastPing = System.currentTimeMillis() - lastPingTime;
          if (timeFromLastPing >= 4*60*1000) { // 4 minutes
            try {
              AGLog.v(CommunicationService.TAG, "th: sending PING");
              ZeroLenghtSendPacket ping = new ZeroLenghtSendPacket(BasePacket.TYPE_PING);
              ping.toChannel(channel);
              lastPingTime = System.currentTimeMillis();
            } catch (IOException e ) {
              AGLog.e(CommunicationService.TAG, "th: error sending ping: " + e.getMessage() );
              // errors are handled by reading thread
            }
          }
        }
        
      } catch (Exception e) {
        AGLog.e(CommunicationService.TAG, "th:Error reading/handling itm: " + e.getMessage());
      } 
      
    } while(quit==false);
    } catch(Exception e) {
      AGLog.e(CommunicationService.TAG, "th: unhandled exception in main loop");
    }
    
    AGLog.i(CommunicationService.TAG, "th: thread exists");
    
    // wait for receiving thread
    closeChannelAndJoin(channel, receivingThread);
    
  }
  
  /// Stops and joins the thread
  /// call from ui thread
  public void stopCommThread() {
    AGLog.i(CommunicationService.TAG, "th:stopCommThread()");
    mInterthreadQueue.offer(new InterThreadMessage(InterThreadMessage.TYPE_QUIT));
    try {
      join();
    } catch (InterruptedException e) {
      ; // nothing
    }
  }
  
  /// Forces to reconnect to server ASAP
  public void forceReconnect() {
    AGLog.i(CommunicationService.TAG, "th:forceReconnect()");
    mInterthreadQueue.offer(new InterThreadMessage(InterThreadMessage.TYPE_RECONECT_NOW));
  }

  /// Disconnects
  public void disconnect() {
    AGLog.i(CommunicationService.TAG, "th:disconnect()");
    mInterthreadQueue.offer(new InterThreadMessage(InterThreadMessage.TYPE_DISCONNECT));
  }
  
  /// Sends packet (if connected)
  public void sendPacket(SendPacket packet) {
    InterThreadMessage itm = new InterThreadMessage(InterThreadMessage.TYPE_SEND_PACKET);
    itm.sendPacket = packet;
    
    mInterthreadQueue.offer(itm);
  }
  
  ////////////////////////////////////////////////////
  // implementation
  
  /// Handles received packet
  private void handlePacket(final ReceivedPacket packet) {
    // simply send yo ui thread
    AGLog.i(CommunicationService.TAG, "th: sending packet to ui thread: " + packet);
    
    mService.sendToUiThread(new Runnable(){
      public void run() { mService.onPacket(packet); }
    });
  }
  
  
  /// Closes channel, joins thread
  static private void closeChannelAndJoin(SocketChannel channel, Thread t) {
    try {
      if (channel != null) {
        AGLog.d(CommunicationService.TAG, "th: closing channel...");
        channel.close();
        AGLog.d(CommunicationService.TAG, "th: channel closed");
      }
      if (t != null) {
        AGLog.d(CommunicationService.TAG, "th: joining thread...");
        t.interrupt(); // TODO this is hack, i HATE it!
        t.join(1000); // 1 second timeout
        if (t.isAlive()) {
          AGLog.e(CommunicationService.TAG, "th: failed to join thread");
        }
        AGLog.d(CommunicationService.TAG, "th: thread joined");
      }
    } catch (Exception e) {
      AGLog.e(CommunicationService.TAG, "th: error closing channel: " + e);
      ; // do nothing
    }
  }
  
  // Tries connecting. Sets status accordingly
  // returns channel if connected, null if not
  private SocketChannel tryConnecting() {
    sendConnectionState(ConnectionState.Connecting());
    
    // read uin and pwd
    Config config = App.getInstance().getConfig();
    if (config.hasLoginDetails()) {
      
      long uin = config.getUin();
      String password = config.getPassword();
      
      // try connecting
      try {
        AGLog.i(CommunicationService.TAG, "th:tryConnect: connecting with uin=" + uin);
        return mConnector.connect(uin, password, config.getStatus());
      } catch (Connector.LoginException e) {
        mReconnect = false;
        sendConnectionState(ConnectionState.Disconnected(ConnectionState.REASON_LOGIN_FAILED));
        AGLog.e(CommunicationService.TAG, "th:tryConnect: login failed: " + e.getMessage());
        return null;
      } catch (Connector.NetworkException e) {
        mReconnect = true; // will try to reconnect after some time
        AGLog.e(CommunicationService.TAG, "th:tryConnect: network error: " + e.getMessage());
        return null;
      } catch (Connector.ProtocolException e) {
        mReconnect = true; // will try to reconnect after some time
        AGLog.e(CommunicationService.TAG, "th:tryConnect: protocol error: " + e.getMessage());
        return null;
      } catch (Connector.RejectException e) {
        mReconnect = false; // will try to reconnect after some time
        AGLog.e(CommunicationService.TAG, "th:tryConnect: connection rejected: " + e.getMessage());
        return null;
      } catch (Connector.ConnectorException e) {
        ; // java sux
      }
    }
    
    
    return null;
  }
  
  // sends connection state to UI thread to service
  private void sendConnectionState(final ConnectionState state) {
    mService.sendToUiThread(new Runnable() {
      public void run() { mService.onConnectionStateChange(state); }
    });
    
  }

}
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.