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.tests.engine.contactsync;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.ListIterator;
  
  import android.app.Instrumentation;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
  import android.test.InstrumentationTestCase;
  import android.test.suitebuilder.annotation.LargeTest;
  import android.test.suitebuilder.annotation.MediumTest;
  import android.test.suitebuilder.annotation.SmallTest;
  import android.test.suitebuilder.annotation.Suppress;
  import android.util.Log;
  
  import com.vodafone360.people.MainApplication;
  import com.vodafone360.people.database.DatabaseHelper;
  import com.vodafone360.people.database.tables.ContactChangeLogTable;
  import com.vodafone360.people.database.tables.ContactDetailsTable;
  import com.vodafone360.people.database.tables.ContactSummaryTable;
  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.ContactDetailDeletion;
  import com.vodafone360.people.datatypes.ContactListResponse;
  import com.vodafone360.people.datatypes.ContactSummary;
  import com.vodafone360.people.datatypes.GroupItem;
  import com.vodafone360.people.datatypes.ItemList;
  import com.vodafone360.people.datatypes.ServerError;
  import com.vodafone360.people.datatypes.StatusMsg;
  import com.vodafone360.people.engine.EngineManager.EngineId;
  import com.vodafone360.people.engine.contactsync.IContactSyncCallback;
  import com.vodafone360.people.engine.contactsync.UploadServerContacts;
  import com.vodafone360.people.service.ServiceStatus;
  import com.vodafone360.people.service.agent.NetworkAgent;
  import com.vodafone360.people.service.io.ResponseQueue;
  import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse;
  import com.vodafone360.people.tests.TestModule;
  import com.vodafone360.people.tests.engine.EngineTestFramework;
  import com.vodafone360.people.tests.engine.IEngineTestFrameworkObserver;
  
  public class UploadServerContactsTest extends InstrumentationTestCase implements
          IEngineTestFrameworkObserver {
      private static final String LOG_TAG = "UploadServerContactsTest";
  
      private static final long MAX_PROCESSOR_TIME = 3000000;
  
      private static final long MAX_WAIT_FOR_REQ_ID = 5000;
  
      private static final int NO_OF_CONTACTS_TO_ADD = 55;
  
      private static final String NEW_DETAIL_VALUE = "0123456789";
  
      private static final ContactDetail.DetailKeys NEW_DETAIL_KEY = ContactDetail.DetailKeys.VCARD_PHONE;
  
      private static final ContactDetail.DetailKeyTypes NEW_DETAIL_TYPE = ContactDetail.DetailKeyTypes.CELL;
  
      private static final String MOD_DETAIL_VALUE = ";Test;One";
  
      private static final ContactDetail.DetailKeys MOD_DETAIL_KEY = ContactDetail.DetailKeys.VCARD_NAME;
  
      private static final ContactDetail.DetailKeyTypes MOD_DETAIL_TYPE = null;
  
      private static final long TEST_GROUP_1 = 860909;
  
      private static final long TEST_GROUP_2 = 860910;
  
      enum State {
          IDLE,
          ADD_CONTACT_LIST,
          MODIFY_CONTACT_LIST,
          DELETE_CONTACT_LIST,
          DELETE_CONTACT_DETAIL_LIST,
          ADD_NEW_GROUP_LIST,
          DELETE_GROUP_LIST
      }
  
      EngineTestFramework mEngineTester = null;
  
      MainApplication mApplication = null;
  
      DummyContactSyncEngine mEng = null;
  
      class UploadServerContactProcessorTest extends UploadServerContacts {
  
          public UploadServerContactProcessorTest(IContactSyncCallback callback, DatabaseHelper db) {
              super(callback, db);
          }
  
          public void testFetchContactChangeList(List<Contact> contactChangeList) {
              contactChangeList.clear();
              contactChangeList.addAll(getContactChangeList());
          }
  
          public void testFetchAddGroupLists(List<Long> contactIdList, List<GroupItem> groupList) {
              contactIdList.clear();
              groupList.clear();
              contactIdList.addAll(getContactIdList());
              groupList.addAll(getGroupList());
          }
  
          public void testFetchContactDeleteList(List<Long> contactIdList) {
              contactIdList.clear();
              contactIdList.addAll(getContactIdList());
          }
  
          public void testFetchContactDetailDeleteList(Contact deleteDetailContact) {
              android.os.Parcel _data = android.os.Parcel.obtain();
              getDeleteDetailContact().writeToParcel(_data, 0);
              _data.setDataPosition(0);
              deleteDetailContact.readFromParcel(_data);
          }
  
          public Long testFetchDeleteGroupList(List<Long> contactIdList) {
              contactIdList.clear();
              contactIdList.addAll(getContactIdList());
              return getActiveGroupId();
          }
  
          public int testGetPageSize() {
              return MAX_UP_PAGE_SIZE;
          }
  
          public void verifyNewContactsState() {
              assertEquals(InternalState.PROCESSING_NEW_CONTACTS, getInternalState());
          }
  
          public void verifyModifyDetailsState() {
              assertEquals(InternalState.PROCESSING_MODIFIED_DETAILS, getInternalState());
          }
  
          public void verifyDeleteContactsState() {
              assertEquals(InternalState.PROCESSING_DELETED_CONTACTS, getInternalState());
          }
  
          public void verifyDeleteDetailsState() {
              assertEquals(InternalState.PROCESSING_DELETED_DETAILS, getInternalState());
          }
  
          public void verifyGroupAddsState() {
              assertEquals(InternalState.PROCESSING_GROUP_ADDITIONS, getInternalState());
          }
  
          public void verifyGroupDelsState() {
              assertEquals(InternalState.PROCESSING_GROUP_DELETIONS, getInternalState());
          }
      };
  
      UploadServerContactProcessorTest mProcessor;
  
      DatabaseHelper mDb = null;
  
      State mState = State.IDLE;
  
      Contact mReplyContact = null;
  
      int mInitialCount;
  
      int mInitialContactGroupCount;
  
      int mItemCount;
  
      boolean mBulkContactTest;
  
      TestModule mTestModule = new TestModule();
  
      int mTestStep;
  
      @Override
      protected void setUp() throws Exception {
          super.setUp();
          mApplication = (MainApplication)Instrumentation.newApplication(MainApplication.class,
                  getInstrumentation().getTargetContext());
          mApplication.onCreate();
          mDb = mApplication.getDatabase();
          mDb.removeUserData();
  
          mEngineTester = new EngineTestFramework(this);
          mEng = new DummyContactSyncEngine(mEngineTester);
          mProcessor = new UploadServerContactProcessorTest(mEng, mApplication.getDatabase());
          mEng.setProcessor(mProcessor);
          mEngineTester.setEngine(mEng);
          mState = State.IDLE;
  
          NetworkAgent.setAgentState(NetworkAgent.AgentState.CONNECTED);
          mBulkContactTest = false;
      }
  
      @Override
      protected void tearDown() throws Exception {
          // stop our dummy thread?
          mEngineTester.stopEventThread();
          mEngineTester = null;
          mEng = null;
          SQLiteDatabase db = mDb.getReadableDatabase();
          if (db.inTransaction()) {
              db.endTransaction();
          }
          db.close();
          super.tearDown();
      }
  
      private void startSubTest(String function, String description) {
          Log.i(LOG_TAG, function + " - step " + mTestStep + ": " + description);
          mTestStep++;
      }
  
      private void runProcessor(int count, State state) {
          mInitialCount = count;
          nextState(state);
          mEng.mProcessorCompleteFlag = false;
          mProcessor.start();
          ServiceStatus status = mEng.waitForProcessorComplete(MAX_PROCESSOR_TIME);
          assertEquals(ServiceStatus.SUCCESS, status);
      }
  
      private void nextState(State state) {
          mItemCount = mInitialCount;
          if (mItemCount == 0) {
              mState = State.IDLE;
          } else {
              mState = state;
          }
      }
  
      @Override
      public void reportBackToEngine(int reqId, EngineId engine) {
          Log.d(LOG_TAG, "reportBackToEngine");
          ResponseQueue respQueue = ResponseQueue.getInstance();
          List<BaseDataType> data = new ArrayList<BaseDataType>();
          try {
              assertEquals(mEng.engineId(), engine);
              synchronized (mEng.mWaitForReqIdLock) {
                  if (mEng.mActiveReqId == null || mEng.mActiveReqId.intValue() != reqId) {
                      try {
                          mEng.mWaitForReqIdLock.wait(MAX_WAIT_FOR_REQ_ID);
                      } catch (InterruptedException e) {
                      }
                      assertEquals(Integer.valueOf(reqId), mEng.mActiveReqId);
                  }
              }
              switch (mState) {
                  case ADD_CONTACT_LIST:
                      reportBackAddContactSuccess(reqId, data);
                      break;
                  case MODIFY_CONTACT_LIST:
                      reportModifyContactSuccess(reqId, data);
                      break;
                  case DELETE_CONTACT_LIST:
                      reportDeleteContactSuccess(reqId, data);
                      break;
                  case DELETE_CONTACT_DETAIL_LIST:
                      reportDeleteContactDetailSuccess(reqId, data);
                      break;
                  case ADD_NEW_GROUP_LIST:
                      reportBackAddGroupSuccess(reqId, data);
                      break;
                  case DELETE_GROUP_LIST:
                      reportDeleteGroupListSuccess(reqId, data);
                      break;
                  default:
                      fail("Unexpected request from processor");
              }
          } catch (Throwable err) {
              ServerError serverError = new ServerError(ServerError.ErrorType.INTERNALERROR);
              serverError.errorDescription = err + "\n";
              for (int i = 0; i < err.getStackTrace().length; i++) {
                  StackTraceElement v = err.getStackTrace()[i];
                  serverError.errorDescription += "\t" + v + "\n";
              }
              Log.e(LOG_TAG, "Exception:\n" + serverError.errorDescription);
              data.clear();
              data.add(serverError);
          }
          respQueue.addToResponseQueue(new DecodedResponse(reqId, data, engine, DecodedResponse.ResponseType.SERVER_ERROR.ordinal()));
          mEng.onCommsInMessage();
          Log.d(LOG_TAG, "reportBackToEngine - message added to response queue");
      }
  
      private void reportBackAddContactSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportBackAddContactSuccess");
          mProcessor.verifyNewContactsState();
          List<Contact> contactChangeList = new ArrayList<Contact>();
          mProcessor.testFetchContactChangeList(contactChangeList);
  
          assertEquals(Math.min(mItemCount, mProcessor.testGetPageSize()), contactChangeList.size());
          ContactChanges contactChanges = new ContactChanges();
          contactChanges.mServerRevisionAfter = 1;
          contactChanges.mServerRevisionBefore = 0;
          data.add(contactChanges);
          for (int i = 0; i < contactChangeList.size(); i++) {
              Contact actualContact = contactChangeList.get(i);
              Contact expectedContact = new Contact();
              ServiceStatus status = mDb.fetchContact(actualContact.localContactID, expectedContact);
              assertEquals(ServiceStatus.SUCCESS, status);
              assertEquals(expectedContact.details.size(), actualContact.details.size());
              assertEquals(null, actualContact.aboutMe);
              assertEquals(null, actualContact.profilePath);
              assertEquals(null, actualContact.contactID);
              assertEquals(null, actualContact.deleted);
              assertEquals(null, actualContact.friendOfMine);
              assertEquals(null, actualContact.gender);
              assertEquals(null, actualContact.groupList);
              assertEquals(null, actualContact.sources);
              assertEquals(expectedContact.synctophone, actualContact.synctophone);
              assertEquals(null, actualContact.updated);
              assertEquals(null, actualContact.userID);
  
              final ListIterator<ContactDetail> itActDetails = actualContact.details.listIterator();
              for (ContactDetail expDetail : expectedContact.details) {
                  ContactDetail actDetail = itActDetails.next();
                  assertTrue(DatabaseHelper.doDetailsMatch(expDetail, actDetail));
                  assertFalse(DatabaseHelper.hasDetailChanged(expDetail, actDetail));
              }
              generateReplyContact(expectedContact);
              contactChanges.mContacts.add(mReplyContact);
          }
  
          mItemCount -= contactChangeList.size();
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              mInitialCount = mInitialContactGroupCount;
              nextState(State.ADD_NEW_GROUP_LIST);
          }
      }
  
      private void reportBackAddGroupSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportBackAddGroupSuccess");
          mProcessor.verifyGroupAddsState();
          final List<Long> contactIdList = new ArrayList<Long>();
          final List<GroupItem> groupList = new ArrayList<GroupItem>();
          mProcessor.testFetchAddGroupLists(contactIdList, groupList);
          assertEquals(1, groupList.size());
          Long activeGroupId = groupList.get(0).mId;
          assertTrue(activeGroupId != null);
  
          ItemList itemList = new ItemList(ItemList.Type.contact_group_relations);
          data.add(itemList);
          for (Long contactServerId : contactIdList) {
              Contact expectedContact = new Contact();
              ServiceStatus status = mDb.fetchContactByServerId(contactServerId, expectedContact);
              assertEquals(ServiceStatus.SUCCESS, status);
              boolean found = false;
              for (Long groupId : expectedContact.groupList) {
                  if (groupId.equals(activeGroupId)) {
                      found = true;
                      break;
                  }
              }
              assertTrue("Contact " + contactServerId + " has been added to group " + activeGroupId
                      + " which is not in the database", found);
              mItemCount--;
          }
          Log.i(LOG_TAG, "Groups/contacts remaining = " + mItemCount);
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              nextState(State.IDLE);
          }
      }
  
      private void reportModifyContactSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportModifyContactSuccess");
          mProcessor.verifyModifyDetailsState();
          List<Contact> contactChangeList = new ArrayList<Contact>();
          mProcessor.testFetchContactChangeList(contactChangeList);
  
          assertEquals(Math.min(mItemCount, mProcessor.testGetPageSize()), contactChangeList.size());
          ContactChanges contactChanges = new ContactChanges();
          contactChanges.mServerRevisionAfter = 1;
          contactChanges.mServerRevisionBefore = 0;
          data.add(contactChanges);
          for (int i = 0; i < contactChangeList.size(); i++) {
              Contact actualContact = contactChangeList.get(i);
              assertTrue(actualContact.contactID != null);
              assertEquals(null, actualContact.aboutMe);
              assertEquals(null, actualContact.profilePath);
              assertEquals(null, actualContact.deleted);
              assertEquals(null, actualContact.friendOfMine);
              assertEquals(null, actualContact.gender);
              assertEquals(null, actualContact.groupList);
              assertEquals(null, actualContact.sources);
              assertEquals(null, actualContact.synctophone);
              assertEquals(null, actualContact.updated);
              assertEquals(null, actualContact.userID);
  
              assertEquals(2, actualContact.details.size());
              ContactDetail modDetail = actualContact.details.get(0); // Modified
                                                                      // detail
                                                                      // always
                                                                      // first
              ContactDetail newDetail = actualContact.details.get(1);
  
              assertEquals(NEW_DETAIL_VALUE, newDetail.value);
              assertEquals(NEW_DETAIL_KEY, newDetail.key);
              assertEquals(NEW_DETAIL_TYPE, newDetail.keyType);
              assertEquals(MOD_DETAIL_VALUE, modDetail.value);
              assertEquals(MOD_DETAIL_KEY, modDetail.key);
              assertEquals(MOD_DETAIL_TYPE, modDetail.keyType);
  
              mReplyContact = new Contact();
              mReplyContact.contactID = actualContact.contactID;
              mReplyContact.userID = generateTestUserID(mReplyContact.contactID);
              ContactDetail replyDetail1 = new ContactDetail();
              ContactDetail replyDetail2 = new ContactDetail();
              replyDetail1.key = modDetail.key;
              replyDetail1.unique_id = modDetail.unique_id;
              replyDetail2.key = newDetail.key;
              replyDetail2.unique_id = newDetail.localDetailID + 2;
              mReplyContact.details.add(replyDetail1);
              mReplyContact.details.add(replyDetail2);
              contactChanges.mContacts.add(mReplyContact);
          }
  
          mItemCount -= contactChangeList.size();
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              nextState(State.IDLE);
          }
  
      }
  
      private void reportDeleteContactSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportDeleteContactSuccess");
          mProcessor.verifyDeleteContactsState();
          List<Long> contactIdList = new ArrayList<Long>();
          mProcessor.testFetchContactDeleteList(contactIdList);
  
          assertEquals(Math.min(mItemCount, mProcessor.testGetPageSize()), contactIdList.size());
          ContactListResponse contactListResponse = new ContactListResponse();
          contactListResponse.mServerRevisionAfter = 1;
          contactListResponse.mServerRevisionBefore = 0;
          data.add(contactListResponse);
          contactListResponse.mContactIdList = new ArrayList<Integer>();
          for (Long serverID : contactIdList) {
              assertTrue(serverID != null);
              contactListResponse.mContactIdList.add(Integer.valueOf(serverID.intValue()));
          }
  
          mItemCount -= contactIdList.size();
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              nextState(State.IDLE);
          }
      }
  
      private void reportDeleteContactDetailSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportDeleteContactDetailSuccess");
          mProcessor.verifyDeleteDetailsState();
          Contact contact = new Contact();
          mProcessor.testFetchContactDetailDeleteList(contact);
  
          assertEquals(2, contact.details.size());
          assertEquals(ContactDetail.DetailKeys.VCARD_NAME, contact.details.get(0).key);
          assertEquals(ContactDetail.DetailKeys.VCARD_PHONE, contact.details.get(1).key);
  
          ContactDetailDeletion contactDetailDeletion = new ContactDetailDeletion();
          contactDetailDeletion.mServerVersionAfter = 1;
          contactDetailDeletion.mServerVersionBefore = 0;
          data.add(contactDetailDeletion);
          contactDetailDeletion.mContactId = contact.contactID.intValue();
          contactDetailDeletion.mDetails = new ArrayList<ContactDetail>();
          for (ContactDetail detail : contact.details) {
              ContactDetail tempDetail = new ContactDetail();
              tempDetail.key = detail.key;
              tempDetail.unique_id = detail.unique_id;
              contactDetailDeletion.mDetails.add(tempDetail);
          }
  
          mItemCount--;
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              nextState(State.IDLE);
          }
      }
  
      private void reportDeleteGroupListSuccess(int reqId, List<BaseDataType> data) {
          Log.d(LOG_TAG, "reportDeleteGroupListSuccess");
          mProcessor.verifyGroupDelsState();
          final List<Long> contactIdList = new ArrayList<Long>();
          Long activeGroupId = mProcessor.testFetchDeleteGroupList(contactIdList);
  
          if (mItemCount == 1) {
              assertEquals(Long.valueOf(TEST_GROUP_2), activeGroupId);
          } else if (mItemCount == 2) {
              assertEquals(Long.valueOf(TEST_GROUP_1), activeGroupId);
          } else {
              fail("Unexpected number of groups in delete group list");
          }
          StatusMsg statusMsg = new StatusMsg();
          statusMsg.mStatus = true;
          data.add(statusMsg);
  
          mItemCount--;
          assertTrue(mItemCount >= 0);
          if (mItemCount == 0) {
              nextState(State.IDLE);
          }
      }
  
      private Long generateTestUserID(Long contactID) {
          if (contactID == null) {
              return null;
          }
          if ((contactID & 15) == 0) {
              return null;
          }
          return contactID + 5;
      }
  
      private void generateReplyContact(Contact expContact) {
          mReplyContact = new Contact();
          mReplyContact.contactID = expContact.localContactID + 1;
          if ((expContact.localContactID & 15) != 0) {
              mReplyContact.userID = expContact.localContactID + 2;
          }
          for (ContactDetail detail : expContact.details) {
              ContactDetail newDetail = new ContactDetail();
              generateReplyDetail(newDetail, detail);
              mReplyContact.details.add(newDetail);
          }
      }
  
      private void generateReplyDetail(ContactDetail replyDetail, ContactDetail expDetail) {
          replyDetail.key = expDetail.key;
          switch (replyDetail.key) {
              case VCARD_NAME:
              case VCARD_NICKNAME:
              case VCARD_DATE:
              case VCARD_TITLE:
              case PRESENCE_TEXT:
              case PHOTO:
              case LOCATION:
                  break;
              default:
                  replyDetail.unique_id = expDetail.localDetailID + 3;
                  break;
          }
      }
  
      @Override
      public void onEngineException(Exception e) {
          mEng.onProcessorComplete(ServiceStatus.ERROR_UNKNOWN, "", e);
      }
  
      @SmallTest
      @Suppress
      // Breaks tests
      public void testRunWithNoContactChanges() {
          final String fnName = "testRunWithNoContactChanges";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor");
          mProcessor.start();
          ServiceStatus status = mEng.waitForProcessorComplete(MAX_PROCESSOR_TIME);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithNewContactChange() {
          final String fnName = "testRunWithNewContactChange";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database");
          Contact contact = mTestModule.createDummyContactData();
          ServiceStatus status = mDb.addContact(contact);
          assertEquals(ServiceStatus.SUCCESS, status);
          mInitialContactGroupCount = contact.groupList.size();
  
          startSubTest(fnName, "Running processor");
          runProcessor(1, State.ADD_CONTACT_LIST);
  
          startSubTest(fnName, "Fetching contact after sync");
          Contact syncContact = mTestModule.createDummyContactData();
          status = mDb.fetchContact(contact.localContactID, syncContact);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Check contact server ID is correct");
          assertEquals(syncContact.contactID, mReplyContact.contactID);
  
          startSubTest(fnName, "Check detail server IDs are correct");
          final ListIterator<ContactDetail> itReplyDetails = mReplyContact.details.listIterator();
          for (ContactDetail syncDetail : syncContact.details) {
              ContactDetail replyDetail = itReplyDetails.next();
              assertEquals(replyDetail.key, syncDetail.key);
              assertEquals(replyDetail.unique_id, syncDetail.unique_id);
          }
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          assertEquals(State.IDLE, mState);
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithDetailChanges() {
          final String fnName = "testRunWithDetailChanges";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contactList.add(contact);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Adding new detail to contact");
          ContactDetail newDetail = new ContactDetail();
          newDetail.value = NEW_DETAIL_VALUE;
          newDetail.key = NEW_DETAIL_KEY;
          newDetail.keyType = NEW_DETAIL_TYPE;
          newDetail.localContactID = contact.localContactID;
          status = mDb.addContactDetail(newDetail);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Modifying detail in contact");
          ContactDetail modDetail = contact.details.get(0);
          modDetail.value = MOD_DETAIL_VALUE;
          modDetail.key = MOD_DETAIL_KEY;
          modDetail.keyType = MOD_DETAIL_TYPE;
          status = mDb.modifyContactDetail(modDetail);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(1, State.MODIFY_CONTACT_LIST);
  
          startSubTest(fnName, "Fetching detail after sync");
          Contact syncContact = new Contact();
          status = mDb.fetchContact(contact.localContactID, syncContact);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Check contact server ID is correct");
          boolean done = false;
          for (ContactDetail detail : syncContact.details) {
              if (detail.localDetailID.equals(newDetail.localDetailID)) {
                  assertEquals(detail.unique_id, Long.valueOf(detail.localDetailID + 2));
                  done = true;
                  break;
              }
          }
          assertTrue(done);
          assertEquals(syncContact.contactID, mReplyContact.contactID);
          assertEquals(syncContact.userID, mReplyContact.userID);
          assertEquals(State.IDLE, mState);
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithContactDeletion() {
          final String fnName = "testRunWithContactDeletion";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database - no sync");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contactList.add(contact);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact");
          status = mDb.deleteContact(contact.localContactID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(1, State.DELETE_CONTACT_LIST);
  
          assertEquals(State.IDLE, mState);
  
          startSubTest(fnName, "Checking change list is empty");
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithContactDetailDeletion() {
          final String fnName = "testRunWithContactDetailDeletion";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database - no sync");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contactList.add(contact);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Adding test contact detail to database - no sync");
          ContactDetail detail = new ContactDetail();
          detail.value = NEW_DETAIL_VALUE;
          detail.key = ContactDetail.DetailKeys.VCARD_PHONE;
          detail.keyType = ContactDetail.DetailKeyTypes.CELL;
          detail.localContactID = contact.localContactID;
          List<ContactDetail> contactDetailList = new ArrayList<ContactDetail>();
          contactDetailList.add(detail);
          status = mDb.syncAddContactDetailList(contactDetailList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact detail without unique ID");
          status = mDb.deleteContactDetail(contact.details.get(0).localDetailID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact detail with unique ID");
          status = mDb.deleteContactDetail(detail.localDetailID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(1, State.DELETE_CONTACT_DETAIL_LIST);
  
          assertEquals(State.IDLE, mState);
  
          startSubTest(fnName, "Checking change list is empty");
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithGroupRemoval() {
          final String fnName = "testRunWithGroupRemoval";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database - no sync");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contactList.add(contact);
          contact.groupList.clear();
          contact.groupList.add(TEST_GROUP_1);
          contact.groupList.add(TEST_GROUP_2);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting group 1");
          status = mDb.deleteContactFromGroup(contact.localContactID, TEST_GROUP_1);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting group 2");
          status = mDb.deleteContactFromGroup(contact.localContactID, TEST_GROUP_2);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(2, State.DELETE_GROUP_LIST);
  
          assertEquals(State.IDLE, mState);
  
          startSubTest(fnName, "Checking change list is empty");
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @LargeTest
      @Suppress
      // Breaks tests
      public void testRunWithManyContacts() {
          final String fnName = "testRunWithManyContacts";
          mBulkContactTest = true;
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding " + NO_OF_CONTACTS_TO_ADD + " test contact to database");
          mInitialContactGroupCount = 0;
          for (int i = 0; i < NO_OF_CONTACTS_TO_ADD; i++) {
              Contact contact = mTestModule.createDummyContactData();
              ServiceStatus status = mDb.addContact(contact);
              assertEquals(ServiceStatus.SUCCESS, status);
              mInitialContactGroupCount += contact.groupList.size();
  
              startSubTest(fnName, "Adding new detail to contact");
              ContactDetail newDetail = new ContactDetail();
              newDetail.value = NEW_DETAIL_VALUE;
              newDetail.key = NEW_DETAIL_KEY;
              newDetail.keyType = NEW_DETAIL_TYPE;
              newDetail.localContactID = contact.localContactID;
              status = mDb.addContactDetail(newDetail);
              assertEquals(ServiceStatus.SUCCESS, status);
  
              startSubTest(fnName, "Modifying detail in contact");
              ContactDetail modDetail = contact.details.get(0);
              modDetail.value = MOD_DETAIL_VALUE;
              modDetail.key = MOD_DETAIL_KEY;
              modDetail.keyType = MOD_DETAIL_TYPE;
              status = mDb.modifyContactDetail(modDetail);
              assertEquals(ServiceStatus.SUCCESS, status);
          }
          Log.i(LOG_TAG, "Total number of groups = " + mItemCount + " out of "
                  + NO_OF_CONTACTS_TO_ADD + " contacts");
  
          startSubTest(fnName, "Running processor");
          runProcessor(NO_OF_CONTACTS_TO_ADD, State.ADD_CONTACT_LIST);
  
          startSubTest(fnName, "Fetching contacts after sync");
          Cursor cursor = mDb.openContactSummaryCursor(null, null);
          assertTrue(cursor != null);
          while (cursor.moveToNext()) {
              ContactSummary summary = ContactSummaryTable.getQueryData(cursor);
              Contact testContact = new Contact();
              ServiceStatus status = mDb.fetchContact(summary.localContactID, testContact);
              assertEquals(ServiceStatus.SUCCESS, status);
              assertEquals(Long.valueOf(summary.localContactID + 1), testContact.contactID);
              if ((testContact.localContactID & 15) != 0) {
                  assertEquals(Long.valueOf(summary.localContactID + 2), testContact.userID);
              } else {
                  assertEquals(null, testContact.userID);
              }
              for (ContactDetail detail : testContact.details) {
                  ContactDetail replyDetail = new ContactDetail();
                  generateReplyDetail(replyDetail, detail);
                  assertEquals(replyDetail.unique_id, detail.unique_id);
                  Long syncServerId = TestModule.fetchSyncServerId(detail.localDetailID, mDb
                          .getReadableDatabase());
                  assertEquals(Long.valueOf(-1), syncServerId);
              }
          }
          cursor.close();
          assertEquals(State.IDLE, mState);
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          mState = State.IDLE;
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @MediumTest
      @Suppress
      // Breaks tests
      public void testRunWithAddDeleteContactChange() {
          final String fnName = "testRunWithAddDeleteContactChange";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database");
          Contact contact = mTestModule.createDummyContactData();
          ServiceStatus status = mDb.addContact(contact);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact from database");
          status = mDb.deleteContact(contact.localContactID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(0, State.IDLE);
          assertEquals(State.IDLE, mState);
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @Suppress
      // Breaks tests
      public void testRunWithAddDeleteContactDetailChange() {
          final String fnName = "testRunWithAddDeleteContactDetailChange";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contactList.add(contact);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Adding new detail to contact");
          ContactDetail newDetail = new ContactDetail();
          newDetail.value = NEW_DETAIL_VALUE;
          newDetail.key = NEW_DETAIL_KEY;
          newDetail.keyType = NEW_DETAIL_TYPE;
          newDetail.localContactID = contact.localContactID;
          status = mDb.addContactDetail(newDetail);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact detail without unique ID");
          status = mDb.deleteContactDetail(contact.details.get(0).localDetailID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Deleting contact detail with unique ID");
          status = mDb.deleteContactDetail(newDetail.localDetailID);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(1, State.DELETE_CONTACT_DETAIL_LIST);
          assertEquals(State.IDLE, mState);
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  
      @Suppress
      // Breaks tests
      public void testRunWithAddDeleteGroupChange() {
          final String fnName = "testRunWithAddDeleteGroupChange";
          Log.i(LOG_TAG, "***** EXECUTING " + fnName + " *****");
          mTestStep = 1;
  
          startSubTest(fnName, "Checking change list is empty");
          long noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Adding test contact to database");
          Contact contact = mTestModule.createDummyContactData();
          contact.contactID = TestModule.generateRandomLong();
          contact.userID = generateTestUserID(contact.contactID);
          List<Contact> contactList = new ArrayList<Contact>();
          contact.groupList.clear();
          contactList.add(contact);
          ServiceStatus status = mDb.syncAddContactList(contactList, false, false);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Adding group to contact");
          status = mDb.addContactToGroup(contact.localContactID, TEST_GROUP_1);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Removing group from contact");
          status = mDb.deleteContactFromGroup(contact.localContactID, TEST_GROUP_1);
          assertEquals(ServiceStatus.SUCCESS, status);
  
          startSubTest(fnName, "Running processor");
          runProcessor(0, State.IDLE);
          assertEquals(State.IDLE, mState);
  
          noOfChanges = ContactChangeLogTable.fetchNoOfContactDetailChanges(null, mDb
                  .getReadableDatabase());
          assertEquals(0, noOfChanges);
          noOfChanges = ContactDetailsTable.syncNativeFetchNoOfChanges(mDb.getReadableDatabase());
          assertEquals(0, noOfChanges);
  
          startSubTest(fnName, "Running processor with no contact changes");
          runProcessor(0, State.IDLE);
  
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, fnName + " has completed successfully");
          Log.i(LOG_TAG, "*************************************************************************");
          Log.i(LOG_TAG, "");
      }
  }
  