/* * 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.database.tables; import java.util.List; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.text.format.Time; import com.vodafone360.people.Settings; import com.vodafone360.people.database.DatabaseHelper; import com.vodafone360.people.datatypes.ContactDetail; import com.vodafone360.people.engine.contactsync.ContactChange; import com.vodafone360.people.utils.CloseUtils; import com.vodafone360.people.utils.LogUtils; /** * Contains all the functionality related to the native contact change log * database table. This table is used to persist database changes that have not * yet been synced to the native Android phonebook. This class is never * instantiated hence all methods must be static. * * @version %I%, %G% */ public abstract class NativeChangeLogTable { /** * Name of the table as it appears in the database */ public static final String TABLE_NAME = "NativeChangeLog"; /** * Name of a temporary table created and used to batch the deletion of sync * data from the change log table. */ private static final String TEMP_CONTACT_CHANGE_TABLE = "SyncNativeInfo"; /** * Primary key for the temporary table */ private static final String TEMP_CONTACT_CHANGE_TABLE_ID = "Id"; /** * Secondary key which links to the primary key of the change log table */ private static final String TEMP_CONTACT_CHANGE_LOG_ID = "LogId"; /** * Query string to get a list of deleted details for a provided type and local id. * * @see #getDeletedDetails(long, SQLiteDatabase) */ private final static String QUERY_DELETED_DETAILS = "SELECT " + Field.CHANGETYPE + ", " + Field.LOCALCONTACTID + ", " + Field.NATIVECONTACTID + ", " + Field.LOCALDETAILID + ", " + Field.NATIVEDETAILID + ", " + Field.DETAILKEY + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALCONTACTID + " = ? AND " + Field.CHANGETYPE + " = ?"; /** * SELECT NativeContactId FROM NativeChangeLog WHERE ChangeType = 0 ORDER BY NativeContactId */ public final static String QUERY_MODIFIED_CONTACTS_LOCAL_IDS = "SELECT DISTINCT " + Field.LOCALCONTACTID + " FROM " + TABLE_NAME + " ORDER BY " + Field.LOCALCONTACTID; /** * */ public final static String QUERY_MODIFIED_CONTACTS_LOCAL_IDS_NO_ORDERBY = "SELECT " + Field.LOCALCONTACTID + " FROM " + TABLE_NAME; /** * An enumeration of all the field names in the database. */ private static enum Field { NATIVECHANGEID("NativeChangeId"), CHANGETYPE("ChangeType"), LOCALCONTACTID("LocalContactId"), NATIVECONTACTID("NativeContactId"), LOCALDETAILID("LocalDetailId"), NATIVEDETAILID("NativeDetailId"), DETAILKEY("DetailKey"), TIMESTAMP("Timestamp"); /** * The name of the field as it appears in the database */ private String mField; /** * Constructor * * @param field - The name of the field (see list above) */ private Field(String field) { mField = field; } /** * @return the name of the field as it appears in the database. */ public String toString() { return mField; } } /** * Enumerates the type of changes supported by this table, including: * <ul> * <li>DELETE_CONTACT: A contact has been deleted from the database and * needs to be deleted in the native. The local Contact ID, native Contact * ID fields are required</li> * <li>DELETE_DETAIL: A contact detail has been deleted from the database * and needs to be deleted from the native. The local Contact ID, native * Contact ID, local Detail ID, native Detail ID (if the detail has one) and * detail key are required</li> </li> * </ul> */ public static enum ContactChangeType { DELETE_CONTACT, DELETE_DETAIL } /** * Wraps up the data present in the change log table */ public static class ContactChangeInfo { public Long mNativeChangeId = null; public ContactChangeType mType = null; public Long mLocalContactId = null; public Integer mNativeContactId = null; public Long mLocalDetailId = null; public Integer mNativeDetailId = null; public ContactDetail.DetailKeys mDetailKey = null; /** * Converts the encapsulated data into a string that can be displayed * for debug purposes. */ @Override public String toString() { return "Contact Change ID: " + mNativeChangeId + "\n" + "Contact Change Type: " + mType + "\n" + "Local Contact ID: " + mLocalContactId + "\n" + "Native Contact ID: " + mNativeContactId + "\n" + "Local Detail ID: " + mLocalDetailId; } } /** * Create Server Change Log Table. * * @param writeableDb A writable SQLite database * @throws SQLException If an SQL compilation error occurs */ public static void create(SQLiteDatabase writableDb) throws SQLException { DatabaseHelper.trace(true, "NativeChangeLogTable.create()"); writableDb.execSQL("CREATE TABLE " + TABLE_NAME + " (" + Field.NATIVECHANGEID + " INTEGER PRIMARY KEY, " + Field.CHANGETYPE + " INTEGER NOT NULL, " + Field.LOCALCONTACTID + " LONG NOT NULL, " + Field.NATIVECONTACTID + " LONG, " + Field.LOCALDETAILID + " LONG, " + Field.NATIVEDETAILID + " INTEGER, " + Field.DETAILKEY + " INTEGER, " + Field.TIMESTAMP + " DATE);"); } /** * Fetches a comma separated list of table fields which can be used in an * SQL SELECT statement as the query projection. The * {@link #getQueryData(Cursor)} method can used to fetch data from the * cursor. * * @param whereClause A where clause (without the where) to filter the * result. Cannot be null. * @return SQL string */ private static String getQueryStringSql(String whereClause) { DatabaseHelper.trace(false, "NativeChangeLogTable.getQueryStringSql()"); String whereClauseFull = ""; if (whereClause != null) { whereClauseFull = " WHERE " + whereClause; } return "SELECT " + Field.NATIVECHANGEID + ", " + Field.CHANGETYPE + ", " + Field.LOCALCONTACTID + ", " + Field.NATIVECONTACTID + ", " + Field.LOCALDETAILID + ", " + Field.NATIVEDETAILID + ", " + Field.DETAILKEY + ", " + Field.TIMESTAMP + " FROM " + TABLE_NAME + whereClauseFull; } /** * Column indices for database queries */ private static final int NATIVECHANGEID = 0; private static final int TYPE = 1; private static final int LOCALCONTACTID = 2; private static final int NATIVECONTACTID = 3; private static final int LOCALDETAILID = 4; private static final int NATIVEDETAILID = 5; private static final int DETAILKEY = 6; /** * Fetches change log information from a cursor at the current position. The * {@link #getQueryStringSql(String)} method should be used to make the * query. * * @param c The cursor from the query * @return A filled in ContactChangeInfo object */ public static ContactChangeInfo getQueryData(Cursor c) { DatabaseHelper.trace(false, "NativeChangeLogTable.getQueryData()"); ContactChangeInfo info = new ContactChangeInfo(); if (!c.isNull(NATIVECHANGEID)) { info.mNativeChangeId = c.getLong(NATIVECHANGEID); } if (!c.isNull(TYPE)) { final int typeIdx = c.getInt(TYPE); if (typeIdx < ContactChangeType.values().length) { info.mType = ContactChangeType.values()[typeIdx]; } } if (!c.isNull(LOCALCONTACTID)) { info.mLocalContactId = c.getLong(LOCALCONTACTID); } if (!c.isNull(NATIVECONTACTID)) { info.mNativeContactId = c.getInt(NATIVECONTACTID); } if (!c.isNull(LOCALDETAILID)) { info.mLocalDetailId = c.getLong(LOCALDETAILID); } if (!c.isNull(NATIVEDETAILID)) { info.mNativeDetailId = c.getInt(NATIVEDETAILID); } if (!c.isNull(DETAILKEY)) { final int keyIdx = c.getInt(DETAILKEY); if (keyIdx < ContactDetail.DetailKeys.values().length) { info.mDetailKey = ContactDetail.DetailKeys.values()[keyIdx]; } } // Ignore timestamp return info; } /** * Inserts "Delete Contact" operation into the change log. * * @param localContactId Local contact ID from the Contacts table * @param nativeContactId Native contact ID. If this is null it is assumed * that the contact hasn't yet been synced with the native * phonebook, so the new contact change is removed from the log * and the function returns. * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ public static boolean addDeletedContactChange(Long localContactId, Integer nativeContactId, SQLiteDatabase writableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.addDeletedContactChange()"); if (localContactId == null) { LogUtils.logE("NativeChangeLogTable.addDeletedContactChange() Invalid parameter"); return false; } boolean addToLog = true; if (nativeContactId == null || nativeContactId == -1 || nativeContactId == 0) { // FIXME: invalid native id, no need to add it to the table. Why is this happening??? addToLog = false; } removeContactChanges(localContactId, writableDb); if (!addToLog) { return true; } ContactChangeInfo info = new ContactChangeInfo(); info.mType = ContactChangeType.DELETE_CONTACT; info.mLocalContactId = localContactId; info.mNativeContactId = nativeContactId; if (!addContactChange(info, writableDb)) { return false; } return true; } /** * Inserts "Delete Contact Detail" operation into the change log. * * @param detail Must have a valid local contact ID, local detail ID and * key. Will not be added to the change log if the contact native * ID is null. * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ public static boolean addDeletedContactDetailChange(ContactDetail detail, SQLiteDatabase writableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.addDeletedContactDetailChange()"); if (detail.localContactID == null || detail.localDetailID == null || detail.key == null) { LogUtils.logE("NativeChangeLogTable.addDeletedContactDetailChange() Invalid parameter"); return false; } if (isContactChangeInList(detail.localContactID, ContactChangeType.DELETE_CONTACT, writableDb)) { LogUtils .logE("NativeChangeLogTable.addDeletedContactDetailChange() Associated contact has already been deleted (#1)"); return false; } if (isContactDetailChangeInList(detail.localDetailID, ContactChangeType.DELETE_DETAIL, writableDb)) { LogUtils .logE("NativeChangeLogTable.addDeletedContactDetailChange() Associated contact detail has already been deleted (#2)"); return false; } removeContactDetailChanges(detail.localDetailID, writableDb); ContactChangeInfo info = new ContactChangeInfo(); info.mLocalContactId = detail.localContactID; info.mNativeContactId = detail.nativeContactId; info.mLocalDetailId = detail.localDetailID; info.mType = ContactChangeType.DELETE_DETAIL; info.mDetailKey = detail.key; info.mNativeDetailId = detail.nativeDetailId; if (info.mNativeContactId == null || info.mNativeContactId == -1) { // FIXME: this shall not happen (has been potentially fixed) but currently guarding against it... // This line will be removed later when the root cause is completely identified LogUtils.logE("=========================================================================================================================="); LogUtils.logE("NativeChangeLogTable.addDeletedContactDetailChange(): a detail without a native contact id can't be added to the table!!!!"); LogUtils.logE("=========================================================================================================================="); return true; } if (!addContactChange(info, writableDb)) { return false; } return true; } /** * Inserts a contact change into the table. * * @param info contact change info. This must have a valid type field and * depending on type other fields may also be required (see * {@link ContactChangeType}). * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ private static boolean addContactChange(ContactChangeInfo info, SQLiteDatabase writableDb) { DatabaseHelper.trace(true, "NativeChangeLogTable.addContactChange()"); try { ContentValues changeValues = new ContentValues(); changeValues.put(Field.CHANGETYPE.toString(), info.mType.ordinal()); changeValues.put(Field.LOCALCONTACTID.toString(), info.mLocalContactId); if (info.mDetailKey != null) { changeValues.put(Field.DETAILKEY.toString(), info.mDetailKey.ordinal()); } if (info.mNativeContactId != null) { changeValues.put(Field.NATIVECONTACTID.toString(), info.mNativeContactId); } if (info.mNativeDetailId != null) { changeValues.put(Field.NATIVEDETAILID.toString(), info.mNativeDetailId); } if (info.mLocalDetailId != null) { changeValues.put(Field.LOCALDETAILID.toString(), info.mLocalDetailId); } Time time = new Time(); time.setToNow(); changeValues.put(Field.TIMESTAMP.toString(), time.format2445()); long id = writableDb.insertOrThrow(TABLE_NAME, null, changeValues); if (id < 0) { LogUtils .logE("NativeChangeLogTable.addContactChange() Unable to add contact change to log table - a database error has occurred"); return false; } info.mNativeChangeId = id; return true; } catch (SQLException e) { LogUtils .logE( "NativeChangeLogTable.addContactChange() SQLException - Unable to add contact change to log table", e); return false; } } /** * Determines if a specific contact and change type exists in the change * log. * * @param localContactId Local contact ID from the Contacts table * @param type The change type to find * @param readableDb Readable SQLite database * @return true if the change is found, false otherwise */ public static boolean isContactChangeInList(Long localContactId, ContactChangeType type, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.isContactChangeInList()"); Cursor c = null; try { c = readableDb.rawQuery("SELECT " + Field.LOCALCONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALCONTACTID + "=" + localContactId + " AND " + Field.CHANGETYPE + "=" + type.ordinal(), null); if (c.moveToFirst()) { return true; } return false; } catch (SQLException e) { return false; } finally { CloseUtils.close(c); c = null; } } /** * * @param nativeContactId * @param type * @param readableDb * @return */ public static boolean isContactChangeInList(long nativeContactId, ContactChangeType type, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.isContactChangeInList()"); Cursor c = null; try { c = readableDb.rawQuery("SELECT " + Field.LOCALCONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.NATIVECONTACTID + "=" + nativeContactId + " AND " + Field.CHANGETYPE + "=" + type.ordinal(), null); if (c.moveToFirst()) { return true; } return false; } catch (SQLException e) { return false; } finally { CloseUtils.close(c); c = null; } } /** * Determines if a specific contact detail and change type exists in the * change log. * * @param localDetailId Local contact detail ID from ContactDetail table * @param type The change type to find * @param readableDb Readable SQLite database * @return true if the change is found, false otherwise */ private static boolean isContactDetailChangeInList(Long localDetailId, ContactChangeType type, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.isContactDetailChangeInList()"); Cursor c = null; try { c = readableDb.rawQuery("SELECT " + Field.LOCALDETAILID + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALDETAILID + "=" + localDetailId + " AND " + Field.CHANGETYPE + "=" + type.ordinal(), null); if (c.moveToFirst()) { return true; } return false; } catch (SQLException e) { return false; } finally { CloseUtils.close(c); c = null; } } /** * Removes all changes from the change log associated with a particular * contact. * * @param localContactId Local contact ID from the Contacts table * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ public static boolean removeContactChanges(Long localContactId, SQLiteDatabase writableDb) { DatabaseHelper.trace(true, "NativeChangeLogTable.removeContactChanges()"); try { int result = writableDb.delete(TABLE_NAME, Field.LOCALCONTACTID + "=" + localContactId, null); if (result <= 0) { return false; } } catch (SQLException e) { return false; } return true; } /** * Removes all changes from the change log associated with a particular * contact detail. * * @param localDetailId Local contact detail ID from the Contact details * table * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ public static boolean removeContactDetailChanges(Long localDetailId, SQLiteDatabase writableDb) { DatabaseHelper.trace(true, "NativeChangeLogTable.removeContactDetailChanges()"); try { if (writableDb.delete(TABLE_NAME, Field.LOCALDETAILID + "=" + localDetailId, null) < 0) { return false; } // When 0 rows are deleted we still should return success } catch (SQLException e) { return false; } return true; } /** * Fetches the number of changes listed in the table. * * @param type The type of change to count, if null all change types are * included * @param readableDb Readable SQLite database * @return The number of records */ public static long fetchNoOfChanges(ContactChangeType type, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.fetchNoOfChanges()"); long noOfChanges = 0; Cursor c = null; try { String query = "SELECT COUNT(*) FROM " + TABLE_NAME; if (type != null) { query += " WHERE " + Field.CHANGETYPE.toString() + "=" + type.ordinal(); } c = readableDb.rawQuery(query, null); if (c.moveToFirst()) { noOfChanges = c.getLong(0); } } catch (SQLException e) { LogUtils .logE( "NativeChangeLogTable.fetchNoOfChanges() SQLException - Unable to fetch changes", e); } finally { CloseUtils.close(c); c = null; } return noOfChanges; } /** * Removes a list of changes from the change log table. This method is used * once the native has been successfully updated by the client. * * @param changeIdList A list of contact change IDs (can be obtained from * {@link ContactChangeInfo#mNativeChangeId}. * @param writableDb Writable SQLite database * @return true if successful, false otherwise */ public static boolean syncDeleteNativeChangeLog(List<Long> changeIdList, SQLiteDatabase writableDb) { DatabaseHelper.trace(true, "NativeChangeLogTable.syncDeleteNativeChangeLog()"); try { writableDb.beginTransaction(); writableDb.execSQL("DROP TABLE IF EXISTS " + TEMP_CONTACT_CHANGE_TABLE); writableDb.execSQL("CREATE TEMPORARY TABLE " + TEMP_CONTACT_CHANGE_TABLE + "(" + TEMP_CONTACT_CHANGE_TABLE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + TEMP_CONTACT_CHANGE_LOG_ID + " LONG);"); ContentValues values = new ContentValues(); for (Long changeId : changeIdList) { values.put(TEMP_CONTACT_CHANGE_LOG_ID, changeId); if (writableDb.insertOrThrow(TEMP_CONTACT_CHANGE_TABLE, null, values) < 0) { return false; } } writableDb.execSQL("DELETE FROM " + TABLE_NAME + " WHERE " + Field.NATIVECHANGEID + " IN (SELECT " + TEMP_CONTACT_CHANGE_LOG_ID + " FROM " + TEMP_CONTACT_CHANGE_TABLE + ");"); writableDb.setTransactionSuccessful(); return true; } catch (SQLException e) { LogUtils.logE("NativeChangeLogTable.syncDeleteNativeChangeLog() " + "SQLException - Unable to remove contact detail change from log", e); return false; } finally { writableDb.endTransaction(); } } /** * Fetches a list of changes from the table for a specific type. The list is * ordered by local contact ID. * * @param type The type of change to return (cannot be null) * @param readableDb Readable SQLite database * @return A cursor for use with {@link #getQueryData(Cursor)} if * successful, null otherwise */ public static Cursor fetchContactChangeLogCursor(ContactChangeType type, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.fetchContactChangeLogCursor()"); try { return readableDb.rawQuery(getQueryStringSql(Field.CHANGETYPE + "=" + type.ordinal() + " ORDER BY " + Field.LOCALCONTACTID), null); } catch (SQLiteException e) { LogUtils.logE("NativeChangeLogTable.fetchContactChangeLogCursor() " + "Exception - Unable to fetch native change log cursor", e); return null; } } /** * Fetches a list of changes from the table for a specific type. The list is * ordered by local contact ID. * * @param type The type of change to return * @param contactChangeList The list to be populated * @param readableDb Readable SQLite database * @return true if successful, false otherwise */ public static boolean fetchContactChangeLog(ContactChangeType type, List<ContactChangeInfo> nativeInfoList, SQLiteDatabase readableDb) { DatabaseHelper.trace(false, "NativeChangeLogTable.fetchContactChangeLog()"); Cursor c = null; try { nativeInfoList.clear(); String whereClause = null; if (type != null) { whereClause = Field.CHANGETYPE + "=" + type.ordinal(); } c = readableDb.rawQuery(getQueryStringSql(whereClause) + " ORDER BY " + Field.NATIVECONTACTID, null); while (c.moveToNext()) { nativeInfoList.add(getQueryData(c)); } return true; } catch (SQLiteException e) { LogUtils.logE("NativeChangeLogTable.fetchContactChangeLog() " + "Exception - Unable to fetch native change log", e); return false; } finally { CloseUtils.close(c); c = null; } } /** * * @param readableDb * @return */ public static long[] getModifiedContactsLocalIds(SQLiteDatabase readableDb) { long[] ids = null; Cursor cursor = null; try { final int LOCAL_ID_INDEX = 0; cursor = readableDb.rawQuery(QUERY_MODIFIED_CONTACTS_LOCAL_IDS, null); if (cursor.getCount() > 0) { int i = 0; ids = new long[cursor.getCount()]; while (cursor.moveToNext()) { ids[i++] = cursor.getInt(LOCAL_ID_INDEX); } } else { return null; } } catch (Exception e) { if (Settings.ENABLED_DATABASE_TRACE) { DatabaseHelper.trace(false, "getModifiedContactsNativeIds(): "+e); } } finally { CloseUtils.close(cursor); cursor = null; } return ids; } /** * SELECT NativeContactId FROM NativeChangeLog WHERE ChangeType = 0 ORDER BY NativeContactId */ private final static String QUERY_DELETED_CONTACTS_NATIVE_IDS = "SELECT " + Field.NATIVECONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.CHANGETYPE + " = " + ContactChangeType.DELETE_CONTACT.ordinal() + " ORDER BY " + Field.NATIVECONTACTID; /** * */ private final static String QUERY_DELETED_CONTACT_NATIVE_ID = "SELECT " + Field.NATIVECONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.CHANGETYPE + " = " + ContactChangeType.DELETE_CONTACT.ordinal() + " AND " + Field.LOCALCONTACTID + " = ?"; /** * Gets the list of native ids for the deleted contacts. * * @param readableDb the db to query from * @return an array of long ids of deleted native contacts, null if none */ public static long[] getDeletedContactsNativeIds(SQLiteDatabase readableDb) { long[] ids = null; Cursor cursor = null; try { final int NATIVE_ID_INDEX = 0; cursor = readableDb.rawQuery(QUERY_DELETED_CONTACTS_NATIVE_IDS, null); if (cursor.getCount() > 0) { int i = 0; ids = new long[cursor.getCount()]; while (cursor.moveToNext()) { ids[i++] = cursor.getInt(NATIVE_ID_INDEX); } } else { return null; } } catch (Exception e) { if (Settings.ENABLED_DATABASE_TRACE) { DatabaseHelper.trace(false, "NativeChangeLogTable.getDeletedContactsNativeIds(): "+e); } } finally { CloseUtils.close(cursor); cursor = null; } return ids; } /** * Gets the list of deleted details as a ContactChange array. * * Note: the ContactChange object will have the type ContactChange.TYPE_DELETE_DETAIL * * @see ContactChange * * @param localContactId the local contact id to query * @param readableDb the db to query from * @return an array of ContactChange, null if no details where found */ public static ContactChange[] getDeletedDetails(long localContactId, SQLiteDatabase readableDb) { final String[] SELECTION = { String.valueOf(localContactId), Integer.toString(ContactChangeType.DELETE_DETAIL.ordinal()) }; Cursor cursor = null; try { cursor = readableDb.rawQuery(QUERY_DELETED_DETAILS, SELECTION); if (cursor.getCount() > 0) { final ContactChange[] deletedDetails = new ContactChange[cursor.getCount()]; int index = 0; while (cursor.moveToNext()) { // fill the ContactChange class with contact detail data final ContactChange change = new ContactChange(); deletedDetails[index++] = change; // set the ContactChange as a deleted contact detail change.setType(ContactChange.TYPE_DELETE_DETAIL); // LocalContactId=1 change.setInternalContactId(cursor.isNull(1) ? ContactChange.INVALID_ID : cursor.getLong(1)); // NativeContactId=2 change.setNabContactId(cursor.isNull(2) ? ContactChange.INVALID_ID : cursor.getLong(2)); // DetailLocalId=3 change.setInternalDetailId(cursor.isNull(3) ? ContactChange.INVALID_ID : cursor.getInt(3)); // NativeDetailId=4 change.setNabDetailId(cursor.isNull(4) ? ContactChange.INVALID_ID : cursor.getLong(4)); // DetailKey=5 change.setKey(cursor.isNull(5) ? ContactChange.KEY_UNKNOWN : ContactDetailsTable.mapInternalKeyToContactChangeKey(cursor.getInt(5))); if (change.getNabContactId() == ContactChange.INVALID_ID) { LogUtils.logE("NativeChangeLogTable.getDeletedDetails(): the native contact id shall not be null! cc.internalContactId="+change.getInternalContactId()+", cc.key="+change.getKey()); } } return deletedDetails; } } catch (Exception e) { if (Settings.ENABLED_DATABASE_TRACE) { DatabaseHelper.trace(false, "NativeChangeLogTable.getDeletedContactsNativeIds(): "+e); } } finally { CloseUtils.close(cursor); cursor = null; } return null; } /** * * @param localContactId * @param readableDb * @return */ public static long getDeletedContactNativeId(long localContactId, SQLiteDatabase readableDb) { final String[] SELECTION = { String.valueOf(localContactId) }; Cursor cursor = null; try { cursor = readableDb.rawQuery(QUERY_DELETED_CONTACT_NATIVE_ID, SELECTION); if (cursor.getCount() > 0) { cursor.moveToNext(); final Long nativeId = cursor.getLong(0); return nativeId == null ? -1 : nativeId; } } catch (Exception e) { if (Settings.ENABLED_DATABASE_TRACE) { DatabaseHelper.trace(false, "NativeChangeLogTable.getDeletedContactsNativeIds(): "+e); } } finally { CloseUtils.close(cursor); cursor = null; } return -1; } }