localMap,
long localPersonID, int kind) {
Long primaryId = diffsMap.containsKey(kind) ? diffsMap.get(kind) : null;
if (primaryId == null) {
primaryId = localMap.containsKey(kind) ? localMap.get(kind) : null;
}
if (primaryId == null) {
primaryId = findNewPrimary(kind, localPersonID, null);
}
clearOtherIsPrimary(kind, localPersonID, primaryId);
return primaryId;
}
/**
* Returns a cursor on the specified table that selects rows where
* the "person" column is equal to the personId parameter. The cursor
* is also saved and may be returned in future calls where db and table
* parameter are the same. In that case the projection and orderBy parameters
* are ignored, so one must take care to not change those parameters across
* multiple calls to the same db/table.
*
* Since the cursor may be saced by this call, the caller must be sure to not
* close the cursor, though they still must deactivate it when they are done
* with it.
*/
private Cursor doSubQuery(SQLiteDatabase db, String table, String[] projection,
long personId, String orderBy) {
final String[] selectArgs = new String[]{Long.toString(personId)};
final String key = (db == getDatabase() ? "local_" : "remote_") + table;
SQLiteCursor cursor = mCursorMap.get(key);
// don't use the cached cursor if it is from a different DB
if (cursor != null && cursor.getDatabase() != db) {
cursor.close();
cursor = null;
}
// If we can't find a cached cursor then create a new one and add it to the cache.
// Otherwise just change the selection arguments and requery it.
if (cursor == null) {
cursor = (SQLiteCursor)db.query(table, projection, "person=?", selectArgs,
null, null, orderBy);
mCursorMap.put(key, cursor);
} else {
cursor.setSelectionArguments(selectArgs);
cursor.requery();
}
return cursor;
}
}
protected class GroupMerger extends AbstractTableMerger {
private ContentValues mValues = new ContentValues();
private static final String UNSYNCED_GROUP_BY_NAME_WHERE_CLAUSE =
Groups._SYNC_ID + " is null AND "
+ Groups._SYNC_ACCOUNT + " is null AND "
+ Groups.NAME + "=?";
private static final String UNSYNCED_GROUP_BY_SYSTEM_ID_WHERE_CLAUSE =
Groups._SYNC_ID + " is null AND "
+ Groups._SYNC_ACCOUNT + " is null AND "
+ Groups.SYSTEM_ID + "=?";
public GroupMerger()
{
super(getDatabase(), sGroupsTable, sGroupsURL, sDeletedGroupsTable, sDeletedGroupsURL);
}
@Override
protected void notifyChanges() {
// notify that a change has occurred.
getContext().getContentResolver().notifyChange(Contacts.CONTENT_URI,
null /* observer */, false /* do not sync to network */);
}
@Override
public void insertRow(ContentProvider diffs, Cursor cursor) {
// if an unsynced group with this name already exists then update it, otherwise
// insert a new group
mValues.clear();
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ID, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_TIME, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_VERSION, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT_TYPE, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.NAME, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.NOTES, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.SYSTEM_ID, mValues);
mValues.put(Groups._SYNC_DIRTY, 0);
final String systemId = mValues.getAsString(Groups.SYSTEM_ID);
boolean rowUpdated = false;
if (TextUtils.isEmpty(systemId)) {
rowUpdated = getDatabase().update(mTable, mValues,
UNSYNCED_GROUP_BY_NAME_WHERE_CLAUSE,
new String[]{mValues.getAsString(Groups.NAME)}) > 0;
} else {
rowUpdated = getDatabase().update(mTable, mValues,
UNSYNCED_GROUP_BY_SYSTEM_ID_WHERE_CLAUSE,
new String[]{systemId}) > 0;
}
if (!rowUpdated) {
mGroupsInserter.insert(mValues);
} else {
// We may have just synced the metadata for a groups we previously marked for
// syncing.
final ContentResolver cr = getContext().getContentResolver();
final String accountName = mValues.getAsString(Groups._SYNC_ACCOUNT);
final String accountType = mValues.getAsString(Groups._SYNC_ACCOUNT_TYPE);
onLocalChangesForAccount(cr, new Account(accountName, accountType), false);
}
String oldName = null;
String newName = cursor.getString(cursor.getColumnIndexOrThrow(Groups.NAME));
String accountName = cursor.getString(
cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT));
String accountType = cursor.getString(
cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT_TYPE));
String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ID));
// this must come after the insert, otherwise the join won't work
fixupPeopleStarredOnGroupRename(oldName, newName, new Account(accountName, accountType),
syncId);
}
@Override
public void updateRow(long localId, ContentProvider diffs, Cursor diffsCursor) {
updateOrResolveRow(localId, null, diffs, diffsCursor, false);
}
@Override
public void resolveRow(long localId, String syncID,
ContentProvider diffs, Cursor diffsCursor) {
updateOrResolveRow(localId, syncID, diffs, diffsCursor, true);
}
protected void updateOrResolveRow(long localRowId, String syncID,
ContentProvider diffs, Cursor cursor, boolean conflicts) {
final SQLiteDatabase db = getDatabase();
String oldName = DatabaseUtils.stringForQuery(db,
"select name from groups where _id=" + localRowId, null);
String newName = cursor.getString(cursor.getColumnIndexOrThrow(Groups.NAME));
String accountName = cursor.getString(
cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT));
String accountType = cursor.getString(
cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT_TYPE));
String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ID));
// this can come before or after the delete
fixupPeopleStarredOnGroupRename(oldName, newName,
new Account(accountName, accountType), syncId);
mValues.clear();
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ID, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_TIME, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_VERSION, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT_TYPE, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.NAME, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.NOTES, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Groups.SYSTEM_ID, mValues);
mValues.put(Groups._SYNC_DIRTY, 0);
db.update(mTable, mValues, Groups._ID + '=' + localRowId, null);
}
@Override
public void deleteRow(Cursor cursor) {
// we have to read this row from the DB since the projection that is used
// by cursor doesn't necessarily contain the columns we need
Cursor c = getDatabase().query(sGroupsTable, null,
"_id=" + cursor.getLong(cursor.getColumnIndexOrThrow(Groups._ID)),
null, null, null, null);
try {
c.moveToNext();
String oldName = c.getString(c.getColumnIndexOrThrow(Groups.NAME));
String newName = null;
String accountName = c.getString(c.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT));
String accountType = c.getString(
c.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT_TYPE));
String syncId = c.getString(c.getColumnIndexOrThrow(Groups._SYNC_ID));
String systemId = c.getString(c.getColumnIndexOrThrow(Groups.SYSTEM_ID));
if (!TextUtils.isEmpty(systemId)) {
// We don't support deleting of system groups, but due to a server bug they
// occasionally get sent. Ignore the delete.
Log.w(TAG, "ignoring a delete for a system group: " +
DatabaseUtils.dumpCurrentRowToString(c));
cursor.moveToNext();
return;
}
// this must come before the delete, since the join won't work once this row is gone
fixupPeopleStarredOnGroupRename(oldName, newName,
new Account(accountName, accountType), syncId);
} finally {
c.close();
}
cursor.deleteRow();
}
}
protected class PhotoMerger extends AbstractTableMerger {
private ContentValues mValues = new ContentValues();
public PhotoMerger() {
super(getDatabase(), sPhotosTable, sPhotosURL, null, null);
}
@Override
protected void notifyChanges() {
// notify that a change has occurred.
getContext().getContentResolver().notifyChange(Contacts.CONTENT_URI,
null /* observer */, false /* do not sync to network */);
}
@Override
public void insertRow(ContentProvider diffs, Cursor cursor) {
// This photo may correspond to a contact that is in the delete table. If so then
// ignore this insert.
String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Photos._SYNC_ID));
boolean contactIsDeleted = DatabaseUtils.longForQuery(getDatabase(),
"select count(*) from _deleted_people where _sync_id=?",
new String[]{syncId}) > 0;
if (contactIsDeleted) {
return;
}
throw new UnsupportedOperationException(
"the photo row is inserted by PersonMerger.insertRow");
}
@Override
public void updateRow(long localId, ContentProvider diffs, Cursor diffsCursor) {
updateOrResolveRow(localId, null, diffs, diffsCursor, false);
}
@Override
public void resolveRow(long localId, String syncID,
ContentProvider diffs, Cursor diffsCursor) {
updateOrResolveRow(localId, syncID, diffs, diffsCursor, true);
}
protected void updateOrResolveRow(long localRowId, String syncID,
ContentProvider diffs, Cursor cursor, boolean conflicts) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "PhotoMerger.updateOrResolveRow: localRowId " + localRowId
+ ", syncId " + syncID + ", conflicts " + conflicts
+ ", server row " + DatabaseUtils.dumpCurrentRowToString(cursor));
}
mValues.clear();
DatabaseUtils.cursorStringToContentValues(cursor, Photos._SYNC_TIME, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Photos._SYNC_VERSION, mValues);
DatabaseUtils.cursorStringToContentValues(cursor, Photos.EXISTS_ON_SERVER, mValues);
// reset the error field to allow the phone to attempt to redownload the photo.
mValues.put(Photos.SYNC_ERROR, (String)null);
// If the photo didn't change locally and the server doesn't have a photo for this
// contact then delete the local photo.
long syncDirty = DatabaseUtils.longForQuery(getDatabase(),
"SELECT _sync_dirty FROM photos WHERE _id=" + localRowId
+ " UNION SELECT 0 AS _sync_dirty ORDER BY _sync_dirty DESC LIMIT 1",
null);
if (syncDirty == 0) {
if (mValues.getAsInteger(Photos.EXISTS_ON_SERVER) == 0) {
mValues.put(Photos.DATA, (String)null);
mValues.put(Photos.LOCAL_VERSION, mValues.getAsString(Photos.LOCAL_VERSION));
}
// if it does exist on the server then we will attempt to download it later
}
// if it does conflict then we will send the client version of the photo to
// the server later. That will trigger a new sync of the photo data which will
// cause this method to be called again, at which time the row will no longer
// conflict. We will then download the photo we just sent to the server and
// set the LOCAL_VERSION to match the data we just downloaded.
getDatabase().update(mTable, mValues, Photos._ID + '=' + localRowId, null);
}
@Override
public void deleteRow(Cursor cursor) {
// this row is never deleted explicitly, instead it is deleted by a trigger on
// the people table
cursor.moveToNext();
}
}
private static final String TAG = "ContactsProvider";
/* package private */ static final String DATABASE_NAME = "contacts.db";
/* package private */ static final int DATABASE_VERSION = 83;
protected static final String CONTACTS_AUTHORITY = "contacts";
protected static final String CALL_LOG_AUTHORITY = "call_log";
private static final int PEOPLE_BASE = 0;
private static final int PEOPLE = PEOPLE_BASE;
private static final int PEOPLE_FILTER = PEOPLE_BASE + 1;
private static final int PEOPLE_ID = PEOPLE_BASE + 2;
private static final int PEOPLE_PHONES = PEOPLE_BASE + 3;
private static final int PEOPLE_PHONES_ID = PEOPLE_BASE + 4;
private static final int PEOPLE_CONTACTMETHODS = PEOPLE_BASE + 5;
private static final int PEOPLE_CONTACTMETHODS_ID = PEOPLE_BASE + 6;
private static final int PEOPLE_RAW = PEOPLE_BASE + 7;
private static final int PEOPLE_WITH_PHONES_FILTER = PEOPLE_BASE + 8;
private static final int PEOPLE_STREQUENT = PEOPLE_BASE + 9;
private static final int PEOPLE_STREQUENT_FILTER = PEOPLE_BASE + 10;
private static final int PEOPLE_ORGANIZATIONS = PEOPLE_BASE + 11;
private static final int PEOPLE_ORGANIZATIONS_ID = PEOPLE_BASE + 12;
private static final int PEOPLE_GROUPMEMBERSHIP = PEOPLE_BASE + 13;
private static final int PEOPLE_GROUPMEMBERSHIP_ID = PEOPLE_BASE + 14;
private static final int PEOPLE_PHOTO = PEOPLE_BASE + 15;
private static final int PEOPLE_EXTENSIONS = PEOPLE_BASE + 16;
private static final int PEOPLE_EXTENSIONS_ID = PEOPLE_BASE + 17;
private static final int PEOPLE_CONTACTMETHODS_WITH_PRESENCE = PEOPLE_BASE + 18;
private static final int PEOPLE_OWNER = PEOPLE_BASE + 19;
private static final int PEOPLE_UPDATE_CONTACT_TIME = PEOPLE_BASE + 20;
private static final int PEOPLE_PHONES_WITH_PRESENCE = PEOPLE_BASE + 21;
private static final int PEOPLE_WITH_EMAIL_OR_IM_FILTER = PEOPLE_BASE + 22;
private static final int PEOPLE_PHOTO_DATA = PEOPLE_BASE + 23;
private static final int DELETED_BASE = 1000;
private static final int DELETED_PEOPLE = DELETED_BASE;
private static final int DELETED_GROUPS = DELETED_BASE + 1;
private static final int PHONES_BASE = 2000;
private static final int PHONES = PHONES_BASE;
private static final int PHONES_ID = PHONES_BASE + 1;
private static final int PHONES_FILTER = PHONES_BASE + 2;
private static final int PHONES_FILTER_NAME = PHONES_BASE + 3;
private static final int PHONES_MOBILE_FILTER_NAME = PHONES_BASE + 4;
private static final int PHONES_WITH_PRESENCE = PHONES_BASE + 5;
private static final int CONTACTMETHODS_BASE = 3000;
private static final int CONTACTMETHODS = CONTACTMETHODS_BASE;
private static final int CONTACTMETHODS_ID = CONTACTMETHODS_BASE + 1;
private static final int CONTACTMETHODS_EMAIL = CONTACTMETHODS_BASE + 2;
private static final int CONTACTMETHODS_EMAIL_FILTER = CONTACTMETHODS_BASE + 3;
private static final int CONTACTMETHODS_WITH_PRESENCE = CONTACTMETHODS_BASE + 4;
private static final int CALLS_BASE = 4000;
private static final int CALLS = CALLS_BASE;
private static final int CALLS_ID = CALLS_BASE + 1;
private static final int CALLS_FILTER = CALLS_BASE + 2;
private static final int PRESENCE_BASE = 5000;
private static final int PRESENCE = PRESENCE_BASE;
private static final int PRESENCE_ID = PRESENCE_BASE + 1;
private static final int ORGANIZATIONS_BASE = 6000;
private static final int ORGANIZATIONS = ORGANIZATIONS_BASE;
private static final int ORGANIZATIONS_ID = ORGANIZATIONS_BASE + 1;
private static final int VOICE_DIALER_TIMESTAMP = 7000;
private static final int SEARCH_SUGGESTIONS = 7001;
private static final int SEARCH_SHORTCUT = 7002;
private static final int GROUPS_BASE = 8000;
private static final int GROUPS = GROUPS_BASE;
private static final int GROUPS_ID = GROUPS_BASE + 2;
private static final int GROUP_NAME_MEMBERS = GROUPS_BASE + 3;
private static final int GROUP_NAME_MEMBERS_FILTER = GROUPS_BASE + 4;
private static final int GROUP_SYSTEM_ID_MEMBERS = GROUPS_BASE + 5;
private static final int GROUP_SYSTEM_ID_MEMBERS_FILTER = GROUPS_BASE + 6;
private static final int GROUPMEMBERSHIP_BASE = 9000;
private static final int GROUPMEMBERSHIP = GROUPMEMBERSHIP_BASE;
private static final int GROUPMEMBERSHIP_ID = GROUPMEMBERSHIP_BASE + 2;
private static final int GROUPMEMBERSHIP_RAW = GROUPMEMBERSHIP_BASE + 3;
private static final int PHOTOS_BASE = 10000;
private static final int PHOTOS = PHOTOS_BASE;
private static final int PHOTOS_ID = PHOTOS_BASE + 1;
private static final int EXTENSIONS_BASE = 11000;
private static final int EXTENSIONS = EXTENSIONS_BASE;
private static final int EXTENSIONS_ID = EXTENSIONS_BASE + 2;
private static final int SETTINGS = 12000;
private static final int LIVE_FOLDERS_BASE = 13000;
private static final int LIVE_FOLDERS_PEOPLE = LIVE_FOLDERS_BASE + 1;
private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = LIVE_FOLDERS_BASE + 2;
private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = LIVE_FOLDERS_BASE + 3;
private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = LIVE_FOLDERS_BASE + 4;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final HashMap sGroupsProjectionMap;
private static final HashMap sPeopleProjectionMap;
private static final HashMap sPeopleWithPhotoProjectionMap;
private static final HashMap sPeopleWithEmailOrImProjectionMap;
/** Used to force items to the top of a times_contacted list */
private static final HashMap sStrequentStarredProjectionMap;
private static final HashMap sCallsProjectionMap;
private static final HashMap sPhonesProjectionMap;
private static final HashMap sPhonesWithPresenceProjectionMap;
private static final HashMap sContactMethodsProjectionMap;
private static final HashMap sContactMethodsWithPresenceProjectionMap;
private static final HashMap sPresenceProjectionMap;
private static final HashMap sEmailSearchProjectionMap;
private static final HashMap sOrganizationsProjectionMap;
private static final HashMap sSearchSuggestionsProjectionMap;
private static final HashMap sGroupMembershipProjectionMap;
private static final HashMap sPhotosProjectionMap;
private static final HashMap sExtensionsProjectionMap;
private static final HashMap sLiveFoldersProjectionMap;
private static final String sPhonesKeyOrderBy;
private static final String sContactMethodsKeyOrderBy;
private static final String sOrganizationsKeyOrderBy;
private static final String sGroupmembershipKeyOrderBy;
private static final String DISPLAY_NAME_SQL
= "(CASE WHEN (name IS NOT NULL AND name != '') "
+ "THEN name "
+ "ELSE "
+ "(CASE WHEN primary_organization is NOT NULL THEN "
+ "(SELECT company FROM organizations WHERE "
+ "organizations._id = primary_organization) "
+ "ELSE "
+ "(CASE WHEN primary_phone IS NOT NULL THEN "
+"(SELECT number FROM phones WHERE phones._id = primary_phone) "
+ "ELSE "
+ "(CASE WHEN primary_email IS NOT NULL THEN "
+ "(SELECT data FROM contact_methods WHERE "
+ "contact_methods._id = primary_email) "
+ "ELSE "
+ "null "
+ "END) "
+ "END) "
+ "END) "
+ "END) ";
private static final String PHONETICALLY_SORTABLE_STRING_SQL =
"GET_PHONETICALLY_SORTABLE_STRING("
+ "CASE WHEN (phonetic_name IS NOT NULL AND phonetic_name != '') "
+ "THEN phonetic_name "
+ "ELSE "
+ "(CASE WHEN (name is NOT NULL AND name != '')"
+ "THEN name "
+ "ELSE "
+ "(CASE WHEN primary_email IS NOT NULL THEN "
+ "(SELECT data FROM contact_methods WHERE "
+ "contact_methods._id = primary_email) "
+ "ELSE "
+ "(CASE WHEN primary_phone IS NOT NULL THEN "
+ "(SELECT number FROM phones WHERE phones._id = primary_phone) "
+ "ELSE "
+ "null "
+ "END) "
+ "END) "
+ "END) "
+ "END"
+ ")";
private static final String PRIMARY_ORGANIZATION_WHEN_SQL
= " WHEN primary_organization is NOT NULL THEN "
+ "(SELECT company FROM organizations WHERE organizations._id = primary_organization)";
private static final String PRIMARY_PHONE_WHEN_SQL
= " WHEN primary_phone IS NOT NULL THEN "
+ "(SELECT number FROM phones WHERE phones._id = primary_phone)";
private static final String PRIMARY_EMAIL_WHEN_SQL
= " WHEN primary_email IS NOT NULL THEN "
+ "(SELECT data FROM contact_methods WHERE contact_methods._id = primary_email)";
// The outer CASE is for figuring out what info DISPLAY_NAME_SQL returned.
// We then pick the next piece of info, to avoid the two lines in the search
// suggestion being identical.
private static final String SUGGEST_DESCRIPTION_SQL
= "(CASE"
// DISPLAY_NAME_SQL returns name, try org, phone, email
+ " WHEN (name IS NOT NULL AND name != '') THEN "
+ "(CASE"
+ PRIMARY_ORGANIZATION_WHEN_SQL
+ PRIMARY_PHONE_WHEN_SQL
+ PRIMARY_EMAIL_WHEN_SQL
+ " ELSE null END)"
// DISPLAY_NAME_SQL returns org, try phone, email
+ " WHEN primary_organization is NOT NULL THEN "
+ "(CASE"
+ PRIMARY_PHONE_WHEN_SQL
+ PRIMARY_EMAIL_WHEN_SQL
+ " ELSE null END)"
// DISPLAY_NAME_SQL returns phone, try email
+ " WHEN primary_phone IS NOT NULL THEN "
+ "(CASE"
+ PRIMARY_EMAIL_WHEN_SQL
+ " ELSE null END)"
// DISPLAY_NAME_SQL returns email or NULL, return NULL
+ " ELSE null END)";
private static final String PRESENCE_ICON_SQL
= "(CASE"
+ buildPresenceStatusWhen(People.OFFLINE)
+ buildPresenceStatusWhen(People.INVISIBLE)
+ buildPresenceStatusWhen(People.AWAY)
+ buildPresenceStatusWhen(People.IDLE)
+ buildPresenceStatusWhen(People.DO_NOT_DISTURB)
+ buildPresenceStatusWhen(People.AVAILABLE)
+ " ELSE null END)";
private static String buildPresenceStatusWhen(int status) {
return " WHEN " + Presence.PRESENCE_STATUS + " = " + status
+ " THEN " + Presence.getPresenceIconResourceId(status);
}
private static final String[] sPhonesKeyColumns;
private static final String[] sContactMethodsKeyColumns;
private static final String[] sOrganizationsKeyColumns;
private static final String[] sGroupmembershipKeyColumns;
private static final String[] sExtensionsKeyColumns;
static private String buildOrderBy(String table, String... columns) {
StringBuilder sb = null;
for (String column : columns) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.append(", ");
}
sb.append(table);
sb.append('.');
sb.append(column);
}
return (sb == null) ? "" : sb.toString();
}
static {
// Contacts URI matching table
UriMatcher matcher = sURIMatcher;
matcher.addURI(CONTACTS_AUTHORITY, "extensions", EXTENSIONS);
matcher.addURI(CONTACTS_AUTHORITY, "extensions/#", EXTENSIONS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "groups", GROUPS);
matcher.addURI(CONTACTS_AUTHORITY, "groups/#", GROUPS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "groups/name/*/members", GROUP_NAME_MEMBERS);
matcher.addURI(CONTACTS_AUTHORITY, "groups/name/*/members/filter/*",
GROUP_NAME_MEMBERS_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS);
matcher.addURI(CONTACTS_AUTHORITY, "groups/system_id/*/members/filter/*",
GROUP_SYSTEM_ID_MEMBERS_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "groupmembership", GROUPMEMBERSHIP);
matcher.addURI(CONTACTS_AUTHORITY, "groupmembership/#", GROUPMEMBERSHIP_ID);
matcher.addURI(CONTACTS_AUTHORITY, "groupmembershipraw", GROUPMEMBERSHIP_RAW);
matcher.addURI(CONTACTS_AUTHORITY, "people", PEOPLE);
matcher.addURI(CONTACTS_AUTHORITY, "people/strequent", PEOPLE_STREQUENT);
matcher.addURI(CONTACTS_AUTHORITY, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "people/filter/*", PEOPLE_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "people/with_phones_filter/*",
PEOPLE_WITH_PHONES_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "people/with_email_or_im_filter/*",
PEOPLE_WITH_EMAIL_OR_IM_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "people/#", PEOPLE_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/extensions", PEOPLE_EXTENSIONS);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/phones", PEOPLE_PHONES);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/phones_with_presence",
PEOPLE_PHONES_WITH_PRESENCE);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/photo", PEOPLE_PHOTO);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/photo/data", PEOPLE_PHOTO_DATA);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/phones/#", PEOPLE_PHONES_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods_with_presence",
PEOPLE_CONTACTMETHODS_WITH_PRESENCE);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/organizations", PEOPLE_ORGANIZATIONS);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID);
matcher.addURI(CONTACTS_AUTHORITY, "people/raw", PEOPLE_RAW);
matcher.addURI(CONTACTS_AUTHORITY, "people/owner", PEOPLE_OWNER);
matcher.addURI(CONTACTS_AUTHORITY, "people/#/update_contact_time",
PEOPLE_UPDATE_CONTACT_TIME);
matcher.addURI(CONTACTS_AUTHORITY, "deleted_people", DELETED_PEOPLE);
matcher.addURI(CONTACTS_AUTHORITY, "deleted_groups", DELETED_GROUPS);
matcher.addURI(CONTACTS_AUTHORITY, "phones", PHONES);
matcher.addURI(CONTACTS_AUTHORITY, "phones_with_presence", PHONES_WITH_PRESENCE);
matcher.addURI(CONTACTS_AUTHORITY, "phones/filter/*", PHONES_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "phones/filter_name/*", PHONES_FILTER_NAME);
matcher.addURI(CONTACTS_AUTHORITY, "phones/mobile_filter_name/*",
PHONES_MOBILE_FILTER_NAME);
matcher.addURI(CONTACTS_AUTHORITY, "phones/#", PHONES_ID);
matcher.addURI(CONTACTS_AUTHORITY, "photos", PHOTOS);
matcher.addURI(CONTACTS_AUTHORITY, "photos/#", PHOTOS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "contact_methods", CONTACTMETHODS);
matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/email", CONTACTMETHODS_EMAIL);
matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER);
matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/#", CONTACTMETHODS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/with_presence",
CONTACTMETHODS_WITH_PRESENCE);
matcher.addURI(CONTACTS_AUTHORITY, "presence", PRESENCE);
matcher.addURI(CONTACTS_AUTHORITY, "presence/#", PRESENCE_ID);
matcher.addURI(CONTACTS_AUTHORITY, "organizations", ORGANIZATIONS);
matcher.addURI(CONTACTS_AUTHORITY, "organizations/#", ORGANIZATIONS_ID);
matcher.addURI(CONTACTS_AUTHORITY, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP);
matcher.addURI(CONTACTS_AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,
SEARCH_SUGGESTIONS);
matcher.addURI(CONTACTS_AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
SEARCH_SUGGESTIONS);
matcher.addURI(CONTACTS_AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/#",
SEARCH_SHORTCUT);
matcher.addURI(CONTACTS_AUTHORITY, "settings", SETTINGS);
matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people", LIVE_FOLDERS_PEOPLE);
matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people/*",
LIVE_FOLDERS_PEOPLE_GROUP_NAME);
matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people_with_phones",
LIVE_FOLDERS_PEOPLE_WITH_PHONES);
matcher.addURI(CONTACTS_AUTHORITY, "live_folders/favorites",
LIVE_FOLDERS_PEOPLE_FAVORITES);
// Call log URI matching table
matcher.addURI(CALL_LOG_AUTHORITY, "calls", CALLS);
matcher.addURI(CALL_LOG_AUTHORITY, "calls/filter/*", CALLS_FILTER);
matcher.addURI(CALL_LOG_AUTHORITY, "calls/#", CALLS_ID);
HashMap map;
// Create the common people columns
HashMap peopleColumns = new HashMap();
peopleColumns.put(PeopleColumns.NAME, People.NAME);
peopleColumns.put(PeopleColumns.NOTES, People.NOTES);
peopleColumns.put(PeopleColumns.TIMES_CONTACTED, People.TIMES_CONTACTED);
peopleColumns.put(PeopleColumns.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED);
peopleColumns.put(PeopleColumns.STARRED, People.STARRED);
peopleColumns.put(PeopleColumns.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE);
peopleColumns.put(PeopleColumns.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL);
peopleColumns.put(PeopleColumns.PHONETIC_NAME, People.PHONETIC_NAME);
peopleColumns.put(PeopleColumns.DISPLAY_NAME,
DISPLAY_NAME_SQL + " AS " + People.DISPLAY_NAME);
peopleColumns.put(PeopleColumns.SORT_STRING,
PHONETICALLY_SORTABLE_STRING_SQL + " AS " + People.SORT_STRING);
// Create the common groups columns
HashMap groupsColumns = new HashMap();
groupsColumns.put(GroupsColumns.NAME, Groups.NAME);
groupsColumns.put(GroupsColumns.NOTES, Groups.NOTES);
groupsColumns.put(GroupsColumns.SYSTEM_ID, Groups.SYSTEM_ID);
groupsColumns.put(GroupsColumns.SHOULD_SYNC, Groups.SHOULD_SYNC);
// Create the common presence columns
HashMap presenceColumns = new HashMap();
presenceColumns.put(PresenceColumns.IM_PROTOCOL, PresenceColumns.IM_PROTOCOL);
presenceColumns.put(PresenceColumns.IM_HANDLE, PresenceColumns.IM_HANDLE);
presenceColumns.put(PresenceColumns.IM_ACCOUNT, PresenceColumns.IM_ACCOUNT);
presenceColumns.put(PresenceColumns.PRESENCE_STATUS, PresenceColumns.PRESENCE_STATUS);
presenceColumns.put(PresenceColumns.PRESENCE_CUSTOM_STATUS,
PresenceColumns.PRESENCE_CUSTOM_STATUS);
// Create the common sync columns
HashMap syncColumns = new HashMap();
syncColumns.put(SyncConstValue._SYNC_ID, SyncConstValue._SYNC_ID);
syncColumns.put(SyncConstValue._SYNC_TIME, SyncConstValue._SYNC_TIME);
syncColumns.put(SyncConstValue._SYNC_VERSION, SyncConstValue._SYNC_VERSION);
syncColumns.put(SyncConstValue._SYNC_LOCAL_ID, SyncConstValue._SYNC_LOCAL_ID);
syncColumns.put(SyncConstValue._SYNC_DIRTY, SyncConstValue._SYNC_DIRTY);
syncColumns.put(SyncConstValue._SYNC_ACCOUNT, SyncConstValue._SYNC_ACCOUNT);
syncColumns.put(SyncConstValue._SYNC_ACCOUNT_TYPE, SyncConstValue._SYNC_ACCOUNT_TYPE);
// Phones columns
HashMap phonesColumns = new HashMap();
phonesColumns.put(Phones.NUMBER, Phones.NUMBER);
phonesColumns.put(Phones.NUMBER_KEY, Phones.NUMBER_KEY);
phonesColumns.put(Phones.TYPE, Phones.TYPE);
phonesColumns.put(Phones.LABEL, Phones.LABEL);
// People projection map
map = new HashMap();
map.put(People._ID, "people._id AS " + People._ID);
peopleColumns.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID);
peopleColumns.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID);
peopleColumns.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID);
map.putAll(peopleColumns);
map.putAll(phonesColumns);
map.putAll(syncColumns);
map.putAll(presenceColumns);
sPeopleProjectionMap = map;
// People with photo projection map
map = new HashMap(sPeopleProjectionMap);
map.put("photo_data", "photos.data AS photo_data");
sPeopleWithPhotoProjectionMap = map;
// People with E-mail or IM projection map
map = new HashMap();
map.put(People._ID, "people._id AS " + People._ID);
map.put(ContactMethods.DATA,
"contact_methods." + ContactMethods.DATA + " AS " + ContactMethods.DATA);
map.put(ContactMethods.KIND,
"contact_methods." + ContactMethods.KIND + " AS " + ContactMethods.KIND);
map.putAll(peopleColumns);
sPeopleWithEmailOrImProjectionMap = map;
// Groups projection map
map = new HashMap();
map.put(Groups._ID, Groups._ID);
map.putAll(groupsColumns);
map.putAll(syncColumns);
sGroupsProjectionMap = map;
// Group Membership projection map
map = new HashMap();
map.put(GroupMembership._ID, "groupmembership._id AS " + GroupMembership._ID);
map.put(GroupMembership.PERSON_ID, GroupMembership.PERSON_ID);
map.put(GroupMembership.GROUP_ID, "groups._id AS " + GroupMembership.GROUP_ID);
map.put(GroupMembership.GROUP_SYNC_ACCOUNT, GroupMembership.GROUP_SYNC_ACCOUNT);
map.put(GroupMembership.GROUP_SYNC_ACCOUNT_TYPE, GroupMembership.GROUP_SYNC_ACCOUNT_TYPE);
map.put(GroupMembership.GROUP_SYNC_ID, GroupMembership.GROUP_SYNC_ID);
map.putAll(groupsColumns);
sGroupMembershipProjectionMap = map;
// Use this when you need to force items to the top of a times_contacted list
map = new HashMap(sPeopleProjectionMap);
map.put(People.TIMES_CONTACTED, Long.MAX_VALUE + " AS " + People.TIMES_CONTACTED);
map.put("photo_data", "photos.data AS photo_data");
sStrequentStarredProjectionMap = map;
// Calls projection map
map = new HashMap();
map.put(Calls._ID, Calls._ID);
map.put(Calls.NUMBER, Calls.NUMBER);
map.put(Calls.DATE, Calls.DATE);
map.put(Calls.DURATION, Calls.DURATION);
map.put(Calls.TYPE, Calls.TYPE);
map.put(Calls.NEW, Calls.NEW);
map.put(Calls.CACHED_NAME, Calls.CACHED_NAME);
map.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE);
map.put(Calls.CACHED_NUMBER_LABEL, Calls.CACHED_NUMBER_LABEL);
sCallsProjectionMap = map;
// Phones projection map
map = new HashMap();
map.put(Phones._ID, "phones._id AS " + Phones._ID);
map.putAll(phonesColumns);
map.put(Phones.PERSON_ID, "phones.person AS " + Phones.PERSON_ID);
map.put(Phones.ISPRIMARY, Phones.ISPRIMARY);
map.putAll(peopleColumns);
sPhonesProjectionMap = map;
// Phones with presence projection map
map = new HashMap(sPhonesProjectionMap);
map.putAll(presenceColumns);
sPhonesWithPresenceProjectionMap = map;
// Organizations projection map
map = new HashMap();
map.put(Organizations._ID, "organizations._id AS " + Organizations._ID);
map.put(Organizations.LABEL, Organizations.LABEL);
map.put(Organizations.TYPE, Organizations.TYPE);
map.put(Organizations.PERSON_ID, Organizations.PERSON_ID);
map.put(Organizations.COMPANY, Organizations.COMPANY);
map.put(Organizations.TITLE, Organizations.TITLE);
map.put(Organizations.ISPRIMARY, Organizations.ISPRIMARY);
sOrganizationsProjectionMap = map;
// Extensions projection map
map = new HashMap();
map.put(Extensions._ID, Extensions._ID);
map.put(Extensions.NAME, Extensions.NAME);
map.put(Extensions.VALUE, Extensions.VALUE);
map.put(Extensions.PERSON_ID, Extensions.PERSON_ID);
sExtensionsProjectionMap = map;
// Contact methods projection map
map = new HashMap();
map.put(ContactMethods._ID, "contact_methods._id AS " + ContactMethods._ID);
map.put(ContactMethods.KIND, ContactMethods.KIND);
map.put(ContactMethods.TYPE, ContactMethods.TYPE);
map.put(ContactMethods.LABEL, ContactMethods.LABEL);
map.put(ContactMethods.DATA, ContactMethods.DATA);
map.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA);
map.put(ContactMethods.PERSON_ID, "contact_methods.person AS " + ContactMethods.PERSON_ID);
map.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY);
map.putAll(peopleColumns);
sContactMethodsProjectionMap = map;
// Contact methods with presence projection map
map = new HashMap(sContactMethodsProjectionMap);
map.putAll(presenceColumns);
sContactMethodsWithPresenceProjectionMap = map;
// Email search projection map
map = new HashMap();
map.put(ContactMethods.NAME, ContactMethods.NAME);
map.put(ContactMethods.DATA, ContactMethods.DATA);
map.put(ContactMethods._ID, "contact_methods._id AS " + ContactMethods._ID);
sEmailSearchProjectionMap = map;
// Presence projection map
map = new HashMap();
map.put(Presence._ID, "presence._id AS " + Presence._ID);
map.putAll(presenceColumns);
map.putAll(peopleColumns);
sPresenceProjectionMap = map;
// Search suggestions projection map
map = new HashMap();
map.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
DISPLAY_NAME_SQL + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1);
map.put(SearchManager.SUGGEST_COLUMN_TEXT_2,
SUGGEST_DESCRIPTION_SQL + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_2);
map.put(SearchManager.SUGGEST_COLUMN_ICON_1,
"(CASE WHEN " + Photos.DATA + " IS NOT NULL"
+ " THEN '" + People.CONTENT_URI + "/' || people._id ||"
+ " '/" + Photos.CONTENT_DIRECTORY + "/data'"
+ " ELSE " + com.android.internal.R.drawable.ic_contact_picture
+ " END) AS " + SearchManager.SUGGEST_COLUMN_ICON_1);
map.put(SearchManager.SUGGEST_COLUMN_ICON_2,
PRESENCE_ICON_SQL + " AS " + SearchManager.SUGGEST_COLUMN_ICON_2);
map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID,
"people._id AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
"people._id AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
map.put(People._ID, "people._id AS " + People._ID);
sSearchSuggestionsProjectionMap = map;
// Photos projection map
map = new HashMap();
map.put(Photos._ID, Photos._ID);
map.put(Photos.LOCAL_VERSION, Photos.LOCAL_VERSION);
map.put(Photos.EXISTS_ON_SERVER, Photos.EXISTS_ON_SERVER);
map.put(Photos.SYNC_ERROR, Photos.SYNC_ERROR);
map.put(Photos.PERSON_ID, Photos.PERSON_ID);
map.put(Photos.DATA, Photos.DATA);
map.put(Photos.DOWNLOAD_REQUIRED, ""
+ "(exists_on_server!=0 "
+ " AND sync_error IS NULL "
+ " AND (local_version IS NULL OR _sync_version != local_version)) "
+ "AS " + Photos.DOWNLOAD_REQUIRED);
map.putAll(syncColumns);
sPhotosProjectionMap = map;
// Live folder projection
map = new HashMap();
map.put(LiveFolders._ID, "people._id AS " + LiveFolders._ID);
map.put(LiveFolders.NAME, DISPLAY_NAME_SQL + " AS " + LiveFolders.NAME);
// TODO: Put contact photo back when we have a way to display a default icon
// for contacts without a photo
// map.put(LiveFolders.ICON_BITMAP, Photos.DATA + " AS " + LiveFolders.ICON_BITMAP);
sLiveFoldersProjectionMap = map;
// Order by statements
sPhonesKeyOrderBy = buildOrderBy(sPhonesTable, Phones.NUMBER);
sContactMethodsKeyOrderBy = buildOrderBy(sContactMethodsTable,
ContactMethods.DATA, ContactMethods.KIND);
sOrganizationsKeyOrderBy = buildOrderBy(sOrganizationsTable, Organizations.COMPANY);
sGroupmembershipKeyOrderBy =
buildOrderBy(sGroupmembershipTable, GroupMembership.GROUP_SYNC_ACCOUNT_TYPE,
GroupMembership.GROUP_SYNC_ACCOUNT);
sPhonesKeyColumns = new String[]{Phones.NUMBER};
sContactMethodsKeyColumns = new String[]{ContactMethods.DATA, ContactMethods.KIND};
sOrganizationsKeyColumns = new String[]{Organizations.COMPANY};
sGroupmembershipKeyColumns = new String[]{GroupMembership.GROUP_SYNC_ACCOUNT,
GroupMembership.GROUP_SYNC_ACCOUNT_TYPE};
sExtensionsKeyColumns = new String[]{Extensions.NAME};
String groupJoinByLocalId = "groups._id=groupmembership.group_id";
String groupJoinByServerId = "("
+ "groups._sync_account=groupmembership.group_sync_account"
+ " AND "
+ "groups._sync_account_type=groupmembership.group_sync_account_type"
+ " AND "
+ "groups._sync_id=groupmembership.group_sync_id"
+ ")";
sGroupsJoinString = "(" + groupJoinByLocalId + " OR " + groupJoinByServerId + ")";
}
}