/**
  * Receive notification from ContactSyncEngine that a Contact sync has completed. Start Activity
  * sync if we have not previously synced.
  */
 @Override
 public void onSyncComplete(ServiceStatus status) {
   LogUtils.logD(
       "ActivityEngine onSyncComplete, time:" + mLastStatusUpdated + ", state=" + mState);
   // fire off background grab of activities
   if (ServiceStatus.SUCCESS == status && (mLastStatusUpdated == 0) && (mState == State.IDLE)) {
     addStatusesSyncRequest();
     LogUtils.logD("ActivityEngine onSyncComplete FULL_SYNC_FIRST_TIME.");
   }
 }
 /** Handle an outstanding UI request. */
 @Override
 protected void processUiRequest(ServiceUiRequest requestId, Object data) {
   LogUtils.logD("ActivityEngine processUiRequest:" + requestId);
   switch (requestId) {
     case UPDATE_STATUSES:
       // this is full sync or push, or "refresh" button
       requestStatusesFromServer(true);
       break;
     case FETCH_STATUSES:
       // "more" button
       requestStatusesFromServer(false);
       break;
     case FETCH_TIMELINES:
       // "more" button - we only need time lines
       enqueueRequest(
           ServiceUiRequest.FETCH_TIMELINES.ordinal(), ActivitiesState.FETCHING_OLDER_TIMELINE);
       newState(State.FETCH_OLDER_CALLLOG_FROM_NATIVE_DB);
       startCallLogSync(false);
       break;
     case UPDATE_PHONE_CALLS:
       // something on the NAB has changed
       newState(State.UPDATE_CALLOG_FROM_NATIVE);
       startCallLogSync(true);
       break;
     case UPDATE_SMS:
       newState(State.FETCH_SMS_FROM_NATIVE_DB);
       startFetchingSMS();
       break;
     default:
       break;
   }
 }
  /**
   * This method stores the current busy state of engine in ApplicationCache to make it available to
   * UI. This method is normally called before setting new state of the engine.
   *
   * @param requestId int - the request Id for network communications, or the ServiceUIRequest
   *     ordinal for fetching/updating timelines
   * @param requestType one of UPDATING_STATUSES, FETCHING_OLDER_STATUSES, FETCHING_OLDER_TIMELINE
   */
  private void enqueueRequest(int requestId, ActivitiesState requestType) {
    synchronized (mQueueMutex) {
      if (!mActiveRequests.containsKey(requestId)) {
        LogUtils.logE("ActivityEngine.enqueueRequest:" + requestId + ", " + requestType);
        mActiveRequests.put(requestId, requestType);
        cacheRequestType(requestType, true);

      } else {
        LogUtils.logE(
            "ActivityEngine.enqueueRequest: already have this type!"
                + requestId
                + ", "
                + requestType);
      }
    }
  }
 /** {@inheritDoc} */
 @Override
 public void onContactSyncStateChange(
     ContactSyncEngine.Mode mode,
     ContactSyncEngine.State oldState,
     ContactSyncEngine.State newState) {
   LogUtils.logD("ActivityEngine onContactSyncStateChange called.");
 }
 /**
  * This method stores removes a busy state of engine and males the change available to UI through
  * the ApplicationCache. This method is normally called before setting new state of the engine.
  *
  * @param requestId int - the request Id for network communications, or the ServiceUIRequest
  *     ordinal for fetching/updating timelines
  * @param requestType one of UPDATING_STATUSES, FETCHING_OLDER_STATUSES, FETCHING_OLDER_TIMELINE
  */
 private void dequeueRequest(int requestId) {
   synchronized (mQueueMutex) {
     ActivitiesState requestType = mActiveRequests.get(requestId);
     if (requestType != null) {
       mActiveRequests.remove(requestId);
       LogUtils.logE("ActivityEngine.dequeueRequest:" + requestId + ", " + requestType);
       cacheRequestType(requestType, false);
     } else {
       LogUtils.logE(
           "ActivityEngine.dequeueRequest: the request is not in the queue!"
               + requestId
               + ", "
               + requestType);
     }
   }
 }
  /**
   * Request Activities (Status/Timeline) events from Server.
   *
   * @param refresh boolean - is true when fetching latest statuses, false - when older
   */
  private void requestStatusesFromServer(boolean refresh) {
    if (!checkConnectivity()) {
      mRequestActivitiesRequired = true;
      return;
    }
    mRequestActivitiesRequired = false;
    if (!isContactSyncReady()
        || !EngineManager.getInstance().getContactSyncEngine().isFirstTimeSyncComplete()) {
      // this method will then call completeUiRequest(status, null);
      onSyncHelperComplete(ServiceStatus.ERROR_NOT_READY);
      return;
    }
    mLastStatusUpdated = StateTable.fetchLatestStatusUpdateTime(mDb.getReadableDatabase());
    mOldestStatusUpdated = StateTable.fetchOldestStatusUpdate(mDb.getReadableDatabase());

    LogUtils.logD("ActivityEngine getActivites last update = " + mLastStatusUpdated);

    int reqId = Activities.getActivities(this, null, applyActivitiesFilter(refresh));
    if (reqId > 0) {
      setReqId(reqId);
      enqueueRequest(
          reqId,
          refresh ? ActivitiesState.UPDATING_STATUSES : ActivitiesState.FETCHING_OLDER_STATUSES);

      if (mLastStatusUpdated == 0) {
        newState(State.FETCH_STATUSES_FIRST_TIME);
      } else {
        newState(State.UPDATE_STATUSES);
      }
    }
  }
 /**
  * TODO: investigate why duplicates here can appear. this method might be not necessary. Remove
  * Activities from list of Activities with Activity IDS matching ones we have already retrieved
  * (i.e .duplicates).
  */
 private void removeDuplicates(ArrayList<ActivityItem> activityList) {
   if (activityList.size() == 0) {
     return;
   }
   int dupCount = 0;
   List<Long> actIdList = new ArrayList<Long>();
   mDb.fetchActivitiesIds(actIdList, findFirstStatusUpdateTime(activityList));
   for (int i = 0; i < activityList.size(); ) {
     boolean inc = true;
     Long id = activityList.get(i).activityId;
     if (id != null) {
       for (Long l : actIdList) {
         if (l.compareTo(id) == 0) {
           activityList.remove(i);
           inc = false;
           dupCount++;
           break;
         }
       }
     }
     if (inc) {
       i++;
     }
   }
   LogUtils.logD("ActivityEngine removeDuplicates. Count dups = " + dupCount);
 }
 private void updateOldestStatusUpdateTime() {
   long tloStatus = StateTable.fetchLatestStatusUpdateTime(mDb.getReadableDatabase());
   // modify the timelines dates
   if (mOldestStatusUpdated < tloStatus) {
     StateTable.modifyOldestStatusTime(mOldestStatusUpdated, mDb.getWritableDatabase());
     LogUtils.logD("ActivityEngine: oldest status update set to = " + mOldestStatusUpdated);
   }
 }
 /**
  * This method is necessary for the tests. It catches the InvalidParameterException coming from
  * EngineManager.getInstance(), when the instance is null
  *
  * @return boolean - TRUE if ContactSyncEngine is not null
  */
 private boolean isContactSyncReady() {
   try {
     return (EngineManager.getInstance() != null)
         && (EngineManager.getInstance().getContactSyncEngine() != null);
   } catch (InvalidParameterException ipe) {
     LogUtils.logE(ipe.toString());
     return false;
   }
 }
 /**
  * Create ChatMessage from Hashtable generated by Hessian-decoder
  *
  * @param hash Hashtable containing ChatMessage parameters
  */
 public void createFromHashtable(Hashtable<String, Object> hash) {
   LogUtils.logI("Conversation.createFromHashtable() hash[" + hash.toString() + "]");
   Enumeration<String> e = hash.keys();
   while (e.hasMoreElements()) {
     String key = e.nextElement();
     Tags tag = Tags.findTag(key);
     if (tag != null) {
       setValue(tag, hash.get(key));
     }
   }
 }
 /**
  * Handle Status or Timeline Activity change Push message
  *
  * @param evt Push message type (Status change or Timeline change).
  */
 private void handlePushRequest(PushMessageTypes evt) {
   LogUtils.logD("ActivityEngine handlePushRequest");
   switch (evt) {
     case STATUS_ACTIVITY_CHANGE:
     case TIMELINE_ACTIVITY_CHANGE:
       addUiRequestToQueue(ServiceUiRequest.UPDATE_STATUSES, null);
       break;
     default:
       // do nothing
   }
 }
 /**
  * ActivitiesEngine run implementation Processes a response if one is available, Processes any
  * events in engine's UI queue. Issues get-activities request to server as part of sync.
  */
 @Override
 public void run() {
   LogUtils.logD("ActivityEngine run");
   processTimeout();
   if (mNextCleanup < System.currentTimeMillis()) {
     cleanDatabase();
     mNextCleanup = System.currentTimeMillis() + ACTIVITES_CLEANUP_SEC;
     LogUtils.logD("ActivityEngine.run() Clean database again at [" + mNextCleanup + "]");
     return;
   }
   if (isCommsResponseOutstanding() && processCommsInQueue()) {
     return;
   }
   if (isUiRequestOutstanding()) {
     processUiQueue();
   }
   if (mRequestActivitiesRequired) {
     requestStatusesFromServer(true);
   }
 }
  private Map<String, List<String>> applyActivitiesFilter(boolean refresh) {

    Map<String, List<String>> filter = new Hashtable<String, List<String>>();

    // filter out types we're not interested in
    List<String> statusFilter = new ArrayList<String>();
    statusFilter.add(FILTER_TRUE);
    filter.put(FILTER_STATUS, statusFilter);

    if (mLastStatusUpdated > 0) {
      if (refresh) {
        List<String> updateFilter = new ArrayList<String>();
        LogUtils.logD(
            "ActivityEngine TimeFilter newer= '"
                + FILTER_GT
                + (mLastStatusUpdated / MS_IN_SECOND)
                + "'");
        updateFilter.add(FILTER_GT + mLastStatusUpdated / MS_IN_SECOND);
        filter.put(FILTER_UPDATED, updateFilter);
      } else {
        List<String> updateFilter = new ArrayList<String>();
        LogUtils.logD(
            "ActivityEngine TimeFilter older= '"
                + FILTER_GT
                + (mOldestStatusUpdated / MS_IN_SECOND)
                + "'");
        updateFilter.add(FILTER_GT + mOldestStatusUpdated / MS_IN_SECOND);
        filter.put(FILTER_UPDATED, updateFilter);
      }
    } else { // 1st time
      List<String> fNum = new ArrayList<String>();
      fNum.add(FILTER_NUM);
      filter.put(FILTER_LIDS, fNum);
      List<String> sort = new ArrayList<String>();
      sort.add(FILTER_UPDATED_REV);
      filter.put(FILTER_SORT, sort);

      mOldestStatusUpdated = (System.currentTimeMillis() - WEEK_OLD_MILLIS) / MS_IN_SECOND;
    }
    return filter;
  }
 /**
  * Handle response received from transport layer (via EngineManager)
  *
  * @param resp Received Response item either a Status/Timeline related push message or a response
  *     to a get activities request.
  */
 @Override
 protected void processCommsResponse(DecodedResponse resp) {
   LogUtils.logD("ActivitiesEngine processCommsResponse");
   // handle push response
   if (resp.mReqId == 0 && resp.mDataTypes.size() > 0) {
     PushEvent evt = (PushEvent) resp.mDataTypes.get(0);
     handlePushRequest(evt.mMessageType);
   } else {
     dequeueRequest(resp.mReqId);
     handleGetActivitiesResponse(resp.mDataTypes);
   }
 }
 /**
  * Handle GetActivities response message received from Server
  *
  * @param reqId Request ID contained in response. This should match an ID of a request we have
  *     issued to the Server.
  * @param data List array of ActivityItem items returned from Server.
  */
 private void handleGetActivitiesResponse(List<BaseDataType> data) {
   /** Array of Activities retrieved from Server. */
   ArrayList<ActivityItem> activityList = new ArrayList<ActivityItem>();
   ServiceStatus errorStatus = getResponseStatus(BaseDataType.ACTIVITY_ITEM_DATA_TYPE, data);
   LogUtils.logE(
       "ActivityEngine.handleGetActivitiesResponse status from generic = " + errorStatus);
   if (ServiceStatus.SUCCESS == errorStatus) {
     for (BaseDataType item : data) {
       if (item.getType() == BaseDataType.ACTIVITY_ITEM_DATA_TYPE) {
         activityList.add((ActivityItem) item);
       } else {
         LogUtils.logE(
             "ActivityEngine.handleGetActivitiesResponse will not handle strange type = "
                 + item.getType());
       }
     }
     errorStatus = updateDatabase(activityList);
     // we set timeout for the next execution
   }
   // this method will then call completeUiRequest(status, null);
   onSyncHelperComplete(errorStatus);
 }
  /**
   * Changes the state of the engine.
   *
   * @param newState The new state
   */
  private void newState(State newState) {
    State oldState = mState;
    synchronized (mMutex) {
      mState = newState;
    }
    switch (mState) {
      case FETCH_OLDER_CALLLOG_FROM_NATIVE_DB:
      case UPDATE_STATUSES:
      case FETCH_STATUSES_FIRST_TIME:
        fireNewState(ServiceUiRequest.UPDATING_UI, null);
        break;
      case IDLE:
        fireNewState(ServiceUiRequest.UPDATING_UI_FINISHED, null);
        break;
      default:
        // nothing to do
    }

    LogUtils.logV("ActivitiesEngine.newState(): " + oldState + " -> " + mState);
  }
  private ServiceStatus updateDatabase(ArrayList<ActivityItem> activityList) {
    ServiceStatus errorStatus = ServiceStatus.SUCCESS;

    // add retrieved items to Activities table in db
    removeDuplicates(activityList);

    // update the newest activity
    Long temp = findLastStatusUpdateTime(activityList);
    if (temp != Long.MIN_VALUE) {
      mLastStatusUpdated = temp;
    }
    if (activityList.size() > 0) {
      LogUtils.logD("ActivityEngine Added ActivityItems = " + activityList.size());
      // update database
      errorStatus = mDb.addActivities(activityList);
      if (errorStatus == ServiceStatus.SUCCESS) {
        updateLatestStatusUpdateTime();
        updateOldestStatusUpdateTime();
      }
    }
    return errorStatus;
  }
 /**
  * Drive state machine for Activities sync. If we are currently fetching native events run the
  * appropriated ISyncHelper. Otherwise request retrieval of Activities from Server.
  */
 @Override
 protected void onTimeoutEvent() {
   // run now
   LogUtils.logV("ActivitiesEngine onTimeoutEvent:" + mState);
   switch (mState) {
     case FETCH_CALLOG_FIRST_TIME:
     case FETCH_OLDER_CALLLOG_FROM_NATIVE_DB:
     case FETCH_SMS_FROM_NATIVE_DB:
     case UPDATE_CALLOG_FROM_NATIVE:
       mActiveSyncHelper.run();
       if (mActiveSyncHelper != null) {
         // will be null when the last batch is read, so no need to
         // fire timeout event - onSyncComplete sets it to null
         setTimeout(READ_TIMELINES_TIMEOUT_MILLS);
       }
       break;
     case IDLE:
       addStatusesSyncRequest();
       break;
     default:
       // do nothing
   }
 }
 @SuppressWarnings("unchecked")
 private void setValue(Tags key, Object value) {
   switch (key) {
     case PAYLOAD:
       Hashtable payload = (Hashtable) value;
       if (payload.containsKey(CONVERSATION_ID)) {
         mConversationId = (String) payload.get(CONVERSATION_ID);
       }
       if (payload.containsKey(TOS)) {
         mTos = (List<String>) payload.get(TOS);
       }
       break;
     case USERID:
       mUserId = (Long) value;
       break;
     case TYPE:
       mType = (String) value;
       break;
     default:
       LogUtils.logE(
           "Conversation.setValue() key[" + key + "] value[" + value + "] Unsupported KEY");
   }
 }
  /**
   * Read Identity item from Parcel.
   *
   * @param in Parcel containing Identity information.
   */
  private void readFromParcel(Parcel in) {
    mPluginId = null;
    mNetwork = null;
    mNetworkUrl = null;
    mIconUrl = null;
    mAuthType = null;
    mIconMime = null;
    mOrder = -1;
    mName = null;
    mCapabilities = null;

    boolean[] validDataList = new boolean[MemberData.values().length];
    in.readBooleanArray(validDataList);

    if (validDataList[MemberData.PLUGIN_ID.ordinal()]) {
      mPluginId = in.readString();
    }

    if (validDataList[MemberData.NETWORK.ordinal()]) {
      mNetwork = in.readString();
    }

    if (validDataList[MemberData.NETWORK_URL.ordinal()]) {
      try {
        mNetworkUrl = new URL(in.readString());
      } catch (MalformedURLException e) {
        LogUtils.logW(
            "Identity.readFromParcel() " + "MalformedURLException on MemberData.NETWORK_URL");
      }
    }

    if (validDataList[MemberData.ICON_URL.ordinal()]) {
      try {
        mIconUrl = new URL(in.readString());
      } catch (MalformedURLException e) {
        LogUtils.logW(
            "Identity.readFromParcel() " + "MalformedURLException on MemberData.ICON_URL");
      }
    }

    if (validDataList[MemberData.AUTH_TYPE.ordinal()]) {
      mAuthType = in.readString();
    }

    if (validDataList[MemberData.ICON_MIME.ordinal()]) {
      mIconMime = in.readString();
    }

    if (validDataList[MemberData.ORDER.ordinal()]) {
      mOrder = in.readInt();
    }

    if (validDataList[MemberData.NAME.ordinal()]) {
      mName = in.readString();
    }

    int noOfCapabilities = in.readInt();
    if (noOfCapabilities > 0) {
      mCapabilities = new ArrayList<IdentityCapability>(noOfCapabilities);
      for (int i = 0; i < noOfCapabilities; i++) {
        IdentityCapability cap = IdentityCapability.CREATOR.createFromParcel(in);
        mCapabilities.add(cap);
      }
    }
  }
  /**
   * Sets the value of the member data item associated with the specified tag.
   *
   * @param tag Current tag.
   * @param val Value associated with the tag.
   */
  private void setValue(Tags tag, Object val) {
    switch (tag) {
      case AUTH_TYPE:
        mAuthType = (String) val;
        break;

      case ICON_MIME:
        mIconMime = (String) val;
        break;

      case ICON2_MIME:
        // TODO: Remove TAG value?
        // mIcon2Mime = (String)val;
        break;

      case ICON_URL:
        try {
          mIconUrl = new URL((String) val);
        } catch (MalformedURLException e) {
          LogUtils.logE("Wrong icon url: '" + val + "'");
          mIconUrl = null;
        }
        break;

      case ICON2_URL:
        try {
          mIcon2Url = new URL((String) val);
        } catch (MalformedURLException e) {
          LogUtils.logE("Wrong icon url: '" + val + "'");
          mIcon2Url = null;
        }
        break;

      case IDENTITY_CAPABILITY_LIST:
        /** Create id capability list. */
        @SuppressWarnings("unchecked")
        Vector<Hashtable<String, Object>> v = (Vector<Hashtable<String, Object>>) val;
        if (mCapabilities == null) {
          mCapabilities = new ArrayList<IdentityCapability>();
        }
        for (Hashtable<String, Object> obj : v) {
          IdentityCapability cap = new IdentityCapability();
          cap.createFromHashtable(obj);

          mCapabilities.add(cap);
        }
        break;

      case IDENTITY_MAIN_TAG:
        // Not currently handled.
        break;

      case NAME:
        mName = (String) val;
        break;

      case NETWORK:
        mNetwork = (String) val;
        break;

      case NETWORK_URL:
        try {
          mNetworkUrl = new URL((String) val);
        } catch (MalformedURLException e) {
          LogUtils.logE("Wrong network url: '" + val + "'");
          mNetworkUrl = null;
        }
        break;

      case ORDER:
        mOrder = (Integer) val;
        break;

      case PLUGIN_ID:
        mPluginId = (String) val;
        break;

      case ACTIVE:
        mActive = (Boolean) val;
        break;

      case CREATED:
        mCreated = (Long) val;
        break;

      case DISPLAY_NAME:
        mDisplayName = (String) val;
        break;

      case IDENTITY_ID:
        mIdentityId = (String) val;
        break;

      case IDENTITY_TYPE:
        mIdentityType = (String) val;
        break;

      case UPDATED:
        mUpdated = (Long) val;
        break;

      case USER_ID:
        mUserId = ((Long) val).intValue();
        break;

      case USER_NAME:
        mUserName = (String) val;
        break;

      case COUNTRY_LIST:
        if (mCountryList == null) {
          mCountryList = new ArrayList<String>();
        }
        break;

      default:
        // Do nothing.
        break;
    }
  }
 /** {@inheritDoc} */
 @Override
 public void onProgressEvent(ContactSyncEngine.State currentState, int percent) {
   LogUtils.logD("ActivityEngine onProgressEvent");
 }
 /** This method adds a request to get latest timelines. */
 protected void addGetNewPhonesCallsRequest() {
   LogUtils.logD("ActivitiesEngine addGetNewTimelinesRequest()");
   // TODO: I noticed there 2 NAB change events coming when the phone call
   // is made: try to filter one out
   addUiRequestToQueue(ServiceUiRequest.UPDATE_PHONE_CALLS, null);
 }
 /** This method adds a request to get latest timelines. */
 protected void addGetNewSMSRequest() {
   LogUtils.logD("ActivitiesEngine addGetNewTimelinesRequest()");
   addUiRequestToQueue(ServiceUiRequest.UPDATE_SMS, null);
 }
 /** This method adds a request to get older timelines. */
 public void addOlderTimelinesRequest() {
   LogUtils.logD("ActivitiesEngine addOlderTimelinesRequest()");
   addUiRequestToQueue(ServiceUiRequest.FETCH_TIMELINES, null);
 }
 /** This method adds a request to start sync of older statuses from Now+ server. */
 public void addGetOlderStatusesRequest() {
   LogUtils.logD("ActivityEngine addGetOlderStatusesRequest");
   addUiRequestToQueue(ServiceUiRequest.FETCH_STATUSES, null);
 }
 /** This method adds a request to start sync of the most recent statuses from Now+ server. */
 public void addStatusesSyncRequest() {
   LogUtils.logD("ActivityEngine addStatusesSyncRequest()");
   addUiRequestToQueue(ServiceUiRequest.UPDATE_STATUSES, null);
 }