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.contactsync;
  
  import java.security.InvalidParameterException;
  import java.util.ArrayList;
  
  import android.content.Context;
  import android.os.Handler;
  import android.os.Message;
  import android.os.PowerManager;
  
  import com.vodafone360.people.ApplicationCache;
  import com.vodafone360.people.Settings;
  import com.vodafone360.people.database.DatabaseHelper;
  import com.vodafone360.people.database.DatabaseHelper.DatabaseChangeType;
  import com.vodafone360.people.datatypes.BaseDataType;
  import com.vodafone360.people.datatypes.PushEvent;
  import com.vodafone360.people.engine.BaseEngine;
  import com.vodafone360.people.engine.EngineManager;
  import com.vodafone360.people.engine.EngineManager.EngineId;
  import com.vodafone360.people.engine.IEngineEventCallback;
  import com.vodafone360.people.engine.content.ThumbnailHandler;
  import com.vodafone360.people.service.PersistSettings;
  import com.vodafone360.people.service.ServiceStatus;
  import com.vodafone360.people.service.ServiceUiRequest;
  import com.vodafone360.people.service.agent.NetworkAgent;
  import com.vodafone360.people.service.agent.NetworkAgent.AgentState;
  import com.vodafone360.people.service.agent.UiAgent;
  import com.vodafone360.people.service.io.ResponseQueue;
  import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse;
  import com.vodafone360.people.utils.LogUtils;
  import com.vodafone360.people.utils.VersionUtils;
  
  /**
   * Implementation of engine handling Contact-sync. Contact sync is a multi-stage
   * process, involving sync of contacts from the native database, sync of server
   * contacts, fetching of groups and thumbnails. Each phase is handled by a
   * separate processor created and managed by the Contact sync engine.
   */
  public class ContactSyncEngine extends BaseEngine implements IContactSyncCallback,
          NativeContactsApi.ContactsObserver {
  
      /**
       * Definition of states for Contact sync.
       */
      public static enum State {
          IDLE,
          FETCHING_NATIVE_CONTACTS,
          UPDATING_NATIVE_CONTACTS,
          FETCHING_SERVER_CONTACTS,
          UPDATING_SERVER_CONTACTS,
      }
  
      /**
       * Defines the contact sync mode. The mode determines the sequence in which
       * the contact sync processors are run.
       */
      public static enum Mode {
          NONE,
          FULL_SYNC_FIRST_TIME,
          SERVER_SYNC,
          THUMBNAIL_SYNC,
          FETCH_NATIVE_SYNC,
          UPDATE_NATIVE_SYNC
      }
  
      /**
       * Mutex for thread synchronization
       */
      private final Object mMutex = new Object();
  
      private final UiAgent mUiAgent = mEventCallback.getUiAgent();
  
      private final ApplicationCache mCache = mEventCallback.getApplicationCache();
      
      /** The last known status of the contacts sync. */
      private ServiceStatus mLastStatus = ServiceStatus.SUCCESS;
  
      /**
       * Observer interface allowing interested parties to receive notification of
       * changes in Contact sync state.
       */
      public static interface IContactSyncObserver {
          /**
           * Called a contact sync finishes.
           * 
           * @param status SUCCESS if the sync was successful, a suitable error
           *            code otherwise.
           */
          void onSyncComplete(ServiceStatus status);
  
          /**
           * Called when the contact sync engine changes state or mode
           * 
           * @param mode Current mode
           * @param oldState Previous state
           * @param newState New state
           */
          void onContactSyncStateChange(Mode mode, State oldState, State newState);
  
          /**
           * Called to update interested parties on contact sync progress. This is
           * made up of two parts the state and the percentage. Each time the
           * state changes the percentage value will go back to 0.
           * 
           * @param currentState What the contact sync engine is currently doing
           * @param percent Percentage complete for the current task
           */
          void onProgressEvent(State currentState, int percent);
      }
  
      /**
       * Number of retries when first time sync fails
       */
      private static final long FULL_SYNC_RETRIES = 3;
  
      /**
       * Counter for first time sync failures
       */
      private int mServerSyncRetryCount = 0;
  
      /**
       * Current state of the contact sync engine (determines which processor is
       * currently active)
       */
      private State mState = State.IDLE;
      
      /**
       * Last state of the contact sync engine (to check if engine was paused).
       */
      private State mLastState = State.IDLE;
  
      /**
       * Current mode (or stragegy) the contact sync engine is in. The mode
       * determines the order which the processors are run.
       */
      private Mode mMode = Mode.NONE;
  
      /**
       * A failure list (currently containing unlocalised English strings) of
       * contacts which could not be sync'ed to the server.
       */
      private String mFailureList;
  
      /**
       * Database changed flag. Will be set to true if at any stage of the contact
       * sync the NowPlus database is changed.
       */
      private boolean mDatabaseChanged;
  
      /**
       * Last time the database was updated (in milliseconds)
       */
      private Long mLastDbUpdateTime;
  
      /**
       * DatabaseHelper object used for accessing NowPlus database.
       */
      private DatabaseHelper mDb;
  
      /**
       * Currently active processor (the processor which is running) or null
       */
      private BaseSyncProcessor mActiveProcessor;
  
      /**
       * The factory class which is used for creating processors for a particular
       * state.
       */
      private ProcessorFactory mProcessorFactory;
  
      /**
       * If a contacts sync is triggered by the Contact tab, we should wait a
       * little bit of time before beginning any heavy background work.  This
       * will give the main thread a chance to render its UI.
       */
      private static final long UI_PING_SYNC_DELAY = 3 * 1000L;
      
      /**
       * Time to wait after the user modifies a contact before a contact sync with
       * the server will be initiated (in milliseconds). The timer will be reset
       * each time a modification takes place.
       */
      private static final long SERVER_CONTACT_SYNC_TIMEOUT_MS = 30000L;
  
      /**
       * Time to wait after the user modifies a contact before a contact sync with
       * the native database will be initiated (in milliseconds). The timer will
       * be reset each time a modification takes place.
       */
      private static final long NATIVE_CONTACT_SYNC_TIMEOUT_MS = 30000L;
  
      /**
       * The time to wait before requesting a new server sync when the user is
       * using the application.
       */
      private static final long USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS = 20 * 60 * 1000;
  
      /**
       * Determines the time that should be waited between publishing database
       * change events. This is to prevent the UI updating too frequently during a
       * contact sync. The time is specified in nanoseconds.
       */
      private static final long UI_REFRESH_WAIT_TIME_NANO = 5000000000L;
      
      private static final long SYNC_ERROR_WAIT_TIMEOUT = 300000L;
  
      /**
       * Specifies the time that a server sync should be started relative to
       * current time in milliseconds. Will be NULL when a server sync timeout is
       * not required.
       */
      private volatile Long mServerSyncTimeout;
  
      /**
       * Specifies the time that a fetch native sync should be started relative to
       * current time in milliseconds. Will be NULL when a fetch native sync
       * timeout is not required.
       */
      private volatile Long mFetchNativeSyncTimeout;
  
      /**
       * Specifies the time that a update native sync should be started relative
       * to current time in milliseconds. Will be NULL when a update native sync
       * timeout is not required.
       */
      private volatile Long mUpdateNativeSyncTimeout;
  
      /**
       * Keeps track of the last time a server sync happened.
       */
      private long mLastServerSyncTime = 0L;
  
      /**
       * Flag which matches the persisted equivalent in the NowPlus database state
       * table. Will be set to true when the first time sync is completed and will
       * remain true until a remove user data is performed.
       */
      private boolean mFirstTimeSyncComplete;
  
      /**
       * Flag which matches the persisted equivalent in the NowPlus database state
       * table. Will be set to true when the first time sync is started and will
       * remain true until a remove user data is performed.
       */
      private boolean mFirstTimeSyncStarted;
  
      /**
       * Flag which matches the persisted equivalent in the NowPlus database state
       * table. Will be set to true when the part of the first time sync to fetch
       * native contacts is started and will remain true until a remove user data
       * is performed. Once this flag has been set to true, the next time a full
       * sync is started the full sync normal mode will be used instead of full
       * sync first time.
       */
      private volatile boolean mFirstTimeNativeSyncComplete;
  
      /**
       * True if a server sync should be started as soon as possible
       */
      private boolean mServerSyncRequired;
  
      /**
       * True if a native fetch sync should be started as soon as possible
       */
      private boolean mNativeFetchSyncRequired;
  
      /**
       * True if a native update sync should be started as soon as possible
       */
      private boolean mNativeUpdateSyncRequired;
  
      /**
       * True if a server thumbnail (avatar) sync should be started as soon as
       * possible
       */
      private boolean mThumbnailSyncRequired;
  
      /**
       * Maintains a list of contact sync observers
       */
      private final ArrayList<IContactSyncObserver> mEventCallbackList = new ArrayList<IContactSyncObserver>();
  
      /**
       * Current progress value (used to check if the progress has changed)
       */
      private int mCurrentProgressPercent = 0;
  
      /**
       * Flag which is set when the current processor changes the database
       */
      private boolean mDbChangedByProcessor;
  
      /**
       * Backup of the previous active request before processing the new one.
       */
      private ServiceUiRequest mActiveUiRequestBackup = null;
  
      /**
       * Native Contacts API access. The appropriate version should be used
       * depending on the SDK.
       */
      private final NativeContactsApi mNativeContactsApi = NativeContactsApi.getInstance();
      
      /**
       * True if changes on native contacts shall be detected.
       */
      private final boolean mFetchNativeContactsOnChange;
      
      /**
       * True if native contacts shall be fetched from native.
       */
      private final boolean mFetchNativeContacts;
      
      /**
       * True if changes on 360 contacts shall be forwarded to native contacts.
       */
      private final boolean mUpdateNativeContacts;
      
      /**
       * WakeLock to be used during full sync.
       */
      private PowerManager.WakeLock mWakeLock = null;
      
      /**
       * Service Context.
       */
      private Context mContext = null;
  
      /**
       * Check if sync is paused.
       */
      private boolean mIsSyncPaused = false;
  
      /**
       * Copy paused state in mIsSyncPausedLast to resume sync in engine's run method.
       */
      private boolean mIsSyncPausedLast = false;
      
      /**
       * Used to listen for NowPlus database change events. Such events will be
       * received when the user modifies a contact in the people application.
       */
      private final Handler mDbChangeHandler = new Handler() {
          /**
           * Processes a database change event
           */
          @Override
          public void handleMessage(Message msg) {
              processDbMessage(msg);
          }
      };
  
      /**
       * ContactSyncEngine constructor.
       * 
       * @param eventCallback Engine-event callback interface allowing engine to
       *            report back to client on request completion.
       * @param context Context.
       * @param db Handle to People database.
       * @param processorFactory the processor factory
       */
      public ContactSyncEngine(Context context, IEngineEventCallback eventCallback, DatabaseHelper db, 
      		ProcessorFactory factory) {
          super(eventCallback);
          
          mDb = db;
          mEngineId = EngineId.CONTACT_SYNC_ENGINE;
          mContext = context;
  
          final boolean enableNativeSync = VersionUtils.is2XPlatform() || !Settings.DISABLE_NATIVE_SYNC_AFTER_IMPORT_ON_ANDROID_1X;
          mFetchNativeContactsOnChange = Settings.ENABLE_FETCH_NATIVE_CONTACTS_ON_CHANGE && enableNativeSync;
          mFetchNativeContacts = Settings.ENABLE_FETCH_NATIVE_CONTACTS && enableNativeSync;
          mUpdateNativeContacts = Settings.ENABLE_UPDATE_NATIVE_CONTACTS && enableNativeSync;
          
          // use standard processor factory if provided one is null
          mProcessorFactory = (factory != null) ? factory : new DefaultProcessorFactory();
      }
  
      /**
       * Called after the engine has been created to do some extra initialisation.
       */
      @Override
      public void onCreate() {
          mDb.addEventCallback(mDbChangeHandler);
  
          PersistSettings setting1 = mDb.fetchOption(PersistSettings.Option.FIRST_TIME_SYNC_STARTED);
          PersistSettings setting2 = mDb.fetchOption(PersistSettings.Option.FIRST_TIME_SYNC_COMPLETE);
          PersistSettings setting3 = mDb.fetchOption(PersistSettings.Option.FIRST_TIME_NATIVE_SYNC_COMPLETE);
          
          if (setting1 != null) {
              mFirstTimeSyncStarted = setting1.getFirstTimeSyncStarted();
          }
          if (setting2 != null) {
              mFirstTimeSyncComplete = setting2.getFirstTimeSyncComplete();
          }
          if (setting3 != null) {
              mFirstTimeNativeSyncComplete = setting3.getFirstTimeNativeSyncComplete();
          }
          
          LogUtils.logI("ContactSyncEngine.onCreate() " +
          		"[mFirstTimeSyncStarted==" + mFirstTimeSyncStarted + 
          		", mFirstTimeSyncComplete==" + mFirstTimeSyncComplete + 
          		", mFirstTimeNativeSyncComplete==" + mFirstTimeNativeSyncComplete + "]");
  
          if (mFetchNativeContactsOnChange) {
              mNativeContactsApi.registerObserver(this);
          }
          if (mFirstTimeSyncComplete) {
              // native sync shall be performed only if the first time sync has
              // been completed
              startUpdateNativeContactSyncTimer();
              startFetchNativeContactSyncTimer();
          }
      }
  
      /**
       * Called just before engine is about to be closed. Cleans up resources.
       */
      @Override
      public void onDestroy() {
          if (mFetchNativeContactsOnChange) {
              mNativeContactsApi.unregisterObserver();
          }
          mDb.removeEventCallback(mDbChangeHandler);
      }
  
      /**
       * Triggers a full contact sync from the UI (via the service interface).
       * Will start a first time sync if necessary, otherwise a normal full sync
       * will be executed. A {@link ServiceUiRequest#NOWPLUSSYNC} event will be
       * sent to notify the UI when the sync has completed.
       */
      public void addUiStartFullSync() {
          // acquire wake lock before full contact sync is started.
          acquireSyncLock(); 
      	
          // reset last status to enable synchronization of contacts again
          mLastStatus = ServiceStatus.SUCCESS;
          
          LogUtils.logI("ContactSyncEngine.addUiStartFullSync()");
  
          emptyUiRequestQueue();
          addUiRequestToQueue(ServiceUiRequest.NOWPLUSSYNC, null);
      }
  
      /**
       * Tells the ContactSyncEngine that the user is actively using the service
       * and adjust sync timeout accordingly. Note: A server sync should occur
       * every 20 minutes during application intensive usage or immediately if the
       * application is used again after sleeping more than 20 minutes.
       */
      public void pingUserActivity() {
          LogUtils.logI("ContactSyncEngine.pingUserActivity()");
          long delay;
          synchronized (this) {
              final long currentDelay = System.currentTimeMillis() - mLastServerSyncTime;
              if ((mMode == Mode.FULL_SYNC_FIRST_TIME || mMode == Mode.SERVER_SYNC)
                      && mState != State.IDLE) {
                  // Already performing a sync, scheduling a new one in
                  // USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS
                  delay = USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS;
              } else if (currentDelay >= USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS) {
                  // Last sync timeout has passed, schedule a new one now
                  delay = UI_PING_SYNC_DELAY;
              } else if ((currentDelay < USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS)
                      && (mServerSyncTimeout == null)) {
                  // Last sync timeout has not passed but no new one is scheduled,
                  // schedule one to happen accordingly with the timeout
                  delay = USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS - currentDelay;
              } else {
                  // Nothing to do, a timeout will trigger the new sync
                  LogUtils.logD("A new sync is already scheduled in "
                          + (USER_ACTIVITY_SERVER_SYNC_TIMEOUT_MS - currentDelay) + " milliseconds");
                  return;
              }
          }
  
          LogUtils.logD("Scheduling a new sync in " + delay + " milliseconds");
          addUiRequestToQueue(ServiceUiRequest.NOWPLUSSYNC, delay);
      }
  
      /**
       * Determines if the first time contact sync has been completed.
       * 
       * @return true if completed.
       */
      public synchronized boolean isFirstTimeSyncComplete() {
          return mFirstTimeSyncComplete;
      }
  
      /**
       * Add observer of Contact-sync.
       * 
       * @param observer IContactSyncObserver handle.
       */
      public synchronized void addEventCallback(IContactSyncObserver observer) {
          if (!mEventCallbackList.contains(observer)) {
              mEventCallbackList.add(observer);
          }
      }
  
      /**
       * Starts a timer to trigger a server contact sync in a short while
       * (normally around 30 seconds).
       */
      private void startServerContactSyncTimer(long delay) {
          if (!Settings.ENABLE_SERVER_CONTACT_SYNC) {
              return;
          }
          synchronized (this) {
              if (mServerSyncTimeout == null) {
                  LogUtils
                          .logI("ContactSyncEngine - will sync contacts with server shortly... (in about "
                                  + delay + " milliseconds)");
              }
              mServerSyncTimeout = System.currentTimeMillis() + delay;
              if (mCurrentTimeout == null || mCurrentTimeout.compareTo(mServerSyncTimeout) > 0) {
                  mCurrentTimeout = mServerSyncTimeout;
              }
          }
          if (mCurrentTimeout.equals(mServerSyncTimeout)) {
              mEventCallback.kickWorkerThread();
          }
      }
  
      /**
       * Starts a timer to trigger a fetch native contact sync in a short while
       * (normally around 30 seconds).
       */
      private void startFetchNativeContactSyncTimer() {
          if (!mFetchNativeContacts) {
              return;
          }
          synchronized (this) {
              if (mFetchNativeSyncTimeout == null) {
                  LogUtils.logI("ContactSyncEngine - will fetch native contacts shortly...");
              }
              mFetchNativeSyncTimeout = System.currentTimeMillis() + NATIVE_CONTACT_SYNC_TIMEOUT_MS;
              if (mCurrentTimeout == null || mCurrentTimeout.compareTo(mFetchNativeSyncTimeout) > 0) {
                  mCurrentTimeout = mFetchNativeSyncTimeout;
                  mEventCallback.kickWorkerThread();
              }
          }
      }
  
      /**
       * Starts a timer to trigger a update native contact sync in a short while
       * (normally around 30 seconds).
       */
      private void startUpdateNativeContactSyncTimer() {
          if (!mUpdateNativeContacts) {
              return;
          }
          synchronized (this) {
              if (mUpdateNativeSyncTimeout == null) {
                  LogUtils.logI("ContactSyncEngine - will update native contacts shortly...");
              }
              mUpdateNativeSyncTimeout = System.currentTimeMillis() + NATIVE_CONTACT_SYNC_TIMEOUT_MS;
              if (mCurrentTimeout == null || mCurrentTimeout.compareTo(mUpdateNativeSyncTimeout) > 0) {
                  mCurrentTimeout = mUpdateNativeSyncTimeout;
              }
          }
          if (mCurrentTimeout.equals(mUpdateNativeSyncTimeout)) {
              mEventCallback.kickWorkerThread();
          }
      }
  
      /**
       * Helper function to start a processor running.
       * 
       * @param processor Processor which was created by processor factory.
       */
      private void startProcessor(BaseSyncProcessor processor) {
          if (mActiveProcessor != null) {
              LogUtils.logE("ContactSyncEngine.startProcessor - Cannot start " + processor.getClass()
                      + ", because the processor " + mActiveProcessor.getClass() + " is running");
              throw new RuntimeException(
                      "ContactSyncEngine - Cannot start processor while another is active");
          }
          mActiveProcessor = processor;
          mCurrentProgressPercent = -1;
          mDbChangedByProcessor = false;
          processor.start();
      }
  
      /**
       * Framework function to determine when the contact sync engine next needs
       * to run.
       * 
       * @return -1 if the engine does not need to run 0 if the engine needs to
       *         run as soon as possible x where x > 0, the engine needs to run
       *         when current time in milliseconds >= x
       */
      @Override
      public long getNextRunTime() {
  
          if (mLastStatus != ServiceStatus.SUCCESS) {
              return getCurrentTimeout();
          }
          
          if (isCommsResponseOutstanding()) {
              return 0;
          }
          if (isUiRequestOutstanding() && mActiveUiRequest == null) {
              return 0;
          }
          if (readyToStartServerSync()) {
              if (mServerSyncRequired || mThumbnailSyncRequired) {
                  return 0;
              } else if (mFirstTimeSyncStarted && !mFirstTimeSyncComplete
                      && mServerSyncRetryCount < FULL_SYNC_RETRIES) {
                  mServerSyncRetryCount++;
                  mServerSyncRequired = true;
                  return 0;
              }
          }
          if (mNativeFetchSyncRequired && readyToStartFetchNativeSync()) {
              return 0;
          }
          if (mNativeUpdateSyncRequired && readyToStartUpdateNativeSync()) {
              return 0;
          }
          return getCurrentTimeout();
      }
  
      /**
       * Called by framework when {@link #getNextRunTime()} reports that the
       * engine needs to run, to carry out the next task. Each task should not
       * take longer than a second to complete.
       */
      @Override
      public void run() {
     	
          // Pause contact sync engine.
          if (mIsSyncPaused) {
              return;
          }
          
          if (mIsSyncPausedLast) {
              mIsSyncPausedLast = false;
              if (mFirstTimeSyncComplete) {       
                  startServerSync();
              } else {
                  resumeFirstTimeSync();
              }
              return;
          }
      	
          if (processTimeout()) {
              return;
          }
          if (isUiRequestOutstanding()) {
              mActiveUiRequestBackup = mActiveUiRequest;
              if (processUiQueue()) {
                  return;
              }
          }
          if (isCommsResponseOutstanding() && processCommsInQueue()) {
              return;
          }
          if (readyToStartServerSync()) {
              if (mThumbnailSyncRequired) {
                  startThumbnailSync();
                  return;
              }
              if (mServerSyncRequired) {
                  startServerSync();
                  return;
              }
          }
  
          if (mNativeFetchSyncRequired && readyToStartFetchNativeSync()) {
              startFetchNativeSync();
              return;
          }
          if (mNativeUpdateSyncRequired && readyToStartUpdateNativeSync()) {
              startUpdateNativeSync();
              return;
          }
      }
  
      /**
       * Called by base class when a contact sync UI request has been completed.
       * Not currently used.
       */
      @Override
      protected void onRequestComplete() {
      }
  
      /**
       * Called by base class when a timeout has been completed. If there is an
       * active processor the timeout event will be passed to it, otherwise the
       * engine will check if it needs to schedule a new sync and set the next
       * pending timeout.
       */
      @Override
      protected void onTimeoutEvent() {
          if (mActiveProcessor != null) {
              mActiveProcessor.onTimeoutEvent();
          } else {
              startSyncIfRequired();
              setTimeoutIfRequired();
          }
      }
  
      /**
       * Based on current timeout values schedules a new sync if required.
       */
      private void startSyncIfRequired() {
          if (mFirstTimeSyncStarted && !mFirstTimeSyncComplete) {
          	mServerSyncRequired = true;
              mServerSyncRetryCount = 0;
          }
          long currentTimeMs = System.currentTimeMillis();
          if (mServerSyncTimeout != null && mServerSyncTimeout.longValue() < currentTimeMs) {
              mServerSyncRequired = true;
              mServerSyncTimeout = null;
          } else if (mFetchNativeSyncTimeout != null
                  && mFetchNativeSyncTimeout.longValue() < currentTimeMs) {
              mNativeFetchSyncRequired = true;
              mFetchNativeSyncTimeout = null;
          } else if (mUpdateNativeSyncTimeout != null
                  && mUpdateNativeSyncTimeout.longValue() < currentTimeMs) {
              mNativeUpdateSyncRequired = true;
              mUpdateNativeSyncTimeout = null;
          }
      }
  
      /**
       * Called when a response to a request or a push message is received from
       * the server. Push messages are processed by the engine, responses are
       * passed to the active processor.
       * 
       * @param resp Response or push message received
       */
      @Override
      protected void processCommsResponse(DecodedResponse resp) {
          if (processPushEvent(resp)) {
              return;
          }
          if (resp.mDataTypes != null && resp.mDataTypes.size() > 0) {
              LogUtils.logD("ContactSyncEngine.processCommsResponse: Req ID = " + resp.mReqId
                      + ", type = " + resp.mDataTypes.get(0).getType());
          } else {
              LogUtils.logD("ContactSyncEngine.processCommsResponse: Req ID = " + resp.mReqId
                      + ", type = NULL");
          }
          if (mActiveProcessor != null) {
              mActiveProcessor.processCommsResponse(resp);
          }
      }
  
      /**
       * Determines if a given response is a push message and processes in this
       * case TODO: we need the check for Me Profile be migrated to he new engine
       * 
       * @param resp Response to check and process
       * @return true if the response was processed
       */
      private boolean processPushEvent(DecodedResponse resp) {
          if (resp.mDataTypes == null || resp.mDataTypes.size() == 0) {
              return false;
          }
          BaseDataType dataType = resp.mDataTypes.get(0);
          if ((dataType == null) || dataType.getType() != BaseDataType.PUSH_EVENT_DATA_TYPE) {
              return false;
          }
          PushEvent pushEvent = (PushEvent)dataType;
          LogUtils.logV("Push Event Type = " + pushEvent.mMessageType);
          switch (pushEvent.mMessageType) {
              case CONTACTS_CHANGE:
                  LogUtils.logI("ContactSyncEngine.processCommsResponse - Contacts changed push message received");
                  mServerSyncRequired = true;
                  // fetch the newest groups
                  EngineManager.getInstance().getGroupsEngine().addUiGetGroupsRequest(); 
                  mEventCallback.kickWorkerThread();
                  break;
              case SYSTEM_NOTIFICATION:
                  LogUtils.logI("ContactSyncEngine.processCommsResponse - System notification push message received");
                  break;
              default:
                  // do nothing.
                  break;
          }
          return true;
      }
  
      /**
       * Called by base class to process a NOWPLUSSYNC UI request. This will be
       * called to process a full sync or server sync UI request.
       * 
       * @param requestId ID of the request to process, only
       *            ServiceUiRequest.NOWPLUSSYNC is currently supported.
       * @param data Type is determined by request ID, in case of NOWPLUSSYNC this
       *            is a flag which determines if a full sync is required (true =
       *            full sync, false = server sync).
       */
      @Override
      protected void processUiRequest(ServiceUiRequest requestId, Object data) {        
          switch (requestId) {
              case NOWPLUSSYNC:  
                  if (data ==  null) {
                  	clearCurrentSyncAndPatchBaseEngine();
                  	mServerSyncRetryCount = 0;
                  	startServerSync();
                  } 
                  else {
                  	startServerContactSyncTimer((Long) data);
                  }
                  break;
              default:
                  // do nothing.
                  break;
          }
      }
  
      /**
       * Clears the current sync and make sure that if we cancel a previous sync,
       * it doesn't notify a wrong UI request. TODO: Find another way to not have
       * to hack the BaseEngine!
       */
      private void clearCurrentSyncAndPatchBaseEngine() {
          // Cancel background sync
          if (mActiveProcessor != null) {
              // the mActiveUiRequest is already the new one so if
              // onCompleteUiRequest(Error) is called,
              // this will reset it to null even if we didn't start to process it.
              ServiceUiRequest newActiveUiRequest = mActiveUiRequest;
              mActiveUiRequest = mActiveUiRequestBackup;
              mActiveProcessor.cancel();
  //            cancelSync();
              // restore the active UI request...
              mActiveUiRequest = newActiveUiRequest;
              mActiveProcessor = null;
          }
          newState(State.IDLE);
      }
  
      /**
       * Checks if a server sync can be started based on network conditions and
       * engine state
       * 
       * @return true if a sync can be started, false otherwise.
       */
      private boolean readyToStartServerSync() {
          if (!Settings.ENABLE_SERVER_CONTACT_SYNC) {
              return false;
          }
          if (!EngineManager.getInstance().getSyncMeEngine().isFirstTimeMeSyncComplete()) {
              return false;
          }
          if (!mFirstTimeSyncStarted) {
              return false;
          }
          if (mState != State.IDLE || NetworkAgent.getAgentState() != AgentState.CONNECTED) {
              return false;
          }
          return true;
      }
  
      /**
       * Checks if a fetch native sync can be started based on network conditions
       * and engine state
       * 
       * @return true if a sync can be started, false otherwise.
       */
      private boolean readyToStartFetchNativeSync() {
          if (!mFetchNativeContacts) {
              return false;
          }
          if (!mFirstTimeSyncStarted) {
              return false;
          }
          if (mState != State.IDLE) {
              return false;
          }
          return true;
      }
  
      /**
       * Checks if a update native sync can be started based on network conditions
       * and engine state
       * 
       * @return true if a sync can be started, false otherwise.
       */
      private boolean readyToStartUpdateNativeSync() {
          if (!mUpdateNativeContacts) {
              return false;
          }
          if (!mFirstTimeSyncStarted) {
              return false;
          }
          if (mState != State.IDLE) {
              return false;
          }
          return true;
      }
  
      
      /**
       * This is added to support resuming of contact sync from where it is paused during first time sync.
       */
      public void resumeFirstTimeSync() {
          mFailureList = "";
          mDatabaseChanged = false;
          mServerSyncTimeout = null;
          mServerSyncRequired = false;
          setFirstTimeSyncStarted(true);
          mMode = Mode.FULL_SYNC_FIRST_TIME;
          nextTaskFullSyncFirstTime();
      }
      
      /**
       * Starts a full sync. If the native contacts haven't yet been fetched then
       * a first time sync will be started, otherwise will start a normal full
       * sync. Full syncs are always initiated from the UI (via UI request).
       */
      public void startServerSync() {
      	mFailureList = "";
          mDatabaseChanged = false;
          mServerSyncTimeout = null;
          mServerSyncRequired = false;
          setFirstTimeSyncStarted(true);
          
          if (mFirstTimeNativeSyncComplete) {
              LogUtils.logI("ContactSyncEngine.startServerSync - server sync");
              mMode = Mode.SERVER_SYNC;
              nextTaskServerSync();
          } else {
              LogUtils.logI("ContactSyncEngine.startServerSync - first time full sync");
              mMode = Mode.FULL_SYNC_FIRST_TIME;
              nextTaskFullSyncFirstTime();
          }
      }
  
      /**
       * Starts a background thumbnail sync
       */
      private void startThumbnailSync() {
          mThumbnailSyncRequired = false;
          mFailureList = "";
          mDatabaseChanged = false;
          mMode = Mode.THUMBNAIL_SYNC;
          nextTaskThumbnailSync();
      }
  
      /**
       * Starts a background fetch native contacts sync
       */
      private void startFetchNativeSync() {
          mNativeFetchSyncRequired = false;
          mFailureList = "";
          mDatabaseChanged = false;
          mMode = Mode.FETCH_NATIVE_SYNC;
          mFetchNativeSyncTimeout = null;
          setTimeoutIfRequired();
          nextTaskFetchNativeContacts();
      }
  
      /**
       * Starts a background update native contacts sync
       */
      private void startUpdateNativeSync() {
          mNativeUpdateSyncRequired = false;
          mFailureList = "";
          mDatabaseChanged = false;
          mMode = Mode.UPDATE_NATIVE_SYNC;
          mUpdateNativeSyncTimeout = null;
          setTimeoutIfRequired();
          nextTaskUpdateNativeContacts();
      }
  
      /**
       * Helper function to start the fetch native contacts processor
       * 
       * @param isFirstTimeSync true if importing native contacts for the first time
       * @return if this type of sync is enabled in the settings, false otherwise.
       */
      private boolean startFetchNativeContacts(boolean isFirstTimeSync) {
          if (mFetchNativeContacts || (Settings.ENABLE_FETCH_NATIVE_CONTACTS && isFirstTimeSync)) {
              newState(State.FETCHING_NATIVE_CONTACTS);
              startProcessor(mProcessorFactory.create(ProcessorFactory.FETCH_NATIVE_CONTACTS, this, mDb));
              return true;
          }
          return false;
      }
  
      /**
       * Helper function to start the update native contacts processor
       * 
       * @return if this type of sync is enabled in the settings, false otherwise.
       */
      private boolean startUpdateNativeContacts() {
          if (mUpdateNativeContacts) {
              newState(State.UPDATING_NATIVE_CONTACTS);
              startProcessor(mProcessorFactory.create(ProcessorFactory.UPDATE_NATIVE_CONTACTS, this, mDb));
              return true;
          }
          return false;
      }
  
      /**
       * Helper function to start the download server contacts processor
       * 
       * @return if this type of sync is enabled in the settings, false otherwise.
       */
      private boolean startDownloadServerContacts() {
          if (Settings.ENABLE_SERVER_CONTACT_SYNC) {
              newState(State.FETCHING_SERVER_CONTACTS);
              startProcessor(mProcessorFactory.create(ProcessorFactory.DOWNLOAD_SERVER_CONTACTS, this, mDb));
              return true;
          }
          else 
              return false;
      }
  
      /**
       * Helper function to start the upload server contacts processor
       * 
       * @return if this type of sync is enabled in the settings, false otherwise.
       */
      private boolean startUploadServerContacts() {
          if (Settings.ENABLE_SERVER_CONTACT_SYNC) {
              newState(State.UPDATING_SERVER_CONTACTS);
              startProcessor(mProcessorFactory.create(ProcessorFactory.UPLOAD_SERVER_CONTACTS, this, mDb));
              return true;
          }
          else
          	return false;
      }
  
      /**
       * Helper function to start the download thumbnails processor
       * 
       * @return if this type of sync is enabled in the settings, false otherwise.
       */
      private boolean startDownloadServerThumbnails() {
          if (Settings.ENABLE_THUMBNAIL_SYNC) {
              ThumbnailHandler.getInstance().downloadContactThumbnails();
              return true;
          }
          else
          	return false;
      }
  
      /**
       * Called by a processor when it has completed. Will move to the next task.
       * When the active contact sync has totally finished, will complete any
       * pending UI request.
       * 
       * @param status Status of the sync from the processor, any error codes will
       *            stop the sync.
       * @param failureList Contains a list of sync failure information which can
       *            be used as a summary at the end. Otherwise should be an empty
       *            string.
       * @param data Any processor specific data to pass back to the engine. Not
       *            currently used.
       */
      @Override
      public void onProcessorComplete(ServiceStatus status, String failureList, Object data) {
          if (mState == State.IDLE) {
              return;
          }
          if (mActiveProcessor != null) {
              mActiveProcessor.onComplete();
          }
          mActiveProcessor = null;
          mFailureList += failureList;
          if (status != ServiceStatus.SUCCESS) {
              LogUtils.logE("ContactSyncEngine.onProcessorComplete - Failed during " + mState
                      + " with error " + status);
              completeSync(status);
              return;
          }
          if (mDbChangedByProcessor) {
              switch (mState) {
                  case FETCHING_NATIVE_CONTACTS:
                      mServerSyncRequired = true;
                      break;
                  case FETCHING_SERVER_CONTACTS:
                      mThumbnailSyncRequired = true;
                      if (mUpdateNativeContacts) {
                          mNativeUpdateSyncRequired = true;
                      }
                      break;
                  default:
                      // Do nothing.
                      break;
              }
          }
          switch (mMode) {
              case FULL_SYNC_FIRST_TIME:
                  nextTaskFullSyncFirstTime();
                  break;
              case SERVER_SYNC:
                  nextTaskServerSync();
                  break;
              case FETCH_NATIVE_SYNC:
                  nextTaskFetchNativeContacts();
                  break;
              case UPDATE_NATIVE_SYNC:
                  nextTaskUpdateNativeContacts();
                  break;
              case THUMBNAIL_SYNC:
                  nextTaskThumbnailSync();
                  break;
              default:
                  LogUtils.logE("ContactSyncEngine.onProcessorComplete - Unexpected mode: " + mMode);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Moves to the next state for the full sync first time mode, and runs the
       * appropriate processor. Completes the UI request when the sync is complete
       * (if one is pending).
       */
      private void nextTaskFullSyncFirstTime() {
          
          switch (mState) {
              case IDLE:
                  if (startFetchNativeContacts(true)) { 
                      return;
                  }
                  // Fall through
              case FETCHING_NATIVE_CONTACTS:
                  setFirstTimeNativeSyncComplete(true);
                  if (startUploadServerContacts()) {
                      return; 
                  }
                  // Fall through
              case UPDATING_SERVER_CONTACTS:
                  if (startDownloadServerContacts()) {
                      return;
                  }
                  // Fall through
              case FETCHING_SERVER_CONTACTS:
                  mThumbnailSyncRequired = true;
                  mLastServerSyncTime = System.currentTimeMillis();
                  setFirstTimeSyncComplete(true);
                  completeSync(ServiceStatus.SUCCESS);
                  return;
              default:
                  LogUtils.logE("ContactSyncEngine.nextTaskFullSyncFirstTime - Unexpected state: "
                              + mState);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Moves to the next state for the server sync mode, and runs the
       * appropriate processor. Completes the UI request when the sync is complete
       * (if one is pending).
       */
      private void nextTaskServerSync() {
          switch (mState) {
              case IDLE:
                  if (startUploadServerContacts()) {
                      return;
                  }
                  // Fall through
              case UPDATING_SERVER_CONTACTS:
                  if (startDownloadServerContacts()) {
                      return;
                  }
                  // Fall through
              case FETCHING_SERVER_CONTACTS:
                  // force a thumbnail sync in case nothing in the database
                  // changed but we still have failing
                  // thumbnails that we should retry to download
                  mThumbnailSyncRequired = true;
                  mLastServerSyncTime = System.currentTimeMillis();
                  setFirstTimeSyncComplete(true);
                  completeSync(ServiceStatus.SUCCESS);
                  return;
              default:
                  LogUtils.logE("ContactSyncEngine.nextTaskServerSync - Unexpected state: " + mState);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Moves to the next state for the fetch native contacts mode, and runs the
       * appropriate processor. Completes the UI request when the sync is complete
       * (if one is pending).
       */
      private void nextTaskFetchNativeContacts() {
          switch (mState) {
              case IDLE:
                  if (startFetchNativeContacts(false)) {
                      return;
                  }
                  // Fall through
              case FETCHING_NATIVE_CONTACTS:
                  if (startUploadServerContacts()) {
                      return;
                  }
                  // Fall through
              case UPDATING_SERVER_CONTACTS:
                  completeSync(ServiceStatus.SUCCESS);
                  return;
              default:
                  LogUtils.logE("ContactSyncEngine.nextTaskFetchNativeContacts - Unexpected state: "
                          + mState);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Moves to the next state for the update native contacts mode, and runs the
       * appropriate processor. Completes the UI request when the sync is complete
       * (if one is pending).
       */
      private void nextTaskUpdateNativeContacts() {
          switch (mState) {
              case IDLE:
                  if (startUpdateNativeContacts()) {
                      return;
                  }
                  // Fall through
              case UPDATING_NATIVE_CONTACTS:
                  completeSync(ServiceStatus.SUCCESS);
                  return;
              default:
                  LogUtils.logE("ContactSyncEngine.nextTaskUpdateNativeContacts - Unexpected state: "
                          + mState);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Moves to the next state for the thumbnail sync mode, and runs the
       * appropriate processor. Completes the UI request when the sync is complete
       * (if one is pending).
       */
      private void nextTaskThumbnailSync() {
          switch (mState) {
              case IDLE:
                  if (startDownloadServerThumbnails()) {
                      return;
                  }
              default:
                  LogUtils.logE("ContactSyncEngine.nextTaskThumbnailSync - Unexpected state: "
                          + mState);
                  completeSync(ServiceStatus.ERROR_SYNC_FAILED);
          }
      }
  
      /**
       * Changes the state of the engine and informs the observers.
       * 
       * @param newState The new state
       */
      private void newState(State newState) {
          mLastState = mState;
          synchronized (mMutex) {
              if (newState == mState) {
                  return;
              }
              mState = newState;
              if (mState == State.IDLE) {
                  ApplicationCache.setSyncBusy(false);
              } else {
                  ApplicationCache.setSyncBusy(true);
              }
          }
          LogUtils.logI("ContactSyncEngine.newState: " + mLastState + " -> " + mState);
          fireStateChangeEvent(mMode, mLastState, mState);
      }
  
      /**
       * Called when the current mode has finished all the sync tasks. Completes
       * the UI request if one is pending, sends an event to the observer and a
       * database change event if necessary.
       * 
       * @param status The overall status of the contact sync.
       */
      private void completeSync(ServiceStatus status) {
          // release wake lock acquired during full sync    	
          releaseSyncLock();
          if (mState == State.IDLE) {
              return;
          }
          if (mDatabaseChanged) {
              LogUtils.logD("ContactSyncEngine.completeSync - Firing Db changed event");
              mDb.fireDatabaseChangedEvent(DatabaseChangeType.CONTACTS, true);
              mDatabaseChanged = false;
          }
          mActiveProcessor = null;
          
          newState(State.IDLE);
          
          mMode = Mode.NONE;
          completeUiRequest(status, mFailureList);
  
          mCache.setSyncStatus(new SyncStatus(status));
          mUiAgent.sendUnsolicitedUiEvent(ServiceUiRequest.UPDATE_SYNC_STATE, null);
  
          if (mFirstTimeSyncComplete) {
              fireSyncCompleteEvent(status);
          }
  
          if (ServiceStatus.SUCCESS == status) {
              startSyncIfRequired();
          } else {
              setTimeout(SYNC_ERROR_WAIT_TIMEOUT);
          }
          
          mLastStatus = status;
          
          setTimeoutIfRequired();
      }
  
      /**
       * Sets the current timeout to the next pending timer and kicks the engine
       * if necessary.
       */
      private synchronized void setTimeoutIfRequired() {
          Long initTimeout = mCurrentTimeout;        
          if (mCurrentTimeout == null
                  || (mServerSyncTimeout != null && mServerSyncTimeout.compareTo(mCurrentTimeout) < 0)) {
              mCurrentTimeout = mServerSyncTimeout;
          }
          if (mCurrentTimeout == null
                  || (mFetchNativeSyncTimeout != null && mFetchNativeSyncTimeout
                          .compareTo(mCurrentTimeout) < 0)) {
              mCurrentTimeout = mFetchNativeSyncTimeout;
          }
          if (mCurrentTimeout == null
                  || (mUpdateNativeSyncTimeout != null && mUpdateNativeSyncTimeout
                          .compareTo(mCurrentTimeout) < 0)) {
              mCurrentTimeout = mUpdateNativeSyncTimeout;
          }
          if (mCurrentTimeout != null && !mCurrentTimeout.equals(initTimeout)) {
              mEventCallback.kickWorkerThread();
          }
      }
  
      /**
       * Called by the active processor to indicate that the NowPlus database has
       * changed.
       */
      @Override
      public void onDatabaseChanged() {
          mDatabaseChanged = true;
          mDbChangedByProcessor = true;
          final long currentTime = System.nanoTime();
          if (mLastDbUpdateTime == null
                  || mLastDbUpdateTime.longValue() + UI_REFRESH_WAIT_TIME_NANO < currentTime) {
              LogUtils.logD("ContactSyncEngine.onDatabaseChanged - Updating UI...");
              mDatabaseChanged = false;
              mLastDbUpdateTime = currentTime;
              mDb.fireDatabaseChangedEvent(DatabaseChangeType.CONTACTS, true);
          }
      }
  
      /**
       * Used by processors to fetch this engine. The BaseEngine reference is
       * needed to send requests to the server.
       * 
       * @return The BaseEngine reference of this engine.
       */
      @Override
      public BaseEngine getEngine() {
          return this;
      }
  
      /**
       * Used by active processor to set a timeout.
       * 
       * @param timeout Timeout value based on current time in milliseconds
       */
      @Override
      public void setTimeout(long timeout) {
          super.setTimeout(timeout);
      }
  
      /**
       * Used by active processor to set the current progress.
       * 
       * @param SyncStatus Status of the processor, must not be NULL.
       * @throws InvalidParameterException when SyncStatus is NULL.
       */
      @Override
      public void setSyncStatus(final SyncStatus syncStatus) {
          if (syncStatus == null) {
              throw new InvalidParameterException(
                      "ContactSyncEngine.setSyncStatus() SyncStatus cannot be NULL");
          }
  
          /** Indicate that this is a first time sync in progress. **/
          syncStatus.firstTimeSync(mMode == Mode.FULL_SYNC_FIRST_TIME);
  
          mCache.setSyncStatus(syncStatus);
          mUiAgent.sendUnsolicitedUiEvent(ServiceUiRequest.UPDATE_SYNC_STATE, null);
  
          if (mState != State.IDLE && syncStatus.getProgress() != mCurrentProgressPercent) {
              mCurrentProgressPercent = syncStatus.getProgress();
              LogUtils.logI("ContactSyncEngine: Task " + mState + " is " + syncStatus.getProgress()
                      + "% complete");
              fireProgressEvent(mState, syncStatus.getProgress());
          }
      }
  
      /**
       * Called by active processor when issuing a request to store the request id
       * in the base engine class.
       */
      @Override
      public void setActiveRequestId(int reqId) {
          setReqId(reqId);
      }
  
      /**
       * Helper function to update the database when the state of the
       * {@link #mFirstTimeSyncStarted} flag changes.
       * 
       * @param value New value to the flag. True indicates that first time sync
       *            has been started. The flag is never set to false again by the
       *            engine, it will be only set to false when a remove user data
       *            is done (and the database is deleted).
       * @return SUCCESS or a suitable error code if the database could not be
       *         updated.
       */
      private ServiceStatus setFirstTimeSyncStarted(boolean value) {
          if (mFirstTimeSyncStarted == value) {
              return ServiceStatus.SUCCESS;
          }
          PersistSettings setting = new PersistSettings();
          setting.putFirstTimeSyncStarted(value);
          ServiceStatus status = mDb.setOption(setting);
          if (ServiceStatus.SUCCESS == status) {
              synchronized (this) {
                  mFirstTimeSyncStarted = value;
              }
          }
          return status;
      }
  
      /**
       * Helper function to update the database when the state of the
       * {@link #mFirstTimeSyncComplete} flag changes.
       * 
       * @param value New value to the flag. True indicates that first time sync
       *            has been completed. The flag is never set to false again by
       *            the engine, it will be only set to false when a remove user
       *            data is done (and the database is deleted).
       * @return SUCCESS or a suitable error code if the database could not be
       *         updated.
       */
      private ServiceStatus setFirstTimeSyncComplete(boolean value) {
          if (mFirstTimeSyncComplete == value) {
              return ServiceStatus.SUCCESS;
          }
          PersistSettings setting = new PersistSettings();
          setting.putFirstTimeSyncComplete(value);
          ServiceStatus status = mDb.setOption(setting);
          if (ServiceStatus.SUCCESS == status) {
              synchronized (this) {
                  mFirstTimeSyncComplete = value;
              }
          }
          return status;
      }
  
      /**
       * Helper function to update the database when the state of the
       * {@link #mFirstTimeNativeSyncComplete} flag changes.
       * 
       * @param value New value to the flag. True indicates that the native fetch
       *            part of the first time sync has been completed. The flag is
       *            never set to false again by the engine, it will be only set to
       *            false when a remove user data is done (and the database is
       *            deleted).
       * @return SUCCESS or a suitable error code if the database could not be
       *         updated.
       */
      private ServiceStatus setFirstTimeNativeSyncComplete(boolean value) {
          if (mFirstTimeSyncComplete == value) {
              return ServiceStatus.SUCCESS;
          }
          PersistSettings setting = new PersistSettings();
          setting.putFirstTimeNativeSyncComplete(value);
          ServiceStatus status = mDb.setOption(setting);
          if (ServiceStatus.SUCCESS == status) {
          	mFirstTimeNativeSyncComplete = value;
          }
          return status;
      }
  
      /**
       * Called when a database change event is received from the DatabaseHelper.
       * Only internal database change events are processed, external change
       * events are generated by the contact sync engine.
       * 
       * @param msg The message indicating the type of event
       */
      private void processDbMessage(Message message) {
          final ServiceUiRequest event = ServiceUiRequest.getUiEvent(message.what);
          switch (event) {
              case DATABASE_CHANGED_EVENT:
                  if (message.arg1 == DatabaseHelper.DatabaseChangeType.CONTACTS.ordinal()
                          && message.arg2 == 0) {
                      LogUtils.logV("ContactSyncEngine.processDbMessage - Contacts have changed");
                      // startMeProfileSyncTimer();
                      startServerContactSyncTimer(SERVER_CONTACT_SYNC_TIMEOUT_MS);
                      startUpdateNativeContactSyncTimer();
                  }
                  break;
              default:
                  // Do nothing.
                  break;
          }
      }
  
      /**
       * Notifies observers when a state or mode change occurs.
       * 
       * @param mode Current mode
       * @param previousState State before the change
       * @param newState State after the change.
       */
      private void fireStateChangeEvent(Mode mode, State previousState, State newState) {
          ArrayList<IContactSyncObserver> tempList = new ArrayList<IContactSyncObserver>();
          synchronized (this) {
              tempList.addAll(mEventCallbackList);
          }
          for (IContactSyncObserver observer : tempList) {
              observer.onContactSyncStateChange(mode, previousState, newState);
          }
      }
  
      /**
       * Notifies observers when a contact sync complete event occurs.
       * 
       * @param status SUCCESS or a suitable error code
       */
      private void fireSyncCompleteEvent(ServiceStatus status) {
          ArrayList<IContactSyncObserver> tempList = new ArrayList<IContactSyncObserver>();
          synchronized (this) {
              tempList.addAll(mEventCallbackList);
          }
          for (IContactSyncObserver observer : tempList) {
              observer.onSyncComplete(status);
          }
      }
  
      /**
       * Notifies observers when the progress value changes for the current sync.
       * 
       * @param currentState Current sync task being processed
       * @param percent Progress of task (between 0 and 100 percent)
       */
      private void fireProgressEvent(State currentState, int percent) {
          ArrayList<IContactSyncObserver> tempList = new ArrayList<IContactSyncObserver>();
          synchronized (this) {
              tempList.addAll(mEventCallbackList);
          }
          for (IContactSyncObserver observer : tempList) {
              observer.onProgressEvent(currentState, percent);
          }
      }
  
      /**
       * Called by framework to warn the engine that a remove user data is about
       * to start. Sets flags and kicks the engine.
       */
      @Override
      public void onReset() {
          
          synchronized (this) {
              
              mServerSyncRetryCount = 0;
              mState = State.IDLE;
              mMode = Mode.NONE;
              mFailureList = null;
              mDatabaseChanged = false;
              mLastDbUpdateTime = 0L;
              mActiveProcessor = null;
              mServerSyncTimeout = null;
              mFetchNativeSyncTimeout = null;
              mUpdateNativeSyncTimeout = null;
              mLastServerSyncTime = 0L;
              mFirstTimeSyncComplete = false;
              mFirstTimeSyncStarted = false;
              mFirstTimeNativeSyncComplete = false;
              mServerSyncRequired = false;
              mNativeFetchSyncRequired = false;
              mNativeUpdateSyncRequired = false;
              mThumbnailSyncRequired = false;
              mCurrentProgressPercent = 0;
              mDbChangedByProcessor = false;
              mActiveUiRequestBackup = null;
              
              ApplicationCache.setSyncBusy(false);
          }
          super.onReset();
          ThumbnailHandler.getInstance().reset();
      }
  
      /**
       * @see NativeContactsApi.ContactsObserver#onChange()
       */
      @Override
      public void onChange() {
  
          LogUtils.logD("ContactSyncEngine.onChange(): changes detected on native side.");
          // changes detected on native side, start the timer for the
          // FetchNativeContacts processor.
          startFetchNativeContactSyncTimer();
      }
      
      /**
       * Called before full contact sync is started to acquire partial wake lock. 
       * This will ensure that contact sync will continue even if device sleeps.
       */
      public void acquireSyncLock() {
          if(mWakeLock == null) {
              final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
              mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SyncWakeLock");
          }
      	    
          if (mWakeLock != null && !mWakeLock.isHeld()) {
              mWakeLock.acquire();
          }
      }
      
      /**
       * Called after full sync is finished (either successfully or erroneously) to 
       * release partial wake lock.
       */
      public void releaseSyncLock() {
          if (mWakeLock != null && mWakeLock.isHeld()) {
              mWakeLock.release();
          }
      } 
      
      /**
       * Signal contact sync engine to pause ongoing sync. 
       * 
       */
      public synchronized void pauseSync() {
          // Pause sync if it is in progress.
          if (ApplicationCache.isSyncBusy()) {
              mIsSyncPaused = true;
          }
      }
      
      /**
       * Signal contact sync engine to resume sync. 
       * 
       */
      public synchronized void resumeSync() {
          if (mIsSyncPaused) {
  
              // Get last contact sync engine state from where contact sync can be resumed.
              mState = mLastState;
      	    
              mIsSyncPausedLast = mIsSyncPaused;
              mIsSyncPaused = false;
      	    
              // Remove any stale responses from response queue.
              ResponseQueue.getInstance().clearResponseQueue();
      	    
              // Set active processor to null.
              mActiveProcessor = null;
          }
      }
     
  }
  