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.meprofile;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  import android.content.Context;
  
  import com.vodafone360.people.database.DatabaseHelper;
  import com.vodafone360.people.database.tables.ContactSummaryTable;
  import com.vodafone360.people.database.tables.StateTable;
  import com.vodafone360.people.datatypes.BaseDataType;
  import com.vodafone360.people.datatypes.Contact;
  import com.vodafone360.people.datatypes.ContactChanges;
  import com.vodafone360.people.datatypes.ContactDetail;
  import com.vodafone360.people.datatypes.ExternalResponseObject;
  import com.vodafone360.people.datatypes.PushEvent;
  import com.vodafone360.people.datatypes.SystemNotification;
  import com.vodafone360.people.datatypes.UserProfile;
  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.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.io.QueueManager;
  import com.vodafone360.people.service.io.Request;
  import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse;
  import com.vodafone360.people.service.io.api.Contacts;
  import com.vodafone360.people.utils.LogUtils;
  import com.vodafone360.people.utils.ThumbnailUtils;
  import com.vodafone360.people.utils.WidgetUtils;
  
  /**
   * This is an implementation for an engine to synchronize 
   * Me profile data. 
   *
   */
  public class SyncMeEngine extends BaseEngine {
  
      /**
       * Current engine state.
       */
      private State mState = State.IDLE;
      /**
       * Database.
       */
      private DatabaseHelper mDbHelper;
      /**
       * The latest revision of Me Profile.
       */
      private long mFromRevision;
      /**
       * The Me Profile, as it was uploaded.
       */
      private ArrayList<ContactDetail> mUploadedMeDetails;
  
      /**
       * Indicates if the first time sync has been ever initiated.
       */
      private boolean mFirstTimeSyncStarted;
  
      /**
       * Indicates if the first time sync has been completed.
       */
      private boolean mFirstTimeMeSyncComplete;
  
      /**
       * Defines the contact sync mode. The mode determines the sequence in which
       * the contact sync processors are run.
       */
      private enum State {
          /**
           * The state when the engine is not running and has nothing on the todo list.
           */
          IDLE,
          /**
           * The state when the engine is downloading Me Profile from server.
           */
          FETCHING_ME_PROFILE_CHANGES,
          /**
           * The state when the engine is uploading Me Profile to server.
           */
          UPDATING_ME_PROFILE,
          /**
           * The state when the engine is uploading Me Profile status message.
           */
          UPDATING_ME_PRESENCE_TEXT,
          /**
           * The state when the engine is downloading Me Profile thumbnail.
           */
          FETCHING_ME_PROFILE_THUMBNAIL,
      }
      
      /**
       * The service context.
       */
      private Context mContext;
  
      /**
       * The constructor.
       * @param eventCallback IEngineEventCallback
       * @param db DatabaseHelper - database.
       */
      public SyncMeEngine(final Context context, final IEngineEventCallback eventCallback, DatabaseHelper db) {
          super(eventCallback);
          mEngineId = EngineId.SYNCME_ENGINE;
          mDbHelper = db;
          mContext = context;
          
          mFromRevision = StateTable.fetchMeProfileRevision(mDbHelper.getReadableDatabase());
      }
  
      @Override
      public long getNextRunTime() {
          if (!isReady()) {
              return -1;
          }
          if (mFirstTimeSyncStarted && !mFirstTimeMeSyncComplete && (mState == State.IDLE)) {
              return 0;
          }
          if (isCommsResponseOutstanding()) {
              return 0;
          }
          if (isUiRequestOutstanding()) {
              return 0;
          }
          return getCurrentTimeout();
      }
  
      /**
       * The condition for the sync me engine run.
       * @return boolean - TRUE when the engine is ready to run.
       */
      private boolean isReady() {
          return EngineManager.getInstance().getLoginEngine().isLoggedIn() && checkConnectivity()
                  && mFirstTimeSyncStarted;
      }
  
      @Override
      public void run() {
          LogUtils.logD("SyncMeEngine run");
          processTimeout();
          if (isCommsResponseOutstanding() && processCommsInQueue()) {
              return;
          }
          if (isUiRequestOutstanding()) {
              processUiQueue();
          }
  
          if (mFromRevision == 0 && (mState == State.IDLE)) {
              addGetMeProfileContactRequest();
          }
  
      }
  
      @Override
      public void onCreate() {
      	PersistSettings setting = mDbHelper.fetchOption(
          		PersistSettings.Option.FIRST_TIME_MESYNC_STARTED);
          if (setting != null) {
              mFirstTimeSyncStarted = setting.getFirstTimeMeSyncStarted();
          }
  
          setting = mDbHelper.fetchOption(
          		PersistSettings.Option.FIRST_TIME_MESYNC_COMPLETE);
          if (setting != null) {
              mFirstTimeMeSyncComplete = setting.getFirstTimeMeSyncComplete();
          }
      }
  
      @Override
      public void onDestroy() {
      }
  
      @Override
      protected void onRequestComplete() {
      }
  
      @Override
      protected void onTimeoutEvent() {
      }
  
      @Override
      protected final void processUiRequest(final ServiceUiRequest requestId, Object data) {
          switch (requestId) {
              case UPDATE_ME_PROFILE:
                  uploadMeProfile();
                  break;
              case GET_ME_PROFILE:
                  getMeProfileChanges();
                  break;
              case UPLOAD_ME_STATUS:
                  uploadStatusUpdate(SyncMeDbUtils.updateStatus(mDbHelper, (String)data));
                  break;
              default:
                  // do nothing.
                  break;
          }
  
      }
  
      /**
       * Sends a GetMyChanges request to the server, with the current version of
       * the me profile used as a parameter.
       */
      private void getMeProfileChanges() {
          if (NetworkAgent.getAgentState() != NetworkAgent.AgentState.CONNECTED) {
              return;
          }
          newState(State.FETCHING_ME_PROFILE_CHANGES);
          setReqId(Contacts.getMyChanges(this, mFromRevision));
      }
  
      /**
       * The call to download the thumbnail picture for the me profile.
       * @param url String - picture url of Me Profile (comes with getMyChanges())
       * @param localContactId long - local contact id of Me Profile
       */
      private void downloadMeProfileThumbnail(final String url, final long localContactId) {
          if (NetworkAgent.getAgentState() == NetworkAgent.AgentState.CONNECTED) {
              Request request = new Request(url, ThumbnailUtils.REQUEST_THUMBNAIL_URI, engineId());
              newState(State.FETCHING_ME_PROFILE_THUMBNAIL);
              setReqId(QueueManager.getInstance().addRequestAndNotify(request));
          }
      }
  
      /**
       * Starts uploading a status update to the server and ignores all other
       * @param statusDetail - status ContactDetail
       */
      private void uploadStatusUpdate(final ContactDetail statusDetail) {
          LogUtils.logE("SyncMeProfile uploadStatusUpdate()");
  
          if (NetworkAgent.getAgentState() != NetworkAgent.AgentState.CONNECTED) {
              LogUtils.logE("SyncMeProfile uploadStatusUpdate: no internet connection");
              return;
          }
          if (statusDetail == null) {
              LogUtils.logE("SyncMeProfile uploadStatusUpdate: null status can't be posted");
              return;
          }
  
          newState(State.UPDATING_ME_PRESENCE_TEXT);
          List<ContactDetail> details = new ArrayList<ContactDetail>();
          statusDetail.updated = null;
          details.add(statusDetail);
  
          setReqId(Contacts.setMe(this, details, null, null));
      }
  
      /**
       * * Sends a SetMe request to the server in the case that the me profile has
       * been changed locally. If the me profile thumbnail has always been changed
       * it will also be uploaded to the server.
       */
      private void uploadMeProfile() {
          if (NetworkAgent.getAgentState() != NetworkAgent.AgentState.CONNECTED) {
              return;
          }
  
          newState(State.UPDATING_ME_PROFILE);
  
          Contact meProfile = new Contact();
          mDbHelper.fetchContact(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper), meProfile);
  
          mUploadedMeDetails = SyncMeDbUtils.saveContactDetailChanges(mDbHelper, meProfile);
  
          setReqId(Contacts.setMe(this, mUploadedMeDetails, meProfile.aboutMe, meProfile.gender));
      }
  
      /**
       * Get current connectivity state from the NetworkAgent. If not connected
       * completed UI request with COMMs error.
       * @return true NetworkAgent reports we are connected, false otherwise.
       */
      private boolean checkConnectivity() {
          if (NetworkAgent.getAgentState() != NetworkAgent.AgentState.CONNECTED) {
              completeUiRequest(ServiceStatus.ERROR_COMMS, null);
              return false;
          }
          return true;
      }
  
      /**
       * Changes the state of the engine.
       * @param newState The new state
       */
      private void newState(final State newState) {
          State oldState = mState;
          mState = newState;
          LogUtils.logV("SyncMeEngine newState(): " + oldState + " -> " + mState);
      }
  
      /**
       * Called by framework when a response to a server request is received.
       * @param resp The response received
       */
      public final void processCommsResponse(final DecodedResponse resp) {
          if (!processPushEvent(resp)) {
              switch (mState) {
                  case FETCHING_ME_PROFILE_CHANGES:
                      processGetMyChangesResponse(resp);
                      break;
                  case UPDATING_ME_PRESENCE_TEXT:
                      processUpdateStatusResponse(resp);
                      break;
                  case UPDATING_ME_PROFILE:
                      processSetMeResponse(resp);
                      break;
                  case FETCHING_ME_PROFILE_THUMBNAIL:
                      processMeProfileThumbnailResponse(resp);
                      break;
                  default:
                      // do nothing.
                      break;
              }
          }
      }
  
      /**
       * This method stores the thumbnail picture for the me profile
       * @param resp Response - normally contains ExternalResponseObject for the
       *            picture
       */
      private void processMeProfileThumbnailResponse(final DecodedResponse resp) {
          
          if (resp.mDataTypes.size()==0){
              LogUtils.logE("SyncMeProfile processMeProfileThumbnailResponse():"
                      + SystemNotification.SysNotificationCode.EXTERNAL_HTTP_ERROR);
              completeUiRequest(ServiceStatus.ERROR_COMMS_BAD_RESPONSE);
              return;
       
          }
          Contact currentMeProfile = new Contact();
          ServiceStatus status = SyncMeDbUtils.fetchMeProfile(mDbHelper, currentMeProfile);
          if (status == ServiceStatus.SUCCESS) {
              if (resp.mReqId == null || resp.mReqId == 0) {
                  if (resp.mDataTypes.get(0).getType() == BaseDataType.SYSTEM_NOTIFICATION_DATA_TYPE
                          && ((SystemNotification)resp.mDataTypes.get(0)).getSysCode() == SystemNotification.SysNotificationCode.EXTERNAL_HTTP_ERROR) {
                      LogUtils.logE("SyncMeProfile processMeProfileThumbnailResponse():"
                              + SystemNotification.SysNotificationCode.EXTERNAL_HTTP_ERROR);
  
                  }
                  completeUiRequest(status);
                  return;
              } else if (resp.mDataTypes.get(0).getType() == BaseDataType.SYSTEM_NOTIFICATION_DATA_TYPE) {
                  if (((SystemNotification)resp.mDataTypes.get(0)).getSysCode() == SystemNotification.SysNotificationCode.EXTERNAL_HTTP_ERROR) {
                      LogUtils.logE("SyncMeProfile processMeProfileThumbnailResponse():"
                              + SystemNotification.SysNotificationCode.EXTERNAL_HTTP_ERROR);
                  }
                  completeUiRequest(status);
                  return;
  
              }
              status = BaseEngine
                      .getResponseStatus(BaseDataType.EXTERNAL_RESPONSE_OBJECT_DATA_TYPE, resp.mDataTypes);
              if (status != ServiceStatus.SUCCESS) {
                  completeUiRequest(ServiceStatus.ERROR_COMMS_BAD_RESPONSE);
                  LogUtils
                          .logE("SyncMeProfile processMeProfileThumbnailResponse() - Can't read response");
  
                  return;
              }
  
              if (resp.mDataTypes == null || resp.mDataTypes.isEmpty()) {
                  LogUtils
                          .logE("SyncMeProfile processMeProfileThumbnailResponse() - Datatypes are null");
                  completeUiRequest(ServiceStatus.ERROR_COMMS_BAD_RESPONSE);
                  return;
              }
              // finally save the thumbnails
              ExternalResponseObject ext = (ExternalResponseObject)resp.mDataTypes.get(0);
              if (ext.mBody == null) {
                  LogUtils.logE("SyncMeProfile processMeProfileThumbnailResponse() - no body");
                  completeUiRequest(ServiceStatus.ERROR_COMMS_BAD_RESPONSE);
                  return;
              }
  
              try {
                  ThumbnailUtils.saveExternalResponseObjectToFile(currentMeProfile.localContactID,
                          ext);
                  ContactSummaryTable.modifyPictureLoadedFlag(currentMeProfile.localContactID, true,
                          mDbHelper.getWritableDatabase());
                  mDbHelper.markMeProfileAvatarChanged();
              } catch (IOException e) {
                  LogUtils.logE("SyncMeProfile processMeProfileThumbnailResponse()", e);
                  completeUiRequest(ServiceStatus.ERROR_COMMS_BAD_RESPONSE);
              }
          }
          completeUiRequest(status);
      }
  
      /**
       * Processes the response from a GetMyChanges request. The me profile data
       * will be merged in the local database if the response is successful.
       * Otherwise the processor will complete with a suitable error.
       * @param resp Response from server.
       */
      private void processGetMyChangesResponse(final DecodedResponse resp) {
          LogUtils.logD("SyncMeEngine processGetMyChangesResponse()");
          ServiceStatus status = BaseEngine.getResponseStatus(BaseDataType.CONTACT_CHANGES_DATA_TYPE,
                  resp.mDataTypes);
          if (status == ServiceStatus.SUCCESS) {
              ContactChanges changes = (ContactChanges)resp.mDataTypes.get(0);
              Contact currentMeProfile = new Contact();
              status = SyncMeDbUtils.fetchMeProfile(mDbHelper, currentMeProfile);
              
              switch (status) {
                  case SUCCESS:
                      SyncMeDbUtils.updateMeProfile(mDbHelper, currentMeProfile, changes.mUserProfile);
                      break;
                  case ERROR_NOT_FOUND: // this is the 1st time sync
                      currentMeProfile.copy(changes.mUserProfile);
                      status = SyncMeDbUtils.setMeProfile(mDbHelper, currentMeProfile);
                      setFirstTimeMeSyncComplete(true);
                      break;
                  default:
                      completeUiRequest(status);
                      return;
              }
              
              final String url = fetchThumbnailUrlFromProfile(changes.mUserProfile);
              if (url != null) {
                  downloadMeProfileThumbnail(url, currentMeProfile.localContactID);
              } else {
                  completeUiRequest(status);
              }
              
              storeMeProfileRevisionInDb(changes.mCurrentServerVersion);
          } else {
              completeUiRequest(status);
          }
      }
  
      /**
       * 
       * Stores the new server revision of the me profile passed to this method in the database.
       * 
       * @param newServerRevision The new revision of the me profile.
       * 
       */
      private void storeMeProfileRevisionInDb(final int newServerRevision) {
      	mFromRevision = newServerRevision;
          StateTable.modifyMeProfileRevision(mFromRevision, mDbHelper.getWritableDatabase());
          LogUtils.logI("SyncMeEngine.processGetMyChangesResponse() " +
          		"Stored fromRevision: " + mFromRevision);
      }
      
      /**
       * 
       * Fetches the thumbnail URL if there is one in the passed profile.
       * 
       * @param profile The profile which contains the URL of the thumbnail in its details.
       * 
       * @return Returns the thumbnail if it was found as part of the PHOTO-detail or null if the 
       * passed profile was null, the PHOTO-detail was not found or all of the details in the profile
       * are null.
       * 
       */
      private final String fetchThumbnailUrlFromProfile(final UserProfile profile) {
      	if (null == profile) {
      		return null;
      	}
      	
      	List<ContactDetail> details = profile.details;
      	if (null == details) {
      		return null;
      	}
      	
      	Iterator<ContactDetail> iterator = details.iterator();
      	
      	while (iterator.hasNext()) {
      		ContactDetail detail = iterator.next();
      		
      		if (null == detail) {
      			continue;
      		}
      		
      		if (ContactDetail.DetailKeys.PHOTO.equals(detail.key)) {
      			return detail.value;
      		}
      	}
      	
      	return null;
      }
      
      /**
       * Processes the response from a SetMe request. If successful, the server
       * IDs will be stored in the local database if they have changed. Otherwise
       * the processor will complete with a suitable error.
       * @param resp Response from server.
       */
      private void processSetMeResponse(final DecodedResponse resp) {
          LogUtils.logD("SyncMeProfile.processMeProfileUpdateResponse()");
  
          ServiceStatus status = BaseEngine.getResponseStatus(BaseDataType.CONTACT_CHANGES_DATA_TYPE,
                  resp.mDataTypes);
          if (status == ServiceStatus.SUCCESS) {
              ContactChanges result = (ContactChanges) resp.mDataTypes.get(0);
              SyncMeDbUtils.updateMeProfileDbDetailIds(mDbHelper, mUploadedMeDetails, result);
              if (updateRevisionPostUpdate(result.mServerRevisionBefore, result.mServerRevisionAfter,
                      mFromRevision, mDbHelper)) {
                  mFromRevision = result.mServerRevisionAfter;
              }
          }
          completeUiRequest(status);
  
      }
  
      /**
       * This method processes the response to status update by setMe() method
       * @param resp Response - the expected response datatype is ContactChanges
       */
      private void processUpdateStatusResponse(final DecodedResponse resp) {
          LogUtils.logD("SyncMeDbUtils processUpdateStatusResponse()");
  
          ServiceStatus status = BaseEngine.getResponseStatus(BaseDataType.CONTACT_CHANGES_DATA_TYPE,
                  resp.mDataTypes);
          if (status == ServiceStatus.SUCCESS) {
              ContactChanges result = (ContactChanges) resp.mDataTypes.get(0);
              LogUtils.logI("SyncMeProfile.processUpdateStatusResponse() - Me profile userId = "
                      + result.mUserProfile.userID);
              SyncMeDbUtils.savePresenceStatusResponse(mDbHelper, result);
          }
          completeUiRequest(status);
      }
      
      @Override
      protected void completeUiRequest(ServiceStatus status) {
          super.completeUiRequest(status);
          newState(State.IDLE);
  
          WidgetUtils.kickWidgetUpdateNow(mContext);
       }
  
      /**
       * Updates the revision of the me profile in the local state table after the
       * SetMe has completed. This will only happen if the version of the me
       * profile on the server before the update matches our previous version.
       * @param before Version before the update
       * @param after Version after the update
       * @param currentFromRevision Current version from our database
       * @param db Database helper used for storing the change
       * @return true if the update was done, false otherwise.
       */
      private static boolean updateRevisionPostUpdate(final Integer before, final Integer after,
              final long currentFromRevision, final DatabaseHelper db) {
          if (before == null || after == null) {
              return false;
          }
          if (!before.equals(currentFromRevision)) {
              LogUtils
                      .logW("SyncMeProfile.updateRevisionPostUpdate - Previous version is not as expected, current version="
                              + currentFromRevision + ", server before=" + before + ", server after=" + after);
              return false;
          } else {
              StateTable.modifyMeProfileRevision(after, db.getWritableDatabase());
              return true;
          }
      }
  
      /**
       * This method adds an external request to Contacts/setMe() method to update
       * the Me Profile...
       * @param meProfile Contact - contact to be pushed to the server
       */
      public void addUpdateMeProfileContactRequest() {
          LogUtils.logV("SyncMeEngine addUpdateMeProfileContactRequest()");
          addUiRequestToQueue(ServiceUiRequest.UPDATE_ME_PROFILE, null);
      }
  
      /**
       * This method adds an external request to Contacts/setMe() method to update
       * the Me Profile status...
       * @param textStatus String - the new me profile status to be pushed to the
       *            server
       */
      public void addUpdateMyStatusRequest(String textStatus) {
          LogUtils.logV("SyncMeEngine addUpdateMyStatusRequest()");
          addUiRequestToQueue(ServiceUiRequest.UPLOAD_ME_STATUS, textStatus);
      }
  
      /**
       * This method adds an external request to Contacts/getMyChanges() method to
       * update the Me Profile status server. Is called when "pc "push message is
       * received
       */
      private void addGetMeProfileContactRequest() {
          LogUtils.logV("SyncMeEngine addGetMeProfileContactRequest()");
          addUiRequestToQueue(ServiceUiRequest.GET_ME_PROFILE, null);
      }
  
      /**
       * This method adds an external request to Contacts/getMyChanges() method to
       * update the Me Profile status server, is called by the UI at the 1st sync.
       */
      public void addGetMeProfileContactFirstTimeRequest() {
          LogUtils.logV("SyncMeEngine addGetMeProfileContactFirstTimeRequest()");
          setFirstTimeSyncStarted(true);
          addUiRequestToQueue(ServiceUiRequest.GET_ME_PROFILE, null);
      }
  
      /**
       * This method process the "pc" push event.
       * @param resp Response - server response normally containing a "pc"
       *            PushEvent data type
       * @return boolean - TRUE if a push event was found in the response
       */
      private boolean processPushEvent(final 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("SyncMeEngine processPushMessage():" + pushEvent.mMessageType);
          switch (pushEvent.mMessageType) {
              case PROFILE_CHANGE:
                  addGetMeProfileContactRequest();
                  break;
              default:
                  break;
          }
          return true;
      }
  
      /**
       * Helper function to update the database when the state of the
       * {@link #mFirstTimeMeSyncStarted} 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(final boolean value) {
          if (mFirstTimeSyncStarted == value) {
              return ServiceStatus.SUCCESS;
          }
          PersistSettings setting = new PersistSettings();
          setting.putFirstTimeMeSyncStarted(value);
          ServiceStatus status = mDbHelper.setOption(setting);
          if (ServiceStatus.SUCCESS == status) {
              synchronized (this) {
                  mFirstTimeSyncStarted = value;
              }
          }
          return status;
      }
  
      /**
       * Helper function to update the database when the state of the
       * {@link #mFirstTimeMeSyncComplete} 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 setFirstTimeMeSyncComplete(final boolean value) {
          if (mFirstTimeMeSyncComplete == value) {
              return ServiceStatus.SUCCESS;
          }
          PersistSettings setting = new PersistSettings();
          setting.putFirstTimeMeSyncComplete(value);
          ServiceStatus status = mDbHelper.setOption(setting);
          if (ServiceStatus.SUCCESS == status) {
              synchronized (this) {
                  mFirstTimeMeSyncComplete = value;
              }
          }
          return status;
      }
  
      /**
       * This method needs to be called as part of removeAllData()/changeUser()
       * routine.
       */
      /** {@inheritDoc} */
      @Override
      public final void onReset() {
          
          // reset the engine as if it was just created
          super.onReset();
          mFirstTimeMeSyncComplete = false;
          mFirstTimeSyncStarted = false;
          mFromRevision = 0;
          mUploadedMeDetails = null;
          mState = State.IDLE;
      }
  
      /**
       * This method TRUE if the Me Profile has been synced once.
       * @return boolean - TRUE if the Me Profile has been synced once.
       */
      public final boolean isFirstTimeMeSyncComplete() {
          return mFirstTimeMeSyncComplete;
      }
  }
  