/*
* 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import com.vodafone360.people.Settings;
import com.vodafone360.people.database.DatabaseHelper;
import com.vodafone360.people.database.SQLKeys;
import com.vodafone360.people.database.DatabaseHelper.ServerIdInfo;
import com.vodafone360.people.database.persistenceHelper.PersistenceHelper;
import com.vodafone360.people.datatypes.Contact;
import com.vodafone360.people.engine.contactsync.ContactChange;
import com.vodafone360.people.service.ServiceStatus;
import com.vodafone360.people.utils.CloseUtils;
import com.vodafone360.people.utils.LogUtils;
import com.vodafone360.people.utils.StringBufferPool;
/**
* Contains all the functionality related to the Contacts database table. This
* table only contains high level information for a contact. Each contact
* contains many contact details (see {@link ContactDetailsTable}), these
* details include name, phone number, address, etc. This class is never
* instantiated hence all methods must be static.
*
* @version %I%, %G%
*/
public abstract class ContactsTable {
/**
* The name of the table as it appears in the database.
*/
public static final String TABLE_NAME = "Contacts";
/**
* Contains ID information used to identify a contact. Also used during sync
* and merge operations.
*/
public static class ContactIdInfo {
/**
* Local contact ID (primary key)
*/
public long localId = 0;
/**
* Server contact ID (can be null)
*/
public Long serverId = null;
/**
* Native contact ID used by native phonebook database (can be null)
*/
public Integer nativeId = null;
/**
* During contact sync, identifies the original local contact in the
* case that localId refers to a duplicate (a contact which has the same
* server ID and needs to be deleted).
*/
public Long mergedLocalId = null;
/**
* True if the contact should be synced to the native phonebook, false
* otherwise This setting is obtained from the server during sync.
*/
public boolean syncToPhone;
/**
* Returns a string representation of this object which should only be
* used for debug.
*/
@Override
public String toString() {
return "LocalID: " + localId + ", ServerId: " + serverId + ", NativeId:" + nativeId
+ ", Merged LocalId:" + mergedLocalId;
}
}
/**
* An enumeration of all the field names in the database.
*/
private static enum Field {
LOCALID("LocalId"),
SERVERID("ServerId"),
USERID("UserId"),
ABOUTME("AboutMe"),
FRIEND("Friend"),
GENDER("Gender"),
UPDATED("Updated"),
NATIVECONTACTID("NativeContactId"),
SYNCTOPHONE("Synctophone");
/**
* 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;
}
}
/**
* Create Contacts Table.
*
* @param writeableDb A writable SQLite database
* @throws SQLException If an SQL compilation error occurs
*/
public static void create(SQLiteDatabase writeableDb) throws SQLException {
DatabaseHelper.trace(true, "ContactsTable.create()");
writeableDb.execSQL("CREATE TABLE " + TABLE_NAME + " (" + Field.LOCALID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + Field.SERVERID + " LONG UNIQUE, "
+ Field.USERID + " LONG, " + Field.ABOUTME + " TEXT, " + Field.FRIEND
+ " BOOLEAN, " + Field.GENDER + " TEXT, " + Field.UPDATED + " INTEGER, "
+ Field.NATIVECONTACTID + " INTEGER, " + Field.SYNCTOPHONE + " BOOLEAN);");
}
/**
* Fetches the list of table fields that can be injected into an SQL query
* statement. The {@link #getQueryData(Cursor, Contact)} method can be used
* to obtain the data from the query.
*
* @return The query string
*/
private static String getFullQueryList() {
return Field.LOCALID + ", " + Field.SERVERID + ", " + Field.USERID + ", " + Field.ABOUTME
+ ", " + Field.FRIEND + ", " + Field.GENDER + ", " + Field.UPDATED + ", "
+ Field.NATIVECONTACTID + ", " + Field.SYNCTOPHONE;
}
/**
* Returns a full SQL query statement to fetch the contact information. The
* {@link #getQueryData(Cursor, Contact)} method can be used to obtain the
* data from the query.
*
* @param whereClause An SQL where clause (without the "WHERE"). Cannot be
* null.
* @return The query string
*/
private static String getQueryStringSql(String whereClause) {
return "SELECT " + getFullQueryList() + " FROM " + TABLE_NAME + " WHERE " + whereClause;
}
/**
* UPDATE Contacts SET
* NativeContactId = ?
* WHERE LocalId = ?
*/
private static final String UPDATE_NATIVE_ID_BY_LOCAL_CONTACT_ID = "UPDATE "
+TABLE_NAME + " SET " + Field.NATIVECONTACTID + "=? WHERE " + Field.LOCALID + "=?";
/**
* SELECT NativeId
* FROM Contacts
* WHERE LocalId = ?
*/
private static final String QUERY_NATIVE_ID_BY_LOCAL_CONTACT_ID =
"SELECT " + Field.NATIVECONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALID + "=?";
/**
* SELECT LocalId
* FROM Contacts
* WHERE ServerId = ?
*/
private static final String QUERY_LOCAL_ID_BY_SERVER_ID =
"SELECT " + Field.LOCALID + " FROM " + TABLE_NAME + " WHERE " + Field.SERVERID + "=?";
/**
* Column indices which match the query string returned by
* {@link #getQueryStringSql(String)}.
*/
private static final int LOCALID = 0;
private static final int CONTACTID = 1;
private static final int USERID = 2;
private static final int ABOUTME = 3;
private static final int FRIEND = 4;
private static final int GENDER = 5;
private static final int UPDATED = 6;
private static final int NATIVE_CONTACTID = 7;
private static final int SYNCTOPHONE = 8;
/**
* Reads a cursor at its current position to populate a Contact object
*
* @param c A cursor returned from a SELECT query on the projection returned
* by {@link #getFullQueryList()}.
* @param contact An newly constructed Contact object that will be filled
* in.
*/
private static void getQueryData(Cursor c, Contact contact) {
if (!c.isNull(LOCALID)) {
contact.localContactID = c.getLong(LOCALID);
}
if (!c.isNull(CONTACTID)) {
contact.contactID = c.getLong(CONTACTID);
}
if (!c.isNull(USERID)) {
contact.userID = c.getLong(USERID);
}
if (!c.isNull(ABOUTME)) {
contact.aboutMe = c.getString(ABOUTME);
}
if (!c.isNull(FRIEND)) {
contact.friendOfMine = (c.getInt(FRIEND) == 0 ? false : true);
}
if (!c.isNull(GENDER)) {
contact.gender = c.getInt(GENDER);
}
if (!c.isNull(UPDATED)) {
contact.updated = c.getLong(UPDATED);
}
if (!c.isNull(NATIVE_CONTACTID)) {
contact.nativeContactId = c.getInt(NATIVE_CONTACTID);
}
if (!c.isNull(SYNCTOPHONE)) {
contact.synctophone = (c.getInt(SYNCTOPHONE) == 0 ? false : true);
}
}
/**
* Returns a ContentValues object that can be used to insert or modify a
* contact in the table.
*
* @param contact The source contact object
* @return The ContentValues object containing the data.
* @note NULL fields in the given contact will not be included in the
* ContentValues
*/
private static ContentValues fillUpdateData(Contact contact) {
ContentValues contactValues = new ContentValues();
if (contact.contactID != null) {
contactValues.put(Field.SERVERID.toString(), contact.contactID);
}
if (contact.userID != null) {
contactValues.put(Field.USERID.toString(), contact.userID);
}
if (contact.friendOfMine != null) {
contactValues.put(Field.FRIEND.toString(), contact.friendOfMine);
}
if (contact.gender != null) {
contactValues.put(Field.GENDER.toString(), contact.gender);
}
if (contact.updated != null) {
contactValues.put(Field.UPDATED.toString(), contact.updated);
}
if (contact.aboutMe != null) {
contactValues.put(Field.ABOUTME.toString(), contact.aboutMe);
}
if (contact.nativeContactId != null) {
contactValues.put(Field.NATIVECONTACTID.toString(), contact.nativeContactId);
}
if (contact.synctophone != null) {
contactValues.put(Field.SYNCTOPHONE.toString(), contact.synctophone);
}
return contactValues;
}
/**
* Validates a contact exists and returns the relevant contact id
* information.
*
* @param localContactId The local Id of the contact to find
* @param readableDb Readable database object
* @return A ContactIdInfo object if successful, or NULL if the contact does
* not exist
*/
public static ContactIdInfo validateContactId(long localContactId, SQLiteDatabase readableDb) {
ContactIdInfo info = null;
String[] args = {
String.format("%d", localContactId)
};
Cursor c = null;
try {
c = readableDb.rawQuery("SELECT " + Field.SERVERID + "," + Field.NATIVECONTACTID + ","
+ Field.SYNCTOPHONE + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALID
+ " = ?", args);
if (c.moveToFirst()) {
info = new ContactIdInfo();
if (!c.isNull(0)) {
info.serverId = c.getLong(0);
}
if (!c.isNull(1)) {
info.nativeId = c.getInt(1);
}
if (!c.isNull(2)) {
info.syncToPhone = (c.getInt(2) == 0 ? false : true);
}
info.localId = localContactId;
}
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.validateContactId() Exception - "
+ "Unable to validate contact ID", e);
return null;
} finally {
CloseUtils.close(c);
c = null;
}
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(false, "ContactsTable.validateContactId() localContactId["
+ localContactId + "] serverId[" + info.serverId + "] nativeId["
+ info.nativeId + "]");
}
return info;
}
/**
* Deletes a contact from the Contacts table.
*
* @param localContactID The primary key ID of the contact
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus deleteContact(long localContactID, SQLiteDatabase writeableDb) {
DatabaseHelper.trace(true, "ContactsTable.deleteContact() localContactID[" + localContactID
+ "]");
try {
String[] args = {
String.format("%d", localContactID)
};
if (writeableDb.delete(TABLE_NAME, Field.LOCALID + "=?", args) <= 0) {
LogUtils.logE("ContactsTable.deleteContact() Unable to delete contact");
return ServiceStatus.ERROR_NOT_FOUND;
}
return ServiceStatus.SUCCESS;
} catch (SQLException e) {
LogUtils.logE("ContactsTable.deleteContact() SQLException - "
+ "Unable to delete contact", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
/**
* Modifies the contact server ID and user ID stored in the contacts table.
*
* @param localContactId The primary key ID of the contact
* @param serverId The new server ID
* @param userId The nwe user ID
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
*/
public static boolean modifyContactServerId(long localContactId, Long serverId, Long userId,
SQLiteDatabase writeableDb) {
DatabaseHelper.trace(true, "ContactsTable.modifyContactServerId() localContactId["
+ localContactId + "] serverId[" + serverId + "] userId[" + userId + "]");
try {
ContentValues cv = new ContentValues();
cv.put(Field.SERVERID.toString(), serverId);
String[] args = {
String.format("%d", localContactId)
};
if (writeableDb.update(TABLE_NAME, cv, Field.LOCALID + "=?", args) <= 0) {
LogUtils.logE("ContactsTable.modifyContactServerId() "
+ "Unable to update contact server ID");
return false;
}
} catch (SQLException e) {
LogUtils.logE("ContactsTable.modifyContactServerId() SQLException - "
+ "Unable to update contact server ID", e);
return false;
}
return true;
}
/**
* Searches for a contact based on native ID and if found, returns the ID
* information
*
* @param nativeContactId The native contact ID to search for
* @param readableDb Readable SQLite database
* @return A ContactIdInfo object if found, otherwise null.
*/
public static ContactIdInfo fetchContactIdFromNative(int nativeContactId,
SQLiteDatabase readableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(false, "ContactsTable.fetchContactIdFromNative() nativeContactId["
+ nativeContactId + "]");
}
String[] args = {
String.format("%d", nativeContactId)
};
Cursor c1 = null;
ContactIdInfo info = null;
try {
c1 = readableDb.rawQuery("SELECT " + Field.LOCALID + "," + Field.SERVERID + " FROM "
+ TABLE_NAME + " WHERE " + Field.NATIVECONTACTID + " = ?", args);
if (!c1.moveToFirst()) {
return null;
}
info = new ContactIdInfo();
if (!c1.isNull(0)) {
info.localId = c1.getLong(0);
}
if (!c1.isNull(1)) {
info.serverId = c1.getLong(1);
}
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchContactIdFromNative() "
+ "Exception - Unable to fetch contact", e);
return null;
} finally {
CloseUtils.close(c1);
c1 = null;
}
return info;
}
/**
* Adds a contact to the table. See {@link #fillUpdateData(Contact)} for
* which fields from the Contact object are read.
*
* @param contact The source Contact object
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus addContact(Contact contact, SQLiteDatabase writeableDb) {
try {
contact.localContactID = writeableDb.insertOrThrow(ContactsTable.TABLE_NAME,
Field.SERVERID.toString(), ContactsTable.fillUpdateData(contact));
if (contact.localContactID < 0) {
contact.localContactID = null;
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(true, "ContactsTable.addContact() localContactID["
+ contact.localContactID + "]");
}
return ServiceStatus.SUCCESS;
} catch (SQLException e) {
LogUtils.logE("ContactsTable.addContact() SQLException - Unable to add contact", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
/**
* Finds the server ID associated with a contact
*
* @param localContactId The primary key ID of the contact to find
* @param readableDb Readable SQLite database
* @return The server ID if found, otherwise null.
*/
public static Long fetchServerId(Long localContactId, SQLiteDatabase readableDb) {
DatabaseHelper.trace(false, "ContactsTable.fetchServerId() localContactId["
+ localContactId + "]");
Long serverId = null;
String[] args = {
String.format("%d", localContactId)
};
Cursor c = null;
try {
c = readableDb.rawQuery("SELECT " + Field.SERVERID + " FROM " + TABLE_NAME + " WHERE "
+ Field.LOCALID + " = ?", args);
if (c.moveToFirst() && !c.isNull(0)) {
serverId = c.getLong(0);
}
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchServerId() "
+ "Exception - Unable to validate contact ID", e);
return null;
} finally {
CloseUtils.close(c);
c = null;
}
return serverId;
}
/**
* Updates contact information. See {@link #fillUpdateData(Contact)} for
* which fields from the Contact object are read.
*
* @param contact The source contact object
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
* @note Fields in the given contact that are NULL will not be left
* unchanged
*/
public static ServiceStatus modifyContact(Contact contact, SQLiteDatabase writableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(true, "ContactsTable.modifyContact() contact["
+ contact.localContactID + "]");
}
try {
contact.deleted = null;
String[] args = {
String.format("%d", contact.localContactID)
};
if (writableDb.update(ContactsTable.TABLE_NAME, ContactsTable.fillUpdateData(contact),
Field.LOCALID + "=?", args) <= 0) {
LogUtils.logE("ContactsTable.modifyContact() Unable to modify contact - not found");
return ServiceStatus.ERROR_NOT_FOUND;
}
return ServiceStatus.SUCCESS;
} catch (SQLException e) {
LogUtils.logE("ContactsTable.modifyContact() SQLException - Unable to modify contact",
e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
/**
* Fetches a contact from the database.
*
* @param localContactId The primary key ID of the contact to find
* @param contact A newly created contact object that will be populated by
* this function.
* @param readableDb Readable SQLite database
* @return SUCCESS, ERROR_NOT_FOUND or another suitable error code
*/
public static ServiceStatus fetchContact(long localContactId, Contact contact,
SQLiteDatabase readableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(false, "ContactsTable.fetchContact() localContactId["
+ localContactId + "]");
}
String[] args = {
String.format("%d", localContactId)
};
Cursor c = null;
try {
c = readableDb.rawQuery(ContactsTable.getQueryStringSql(ContactsTable.Field.LOCALID
+ " = ?"), args);
if (!c.moveToFirst()) {
return ServiceStatus.ERROR_NOT_FOUND;
}
ContactsTable.getQueryData(c, contact);
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchContact() Unable to fetch contact");
return ServiceStatus.ERROR_DATABASE_CORRUPT;
} finally {
CloseUtils.close(c);
c = null;
}
return ServiceStatus.SUCCESS;
}
/**
* Returns a complete set of server IDs from the Contacts table.
*
* @param serverIdSet A set that will be populated with the server IDs
* @param readableDb Readable SQLite database
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus fetchContactServerIdList(HashSet<Long> serverIdSet,
SQLiteDatabase readableDb) {
DatabaseHelper.trace(false, "ContactsTable.fetchContactServerIdList()");
Cursor c = null;
try {
serverIdSet.clear();
c = readableDb.rawQuery("SELECT " + Field.SERVERID + " FROM " + TABLE_NAME + " WHERE "
+ Field.SERVERID + " IS NOT NULL", null);
while (c.moveToNext()) {
if (!c.isNull(0)) {
serverIdSet.add(c.getLong(0));
}
}
return ServiceStatus.SUCCESS;
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchContactServerIdList() "
+ "Exception - Unable to fetch contact server ID list", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
} finally {
CloseUtils.close(c);
c = null;
}
}
/**
* Provides a statement that can be used to find a contact server ID in the
* table
*
* @param readableDb Readable SQLite database
* @return The SQLite statement
* @see #fetchLocalFromServerId(Long, SQLiteStatement)
*/
public static SQLiteStatement fetchLocalFromServerIdStatement(SQLiteDatabase readableDb) {
try {
return readableDb.compileStatement("SELECT " + Field.LOCALID + " FROM " + TABLE_NAME
+ " WHERE " + Field.SERVERID + "=?");
} catch (SQLException e) {
LogUtils.logE("ContactsTable.fetchLocalFromServerIdStatement() "
+ "Exception - Compile error:\n", e);
return null;
}
}
/**
* Searches the table for a contact server ID and if found, returns the
* local ID
*
* @param contactServerId The server ID
* @param statement The statement returned by
* {@link #fetchLocalFromServerIdStatement(SQLiteDatabase)}
* @return The local contact ID or NULL if it server ID was not found.
* @see #fetchLocalFromServerIdStatement(SQLiteDatabase)
*/
public static Long fetchLocalFromServerId(Long contactServerId, SQLiteStatement statement) {
DatabaseHelper.trace(false, "ContactsTable.fetchLocalFromServerId() contactServerId["
+ contactServerId + "]");
if (statement == null || contactServerId == null) {
return null;
}
try {
statement.bindLong(1, contactServerId);
return statement.simpleQueryForLong();
} catch (SQLException e) {
return null;
}
}
/**
* Updates the server and user IDs for a list of contacts. Also prepares a
* list of duplicates which will be filled with the Ids for contacts already
* present in the table (i.e. server ID has already been used). In the case
* that a duplicate is found, the ContactIdInfo object will also include the
* local ID of the original contact (see {@link ContactIdInfo#mergedLocalId}
* ).
*
* @param serverIdList A list of ServerIdInfo objects. For each object, the
* local ID must match a local contact ID in the table. The
* Server ID and User ID will be used for the update.
* @param dupList On return this will be populated with a list of contacts
* which have server IDs already present in the table.
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus syncSetServerIds(List<ServerIdInfo> serverIdList,
List<ContactIdInfo> dupList, SQLiteDatabase writableDb) {
DatabaseHelper.trace(true, "ContactsTable.syncSetServerIds()");
if (serverIdList.size() == 0) {
return ServiceStatus.SUCCESS;
}
SQLiteStatement statement1 = null;
SQLiteStatement statement2 = null;
try {
writableDb.beginTransaction();
for (int i = 0; i < serverIdList.size(); i++) {
final ServerIdInfo info = serverIdList.get(i);
try {
if (info.serverId != null) {
if (info.userId == null) {
if (statement2 == null) {
statement2 = writableDb.compileStatement("UPDATE " + TABLE_NAME
+ " SET " + Field.SERVERID + "=? WHERE " + Field.LOCALID
+ "=?");
}
statement2.bindLong(1, info.serverId);
statement2.bindLong(2, info.localId);
statement2.execute();
} else {
if (statement1 == null) {
statement1 = writableDb.compileStatement("UPDATE " + TABLE_NAME
+ " SET " + Field.SERVERID + "=?," + Field.USERID
+ "=? WHERE " + Field.LOCALID + "=?");
}
statement1.bindLong(1, info.serverId);
statement1.bindLong(2, info.userId);
statement1.bindLong(3, info.localId);
statement1.execute();
}
}
} catch (SQLiteConstraintException e) {
// server ID is not unique
ContactIdInfo contactInfo = new ContactIdInfo();
contactInfo.localId = info.localId;
contactInfo.serverId = info.serverId;
if(!fetchLocalIDFromServerID(writableDb, contactInfo)) {
writableDb.endTransaction();
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
dupList.add(contactInfo);
} catch (SQLException e) {
LogUtils.logE("ContactsTable.syncSetServerIds() SQLException - "
+ "Unable to update contact server Ids", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
writableDb.setTransactionSuccessful();
} finally {
writableDb.endTransaction();
if(statement1 != null) {
statement1.close();
statement1 = null;
}
if(statement2 != null) {
statement2.close();
statement2 = null;
}
}
return ServiceStatus.SUCCESS;
}
/**
* Updates the native IDs for a list of contacts.
*
* @param contactIdList A list of ContactIdInfo objects. For each object,
* the local ID must match a local contact ID in the table. The
* Native ID will be used for the update. Other fields are
* unused.
* @param writeableDb Writeable SQLite database
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus syncSetNativeIds(List<ContactIdInfo> contactIdList,
SQLiteDatabase writableDb) {
DatabaseHelper.trace(true, "ContactsTable.syncSetNativeIds()");
if (contactIdList.size() == 0) {
return ServiceStatus.SUCCESS;
}
try {
writableDb.beginTransaction();
final SQLiteStatement statement1 = writableDb.compileStatement("UPDATE " + TABLE_NAME
+ " SET " + Field.NATIVECONTACTID + "=? WHERE " + Field.LOCALID + "=?");
for (int i = 0; i < contactIdList.size(); i++) {
final ContactIdInfo info = contactIdList.get(i);
try {
if (info.nativeId == null) {
statement1.bindNull(1);
} else {
statement1.bindLong(1, info.nativeId);
}
statement1.bindLong(2, info.localId);
statement1.execute();
} catch (SQLException e) {
LogUtils.logE("ContactsTable.syncSetNativeIds() SQLException - "
+ "Unable to update contact native Ids", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
writableDb.setTransactionSuccessful();
} finally {
writableDb.endTransaction();
}
return ServiceStatus.SUCCESS;
}
/**
* Provides a statement that can be used to merge the native sync
* information from one contact to another.
*
* @param writeableDb Writeable SQLite database
* @return The SQLite statement
* @see #mergeContact(ContactIdInfo, SQLiteStatement)
*/
public static SQLiteStatement mergeContactStatement(SQLiteDatabase writableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(true, "ContactsTable.mergeContactStatement()");
}
try {
return writableDb.compileStatement(UPDATE_NATIVE_ID_BY_LOCAL_CONTACT_ID);
}
catch (SQLException e) {
LogUtils.logE("ContactsTable.mergeContactStatement() SQLException - compile error:\n", e);
return null;
}
}
/**
* Merges the native sync information from one contact to another.
*
* @param info Copies the {@link ContactIdInfo#nativeId} value to the
* contact with local ID {@link ContactIdInfo#mergedLocalId}.
* @param statement The statement returned by
* {@link #mergeContactStatement(SQLiteDatabase)}.
* @return SUCCESS or a suitable error code
*/
public static ServiceStatus mergeContact(ContactIdInfo info, SQLiteStatement statement) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(true, "ContactsTable.mergeContact() info[" + info.localId + "]");
}
if (statement == null) {
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
try {
if (info.nativeId == null)
statement.bindNull(1);
else
statement.bindLong(1, info.nativeId);
statement.bindLong(2, info.mergedLocalId);
statement.execute();
return ServiceStatus.SUCCESS;
} catch (SQLException e) {
LogUtils.logE("ContactsTable.mergeContact() SQLException - "
+ "Unable to merge contact native info:\n", e);
return ServiceStatus.ERROR_DATABASE_CORRUPT;
}
}
/**
* Provides a statement that can be used to find a contact native ID in the
* table
*
* @param readableDb Readable SQLite database
* @return The SQLite statement
* @see #fetchLocalFromNativeId(Integer, SQLiteStatement)
*/
public static SQLiteStatement fetchLocalFromNativeIdStatement(SQLiteDatabase readableDb) {
try {
return readableDb.compileStatement("SELECT " + Field.LOCALID + " FROM " + TABLE_NAME
+ " WHERE " + Field.NATIVECONTACTID + "=?");
} catch (SQLException e) {
LogUtils.logE("ContactsTable.fetchLocalFromNativeIdStatement() "
+ "Exception - Compile error:\n", e);
return null;
}
}
/**
* Finds a native ID in the table
*
* @param nativeContactId The native ID to find
* @param statement The statement returned by
* {@link #fetchLocalFromNativeIdStatement(SQLiteDatabase)}.
* @return The local contact ID if the contact is found, or NULL.
* @see #fetchLocalFromNativeIdStatement(SQLiteDatabase)
*/
public static Long fetchLocalFromNativeId(Integer nativeContactId, SQLiteStatement statement) {
DatabaseHelper.trace(false, "ContactsTable.fetchLocalFromNativeId() nativeContactId["
+ nativeContactId + "]");
if (statement == null || nativeContactId == null) {
return null;
}
try {
statement.bindLong(1, nativeContactId);
return statement.simpleQueryForLong();
} catch (SQLException e) {
return null;
}
}
/**
* Fetches the syncToPhone flag for a particular contact.
*
* @param localContactId The primary key ID of the contact to find
* @param readableDb Readable SQLite database
* @return The state of the syncToPhone flag (true or false). Defaults to
* false if the contact is not found or a database error occurs.
*/
protected static boolean fetchSyncToPhone(long localContactId, SQLiteDatabase readableDb) {
DatabaseHelper.trace(false, "ContactsTable.fetchSyncToPhone()");
Cursor c = null;
try {
boolean syncToPhone = false;
c = readableDb.rawQuery("SELECT " + Field.SYNCTOPHONE + " FROM " + TABLE_NAME
+ " WHERE " + Field.LOCALID + "=" + localContactId, null);
while (c.moveToNext()) {
if (!c.isNull(0)) {
syncToPhone = (c.getInt(0) == 0 ? false : true);
}
}
return syncToPhone;
}
catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchSyncToPhone() Exception - Unable to run query:\n", e);
return false;
}
finally {
CloseUtils.close(c);
}
}
/**
* Returns the native ID associated with a contact
*
* @param localContactId The primary key ID of the contact to find
* @param statement The statement provided by
* {@link #fetchNativeFromLocalIdStatement(SQLiteDatabase)}.
* @return Native Contact ID or NULL if the contact was not found.
* @see #fetchNativeFromLocalIdStatement(SQLiteDatabase)
*/
public static Integer fetchNativeFromLocalId(SQLiteDatabase readableDb, Long localContactId) {
Cursor c = null;
DatabaseHelper.trace(false, "ContactsTable.fetchNativeFromLocalId() localContactId[" + localContactId + "]");
if (readableDb == null || localContactId == null) {
return null;
}
try {
c = readableDb.rawQuery(QUERY_NATIVE_ID_BY_LOCAL_CONTACT_ID, new String[] { localContactId.toString() });
if (!c.moveToFirst()) {
LogUtils.logW("ContactsTable.fetchNativeFromLocalId() nativeID not found");
return null;
}
return (c.isNull(0)) ? null : c.getInt(0);
}
catch (SQLException e) {
LogUtils.logE("ContactsTable.fetchNativeFromLocalId() Exception - Unable to run query:\n", e);
return null;
}
finally {
CloseUtils.close(c);
}
}
public static long fetchLocalIdFromUserId(Long userId, SQLiteDatabase readableDb) {
DatabaseHelper.trace(false, "ContactsTable.fetchSyncToPhone()");
long localContactId = -1;
Cursor c = null;
try {
StringBuffer query = StringBufferPool.getStringBuffer(SQLKeys.SELECT);
query.append(Field.LOCALID).append(SQLKeys.FROM).append(TABLE_NAME).append(
SQLKeys.WHERE).append(Field.USERID).append(SQLKeys.EQUALS).append(userId);
c = readableDb.rawQuery(StringBufferPool.toStringThenRelease(query), null);
while (c.moveToNext()) {
if (!c.isNull(0)) {
localContactId = c.getLong(0);
}
}
}
catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchSyncToPhone() Exception - Unable to run query:\n", e);
}
finally {
CloseUtils.close(c);
}
return localContactId;
}
public static long fetchUserIdFromLocalContactId(Long localContactId, SQLiteDatabase readableDb) {
DatabaseHelper.trace(false, "ContactsTable.fetchSyncToPhone()");
long userId = -1;
Cursor c = null;
try {
c = readableDb.rawQuery("SELECT " + Field.USERID + " FROM " + TABLE_NAME + " WHERE "
+ Field.LOCALID + "=" + localContactId, null);
while (c.moveToNext()) {
if (!c.isNull(0)) {
userId = c.getLong(0);
}
}
} catch (SQLiteException e) {
LogUtils.logE("ContactsTable.fetchSyncToPhone() Exception - Unable to run query:\n", e);
} finally {
CloseUtils.close(c);
}
return userId;
}
/**
* Selects all contacts from the contacts table
*
* @param readableDb A readable Database
* @return Cursor for the select
*/
public static Cursor openContactsCursor(SQLiteDatabase readableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(false, "ContactSummeryTable.fetchContactList() ");
}
try {
return readableDb.rawQuery("SELECT " + ContactsTable.getFullQueryList() + " FROM "
+ ContactsTable.TABLE_NAME, null);
} catch (SQLException e) {
LogUtils.logE("ContactSummeryTable.fetchContactList() "
+ "SQLException - Unable to fetch filtered summary cursor", e);
return null;
}
}
/**
* Fetches a List with Contacts from Database Uses the persistence helper to
* build the objects The Contactdetails are not read, only the contacts
*
* @param readableDB A readable Database
* @return java.util.list<Contact> with contacts
*/
public static List<Contact> fetchContactList(SQLiteDatabase readableDB) {
ArrayList<Contact> contactList = new ArrayList<Contact>();
Cursor cursor = openContactsCursor(readableDB);
if (null == cursor) {
throw new RuntimeException("no cursor returned");
}
try {
while (cursor.moveToNext()) {
Contact contact = new Contact();
PersistenceHelper.mapCursorToObject(contact, cursor);
contactList.add(contact);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
cursor.close();
}
return contactList;
}
/**
* SELECT NativeContactId FROM ContactsTable WHERE NativeContactId IS NOT
* NULL ORDER BY NativeContactId
*/
private final static String QUERY_NATIVE_CONTACTS_IDS = "SELECT " + Field.NATIVECONTACTID
+ " FROM " + TABLE_NAME + " WHERE " + Field.NATIVECONTACTID + " IS NOT NULL ORDER BY "
+ Field.NATIVECONTACTID;
/**
* Gets a list of native ids for the contacts.
*
* @param readableDb the people database to query from
* @return an ordered array containing all the found native ids, null if
* none
*/
public static long[] getNativeContactsIds(SQLiteDatabase readableDb) {
if (Settings.ENABLED_DATABASE_TRACE) {
DatabaseHelper.trace(false, "ContactsTable.getNativeContactsIds()");
}
long[] ids;
Cursor cursor = null;
try {
final int NATIVE_ID_INDEX = 0;
cursor = readableDb.rawQuery(QUERY_NATIVE_CONTACTS_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 (SQLException e) {
return null;
} finally {
CloseUtils.close(cursor);
cursor = null;
}
return ids;
}
/**
* Gets the native related ContentValues for the provided ContactChange.
*
* @param contactChange the ContactChange from which to extract the native
* values
* @return the native ContentValues
*/
public static ContentValues getNativeContentValues(ContactChange contactChange) {
final ContentValues values = new ContentValues();
final long nativeId = contactChange.getNabContactId();
// add the native contact id
if (nativeId != ContactChange.INVALID_ID) {
values.put(Field.NATIVECONTACTID.toString(), nativeId);
}
// add the synctophone flag to true
values.put(Field.SYNCTOPHONE.toString(), true);
return values;
}
/**
* Adds the provided contact.
*
* @param contact the ContentValues representing the contact to add
* @return the contact id if successful, -1 if the insertion failed
*/
public static long addContact(ContentValues contact, SQLiteDatabase writeableDb) {
long contactId = -1;
try {
contactId = writeableDb.insertOrThrow(TABLE_NAME, null, contact);
} catch (Exception e) {
return -1;
}
return contactId;
}
/**
* SQL query string to get the contact native id from its local id.
*/
private final static String QUERY_CONTACT_NATIVE_ID_FROM_LOCAL_ID = "SELECT "
+ Field.NATIVECONTACTID + " FROM " + TABLE_NAME + " WHERE " + Field.LOCALID + " = ?";
/**
* Gets the native contact id.
*
* @param localContactId the local id of the contact
* @param readDatabase the database to read from
* @return the native contact id, -1 otherwise
*/
public static long getNativeContactId(long localContactId, SQLiteDatabase readableDb) {
final String[] args = {
Long.toString(localContactId)
};
Cursor cursor = null;
try {
cursor = readableDb.rawQuery(QUERY_CONTACT_NATIVE_ID_FROM_LOCAL_ID, args);
if (cursor.getCount() > 0) {
cursor.moveToNext();
if (!cursor.isNull(0)) {
return cursor.getLong(0);
}
}
} catch (Exception e) {
return -1;
} finally {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
return -1;
}
/**
* String equal to "LocalId = ?" for SQL queries.
*/
private final static String SQL_STRING_LOCAL_ID_EQUAL_QUESTION_MARK = Field.LOCALID + " = ?";
/**
* Sets the native contact id for a contact.
*
* @param localContactId the local contact id
* @param nativeContactId the native contact id to set
* @param writableDb the database where to write
* @return true if successful, false otherwise
*/
public static boolean setNativeContactId(long localContactId, long nativeContactId,
SQLiteDatabase writableDb) {
final ContentValues values = new ContentValues();
values.put(Field.NATIVECONTACTID.toString(), nativeContactId);
try {
if (writableDb.update(TABLE_NAME, values, SQL_STRING_LOCAL_ID_EQUAL_QUESTION_MARK,
new String[] {
Long.toString(localContactId)
}) == 1) {
return true;
}
} catch (Exception e) {
LogUtils.logE("ContactsTable.setNativeContactId() Exception - " + e);
}
return false;
}
/**
* Fill in Local Contact ID based on the Server ID.
* @param contactInfo Contact Information including duplication data
* @param readableDB DB object we are using
* @return true if the data was found, false if not (i.e. due to SQL Exception)
*/
private static boolean fetchLocalIDFromServerID(SQLiteDatabase readableDB, ContactIdInfo contactInfo) {
Cursor dupInfoCursor = null;
try {
dupInfoCursor = readableDB.rawQuery(QUERY_LOCAL_ID_BY_SERVER_ID,
new String[] { String.valueOf(contactInfo.serverId) });
if (dupInfoCursor.moveToFirst()) {
contactInfo.mergedLocalId = dupInfoCursor.getLong(0);
}
}
catch (SQLException sqlException) {
LogUtils.logE("ContactsTable.findDuplicateNabIdAttachment() SQLException - "
+ "Unable to fetch original contact data for duplicate \n", sqlException);
return false;
}
finally {
CloseUtils.close(dupInfoCursor);
}
return true;
}
}