content: /*
   * CDDL HEADER START
   *
   * The contents of this file are subject to the terms of the Common Development
   * and Distribution License (the "License").
   * You may not use this file except in compliance with the License.
   *
   * You can obtain a copy of the license at
   * src/com/vodafone360/people/VODAFONE.LICENSE.txt or
   * http://github.com/360/360-Engine-for-Android
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * When distributing Covered Code, include this CDDL HEADER in each file and
   * include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
   * If applicable, add the following below this CDDL HEADER, with the fields
   * enclosed by brackets "[]" replaced with your own identifying information:
   * Portions Copyright [yyyy] [name of copyright owner]
   *
   * CDDL HEADER END
   *
   * Copyright 2010 Vodafone Sales & Services Ltd.  All rights reserved.
   * Use is subject to license terms.
   */
  
  package com.vodafone360.people.engine.presence;
  
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.List;
  
  import android.os.Bundle;
  
  import com.vodafone360.people.database.DatabaseHelper;
  import com.vodafone360.people.database.tables.MePresenceCacheTable;
  import com.vodafone360.people.database.tables.ActivitiesTable.TimelineSummaryItem;
  import com.vodafone360.people.datatypes.BaseDataType;
  import com.vodafone360.people.datatypes.ChatMessage;
  import com.vodafone360.people.datatypes.Conversation;
  import com.vodafone360.people.datatypes.Identity;
  import com.vodafone360.people.datatypes.PresenceList;
  import com.vodafone360.people.datatypes.PushAvailabilityEvent;
  import com.vodafone360.people.datatypes.PushChatMessageEvent;
  import com.vodafone360.people.datatypes.PushClosedConversationEvent;
  import com.vodafone360.people.datatypes.PushEvent;
  import com.vodafone360.people.datatypes.ServerError;
  import com.vodafone360.people.datatypes.SystemNotification;
  import com.vodafone360.people.datatypes.ContactSummary.OnlineStatus;
  import com.vodafone360.people.datatypes.SystemNotification.Tags;
  import com.vodafone360.people.engine.BaseEngine;
  import com.vodafone360.people.engine.EngineManager;
  import com.vodafone360.people.engine.IEngineEventCallback;
  import com.vodafone360.people.engine.EngineManager.EngineId;
  import com.vodafone360.people.engine.login.LoginEngine.ILoginEventsListener;
  import com.vodafone360.people.engine.meprofile.SyncMeDbUtils;
  import com.vodafone360.people.engine.presence.NetworkPresence.SocialNetwork;
  import com.vodafone360.people.service.ServiceStatus;
  import com.vodafone360.people.service.ServiceUiRequest;
  import com.vodafone360.people.service.agent.UiAgent;
  import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse;
  import com.vodafone360.people.service.io.api.Chat;
  import com.vodafone360.people.service.io.api.Presence;
  import com.vodafone360.people.service.transport.ConnectionManager;
  import com.vodafone360.people.service.transport.tcp.ITcpConnectionListener;
  import com.vodafone360.people.utils.LogUtils;
  
  /**
   * Handles the Presence life cycle
   */
  public class PresenceEngine extends BaseEngine implements ILoginEventsListener,
          ITcpConnectionListener {
  
      /** Check every 24 hours **/
      private final static long CHECK_FREQUENCY = 24 * 60 * 60 * 1000;
  
      private DatabaseHelper mDbHelper;
  
      private final Hashtable<String, ChatMessage> mSendMessagesHash; // (to, message)
  
      /**
       * List of not delivered messages.
       */
      private final List<TimelineSummaryItem> mFailedMessagesList; 
      
      /** The list of Users still to be processed. **/
      private List<User> mUsers = null;
  
      /**
       * This state indicates there are no more pending presence payload
       * information to be processed.
       **/
      private static final int IDLE = 0;
  
      /**
       * This state indicates there are some pending presence payload information
       * to be processed.
       **/
      private static final int UPDATE_PROCESSING_GOING_ON = 1;
  
      /** Timeout between each presence update processing. **/
      private static final long UPDATE_PRESENCE_TIMEOUT_MILLS = 0;
  
      /** The page size i.e the number of presence updates processed at a time. **/
      private static final int UPDATE_PRESENCE_PAGE_SIZE = 10;
      
      /** The number of pages after which the HandlerAgent is notified. **/
      private static final int NOTIFY_AGENT_PAGE_INTERVAL = 5;
  
      /** The state of the presence Engine. **/
      private int mState = IDLE;
  
      /**
       * Number of pages of presence Updates done. This is used to control when a
       * notification is sent to the UI.
       **/
      private int mIterations = 0;
      
      /**
       * True if the engine runs for the 1st time.
       */
      private boolean mFirstRun = true;
      
      /**
       * The Hashtable of setAvailability requests that contains the pairs (networkName, targetState)
       */
      private final Hashtable<String, Bundle> mSetAvailabilityUiCalls;
      
      /**
       *  The request id key. 
       */
      private static final String REQUEST_ID_KEY = "reqId";
  
      /**
       * 
       * @param eventCallback
       * @param databaseHelper
       */
      public PresenceEngine(IEngineEventCallback eventCallback, DatabaseHelper databaseHelper) {
          super(eventCallback);
          mEngineId = EngineId.PRESENCE_ENGINE;
          mDbHelper = databaseHelper;
          mSendMessagesHash = new Hashtable<String, ChatMessage>();
          mFailedMessagesList = new ArrayList<TimelineSummaryItem>();
          mSetAvailabilityUiCalls = new Hashtable<String, Bundle>();
      }
  
      @Override
      public void onCreate() {
      }
  
      @Override
      public void onDestroy() {
          if (mDbHelper != null && SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper) != null) {
              PresenceDbUtils.resetPresenceStatesAcceptForMe(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper),
                      mDbHelper);
          }
          EngineManager.getInstance().getLoginEngine().removeListener(this);
      }
  
      /**
       * Checks if the SyncMe and ContactSync Engines have both completed first time sync.
       * 
       * @return true if both engines have completed first time sync
       */
      private boolean isFirstTimeSyncComplete() {
          return EngineManager.getInstance().getSyncMeEngine().isFirstTimeMeSyncComplete() &&
              EngineManager.getInstance().getContactSyncEngine().isFirstTimeSyncComplete();
      }
  
      @Override
      public long getNextRunTime() {
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED || !isLoggedIn()) {
              return -1;
          }
          if (!isFirstTimeSyncComplete()) {
              LogUtils.logV("PresenceEngine.getNextRunTime(): 1st contact sync is not finished:");
              return -1;
          }
  
          /**
           * need isUiRequestOutstanding() because currently the worker thread is
           * running and finishing before PresenceEngine.setNextRuntime is called
           */
          if (isCommsResponseOutstanding() || isUiRequestOutstanding()) {
              LogUtils.logV("PresenceEngine getNextRunTime() comms response outstanding");
              return 0;
          }
          if (mFirstRun) {
              getPresenceList();
              initSetMyAvailabilityRequestAfterLogin();
              mFirstRun = false;
          }
          return getCurrentTimeout();
      }
      
      @Override
      public void run() {
          LogUtils.logV("PresenceEngine.run() isCommsResponseOutstanding["
                  + isCommsResponseOutstanding() + "] mNextRuntime["
                  + getCurrentTimeout() + "]");
          if (isCommsResponseOutstanding() && processCommsInQueue()) {
              LogUtils.logV("PresenceEngine.run() handled processCommsInQueue()");
              return;
          }
          if (processTimeout()) {
              LogUtils.logV("PresenceEngine.run() handled processTimeout()");
              return;
          }
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
              LogUtils.logV("PresenceEngine.run(): AgentState.DISCONNECTED");
              setPresenceOffline();
          }
          /**
           * and the getNextRunTime must check the uiRequestReady() function and
           * return 0 if it is true
           */
          if (uiRequestReady() && processUiQueue()) {
              return;
          }
      }
  
      /**
       * Helper function which returns true if a UI request is waiting on the
       * queue and we are ready to process it.
       * 
       * @return true if the request can be processed now.
       */
      private boolean uiRequestReady() {
          return isUiRequestOutstanding();
      }
  
      private User getMyAvailabilityStatusFromDatabase() {
          return PresenceDbUtils.getMeProfilePresenceStatus(mDbHelper);
      }
  
      @Override
      public void onLoginStateChanged(boolean loggedIn) {
          LogUtils.logI("PresenceEngine.onLoginStateChanged() loggedIn[" + loggedIn + "]");
          if (loggedIn) {
              mFirstRun = true;
              mFailedMessagesList.clear();
              mSendMessagesHash.clear();
              
              setPresenceOffline();
          }
      }
  
  
      /***
       * Set the Global presence status to offline.
       */
      private synchronized void setPresenceOffline() {
          PresenceDbUtils.setPresenceOfflineInDatabase(mDbHelper);
          // We clear the mUsers of any pending presence updates because this
          // Offline presence update request should take the highest priority.
          mUsers = null;
          mState = IDLE;
          mEventCallback.getUiAgent().updatePresence(UiAgent.ALL_USERS);
      }
  
      @Override
      protected void onRequestComplete() {
          LogUtils.logI("PresenceEngine.onRequestComplete()");
      }
  
      @Override
      protected void onTimeoutEvent() {
          LogUtils.logI("PresenceEngine.onTimeoutEvent()");
          switch (mState) {
              case UPDATE_PROCESSING_GOING_ON:
                  updatePresenceDatabaseNextPage();
                  break;
              case IDLE:
              default:
                  if (isLoggedIn()) {
                      if (isFirstTimeSyncComplete()) {
                          getPresenceList();
                          initSetMyAvailabilityRequest(getMyAvailabilityStatusFromDatabase());
                          // Request to update the UI
                          setTimeout(CHECK_FREQUENCY);
                      } else { // check after 30 seconds
                          LogUtils.logE("Can't run PresenceEngine before the contact"
                                  + " list is downloaded:3 - set next runtime in 30 seconds");
                          setTimeout(CHECK_FREQUENCY / 20);
                      }    
                  }
                  break;
          }
      }
  
      @Override
      protected void processCommsResponse(DecodedResponse resp) {
          handleServerResponse(resp.mDataTypes);
      }
  
      private void showErrorNotification(ServiceUiRequest errorEvent, ChatMessage msg) {
          UiAgent uiAgent = mEventCallback.getUiAgent();
          if (uiAgent != null && uiAgent.isSubscribedWithChat()) {
              uiAgent.sendUnsolicitedUiEvent(errorEvent, null);
          }
      }
  
      /**
       * Here we update the PresenceTable, and the ContactSummaryTable afterwards
       * the HandlerAgent receives the notification of presence states changes.
       * 
       * @param users List of users that require updating.
       */
      private synchronized void updatePresenceDatabase(List<User> users) {
          if (users == null || users.size() == 0) {
              LogUtils.logW("PresenceEngine.updatePresenceDatabase() users is NULL or zero size");
          } else {
              if (mUsers == null) {
                  mUsers = users;
              } else {
                  // Doing an add one by one is not an efficient way but then
                  // there is an issue in the addAll API. It crashes sometimes.
                  // And the java code for the AddAll API seems to be erroneous.
                  // More investigation is needed on this.
                  for (User user : users) {
                      mUsers.add(user);
                  }
              }
          }
  
          mState = UPDATE_PROCESSING_GOING_ON;
          updatePresenceDatabaseNextPage();
      }
      
      /**
       * This API makes the presence updates in pages of 10 with a timeout
       * after each page. The HandlerAgent is notified after every 10 pages.
       */
      private synchronized void updatePresenceDatabaseNextPage() {
          UiAgent uiAgent = mEventCallback.getUiAgent();
          if(mUsers == null){
              mState = IDLE;
              return;
          }
          
          int listSize = mUsers.size();
          int start = 0;
          int end = UPDATE_PRESENCE_PAGE_SIZE;
          if (listSize == 0) {
              mState = IDLE;
              mUsers = null;
              
              notifyUiAgentOfResponse();
              
              return;
          } else if(listSize < end) {
              end = listSize;
          }
          List<User> userSubset = mUsers.subList(start, end);
          if ((userSubset != null)) {
              long idListeningTo = UiAgent.ALL_USERS;
              if (uiAgent != null) {
                  idListeningTo = uiAgent.getLocalContactId();
              }      
              
              boolean updateUI = PresenceDbUtils.updateDatabase(userSubset, idListeningTo, mDbHelper);
              userSubset.clear();
              // Send the update notification to UI for every UPDATE_PRESENCE_PAGE_SIZE*NOTIFY_AGENT_PAGE_INTERVAL updates.
              if (updateUI) {
                  if (mUsers.size() == 0 || NOTIFY_AGENT_PAGE_INTERVAL == mIterations) {
                      if (uiAgent != null) {
                          uiAgent.updatePresence(idListeningTo);
                      }
                      mIterations = 0;
                  } else {
                      mIterations++;
                  }
              }
              this.setTimeout(UPDATE_PRESENCE_TIMEOUT_MILLS);
          }
          
      }
  
  
      /**
       * Updates the database with the given ChatMessage and Type.
       * 
       * @param message ChatMessage.
       * @param type TimelineSummaryItem.Type.
       */
      private void updateChatDatabase(ChatMessage message, TimelineSummaryItem.Type type) {
          ChatDbUtils.convertUserIds(message, mDbHelper);
          LogUtils.logD("PresenceEngine.updateChatDatabase() with [" + type.name() + "] message");
          if (message.getLocalContactId() == null || message.getLocalContactId() < 0) {
              LogUtils.logE("PresenceEngine.updateChatDatabase() "
                      + "WILL NOT UPDATE THE DB! - INVALID localContactId = "
                      + message.getLocalContactId());
              return;
          }
  
          /** We mark all incoming messages as unread. **/
          ChatDbUtils.saveChatMessageAsATimeline(message, type, mDbHelper);
  
          UiAgent uiAgent = mEventCallback.getUiAgent();
          if (uiAgent != null && (message.getLocalContactId() != -1)) {
              uiAgent.updateChat(message.getLocalContactId(), true, message.getNetworkId());
          }
      }
  
      /**
       * Here we update the PresenceTable, and the ContactSummaryTable afterwards
       * the HandlerAgent receives the notification of presence states changes.
       * 
       * @param users User that requires updating.
       */
      private void updateMyPresenceInDatabase(User myself) {
          LogUtils.logV("PresenceEnfgine.updateMyPresenceInDatabase() myself[" + myself + "]");
          if (PresenceDbUtils.updateMyPresence(myself, mDbHelper)) {
          	mEventCallback.getUiAgent().updatePresence(myself.getLocalContactId());
          }
      }
  
  
      /**
       * This method handles incoming presence status change push events and the
       * whole PresenceList
       * 
       * @param dataTypes
       */
      private void handleServerResponse(List<BaseDataType> dataTypes) {
  
          if (dataTypes != null) {
              for (BaseDataType mBaseDataType : dataTypes) {
                  int type = mBaseDataType.getType();
                  if (type == BaseDataType.PRESENCE_LIST_DATA_TYPE) {
                      handlePresenceList((PresenceList)mBaseDataType);
                  } else if (type == BaseDataType.PUSH_EVENT_DATA_TYPE) {
                      handlePushEvent(((PushEvent)mBaseDataType));
                  } else if (type == BaseDataType.CONVERSATION_DATA_TYPE) {
                      // a new conversation has just started
                      handleNewConversationId((Conversation)mBaseDataType);
                  } else if (type == BaseDataType.SYSTEM_NOTIFICATION_DATA_TYPE) {
                      handleSystemNotification((SystemNotification)mBaseDataType);
                  } else if (type == BaseDataType.SERVER_ERROR_DATA_TYPE) {
                      handleServerError((ServerError)mBaseDataType);
                  } else {
                      LogUtils.logE("PresenceEngine.handleServerResponse()"
                              + ": response datatype not recognized:" + type);
                  }
              }
          } else {
              LogUtils.logE("PresenceEngine.handleServerResponse(): response is null!");
          }
      }
  
      private void handlePresenceList(PresenceList presenceList) {
          updatePresenceDatabase(presenceList.getUsers());
      }
  
  
      private void handleServerError(ServerError srvError) {
  
          LogUtils.logE("PresenceEngine.handleServerResponse() - Server error: " + srvError);
          ServiceStatus errorStatus = srvError.toServiceStatus();
          if (errorStatus == ServiceStatus.ERROR_COMMS_TIMEOUT) {
              LogUtils.logW("PresenceEngine handleServerResponce(): TIME OUT IS RETURNED TO PRESENCE ENGINE.");
              notifyUiAgentOfTimeOut(srvError.requestId);
          }
      }
  
  
      private void handleNewConversationId(Conversation conversation) {
          if (conversation.getTos() != null) {
              String to = conversation.getTos().get(0);
              if (mSendMessagesHash.containsKey(to)) {
                  ChatMessage message = mSendMessagesHash.get(to);
                  message.setConversationId(conversation.getConversationId());
                  /** Update the DB with an outgoing message. **/
                  updateChatDatabase(message, TimelineSummaryItem.Type.OUTGOING);
                  Chat.sendChatMessage(message);
                  // clean check if DB needs a cleaning (except for
                  // the current conversation id)
                  ChatDbUtils.cleanOldConversationsExceptForContact(message.getLocalContactId(),
                          mDbHelper);
              }
          }
      }
  
      private void handlePushEvent(PushEvent event) {
          switch (event.mMessageType) {
              case AVAILABILITY_STATE_CHANGE:
                  PushAvailabilityEvent pa = (PushAvailabilityEvent)event;
                  updatePresenceDatabase(pa.mChanges);
                  break;
              case CHAT_MESSAGE:
                  PushChatMessageEvent pc = (PushChatMessageEvent)event;
                  // update the DB with an incoming message
                  updateChatDatabase(pc.getChatMsg(), TimelineSummaryItem.Type.INCOMING);
                  break;
              case CLOSED_CONVERSATION:
                  PushClosedConversationEvent pcc = (PushClosedConversationEvent)event;
                  // delete the conversation in DB
                  if (pcc.getConversation() != null) {
                      ChatDbUtils.deleteConversationById(pcc.getConversation(), mDbHelper);
                  }
                  break;
              default:
                  LogUtils.logE("PresenceEngine.handleServerResponse():"
                          + " push message type was not recognized:" + event.getType());
          }
      }
  
      private void handleSystemNotification(SystemNotification sn) {
          LogUtils.logE("PresenceEngine.handleServerResponse(): " + sn);
          switch (sn.getSysCode()) {
              case SEND_MESSAGE_FAILED:
                  ChatMessage msg = mSendMessagesHash.get(sn.getInfo().get(Tags.TOS.toString()));
                   if (msg != null) {
                       ChatDbUtils.deleteUnsentMessage(mDbHelper, msg);
                   }
                  showErrorNotification(ServiceUiRequest.UNSOLICITED_CHAT_ERROR_REFRESH, null);
                  break;
              case CONVERSATION_NULL:
                  if (!mSendMessagesHash.isEmpty()) {
                      mSendMessagesHash.remove(sn.getInfo().get(Tags.TOS.toString()));
                  }
                  showErrorNotification(ServiceUiRequest.UNSOLICITED_CHAT_ERROR, null);
                  break;
              case COMMUNITY_LOGOUT_SUCCESSFUL:
                  break;    
                  
              default:
                  LogUtils.logE("PresenceEngine.handleServerResponse()"
                          + " - unhandled notification: " + sn);
          }
      }
  
      @Override
      protected void processUiRequest(ServiceUiRequest requestId, Object data) {
          if (!isFirstTimeSyncComplete()) {
              LogUtils.logE("PresenceEngine.processUIRequest():"
                      + " Can't run PresenceEngine before the contact list is downloaded:1");
              return;
          }
          
          LogUtils.logW("PresenceEngine.processUiRequest() requestId.name[" + requestId.name() + "]");
          
          if(data == null && requestId != ServiceUiRequest.GET_PRESENCE_LIST) {
              LogUtils.logW("PresenceEngine.processUiRequest() skipping processing for request with no data!");
              return;
          }
          
          switch (requestId) {
              case SET_MY_AVAILABILITY:
                  Hashtable<String, String> presenceHash = (Hashtable<String, String>)data;
                  notifyUiAgentOfRequest(Presence.setMyAvailability(presenceHash), presenceHash);
                  break;
              case GET_PRESENCE_LIST:
                  Presence.getPresenceList(EngineId.PRESENCE_ENGINE, null);
                  break;
              case CREATE_CONVERSATION:
                  List<String> tos = ((ChatMessage)data).getTos();
                  LogUtils.logW("PresenceEngine processUiRequest() CREATE_CONVERSATION with: "
                          + tos);
                  Chat.startChat(tos);
                  break;
              case SEND_CHAT_MESSAGE:
                  ChatMessage msg = (ChatMessage)data;
                  updateChatDatabase(msg, TimelineSummaryItem.Type.OUTGOING);
                  //cache the message (necessary for failed message sending failures)
                  mSendMessagesHash.put(msg.getTos().get(0), msg);
                  
                  Chat.sendChatMessage(msg);
                  break;
              default:
                  LogUtils.logE("PresenceEngine processUiRequest() Unhandled UI request ["
                          + requestId.name() + "]");
                  return; // don't complete with success and schedule the next runtime
          }
          
          completeUiRequest(ServiceStatus.SUCCESS, null);
          setTimeout(CHECK_FREQUENCY);
      }
  
      /**
       * Initiate the "get presence list" request sending to server. Makes the
       * engine run asap.
       * 
       * @return
       */
      synchronized public void getPresenceList() {
          addUiRequestToQueue(ServiceUiRequest.GET_PRESENCE_LIST, null);
      }
  
      /**
       * Method used to jump start setting the availability. 
       * This is primarily used for reacting to login/connection state changes.
       * @param me Our User
       */
      private void initSetMyAvailabilityRequest(User me) {
          if (me == null) {
              LogUtils.logE("PresenceEngine.initSetMyAvailabilityRequest():"
                      + " Can't send the setAvailability request due to DB reading errors");
              return;
          }
  
          if ((me.isOnline() == OnlineStatus.ONLINE.ordinal() &&
                  ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) 
                  || !isFirstTimeSyncComplete()) {
            LogUtils.logD("PresenceEngine.initSetMyAvailabilityRequest():"
                    + " return NO NETWORK CONNECTION or not ready");
            return;
          }
          
          Hashtable<String, String> availability = new Hashtable<String, String>();
          
          for (NetworkPresence presence : me.getPayload()) {
              availability.put(SocialNetwork.getSocialNetworkValue(presence.getNetworkId()).toString(),
                      OnlineStatus.getValue(presence.getOnlineStatusId()).toString());
          }
  
          // set the DB values
          me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
          
          updateMyPresenceInDatabase(me);
          
          addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
      }
  
      /**
       * This method notifies UiAgent of setAvailability call sent by the engine.
       * @param reqId - the request id.
       * @param availability - "Me" availability Hashtable - the parameter of the setAvailability call.
       */
      private void notifyUiAgentOfRequest(int reqId, Hashtable<String, String> availability) {
          
          String network = null;
          String state = null;
          synchronized (mSetAvailabilityUiCalls) {
              for (Enumeration<String> en = availability.keys(); en.hasMoreElements();) {
                  network = en.nextElement();
                  state = availability.get(network);
                  final Bundle bundle = createBundle(network, state, User.SOURCE_REQUESTED);
                  bundle.putInt(REQUEST_ID_KEY, reqId);
                  mSetAvailabilityUiCalls.put(network, bundle);
                  fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY, bundle);
              }    
          }
      }
      
      /**
       * This method notifies UiAgent of the presence push/getPresenceList() response. 
       */
      private void notifyUiAgentOfResponse() {
          final User user = getMyAvailabilityStatusFromDatabase();
          if (user != null) {
              String network = null;
              String state = null;
              String stateFromDb = null;
              synchronized (mSetAvailabilityUiCalls) {
                  for (Iterator<String> it = mSetAvailabilityUiCalls.keySet().iterator(); it.hasNext();) {
                      network = it.next();
                      state = ((Bundle)mSetAvailabilityUiCalls.get(network)).getString(User.STATUS);
                      stateFromDb = user.getStatusForNetwork(SocialNetwork.getValue(network)).toString();
                      if (state.equals(stateFromDb) ||
                              (state.equals(OnlineStatus.INVISIBLE.toString()) && 
                                      stateFromDb.equals(OnlineStatus.OFFLINE.toString()))) {
                          fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY, 
                                  createBundle(network, state, User.SOURCE_RECEIVED));
                          it.remove();
                      }
                  }
              }
          }
      }
      
      /**
       * This method notifies UiAgent of the presence push not coming in a timely manner. 
       */
      private void notifyUiAgentOfTimeOut(int reqId) {
          String network = null;
          int requestId = 0;
          synchronized (mSetAvailabilityUiCalls) {
              for (Iterator<String> it = mSetAvailabilityUiCalls.keySet().iterator(); it.hasNext();) {
                  network = it.next();
                  requestId = ((Bundle)mSetAvailabilityUiCalls.get(network)).getInt(REQUEST_ID_KEY);
                  if (requestId == reqId) {
                      fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY, 
                              createBundle(network, 
                                      ((Bundle)mSetAvailabilityUiCalls.get(network)).getString(User.STATUS), 
                                      User.SOURCE_TIMED_OUT));
                      it.remove();
                  }
              }
          }
      }
      
      /**
       * This method creates a Bundle for sending in the notification about setAvailability() calls
       *  to UiAgent with the following data. 
       * @param network - String name of the network @see {@link SocialNetwork}
       * @param status - String name of the status @see {@link OnlineStatus}
       * @param sourceType - type of the status, @see User.SOURCE_TIMED_OUT, User.SOURCE_REQUESTED, SOURCE_RECEIVED.
       * @return Bundle for sending in the notification about setAvailability() calls to UiAgent. 
       */
      private Bundle createBundle(String network, String status, int sourceType) {
          final Bundle bundle = new Bundle();
          bundle.putString(User.NETWORK, network);
          bundle.putString(User.STATUS, status);
          bundle.putInt(User.SOURCE, sourceType);
          return bundle;
      }
      
      /**
       * Method used to set availability of me profile when user logs in. 
       * This is primarily used for reacting to login/connection state changes.
       */
      private void initSetMyAvailabilityRequestAfterLogin() {
  
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED 
                  || !isFirstTimeSyncComplete()) {
            LogUtils.logD("PresenceEngine.initSetMyAvailabilityRequest():"
                    + " return NO NETWORK CONNECTION or not ready");
            return;
          }
  
          String meProfileUserId = Long.toString(PresenceDbUtils.getMeProfileUserId(mDbHelper));
          Hashtable<String, String> availability = new Hashtable<String, String>();
  
          ArrayList<Identity> identityList = EngineManager.getInstance().getIdentityEngine().getMyChattableIdentities();
  
          if ((identityList.size() != 0)) {
              for (int i=0; i<identityList.size(); i++) {
                  Identity myIdentity = identityList.get(i);
                  availability.put(myIdentity.mNetwork, OnlineStatus.ONLINE.toString());
              }
         }
  
         User meUser = new User(meProfileUserId, availability);
         meUser.setLocalContactId(Long.valueOf(meProfileUserId));
         updateMyPresenceInDatabase(meUser);
  
         addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
      }
  
      /**
       * Changes the user's availability and therefore the state of the engine. 
       * Also displays the login notification if necessary.
       * 
       * @param status Availability to set for all identities we have.
       */
      public void setMyAvailability(OnlineStatus status) {
          if (status == null) {
              LogUtils.logE("PresenceEngine setMyAvailability:"
                      + " Can't send the setAvailability request due to DB reading errors");
              return;
          }
          
          LogUtils.logV("PresenceEngine setMyAvailability() called with status:"+status.toString());
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
              LogUtils.logD("PresenceEnfgine.setMyAvailability(): skip - NO NETWORK CONNECTION");
              return;
          }
          
          if (!isFirstTimeSyncComplete()) {
              LogUtils.logD("PresenceEngine.setMyAvailability(): skip - First time contact sync has not finished yet");
              return;
          }
          
          // Get presences
          Hashtable<String, String> availability = getPresencesForStatus(status);
          
          User me = new User(String.valueOf(PresenceDbUtils.getMeProfileUserId(mDbHelper)),
                  availability);
          
          MePresenceCacheTable.updateCache(me.getPayload(), mDbHelper.getWritableDatabase());
          
          // set the DB values for myself
          me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
          updateMyPresenceInDatabase(me);
          
          addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
      }
      
      /**
       * Changes the user's availability.
       * 
       * @param network - SocialNetwork to set presence on.
       * @param status - OnlineStatus presence status to set.
       */
      public void setMyAvailability(SocialNetwork network, OnlineStatus status) {
          
          LogUtils.logV("PresenceEngine setMyAvailability() called with network presence: "+
                  network + "=" + status);
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
              LogUtils.logD("PresenceEnfgine.setMyAvailability(): skip - NO NETWORK CONNECTION");
              return;
          }
          
          ArrayList<NetworkPresence> presenceList = new ArrayList<NetworkPresence>();
          
          if (!isFirstTimeSyncComplete()) {
              LogUtils.logD("PresenceEngine.setMyAvailability(): skip - First time contact sync has not finished yet");
              return;
          }
  
          String userId = String.valueOf(PresenceDbUtils.getMeProfileUserId(mDbHelper));
          
          final NetworkPresence newPresence = new 
              NetworkPresence(userId, network.ordinal(), status.ordinal());
          
          presenceList.add(newPresence);
          
          User me = new User(userId, null);
          
          me.setPayload(presenceList);
          
          MePresenceCacheTable.updateCache(newPresence, mDbHelper.getWritableDatabase());
          
          // set the DB values for myself
          me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
          updateMyPresenceInDatabase(me);
  
          // set the engine to run now
          Hashtable<String, String> availability = new Hashtable<String, String>();
          availability.put(network.toString(), status.toString());
          
          addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
          
      }
  
  
      private void createConversation(ChatMessage msg) {
          LogUtils.logW("PresenceEngine.createConversation():" + msg);
          // as we currently support sending msgs to just one person getting the 0
          // element of "Tos" must be acceptable
          mSendMessagesHash.put(msg.getTos().get(0), msg);
          addUiRequestToQueue(ServiceUiRequest.CREATE_CONVERSATION, msg);
      }
  
      /**
       * This method should be used to send a message to a contact
       * 
       * @param tos - tlocalContactId of ContactSummary items the message is
       *            intended for. Current protocol version only supports a single
       *            recipient.
       * @param body the message text
       */
      public void sendMessage(long toLocalContactId, String body, int networkId) {
          if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
              LogUtils.logD("PresenceEnfgine.sendMessage: skip - NO NETWORK CONNECTION");
              return;
          }
          ChatMessage msg = new ChatMessage();
          msg.setBody(body);
          msg.setNetworkId(networkId);
          msg.setLocalContactId(toLocalContactId);
  
          ChatDbUtils.fillMessageByLocalContactIdAndNetworkId(msg, mDbHelper);
  
          if (msg.getConversationId() != null) {
              String fullUserId = SocialNetwork.getSocialNetworkValue(msg.getNetworkId()).toString()
                      + ChatDbUtils.COLUMNS + msg.getUserId();
              msg.setUserId(fullUserId);
              addUiRequestToQueue(ServiceUiRequest.SEND_CHAT_MESSAGE, msg);
          } else {
              // if the conversation was not found that means it didn't exist,
              // need start a new one
              if (msg.getUserId() == null) {
                  ChatDbUtils.findUserIdForMessageByLocalContactIdAndNetworkId(msg, mDbHelper);
                  createConversation(msg);
              }
          }
      }
  
      @Override
      public void onConnectionStateChanged(int state) {
          if (isLoggedIn() && isFirstTimeSyncComplete()) {
              switch (state) {
                  case STATE_CONNECTED:
                      getPresenceList();
                      initializeAvailabilityFromDb();
                      break;
                  case STATE_CONNECTING:
                  case STATE_DISCONNECTED:
                      setPresenceOffline();
                      mFailedMessagesList.clear();
                      mSendMessagesHash.clear();
                      mSetAvailabilityUiCalls.clear();   
                      break;
              }    
          }
      }
      
      /**
       * Initializes or re-initializes availability from the Database.
       * If there are cached presences then these will be used 
       * instead of values from the presence table.
       */
      private void initializeAvailabilityFromDb() {
          final User user = getMyAvailabilityStatusFromDatabase();
          ArrayList<NetworkPresence> cachedPresences = 
              MePresenceCacheTable.getCache(mDbHelper.getReadableDatabase());
          if(cachedPresences != null) {
              user.setPayload(cachedPresences);
          }
          
          initSetMyAvailabilityRequest(user);
      }
      
      /**
       * Convenience method.
       * Constructs a Hash table object containing My identities mapped against the provided status.
       * @param status Presence status to set for all identities
       * @return The resulting Hash table, is null if no identities are present
       */
      public Hashtable<String, String> getPresencesForStatus(OnlineStatus status) {
          // Get cached identities from the presence engine 
          ArrayList<Identity> identities = 
              EngineManager.getInstance().getIdentityEngine().getMyChattableIdentities();
      
          if (identities == null) {
              // No identities, just return null
              return null;
          }
      
          Hashtable<String, String> presences = new Hashtable<String, String>();
      
          String statusString = status.toString();
          for (Identity identity : identities) {
              presences.put(identity.mNetwork, statusString);
          }
          
          return presences;
      }
  
      @Override
      public void onReset() {
          
          // reset the engine as if it was just created
          super.onReset();
          mFirstRun = true;
          mIterations = 0;
          mState = IDLE;
          mUsers = null;
          mFailedMessagesList.clear();
          mSendMessagesHash.clear();
          mSetAvailabilityUiCalls.clear();
      }
      
      /**
       * This method returns if the LoginEngine is in the LOGGED_ON state.
       * @return - TRUE if the LoginEngine is in the LOGGED_ON state.
       */
      private boolean isLoggedIn(){
          return EngineManager.getInstance().getLoginEngine().isLoggedIn();
      }
  
      /**
       * This method returns TRUE if the presence status on the passed has not been set yet.   
       * @param network - network name.
       * @return TRUE if the presence status on the passed has not been set yet.
       */
      public boolean isSetStatusInProgress(String network) {
          return mSetAvailabilityUiCalls.containsKey(network);
      }
      
  }
  