FileDocCategorySizeDatePackage
ContactsProvider.javaAPI DocAndroid 1.5 API204145Wed May 06 22:42:48 BST 2009com.android.providers.contacts

ContactsProvider

public class ContactsProvider extends android.content.AbstractSyncableContentProvider

Fields Summary
private static final String
STREQUENT_ORDER_BY
private static final String
STREQUENT_LIMIT
private static final String
PEOPLE_PHONES_JOIN
private static final String
PEOPLE_PHONES_PHOTOS_JOIN
private static final String
GTALK_PROTOCOL_STRING
private static final String[]
ID_TYPE_PROJECTION
private static final String[]
sIsPrimaryProjectionWithoutKind
private static final String[]
sIsPrimaryProjectionWithKind
private static final String
WHERE_ID
private static final String
sGroupsJoinString
private static final String
PREFS_NAME_OWNER
private static final String
PREF_OWNER_ID
private final android.content.ContentValues
mValues
this is suitable for use by insert/update/delete/query and may be passed as a method call parameter. Only insert/update/delete/query should call .clear() on it
private final android.content.ContentValues
mValuesLocal
this is suitable for local use in methods and should never be passed as a parameter to other methods (other than the DB layer)
private String[]
mAccounts
private final Object
mAccountsLock
private DatabaseUtils.InsertHelper
mDeletedPeopleInserter
private DatabaseUtils.InsertHelper
mPeopleInserter
private int
mIndexPeopleSyncId
private int
mIndexPeopleSyncTime
private int
mIndexPeopleSyncVersion
private int
mIndexPeopleSyncDirty
private int
mIndexPeopleSyncAccount
private int
mIndexPeopleName
private int
mIndexPeoplePhoneticName
private int
mIndexPeopleNotes
private DatabaseUtils.InsertHelper
mGroupsInserter
private DatabaseUtils.InsertHelper
mPhotosInserter
private int
mIndexPhotosPersonId
private int
mIndexPhotosSyncId
private int
mIndexPhotosSyncTime
private int
mIndexPhotosSyncVersion
private int
mIndexPhotosSyncDirty
private int
mIndexPhotosSyncAccount
private int
mIndexPhotosExistsOnServer
private int
mIndexPhotosSyncError
private DatabaseUtils.InsertHelper
mContactMethodsInserter
private int
mIndexContactMethodsPersonId
private int
mIndexContactMethodsLabel
private int
mIndexContactMethodsKind
private int
mIndexContactMethodsType
private int
mIndexContactMethodsData
private int
mIndexContactMethodsAuxData
private int
mIndexContactMethodsIsPrimary
private DatabaseUtils.InsertHelper
mOrganizationsInserter
private int
mIndexOrganizationsPersonId
private int
mIndexOrganizationsLabel
private int
mIndexOrganizationsType
private int
mIndexOrganizationsCompany
private int
mIndexOrganizationsTitle
private int
mIndexOrganizationsIsPrimary
private DatabaseUtils.InsertHelper
mExtensionsInserter
private int
mIndexExtensionsPersonId
private int
mIndexExtensionsName
private int
mIndexExtensionsValue
private DatabaseUtils.InsertHelper
mGroupMembershipInserter
private int
mIndexGroupMembershipPersonId
private int
mIndexGroupMembershipGroupSyncAccount
private int
mIndexGroupMembershipGroupSyncId
private DatabaseUtils.InsertHelper
mCallsInserter
private DatabaseUtils.InsertHelper
mPhonesInserter
private int
mIndexPhonesPersonId
private int
mIndexPhonesLabel
private int
mIndexPhonesType
private int
mIndexPhonesNumber
private int
mIndexPhonesNumberKey
private int
mIndexPhonesIsPrimary
protected static String
sPeopleTable
protected static android.net.Uri
sPeopleRawURL
protected static String
sDeletedPeopleTable
protected static android.net.Uri
sDeletedPeopleURL
protected static String
sGroupsTable
protected static String
sSettingsTable
protected static android.net.Uri
sGroupsURL
protected static String
sDeletedGroupsTable
protected static android.net.Uri
sDeletedGroupsURL
protected static String
sPhonesTable
protected static String
sOrganizationsTable
protected static String
sContactMethodsTable
protected static String
sGroupmembershipTable
protected static String
sPhotosTable
protected static android.net.Uri
sPhotosURL
protected static String
sExtensionsTable
protected static String
sCallsTable
private static final String
TAG
static final String
DATABASE_NAME
static final int
DATABASE_VERSION
protected static final String
CONTACTS_AUTHORITY
protected static final String
CALL_LOG_AUTHORITY
private static final int
PEOPLE_BASE
private static final int
PEOPLE
private static final int
PEOPLE_FILTER
private static final int
PEOPLE_ID
private static final int
PEOPLE_PHONES
private static final int
PEOPLE_PHONES_ID
private static final int
PEOPLE_CONTACTMETHODS
private static final int
PEOPLE_CONTACTMETHODS_ID
private static final int
PEOPLE_RAW
private static final int
PEOPLE_WITH_PHONES_FILTER
private static final int
PEOPLE_STREQUENT
private static final int
PEOPLE_STREQUENT_FILTER
private static final int
PEOPLE_ORGANIZATIONS
private static final int
PEOPLE_ORGANIZATIONS_ID
private static final int
PEOPLE_GROUPMEMBERSHIP
private static final int
PEOPLE_GROUPMEMBERSHIP_ID
private static final int
PEOPLE_PHOTO
private static final int
PEOPLE_EXTENSIONS
private static final int
PEOPLE_EXTENSIONS_ID
private static final int
PEOPLE_CONTACTMETHODS_WITH_PRESENCE
private static final int
PEOPLE_OWNER
private static final int
PEOPLE_UPDATE_CONTACT_TIME
private static final int
PEOPLE_PHONES_WITH_PRESENCE
private static final int
PEOPLE_WITH_EMAIL_OR_IM_FILTER
private static final int
DELETED_BASE
private static final int
DELETED_PEOPLE
private static final int
DELETED_GROUPS
private static final int
PHONES_BASE
private static final int
PHONES
private static final int
PHONES_ID
private static final int
PHONES_FILTER
private static final int
PHONES_FILTER_NAME
private static final int
PHONES_MOBILE_FILTER_NAME
private static final int
PHONES_WITH_PRESENCE
private static final int
CONTACTMETHODS_BASE
private static final int
CONTACTMETHODS
private static final int
CONTACTMETHODS_ID
private static final int
CONTACTMETHODS_EMAIL
private static final int
CONTACTMETHODS_EMAIL_FILTER
private static final int
CONTACTMETHODS_WITH_PRESENCE
private static final int
CALLS_BASE
private static final int
CALLS
private static final int
CALLS_ID
private static final int
CALLS_FILTER
private static final int
PRESENCE_BASE
private static final int
PRESENCE
private static final int
PRESENCE_ID
private static final int
ORGANIZATIONS_BASE
private static final int
ORGANIZATIONS
private static final int
ORGANIZATIONS_ID
private static final int
VOICE_DIALER_TIMESTAMP
private static final int
SEARCH_SUGGESTIONS
private static final int
GROUPS_BASE
private static final int
GROUPS
private static final int
GROUPS_ID
private static final int
GROUP_NAME_MEMBERS
private static final int
GROUP_NAME_MEMBERS_FILTER
private static final int
GROUP_SYSTEM_ID_MEMBERS
private static final int
GROUP_SYSTEM_ID_MEMBERS_FILTER
private static final int
GROUPMEMBERSHIP_BASE
private static final int
GROUPMEMBERSHIP
private static final int
GROUPMEMBERSHIP_ID
private static final int
GROUPMEMBERSHIP_RAW
private static final int
PHOTOS_BASE
private static final int
PHOTOS
private static final int
PHOTOS_ID
private static final int
EXTENSIONS_BASE
private static final int
EXTENSIONS
private static final int
EXTENSIONS_ID
private static final int
SETTINGS
private static final int
LIVE_FOLDERS_BASE
private static final int
LIVE_FOLDERS_PEOPLE
private static final int
LIVE_FOLDERS_PEOPLE_GROUP_NAME
private static final int
LIVE_FOLDERS_PEOPLE_WITH_PHONES
private static final int
LIVE_FOLDERS_PEOPLE_FAVORITES
private static final android.content.UriMatcher
sURIMatcher
private static final HashMap
sGroupsProjectionMap
private static final HashMap
sPeopleProjectionMap
private static final HashMap
sPeopleWithPhotoProjectionMap
private static final HashMap
sPeopleWithEmailOrImProjectionMap
private static final HashMap
sStrequentStarredProjectionMap
Used to force items to the top of a times_contacted list
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
private static final String
PHONETICALLY_SORTABLE_STRING_SQL
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
Constructors Summary
public ContactsProvider()


      
        super(DATABASE_NAME, DATABASE_VERSION, Contacts.CONTENT_URI);
    
Methods Summary
private static java.lang.StringaddIdToWhereClause(java.lang.String id, java.lang.String where)

        if (id != null) {
            StringBuilder whereSb = new StringBuilder("_id=");
            whereSb.append(id);
            if (!TextUtils.isEmpty(where)) {
                whereSb.append(" AND (");
                whereSb.append(where);
                whereSb.append(')");
            }
            return whereSb.toString();
        } else {
            return where;
        }
    
private java.lang.String[]appendSelectionArg(java.lang.String[] selectionArgs, java.lang.String newArg)
Append a string to a selection args array

param
selectionArgs the old arg
param
newArg the new arg to append
return
a new string array with all of the args

        if (selectionArgs == null || selectionArgs.length == 0) {
            return new String[] { newArg };
        } else {
            int length = selectionArgs.length;
            String[] newArgs = new String[length + 1];
            System.arraycopy(selectionArgs, 0, newArgs, 0, length);
            newArgs[length] = newArg;
            return newArgs;
        }
    
protected voidbootstrapDatabase(android.database.sqlite.SQLiteDatabase db)

        super.bootstrapDatabase(db);
        db.execSQL("CREATE TABLE people (" +
                    People._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                    People._SYNC_ACCOUNT + " TEXT," + // From the sync source
                    People._SYNC_ID + " TEXT," + // From the sync source
                    People._SYNC_TIME + " TEXT," + // From the sync source
                    People._SYNC_VERSION + " TEXT," + // From the sync source
                    People._SYNC_LOCAL_ID + " INTEGER," + // Used while syncing, not persistent
                    People._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0," +
                                                       // if syncable, non-zero if the record
                                                       // has local, unsynced, changes
                    People._SYNC_MARK + " INTEGER," + // Used to filter out new rows

                    People.NAME + " TEXT COLLATE LOCALIZED," +
                    People.NOTES + " TEXT COLLATE LOCALIZED," +
                    People.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
                    People.LAST_TIME_CONTACTED + " INTEGER," +
                    People.STARRED + " INTEGER NOT NULL DEFAULT 0," +
                    People.PRIMARY_PHONE_ID + " INTEGER REFERENCES phones(_id)," +
                    People.PRIMARY_ORGANIZATION_ID + " INTEGER REFERENCES organizations(_id)," +
                    People.PRIMARY_EMAIL_ID + " INTEGER REFERENCES contact_methods(_id)," +
                    People.PHOTO_VERSION + " TEXT," +
                    People.CUSTOM_RINGTONE + " TEXT," +
                    People.SEND_TO_VOICEMAIL + " INTEGER," +
                    People.PHONETIC_NAME + " TEXT COLLATE LOCALIZED" +
                    ");");

        db.execSQL("CREATE INDEX peopleNameIndex ON people (" + People.NAME + ");");
        db.execSQL("CREATE INDEX peopleSyncDirtyIndex ON people (" + People._SYNC_DIRTY + ");");
        db.execSQL("CREATE INDEX peopleSyncIdIndex ON people (" + People._SYNC_ID + ");");
        
        db.execSQL("CREATE TRIGGER people_timesContacted UPDATE OF last_time_contacted ON people " +
                    "BEGIN " +
                        "UPDATE people SET "
                            + People.TIMES_CONTACTED + " = (new." + People.TIMES_CONTACTED + " + 1)"
                            + " WHERE _id = new._id;" +
                    "END");

        // table of all the groups that exist for an account
        db.execSQL("CREATE TABLE groups (" +
                Groups._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                Groups._SYNC_ACCOUNT + " TEXT," + // From the sync source
                Groups._SYNC_ID + " TEXT," + // From the sync source
                Groups._SYNC_TIME + " TEXT," + // From the sync source
                Groups._SYNC_VERSION + " TEXT," + // From the sync source
                Groups._SYNC_LOCAL_ID + " INTEGER," + // Used while syncing, not persistent
                Groups._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0," +
                                                          // if syncable, non-zero if the record
                                                          // has local, unsynced, changes
                Groups._SYNC_MARK + " INTEGER," + // Used to filter out new rows

                Groups.NAME + " TEXT NOT NULL," +
                Groups.NOTES + " TEXT," +
                Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 0," +
                Groups.SYSTEM_ID + " TEXT," +
                "UNIQUE(" +
                Groups.NAME + ","  + Groups.SYSTEM_ID + "," + Groups._SYNC_ACCOUNT + ")" +
                ");");

        db.execSQL("CREATE INDEX groupsSyncDirtyIndex ON groups (" + Groups._SYNC_DIRTY + ");");

        if (!isTemporary()) {
            // Add the system groups, since we always need them.
            db.execSQL("INSERT INTO groups (" + Groups.NAME + ", " + Groups.SYSTEM_ID + ") VALUES "
                    + "('" + Groups.GROUP_MY_CONTACTS + "', '" + Groups.GROUP_MY_CONTACTS + "')");
        }

        db.execSQL("CREATE TABLE peopleLookup (" +
                    "token TEXT," +
                    "source INTEGER REFERENCES people(_id)" +
                    ");");
        db.execSQL("CREATE INDEX peopleLookupIndex ON peopleLookup (" +
                    "token," +
                    "source" +
                    ");");

        db.execSQL("CREATE TABLE photos ("
                + Photos._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + Photos.EXISTS_ON_SERVER + " INTEGER NOT NULL DEFAULT 0,"
                + Photos.PERSON_ID + " INTEGER REFERENCES people(_id), "
                + Photos.LOCAL_VERSION + " TEXT,"
                + Photos.DATA + " BLOB,"
                + Photos.SYNC_ERROR + " TEXT,"
                + Photos._SYNC_ACCOUNT + " TEXT,"
                + Photos._SYNC_ID + " TEXT,"
                + Photos._SYNC_TIME + " TEXT,"
                + Photos._SYNC_VERSION + " TEXT,"
                + Photos._SYNC_LOCAL_ID + " INTEGER,"
                + Photos._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0,"
                + Photos._SYNC_MARK + " INTEGER,"
                + "UNIQUE(" + Photos.PERSON_ID + ") "
                + ")");

        db.execSQL("CREATE INDEX photosSyncDirtyIndex ON photos (" + Photos._SYNC_DIRTY + ");");
        db.execSQL("CREATE INDEX photoPersonIndex ON photos (person);");

        // Delete the photo row when the people row is deleted
        db.execSQL(""
                + " CREATE TRIGGER peopleDeleteAndPhotos DELETE ON people "
                + " BEGIN"
                + "   DELETE FROM photos WHERE person=OLD._id;"
                + " END");

        db.execSQL("CREATE TABLE _deleted_people (" +
                    "_sync_version TEXT," + // From the sync source
                    "_sync_id TEXT," +
                    (isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing,
                    "_sync_account TEXT," +
                    "_sync_mark INTEGER)"); // Used to filter out new rows

        db.execSQL("CREATE TABLE _deleted_groups (" +
                    "_sync_version TEXT," + // From the sync source
                    "_sync_id TEXT," +
                    (isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing,
                    "_sync_account TEXT," +
                    "_sync_mark INTEGER)"); // Used to filter out new rows

        db.execSQL("CREATE TABLE phones (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                    "person INTEGER REFERENCES people(_id)," +
                    "type INTEGER NOT NULL," + // kind specific (home, work, etc)
                    "number TEXT," +
                    "number_key TEXT," +
                    "label TEXT," +
                    "isprimary INTEGER NOT NULL DEFAULT 0" +
                    ");");
        db.execSQL("CREATE INDEX phonesIndex1 ON phones (person);");
        db.execSQL("CREATE INDEX phonesIndex2 ON phones (number_key);");

        db.execSQL("CREATE TABLE contact_methods (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                    "person INTEGER REFERENCES people(_id)," +
                    "kind INTEGER NOT NULL," + // the kind of contact method
                    "data TEXT," +
                    "aux_data TEXT," +
                    "type INTEGER NOT NULL," + // kind specific (home, work, etc)
                    "label TEXT," +
                    "isprimary INTEGER NOT NULL DEFAULT 0" +
                    ");");
        db.execSQL("CREATE INDEX contactMethodsPeopleIndex "
                + "ON contact_methods (person);");

        // The table for recent calls is here so we can do table joins
        // on people, phones, and calls all in one place.
        db.execSQL("CREATE TABLE calls (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                    "number TEXT," +
                    "date INTEGER," +
                    "duration INTEGER," +
                    "type INTEGER," +
                    "new INTEGER," +
                    "name TEXT," +
                    "numbertype INTEGER," +
                    "numberlabel TEXT" +
                    ");");

        // Various settings for the contacts sync adapter. The _sync_account column may
        // be null, but it must not be the empty string.
        db.execSQL("CREATE TABLE settings (" +
                    "_id INTEGER PRIMARY KEY," +
                    "_sync_account TEXT," +
                    "key STRING NOT NULL," +
                    "value STRING " +
                    ");");

        // The table for the organizations of a person.
        db.execSQL("CREATE TABLE organizations (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                    "company TEXT," +
                    "title TEXT," +
                    "isprimary INTEGER NOT NULL DEFAULT 0," +
                    "type INTEGER NOT NULL," + // kind specific (home, work, etc)
                    "label TEXT," +
                    "person INTEGER REFERENCES people(_id)" +
                    ");");
        db.execSQL("CREATE INDEX organizationsIndex1 ON organizations (person);");

        // The table for the extensions of a person.
        db.execSQL("CREATE TABLE extensions (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                    "name TEXT NOT NULL," +
                    "value TEXT NOT NULL," +
                    "person INTEGER REFERENCES people(_id)," +
                    "UNIQUE(person, name)" +
                    ");");
        db.execSQL("CREATE INDEX extensionsIndex1 ON extensions (person, name);");

        // The table for the groups of a person.
        db.execSQL("CREATE TABLE groupmembership (" +
                "_id INTEGER PRIMARY KEY," +
                "person INTEGER REFERENCES people(_id)," +
                "group_id INTEGER REFERENCES groups(_id)," +
                "group_sync_account STRING," +
                "group_sync_id STRING" +
                ");");
        db.execSQL("CREATE INDEX groupmembershipIndex1 ON groupmembership (person, group_id);");
        db.execSQL("CREATE INDEX groupmembershipIndex2 ON groupmembership (group_id, person);");
        db.execSQL("CREATE INDEX groupmembershipIndex3 ON groupmembership "
                + "(group_sync_account, group_sync_id);");

        // Trigger to completely remove a contacts data when they're deleted
        db.execSQL("CREATE TRIGGER contact_cleanup DELETE ON people " +
                    "BEGIN " +
                        "DELETE FROM peopleLookup WHERE source = old._id;" +
                        "DELETE FROM phones WHERE person = old._id;" +
                        "DELETE FROM contact_methods WHERE person = old._id;" +
                        "DELETE FROM organizations WHERE person = old._id;" +
                        "DELETE FROM groupmembership WHERE person = old._id;" +
                        "DELETE FROM extensions WHERE person = old._id;" +
                    "END");

        // Trigger to disassociate the groupmembership from the groups when an
        // groups entry is deleted
        db.execSQL("CREATE TRIGGER groups_cleanup DELETE ON groups " +
                    "BEGIN " +
                        "UPDATE groupmembership SET group_id = null WHERE group_id = old._id;" +
                    "END");

        // Trigger to move an account_people row to _deleted_account_people when it is deleted
        db.execSQL("CREATE TRIGGER groups_to_deleted DELETE ON groups " +
                    "WHEN old._sync_id is not null " +
                    "BEGIN " +
                        "INSERT INTO _deleted_groups " +
                            "(_sync_id, _sync_account, _sync_version) " +
                            "VALUES (old._sync_id, old._sync_account, " +
                            "old._sync_version);" +
                    "END");

        // Triggers to keep the peopleLookup table up to date
        db.execSQL("CREATE TRIGGER peopleLookup_update UPDATE OF name ON people " +
                    "BEGIN " +
                        "DELETE FROM peopleLookup WHERE source = new._id;" +
                        "SELECT _TOKENIZE('peopleLookup', new._id, new.name, ' ');" +
                    "END");
        db.execSQL("CREATE TRIGGER peopleLookup_insert AFTER INSERT ON people " +
                    "BEGIN " +
                        "SELECT _TOKENIZE('peopleLookup', new._id, new.name, ' ');" +
                    "END");

        // Triggers to set the _sync_dirty flag when a phone is changed,
        // inserted or deleted
        db.execSQL("CREATE TRIGGER phones_update UPDATE ON phones " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");
        db.execSQL("CREATE TRIGGER phones_insert INSERT ON phones " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person;" +
                    "END");
        db.execSQL("CREATE TRIGGER phones_delete DELETE ON phones " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");

        // Triggers to set the _sync_dirty flag when a contact_method is
        // changed, inserted or deleted
        db.execSQL("CREATE TRIGGER contact_methods_update UPDATE ON contact_methods " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");
        db.execSQL("CREATE TRIGGER contact_methods_insert INSERT ON contact_methods " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person;" +
                    "END");
        db.execSQL("CREATE TRIGGER contact_methods_delete DELETE ON contact_methods " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");

        // Triggers for when an organization is changed, inserted or deleted
        db.execSQL("CREATE TRIGGER organizations_update AFTER UPDATE ON organizations " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER organizations_insert INSERT ON organizations " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER organizations_delete DELETE ON organizations " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");

        // Triggers for when an groupmembership is changed, inserted or deleted
        db.execSQL("CREATE TRIGGER groupmembership_update AFTER UPDATE ON groupmembership " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER groupmembership_insert INSERT ON groupmembership " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER groupmembership_delete DELETE ON groupmembership " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");

        // Triggers for when an extension is changed, inserted or deleted
        db.execSQL("CREATE TRIGGER extensions_update AFTER UPDATE ON extensions " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER extensions_insert INSERT ON extensions " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " +
                    "END");
        db.execSQL("CREATE TRIGGER extensions_delete DELETE ON extensions " +
                    "BEGIN " +
                        "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" +
                    "END");

        createTypeLabelTrigger(db, sPhonesTable, "INSERT");
        createTypeLabelTrigger(db, sPhonesTable, "UPDATE");
        createTypeLabelTrigger(db, sOrganizationsTable, "INSERT");
        createTypeLabelTrigger(db, sOrganizationsTable, "UPDATE");
        createTypeLabelTrigger(db, sContactMethodsTable, "INSERT");
        createTypeLabelTrigger(db, sContactMethodsTable, "UPDATE");

        // Temporary table that holds a time stamp of the last time data the voice
        // dialer is interested in has changed so the grammar won't need to be
        // recompiled when unused data is changed.
        db.execSQL("CREATE TABLE voice_dialer_timestamp (" +
                   "_id INTEGER PRIMARY KEY," +
                   "timestamp INTEGER" +
                   ");");
        db.execSQL("INSERT INTO voice_dialer_timestamp (_id, timestamp) VALUES " +
                       "(1, strftime('%s', 'now'));");
        db.execSQL("CREATE TRIGGER timestamp_trigger1 AFTER UPDATE ON phones " +
                   "BEGIN " +
                       "UPDATE voice_dialer_timestamp SET timestamp=strftime('%s', 'now') "+
                           "WHERE _id=1;" +
                   "END");
        db.execSQL("CREATE TRIGGER timestamp_trigger2 AFTER UPDATE OF name ON people " +
                   "BEGIN " +
                       "UPDATE voice_dialer_timestamp SET timestamp=strftime('%s', 'now') " +
                           "WHERE _id=1;" +
                   "END");
    
private static java.lang.StringbuildOrderBy(java.lang.String table, java.lang.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();
    
private java.lang.StringbuildPeopleLookupWhereClause(java.lang.String filterParam)

        StringBuilder filter = new StringBuilder(
                "people._id IN (SELECT source FROM peopleLookup WHERE token GLOB ");
        // NOTE: Query parameters won't work here since the SQL compiler
        // needs to parse the actual string to know that it can use the
        // index to do a prefix scan.
        DatabaseUtils.appendEscapedSQLString(filter, 
                DatabaseUtils.getHexCollationKey(filterParam) + "*");
        filter.append(')");
        return filter.toString();
    
public booleanchangeRequiresLocalSync(android.net.Uri uri)

        final int match = sURIMatcher.match(uri);
        switch (match) {
            // Changes to these URIs cannot cause syncable data to be changed, so don't
            // bother trying to sync them.
            case CALLS:
            case CALLS_FILTER:
            case CALLS_ID:
            case PRESENCE:
            case PRESENCE_ID:
            case PEOPLE_UPDATE_CONTACT_TIME:
                return false;

            default:
                return true;
        }
    
private voidclearOtherIsPrimary(int kind, java.lang.Long personId, java.lang.Long itemId)
Clears the isprimary flag for all rows other than the itemId.

param
kind the kind of item
param
personId used to limit the updates to rows pertaining to this person
param
itemId which row to leave untouched

        final String table = kindToTable(kind);
        if (personId == null) throw new IllegalArgumentException("personId must not be null");
        StringBuilder sb = new StringBuilder();
        sb.append("person=");
        sb.append(personId);
        if (itemId != null) {
            sb.append(" and _id!=");
            sb.append(itemId);
        }
        if (sContactMethodsTable.equals(table)) {
            sb.append(" and ");
            sb.append(ContactMethods.KIND);
            sb.append("=");
            sb.append(kind);
        }

        mValuesLocal.clear();
        mValuesLocal.put("isprimary", 0);
        getDatabase().update(table, mValuesLocal, sb.toString(), null);
    
private voidcreateTypeLabelTrigger(android.database.sqlite.SQLiteDatabase db, java.lang.String table, java.lang.String operation)

        final String name = table + "_" + operation + "_typeAndLabel";
        db.execSQL("CREATE TRIGGER " + name + " AFTER " + operation + " ON " + table
                + "   WHEN (NEW.type != 0 AND NEW.label IS NOT NULL) OR "
                + "        (NEW.type = 0 AND NEW.label IS NULL)"
                + "   BEGIN "
                + "     SELECT RAISE (ABORT, 'exactly one of type or label must be set'); "
                + "   END");
    
private intdeleteFromGroupMembership(long rowId, java.lang.String where, java.lang.String[] whereArgs)

        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables("groups, groupmembership");
        qb.setProjectionMap(sGroupMembershipProjectionMap);
        qb.appendWhere(sGroupsJoinString);
        qb.appendWhere(" AND groupmembership._id=" + rowId);
        Cursor cursor = qb.query(getDatabase(), null, where, whereArgs, null, null, null);
        try {
            final int indexPersonId = cursor.getColumnIndexOrThrow(GroupMembership.PERSON_ID);
            final int indexName = cursor.getColumnIndexOrThrow(GroupMembership.NAME);
            while (cursor.moveToNext()) {
                if (Groups.GROUP_ANDROID_STARRED.equals(cursor.getString(indexName))) {
                    fixupPeopleStarred(cursor.getLong(indexPersonId), false);
                }
            }
        } finally {
            cursor.close();
        }

        return mDb.delete(sGroupmembershipTable,
                addIdToWhereClause(String.valueOf(rowId), where),
                whereArgs);
    
private intdeleteFromGroups(java.lang.String where, java.lang.String[] whereArgs)

        HashSet<String> modifiedAccounts = Sets.newHashSet();
        Cursor cursor = getDatabase().query(sGroupsTable, null, where, whereArgs,
                null, null, null);
        try {
            final int indexName = cursor.getColumnIndexOrThrow(Groups.NAME);
            final int indexSyncAccount = cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT);
            final int indexSyncId = cursor.getColumnIndexOrThrow(Groups._SYNC_ID);
            final int indexId = cursor.getColumnIndexOrThrow(Groups._ID);
            final int indexShouldSync = cursor.getColumnIndexOrThrow(Groups.SHOULD_SYNC);
            while (cursor.moveToNext()) {
                String oldName = cursor.getString(indexName);
                String syncAccount = cursor.getString(indexSyncAccount);
                String syncId = cursor.getString(indexSyncId);
                boolean shouldSync = cursor.getLong(indexShouldSync) != 0;
                long id = cursor.getLong(indexId);
                fixupPeopleStarredOnGroupRename(oldName, null, id);
                if (!TextUtils.isEmpty(syncAccount) && !TextUtils.isEmpty(syncId)) {
                    fixupPeopleStarredOnGroupRename(oldName, null, syncAccount, syncId);
                }
                if (!TextUtils.isEmpty(syncAccount) && shouldSync) {
                    modifiedAccounts.add(syncAccount);
                }
            }
        } finally {
            cursor.close();
        }

        int numRows = mDb.delete(sGroupsTable, where, whereArgs);
        if (numRows > 0) {
            if (!isTemporary()) {
                final ContentResolver cr = getContext().getContentResolver();
                for (String account : modifiedAccounts) {
                    onLocalChangesForAccount(cr, account, true);
                }
            }
        }
        return numRows;
    
private intdeleteFromPeople(java.lang.String rowId, java.lang.String where, java.lang.String[] whereArgs)

        final SQLiteDatabase db = getDatabase();
        where = addIdToWhereClause(rowId, where);
        Cursor cursor = db.query(sPeopleTable, null, where, whereArgs, null, null, null);
        try {
            final int idxSyncId = cursor.getColumnIndexOrThrow(People._SYNC_ID);
            final int idxSyncAccount = cursor.getColumnIndexOrThrow(People._SYNC_ACCOUNT);
            final int idxSyncVersion = cursor.getColumnIndexOrThrow(People._SYNC_VERSION);
            final int dstIdxSyncId = mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_ID);
            final int dstIdxSyncAccount =
                    mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_ACCOUNT);
            final int dstIdxSyncVersion =
                    mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_VERSION);
            while (cursor.moveToNext()) {
                final String syncId = cursor.getString(idxSyncId);
                if (TextUtils.isEmpty(syncId)) continue;
                // insert into deleted table
                mDeletedPeopleInserter.prepareForInsert();
                mDeletedPeopleInserter.bind(dstIdxSyncId, syncId);
                mDeletedPeopleInserter.bind(dstIdxSyncAccount, cursor.getString(idxSyncAccount));
                mDeletedPeopleInserter.bind(dstIdxSyncVersion, cursor.getString(idxSyncVersion));
                mDeletedPeopleInserter.execute();
            }
        } finally {
            cursor.close();
        }

        // perform the actual delete
        return db.delete(sPeopleTable, where, whereArgs);
    
public intdeleteInternal(android.net.Uri url, java.lang.String userWhere, java.lang.String[] whereArgs)

        String tableToChange;
        String changedItemId;

        final int matchedUriId = sURIMatcher.match(url);
        switch (matchedUriId) {
            case GROUPMEMBERSHIP_ID:
                return deleteFromGroupMembership(Long.parseLong(url.getPathSegments().get(1)),
                        userWhere, whereArgs);
            case GROUPS:
                return deleteFromGroups(userWhere, whereArgs);
            case GROUPS_ID:
                changedItemId = url.getPathSegments().get(1);
                return deleteFromGroups(addIdToWhereClause(changedItemId, userWhere), whereArgs);
            case EXTENSIONS:
                tableToChange = sExtensionsTable;
                changedItemId = null;
                break;
            case EXTENSIONS_ID:
                tableToChange = sExtensionsTable;
                changedItemId = url.getPathSegments().get(1);
                break;
            case PEOPLE_RAW:
            case PEOPLE:
                return deleteFromPeople(null, userWhere, whereArgs);
            case PEOPLE_ID:
                return deleteFromPeople(url.getPathSegments().get(1), userWhere, whereArgs);
            case PEOPLE_PHONES_ID:
                tableToChange = sPhonesTable;
                changedItemId = url.getPathSegments().get(3);
                break;
            case PEOPLE_CONTACTMETHODS_ID:
                tableToChange = sContactMethodsTable;
                changedItemId = url.getPathSegments().get(3);
                break;
            case PHONES_ID:
                tableToChange = sPhonesTable;
                changedItemId = url.getPathSegments().get(1);
                break;
            case ORGANIZATIONS_ID:
                tableToChange = sOrganizationsTable;
                changedItemId = url.getPathSegments().get(1);
                break;
            case CONTACTMETHODS_ID:
                tableToChange = sContactMethodsTable;
                changedItemId = url.getPathSegments().get(1);
                break;
            case PRESENCE:
                tableToChange = "presence";
                changedItemId = null;
                break;
            case CALLS:
                tableToChange = "calls";
                changedItemId = null;
                break;
            default:
                throw new UnsupportedOperationException("Cannot delete that URL: " + url);
        }

        String where = addIdToWhereClause(changedItemId, userWhere);
        IsPrimaryInfo oldPrimaryInfo = null;
        switch (matchedUriId) {
            case PEOPLE_PHONES_ID:
            case PHONES_ID:
            case ORGANIZATIONS_ID:
                oldPrimaryInfo = lookupIsPrimaryInfo(tableToChange,
                        sIsPrimaryProjectionWithoutKind, where, whereArgs);
                break;

            case PEOPLE_CONTACTMETHODS_ID:
            case CONTACTMETHODS_ID:
                oldPrimaryInfo = lookupIsPrimaryInfo(tableToChange,
                        sIsPrimaryProjectionWithKind, where, whereArgs);
                break;
        }

        final SQLiteDatabase db = getDatabase();
        int count = db.delete(tableToChange, where, whereArgs);
        if (count > 0) {
            if (oldPrimaryInfo != null && oldPrimaryInfo.isPrimary) {
                fixupPrimaryAfterDelete(oldPrimaryInfo.kind,
                        oldPrimaryInfo.id, oldPrimaryInfo.person);
            }
        }

        return count;
    
protected voiddropTables(android.database.sqlite.SQLiteDatabase db)

        db.execSQL("DROP TABLE IF EXISTS people");
        db.execSQL("DROP TABLE IF EXISTS peopleLookup");
        db.execSQL("DROP TABLE IF EXISTS _deleted_people");
        db.execSQL("DROP TABLE IF EXISTS phones");
        db.execSQL("DROP TABLE IF EXISTS contact_methods");
        db.execSQL("DROP TABLE IF EXISTS calls");
        db.execSQL("DROP TABLE IF EXISTS organizations");
        db.execSQL("DROP TABLE IF EXISTS voice_dialer_timestamp");
        db.execSQL("DROP TABLE IF EXISTS groups");
        db.execSQL("DROP TABLE IF EXISTS _deleted_groups");
        db.execSQL("DROP TABLE IF EXISTS groupmembership");
        db.execSQL("DROP TABLE IF EXISTS photos");
        db.execSQL("DROP TABLE IF EXISTS extensions");
        db.execSQL("DROP TABLE IF EXISTS settings");
    
private voidensureSyncAccountIsSet(android.content.ContentValues values)

        synchronized (mAccountsLock) {
            String account = values.getAsString(SyncConstValue._SYNC_ACCOUNT);
            if (account == null && mAccounts.length > 0) {
                values.put(SyncConstValue._SYNC_ACCOUNT, mAccounts[0]);
            }
        }
    
private java.lang.LongfindNewPrimary(int kind, java.lang.Long personId, java.lang.Long itemId)
Determines which of the rows in table for the personId should be picked as the primary row based on the rank of the row's type.

param
kind the kind of contact
param
personId used to limit the rows to those pertaining to this person
param
itemId optional, a row to ignore
return
the _id of the row that should be the new primary. Is null if there are no matching rows.

        final String table = kindToTable(kind);
        if (personId == null) throw new IllegalArgumentException("personId must not be null");
        StringBuilder sb = new StringBuilder();
        sb.append("person=");
        sb.append(personId);
        if (itemId != null) {
            sb.append(" and _id!=");
            sb.append(itemId);
        }
        if (sContactMethodsTable.equals(table)) {
            sb.append(" and ");
            sb.append(ContactMethods.KIND);
            sb.append("=");
            sb.append(kind);
        }

        Cursor cursor = getDatabase().query(table, ID_TYPE_PROJECTION, sb.toString(),
                null, null, null, null);
        try {
            Long newPrimaryId = null;
            int bestRank = -1;
            while (cursor.moveToNext()) {
                final int rank = getRankOfType(table, cursor.getInt(1));
                if (bestRank == -1 || rank < bestRank) {
                    newPrimaryId = cursor.getLong(0);
                    bestRank = rank;
                }
            }
            return newPrimaryId;
        } finally {
            cursor.close();
        }
    
private voidfixupGroupMembershipAfterPeopleUpdate(java.lang.String account, long personId, boolean makeStarred)

        ContentValues starredGroupInfo = queryAndroidStarredGroupId(account);
        if (makeStarred) {
            if (starredGroupInfo == null) {
                // we need to add the starred group
                mValuesLocal.clear();
                mValuesLocal.put(Groups.NAME, Groups.GROUP_ANDROID_STARRED);
                mValuesLocal.put(Groups._SYNC_DIRTY, 1);
                mValuesLocal.put(Groups._SYNC_ACCOUNT, account);
                long groupId = mGroupsInserter.insert(mValuesLocal);
                starredGroupInfo = new ContentValues();
                starredGroupInfo.put(Groups._ID, groupId);
                starredGroupInfo.put(Groups._SYNC_ACCOUNT, account);
                // don't put the _SYNC_ID in here since we don't know it yet
            }

            final Long groupId = starredGroupInfo.getAsLong(Groups._ID);
            final String syncId = starredGroupInfo.getAsString(Groups._SYNC_ID);
            final String syncAccount = starredGroupInfo.getAsString(Groups._SYNC_ACCOUNT);

            // check that either groupId is set or the syncId/Account is set
            final boolean hasSyncId = !TextUtils.isEmpty(syncId);
            final boolean hasGroupId = groupId != null;
            if (!hasGroupId && !hasSyncId) {
                throw new IllegalStateException("at least one of the groupId or "
                        + "the syncId must be set, " + starredGroupInfo);
            }
            
            // now add this person to the group
            mValuesLocal.clear();
            mValuesLocal.put(GroupMembership.PERSON_ID, personId);
            mValuesLocal.put(GroupMembership.GROUP_ID, groupId);
            mValuesLocal.put(GroupMembership.GROUP_SYNC_ID, syncId);
            mValuesLocal.put(GroupMembership.GROUP_SYNC_ACCOUNT, syncAccount);
            mGroupMembershipInserter.insert(mValuesLocal);
        } else {
            if (starredGroupInfo != null) {
                // delete the groupmembership rows for this person that match the starred group id
                String syncAccount = starredGroupInfo.getAsString(Groups._SYNC_ACCOUNT);
                String syncId = starredGroupInfo.getAsString(Groups._SYNC_ID);
                if (!TextUtils.isEmpty(syncId)) {
                    mDb.delete(sGroupmembershipTable,
                            "person=? AND group_sync_id=? AND group_sync_account=?",
                            new String[]{String.valueOf(personId), syncId, syncAccount});
                } else {
                    mDb.delete(sGroupmembershipTable, "person=? AND group_id=?",
                            new String[]{
                                    Long.toString(personId),
                                    Long.toString(starredGroupInfo.getAsLong(Groups._ID))});
                }
            }
        }
    
private intfixupPeopleStarred(long personId, boolean inStarredGroup)

        mValuesLocal.clear();
        mValuesLocal.put(People.STARRED, inStarredGroup ? 1 : 0);
        return getDatabase().update(sPeopleTable, mValuesLocal, WHERE_ID,
                new String[]{String.valueOf(personId)});
    
voidfixupPeopleStarredOnGroupRename(java.lang.String oldName, java.lang.String newName, java.lang.String where, java.lang.String[] whereArgs)

        if (TextUtils.equals(oldName, newName)) return;

        int starredValue;
        if (Groups.GROUP_ANDROID_STARRED.equals(newName)) {
            starredValue = 1;
        } else if (Groups.GROUP_ANDROID_STARRED.equals(oldName)) {
            starredValue = 0;
        } else {
            return;
        }

        getDatabase().execSQL("UPDATE people SET starred=" + starredValue + " WHERE _id in ("
                + "SELECT person "
                + "FROM groups, groupmembership "
                + "WHERE " + where + " AND " + sGroupsJoinString + ")",
                whereArgs);
    
voidfixupPeopleStarredOnGroupRename(java.lang.String oldName, java.lang.String newName, java.lang.String syncAccount, java.lang.String syncId)

        fixupPeopleStarredOnGroupRename(oldName, newName, "_sync_account=? AND _sync_id=?",
                new String[]{syncAccount, syncId});
    
voidfixupPeopleStarredOnGroupRename(java.lang.String oldName, java.lang.String newName, long groupId)

        fixupPeopleStarredOnGroupRename(oldName, newName, "group_id=?",
                new String[]{String.valueOf(groupId)});
    
private voidfixupPrimaryAfterDelete(int kind, java.lang.Long itemId, java.lang.Long personId)

        final String table = kindToTable(kind);
        // when you delete an item with isPrimary,
        // select a new one as isPrimary and clear the primary if no more items
        Long newPrimaryId = findNewPrimary(kind, personId, itemId);

        // we found a new primary, set its isprimary flag
        if (newPrimaryId != null) {
            mValuesLocal.clear();
            mValuesLocal.put("isprimary", 1);
            if (getDatabase().update(table, mValuesLocal, "_id=" + newPrimaryId, null) != 1) {
                throw new RuntimeException("error updating " + table + ", _id "
                        + newPrimaryId + ", values " + mValuesLocal);
            }
        }

        // if this kind's primary status should be reflected in the people row, update it
        if (kind == Contacts.KIND_PHONE) {
            updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newPrimaryId);
        } else if (kind == Contacts.KIND_EMAIL) {
            updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newPrimaryId);
        } else if (kind == Contacts.KIND_ORGANIZATION) {
            updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newPrimaryId);
        }
    
private voidfixupPrimaryAfterUpdate(int kind, java.lang.Long personId, java.lang.Long changedItemId, java.lang.Integer isPrimaryValue)

        final String table = kindToTable(kind);

        // - when you update isPrimary to true,
        //   make the changed item the primary, clear others
        // - when you update isPrimary to false,
        //   select a new one as isPrimary, clear the primary if no more phones
        if (isPrimaryValue != null) {
            if (personId == null) {
                personId = lookupPerson(table, changedItemId);
            }
            
            boolean isPrimary = isPrimaryValue != 0;
            Long newPrimary = changedItemId;
            if (!isPrimary) {
                newPrimary = findNewPrimary(kind, personId, changedItemId);
            }
            clearOtherIsPrimary(kind, personId, changedItemId);

            if (kind == Contacts.KIND_PHONE) {
                updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newPrimary);
            } else if (kind == Contacts.KIND_EMAIL) {
                updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newPrimary);
            } else if (kind == Contacts.KIND_ORGANIZATION) {
                updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newPrimary);
            }
        }
    
private java.lang.StringgetContactMethodType(android.net.Uri url)

        String mime = null;

        Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null);
        if (c != null) {
            try {
                if (c.moveToFirst()) {
                    int kind = c.getInt(0);
                    switch (kind) {
                    case Contacts.KIND_EMAIL:
                        mime = "vnd.android.cursor.item/email";
                        break;

                    case Contacts.KIND_IM:
                        mime = "vnd.android.cursor.item/jabber-im";
                        break;

                    case Contacts.KIND_POSTAL:
                        mime = "vnd.android.cursor.item/postal-address";
                        break;
                    }
                }
            } finally {
                c.close();
            }
        }
        return mime;
    
protected java.lang.IterablegetMergers()

        ArrayList<AbstractTableMerger> list = new ArrayList<AbstractTableMerger> ();
        list.add(new PersonMerger());
        list.add(new GroupMerger());
        list.add(new PhotoMerger());
        return list;
    
private intgetRankOfType(java.lang.String table, int type)
Returns the rank of the table-specific type, used when deciding which row should be primary when none are primary. The lower the rank the better the type.

param
table supports "phones", "contact_methods" and "organizations"
param
type the table-specific type from the TYPE column
return
the rank of the table-specific type, the lower the better

        if (table.equals(sPhonesTable)) {
            switch (type) {
                case Contacts.Phones.TYPE_MOBILE: return 0;
                case Contacts.Phones.TYPE_WORK: return 1;
                case Contacts.Phones.TYPE_HOME: return 2;
                case Contacts.Phones.TYPE_PAGER: return 3;
                case Contacts.Phones.TYPE_CUSTOM: return 4;
                case Contacts.Phones.TYPE_OTHER: return 5;
                case Contacts.Phones.TYPE_FAX_WORK: return 6;
                case Contacts.Phones.TYPE_FAX_HOME: return 7;
                default: return 1000;
            }
        }

        if (table.equals(sContactMethodsTable)) {
            switch (type) {
                case Contacts.ContactMethods.TYPE_HOME: return 0;
                case Contacts.ContactMethods.TYPE_WORK: return 1;
                case Contacts.ContactMethods.TYPE_CUSTOM: return 2;
                case Contacts.ContactMethods.TYPE_OTHER: return 3;
                default: return 1000;
            }
        }

        if (table.equals(sOrganizationsTable)) {
            switch (type) {
                case Organizations.TYPE_WORK: return 0;
                case Organizations.TYPE_CUSTOM: return 1;
                case Organizations.TYPE_OTHER: return 2;
                default: return 1000;
            }
        }

        throw new IllegalArgumentException("unexpected table, " + table);
    
public java.lang.StringgetType(android.net.Uri url)

        int match = sURIMatcher.match(url);
        switch (match) {
            case EXTENSIONS:
            case PEOPLE_EXTENSIONS:
                return Extensions.CONTENT_TYPE;
            case EXTENSIONS_ID:
            case PEOPLE_EXTENSIONS_ID:
                return Extensions.CONTENT_ITEM_TYPE;
            case PEOPLE:
                return "vnd.android.cursor.dir/person";
            case PEOPLE_ID:
                return "vnd.android.cursor.item/person";
            case PEOPLE_PHONES:
                return "vnd.android.cursor.dir/phone";
            case PEOPLE_PHONES_ID:
                return "vnd.android.cursor.item/phone";
            case PEOPLE_CONTACTMETHODS:
                return "vnd.android.cursor.dir/contact-methods";
            case PEOPLE_CONTACTMETHODS_ID:
                return getContactMethodType(url);
            case PHONES:
                return "vnd.android.cursor.dir/phone";
            case PHONES_ID:
                return "vnd.android.cursor.item/phone";
            case PHONES_FILTER:
            case PHONES_FILTER_NAME:
            case PHONES_MOBILE_FILTER_NAME:
                return "vnd.android.cursor.dir/phone";
            case CONTACTMETHODS:
                return "vnd.android.cursor.dir/contact-methods";
            case CONTACTMETHODS_ID:
                return getContactMethodType(url);
            case CONTACTMETHODS_EMAIL:
            case CONTACTMETHODS_EMAIL_FILTER:
                return "vnd.android.cursor.dir/email";
            case CALLS:
                return "vnd.android.cursor.dir/calls";
            case CALLS_ID:
                return "vnd.android.cursor.item/calls";
            case ORGANIZATIONS:
                return "vnd.android.cursor.dir/organizations";
            case ORGANIZATIONS_ID:
                return "vnd.android.cursor.item/organization";
            case CALLS_FILTER:
                return "vnd.android.cursor.dir/calls";
            default:
                throw new IllegalArgumentException("Unknown URL");
        }
    
private android.database.CursorhandleSearchSuggestionsQuery(android.net.Uri url, android.database.sqlite.SQLiteQueryBuilder qb)
Either sets up the query builder so we can run the proper query against the database and returns null, or returns a cursor with the results already in it.

param
url the URL passed for the suggestion
param
qb the query builder to use if a query needs to be run on the database
return
null with qb configured for a query, a cursor with the results already in it.

        qb.setTables("people");
        qb.setProjectionMap(sSearchSuggestionsProjectionMap);
        if (url.getPathSegments().size() > 1) {
            // A search term was entered, use it to filter
            final String searchClause = url.getLastPathSegment();
            if (!TextUtils.isDigitsOnly(searchClause)) {
                qb.appendWhere(buildPeopleLookupWhereClause(searchClause));
            } else {
                final String[] columnNames = new String[] {
                        SearchManager.SUGGEST_COLUMN_TEXT_1,
                        SearchManager.SUGGEST_COLUMN_TEXT_2,
                        SearchManager.SUGGEST_COLUMN_INTENT_DATA,
                        SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
                };

                Resources r = getContext().getResources();
                String s;
                int i;

                ArrayList dialNumber = new ArrayList();
                s = r.getString(com.android.internal.R.string.dial_number_using, searchClause);
                i = s.indexOf('\n");
                if (i < 0) {
                    dialNumber.add(s);
                    dialNumber.add("");
                } else {
                    dialNumber.add(s.substring(0, i));
                    dialNumber.add(s.substring(i + 1));
                }
                dialNumber.add("tel:" + searchClause);
                dialNumber.add(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);  

                ArrayList createContact = new ArrayList();
                s = r.getString(com.android.internal.R.string.create_contact_using, searchClause);
                i = s.indexOf('\n");
                if (i < 0) {
                    createContact.add(s);
                    createContact.add("");
                } else {
                    createContact.add(s.substring(0, i));
                    createContact.add(s.substring(i + 1));
                }
                createContact.add("tel:" + searchClause);
                createContact.add(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);

                ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
                rows.add(dialNumber);
                rows.add(createContact);

                ArrayListCursor cursor = new ArrayListCursor(columnNames, rows);
                return cursor;
            }
        }
        return null;
    
private longinsertAndFixupPrimary(int kind, android.content.ContentValues values)

        final String table = kindToTable(kind);
        boolean isPrimary = false;
        Long personId = null;

        if (!isTemporary()) {
            // when you add a item, if isPrimary or if there is no primary,
            // make this it, set the isPrimary flag, and clear other primary flags
            isPrimary = values.containsKey("isprimary")
                    && (values.getAsInteger("isprimary") != 0);
            personId = values.getAsLong("person");
            if (!isPrimary) {
                // make it primary anyway if this person doesn't have any rows of this type yet
                StringBuilder sb = new StringBuilder("person=" + personId);
                if (sContactMethodsTable.equals(table)) {
                    sb.append(" AND kind=");
                    sb.append(kind);
                }
                final boolean isFirstRowOfType = DatabaseUtils.longForQuery(getDatabase(),
                        "SELECT count(*) FROM " + table + " where " + sb.toString(), null) == 0;
                isPrimary = isFirstRowOfType;
            }

            values.put("isprimary", isPrimary ? 1 : 0);
        }

        // do the actual insert
        long newRowId = kindToInserter(kind).insert(values);

        if (newRowId <= 0) {
            throw new RuntimeException("error while inserting into " + table + ", " + values);
        }

        if (!isTemporary()) {
            // If this row was made the primary then clear the other isprimary flags and update
            // corresponding people row, if necessary.
            if (isPrimary) {
                clearOtherIsPrimary(kind, personId, newRowId);
                if (kind == Contacts.KIND_PHONE) {
                    updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newRowId);
                } else if (kind == Contacts.KIND_EMAIL) {
                    updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newRowId);
                } else if (kind == Contacts.KIND_ORGANIZATION) {
                    updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newRowId);
                }
            }
        }

        return newRowId;
    
public android.net.UriinsertInternal(android.net.Uri url, android.content.ContentValues initialValues)

        Uri resultUri = null;
        long rowID;

        final SQLiteDatabase db = getDatabase();
        int match = sURIMatcher.match(url);
        switch (match) {
            case PEOPLE_GROUPMEMBERSHIP:
            case GROUPMEMBERSHIP: {
                mValues.clear();
                mValues.putAll(initialValues);
                if (match == PEOPLE_GROUPMEMBERSHIP) {
                    mValues.put(GroupMembership.PERSON_ID,
                            Long.valueOf(url.getPathSegments().get(1)));
                }
                resultUri = insertIntoGroupmembership(mValues);
            }
            break;

            case PEOPLE_OWNER:
                return insertOwner(initialValues);

            case PEOPLE_EXTENSIONS:
            case EXTENSIONS: {
                ContentValues newMap = new ContentValues(initialValues);
                if (match == PEOPLE_EXTENSIONS) {
                    newMap.put(Extensions.PERSON_ID,
                            Long.valueOf(url.getPathSegments().get(1)));
                }
                rowID = mExtensionsInserter.insert(newMap);
                if (rowID > 0) {
                    resultUri = ContentUris.withAppendedId(Extensions.CONTENT_URI, rowID);
                }
            }
            break;

            case PHOTOS: {
                if (!isTemporary()) {
                    throw new UnsupportedOperationException();
                }
                rowID = mPhotosInserter.insert(initialValues);
                if (rowID > 0) {
                    resultUri = ContentUris.withAppendedId(Photos.CONTENT_URI, rowID);
                }
            }
            break;

            case GROUPS: {
                ContentValues newMap = new ContentValues(initialValues);
                ensureSyncAccountIsSet(newMap);
                newMap.put(Groups._SYNC_DIRTY, 1);
                // Insert into the groups table
                rowID = mGroupsInserter.insert(newMap);
                if (rowID > 0) {
                    resultUri = ContentUris.withAppendedId(Groups.CONTENT_URI, rowID);
                    if (!isTemporary() && newMap.containsKey(Groups.SHOULD_SYNC)) {
                        final String account = newMap.getAsString(Groups._SYNC_ACCOUNT);
                        if (!TextUtils.isEmpty(account)) {
                            final ContentResolver cr = getContext().getContentResolver();
                            onLocalChangesForAccount(cr, account, false);
                        }
                    }
                }
            }
            break;

            case PEOPLE_RAW:
            case PEOPLE: {
                mValues.clear();
                mValues.putAll(initialValues);
                ensureSyncAccountIsSet(mValues);
                mValues.put(People._SYNC_DIRTY, 1);
                // Insert into the people table
                rowID = mPeopleInserter.insert(mValues);
                if (rowID > 0) {
                    resultUri = ContentUris.withAppendedId(People.CONTENT_URI, rowID);
                    if (!isTemporary()) {
                        String account = mValues.getAsString(People._SYNC_ACCOUNT);
                        Long starredValue = mValues.getAsLong(People.STARRED);
                        final String syncId = mValues.getAsString(People._SYNC_ID);
                        boolean isStarred = starredValue != null && starredValue != 0;
                        fixupGroupMembershipAfterPeopleUpdate(account, rowID, isStarred);
                        // create a photo row for this person
                        mDb.delete(sPhotosTable, "person=" + rowID, null);
                        mValues.clear();
                        mValues.put(Photos.PERSON_ID, rowID);
                        mValues.put(Photos._SYNC_ACCOUNT, account);
                        mValues.put(Photos._SYNC_ID, syncId);
                        mValues.put(Photos._SYNC_DIRTY, 0);
                        mPhotosInserter.insert(mValues);
                    }
                }
            }
            break;

            case DELETED_PEOPLE: {
                if (isTemporary()) {
                    // Insert into the people table
                    rowID = db.insert("_deleted_people", "_sync_id", initialValues);
                    if (rowID > 0) {
                        resultUri = Uri.parse("content://contacts/_deleted_people/" + rowID);
                    }
                } else {
                    throw new UnsupportedOperationException();
                }
            }
            break;

            case DELETED_GROUPS: {
                if (isTemporary()) {
                    rowID = db.insert(sDeletedGroupsTable, Groups._SYNC_ID,
                            initialValues);
                    if (rowID > 0) {
                        resultUri =ContentUris.withAppendedId(
                                Groups.DELETED_CONTENT_URI, rowID);
                    }
                } else {
                    throw new UnsupportedOperationException();
                }
            }
            break;

            case PEOPLE_PHONES:
            case PHONES: {
                mValues.clear();
                mValues.putAll(initialValues);
                if (match == PEOPLE_PHONES) {
                    mValues.put(Contacts.Phones.PERSON_ID,
                            Long.valueOf(url.getPathSegments().get(1)));
                }
                String number = mValues.getAsString(Contacts.Phones.NUMBER);
                if (number != null) {
                    mValues.put("number_key", PhoneNumberUtils.getStrippedReversed(number));
                }

                rowID = insertAndFixupPrimary(Contacts.KIND_PHONE, mValues);
                resultUri = ContentUris.withAppendedId(Phones.CONTENT_URI, rowID);
            }
            break;

            case CONTACTMETHODS:
            case PEOPLE_CONTACTMETHODS: {
                mValues.clear();
                mValues.putAll(initialValues);
                if (match == PEOPLE_CONTACTMETHODS) {
                    mValues.put("person", url.getPathSegments().get(1));
                }
                Integer kind = mValues.getAsInteger(ContactMethods.KIND);
                if (kind == null) {
                    throw new IllegalArgumentException("you must specify the ContactMethods.KIND");
                }
                rowID = insertAndFixupPrimary(kind, mValues);
                if (rowID > 0) {
                    resultUri = ContentUris.withAppendedId(ContactMethods.CONTENT_URI, rowID);
                }
            }
            break;

            case CALLS: {
                rowID = mCallsInserter.insert(initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse("content://call_log/calls/" + rowID);
                }
            }
            break;

            case PRESENCE: {
                final String handle = initialValues.getAsString(Presence.IM_HANDLE);
                final String protocol = initialValues.getAsString(Presence.IM_PROTOCOL);
                if (TextUtils.isEmpty(handle) || TextUtils.isEmpty(protocol)) {
                    throw new IllegalArgumentException("IM_PROTOCOL and IM_HANDLE are required");
                }

                // Look for the contact for this presence update
                StringBuilder query = new StringBuilder("SELECT ");
                query.append(ContactMethods.PERSON_ID);
                query.append(" FROM contact_methods WHERE (kind=");
                query.append(Contacts.KIND_IM);
                query.append(" AND ");
                query.append(ContactMethods.DATA);
                query.append("=? AND ");
                query.append(ContactMethods.AUX_DATA);
                query.append("=?)");

                String[] selectionArgs;
                if (GTALK_PROTOCOL_STRING.equals(protocol)) {
                    // For gtalk accounts we usually don't have an explicit IM
                    // entry, so also look for the email address as well
                    query.append(" OR (");
                    query.append("kind=");
                    query.append(Contacts.KIND_EMAIL);
                    query.append(" AND ");
                    query.append(ContactMethods.DATA);
                    query.append("=?)");
                    selectionArgs = new String[] { handle, protocol, handle };
                } else {
                    selectionArgs = new String[] { handle, protocol };
                }

                Cursor c = db.rawQueryWithFactory(null, query.toString(), selectionArgs, null);

                long personId = 0;
                try {
                    if (c.moveToFirst()) {
                        personId = c.getLong(0);
                    } else {
                        // No contact found, return a null URI
                        return null;
                    }
                } finally {
                    c.close();
                }

                mValues.clear();
                mValues.putAll(initialValues);
                mValues.put(Presence.PERSON_ID, personId);

                // Insert the presence update
                rowID = db.replace("presence", null, mValues);
                if (rowID > 0) {
                    resultUri = Uri.parse("content://contacts/presence/" + rowID);
                }
            }
            break;

            case PEOPLE_ORGANIZATIONS:
            case ORGANIZATIONS: {
                ContentValues newMap = new ContentValues(initialValues);
                if (match == PEOPLE_ORGANIZATIONS) {
                    newMap.put(Contacts.Phones.PERSON_ID,
                            Long.valueOf(url.getPathSegments().get(1)));
                }
                rowID = insertAndFixupPrimary(Contacts.KIND_ORGANIZATION, newMap);
                if (rowID > 0) {
                    resultUri = Uri.parse("content://contacts/organizations/" + rowID);
                }
            }
            break;
            default:
                throw new UnsupportedOperationException("Cannot insert into URL: " + url);
        }

        return resultUri;
    
private android.net.UriinsertIntoGroupmembership(android.content.ContentValues values)

        String groupSyncAccount = values.getAsString(GroupMembership.GROUP_SYNC_ACCOUNT);
        String groupSyncId = values.getAsString(GroupMembership.GROUP_SYNC_ID);
        final Long personId = values.getAsLong(GroupMembership.PERSON_ID);
        if (!values.containsKey(GroupMembership.GROUP_ID)) {
            if (TextUtils.isEmpty(groupSyncAccount) || TextUtils.isEmpty(groupSyncId)) {
                throw new IllegalArgumentException(
                        "insertIntoGroupmembership: no GROUP_ID wasn't specified and non-empty "
                        + "GROUP_SYNC_ID and GROUP_SYNC_ACCOUNT fields weren't specifid, "
                        + values);
            }
            if (0 != DatabaseUtils.longForQuery(getDatabase(), ""
                    + "SELECT COUNT(*) "
                    + "FROM groupmembership "
                    + "WHERE group_sync_id=? AND person=?",
                    new String[]{groupSyncId, String.valueOf(personId)})) {
                final String errorMessage =
                        "insertIntoGroupmembership: a row with this server key already exists, "
                                + values;
                if (Config.LOGD) Log.d(TAG, errorMessage);
                return null;
            }
        } else {
            long groupId = values.getAsLong(GroupMembership.GROUP_ID);
            if (!TextUtils.isEmpty(groupSyncAccount) || !TextUtils.isEmpty(groupSyncId)) {
                throw new IllegalArgumentException(
                        "insertIntoGroupmembership: GROUP_ID was specified but "
                        + "GROUP_SYNC_ID and GROUP_SYNC_ACCOUNT fields were also specifid, "
                        + values);
            }
            if (0 != DatabaseUtils.longForQuery(getDatabase(),
                    "SELECT COUNT(*) FROM groupmembership where group_id=? AND person=?",
                    new String[]{String.valueOf(groupId), String.valueOf(personId)})) {
                final String errorMessage =
                        "insertIntoGroupmembership: a row with this local key already exists, "
                                + values;
                if (Config.LOGD) Log.d(TAG, errorMessage);
                return null;
            }
        }

        long rowId = mGroupMembershipInserter.insert(values);
        if (rowId <= 0) {
            final String errorMessage = "insertIntoGroupmembership: the insert failed, values are "
                    + values;
            if (Config.LOGD) Log.d(TAG, errorMessage);
            return null;
        }

        // set the STARRED column in the people row if this group is the GROUP_ANDROID_STARRED
        if (!isTemporary() && queryGroupMembershipContainsStarred(personId)) {
            fixupPeopleStarred(personId, true);
        }

        return ContentUris.withAppendedId(GroupMembership.CONTENT_URI, rowId);
    
private android.net.UriinsertOwner(android.content.ContentValues values)

        // Check the permissions
        getContext().enforceCallingPermission("android.permission.WRITE_OWNER_DATA",
                "No permission to set owner info");

        // Insert the owner info
        Uri uri = insertInternal(People.CONTENT_URI, values);

        // Record which person is the owner
        long id = ContentUris.parseId(uri);
        SharedPreferences.Editor prefs = getContext().getSharedPreferences(PREFS_NAME_OWNER,
                Context.MODE_PRIVATE).edit();
        prefs.putLong(PREF_OWNER_ID, id);
        prefs.commit();
        return uri;
    
private DatabaseUtils.InsertHelperkindToInserter(int kind)

        switch (kind) {
            case Contacts.KIND_EMAIL: return mContactMethodsInserter;
            case Contacts.KIND_POSTAL: return mContactMethodsInserter;
            case Contacts.KIND_IM: return mContactMethodsInserter;
            case Contacts.KIND_PHONE: return mPhonesInserter;
            case Contacts.KIND_ORGANIZATION: return mOrganizationsInserter;
            default: throw new IllegalArgumentException("unknown kind, " + kind);
        }
    
private java.lang.StringkindToTable(int kind)

        switch (kind) {
            case Contacts.KIND_EMAIL: return sContactMethodsTable;
            case Contacts.KIND_POSTAL: return sContactMethodsTable;
            case Contacts.KIND_IM: return sContactMethodsTable;
            case Contacts.KIND_PHONE: return sPhonesTable;
            case Contacts.KIND_ORGANIZATION: return sOrganizationsTable;
            default: throw new IllegalArgumentException("unknown kind, " + kind);
        }
    
private com.android.providers.contacts.ContactsProvider$IsPrimaryInfolookupIsPrimaryInfo(java.lang.String table, java.lang.String[] projection, java.lang.String where, java.lang.String[] whereArgs)
Queries the table to determine the state of the row's isprimary column and the kind. The where and whereArgs must be sufficient to match either 0 or 1 row.

param
table the table of rows to consider, supports "phones" and "contact_methods"
param
projection the projection to use to get the columns that pertain to table
param
where used in conjunction with the whereArgs to identify the row
param
where used in conjunction with the where string to identify the row
return
the IsPrimaryInfo about the matched row, or null if no row was matched

        Cursor cursor = getDatabase().query(table, projection, where, whereArgs, null, null, null);
        try {
            if (!(cursor.getCount() <= 1)) {
                throw new IllegalArgumentException("expected only zero or one rows, got "
                        + DatabaseUtils.dumpCursorToString(cursor));
            }
            if (!cursor.moveToFirst()) return null;
            IsPrimaryInfo info = new IsPrimaryInfo();
            info.isPrimary = cursor.getInt(0) != 0;
            info.person = cursor.getLong(1);
            info.id = cursor.getLong(2);
            if (projection == sIsPrimaryProjectionWithKind) {
                info.kind = cursor.getInt(3);
            } else {
                if (sPhonesTable.equals(table)) {
                    info.kind = Contacts.KIND_PHONE;
                } else if (sOrganizationsTable.equals(table)) {
                    info.kind = Contacts.KIND_ORGANIZATION;
                } else {
                    throw new IllegalArgumentException("unexpected table, " + table);
                }
            }
            return info;
        } finally {
            cursor.close();
        }
    
private longlookupPerson(java.lang.String table, long id)
Queries table to find the value of the person column for the row with _id. There must be exactly one row that matches this id.

param
table the table to query
param
id the id of the row to query
return
the value of the person column for the specified row, returned as a String.

        return DatabaseUtils.longForQuery(
                getDatabase(),
                "SELECT person FROM " + table + " where _id=" + id,
                null);
    
private voidmaybeCreatePresenceTable(android.database.sqlite.SQLiteDatabase db)

        // Load the presence table from the presence_db. Just create the table
        // if we are
        String cpDbName;
        if (!isTemporary()) {
            db.execSQL("ATTACH DATABASE ':memory:' AS presence_db;");
            cpDbName = "presence_db.";
        } else {
            cpDbName = "";
        }
        db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + "presence ("+
                    Presence._ID + " INTEGER PRIMARY KEY," +
                    Presence.PERSON_ID + " INTEGER REFERENCES people(_id)," +
                    Presence.IM_PROTOCOL + " TEXT," +
                    Presence.IM_HANDLE + " TEXT," +
                    Presence.IM_ACCOUNT + " TEXT," +
                    Presence.PRESENCE_STATUS + " INTEGER," +
                    Presence.PRESENCE_CUSTOM_STATUS + " TEXT," +
                    "UNIQUE(" + Presence.IM_PROTOCOL + ", " + Presence.IM_HANDLE + ", "
                            + Presence.IM_ACCOUNT + ")" +
                    ");");

        db.execSQL("CREATE INDEX IF NOT EXISTS " + cpDbName + "presenceIndex ON presence ("
                + Presence.PERSON_ID + ");");
    
protected voidonAccountsChanged(java.lang.String[] accountsArray)

        super.onAccountsChanged(accountsArray);
        synchronized (mAccountsLock) {
            mAccounts = new String[accountsArray.length];
            System.arraycopy(accountsArray, 0, mAccounts, 0, mAccounts.length);
        }
    
protected voidonDatabaseOpened(android.database.sqlite.SQLiteDatabase db)

        maybeCreatePresenceTable(db);

        // Mark all the tables as syncable
        db.markTableSyncable(sPeopleTable, sDeletedPeopleTable);
        db.markTableSyncable(sPhonesTable, Phones.PERSON_ID, sPeopleTable);
        db.markTableSyncable(sContactMethodsTable, ContactMethods.PERSON_ID, sPeopleTable);
        db.markTableSyncable(sOrganizationsTable, Organizations.PERSON_ID, sPeopleTable);
        db.markTableSyncable(sGroupmembershipTable, GroupMembership.PERSON_ID, sPeopleTable);
        db.markTableSyncable(sExtensionsTable, Extensions.PERSON_ID, sPeopleTable);
        db.markTableSyncable(sGroupsTable, sDeletedGroupsTable);

        mDeletedPeopleInserter = new DatabaseUtils.InsertHelper(db, sDeletedPeopleTable);
        mPeopleInserter = new DatabaseUtils.InsertHelper(db, sPeopleTable);
        mIndexPeopleSyncId = mPeopleInserter.getColumnIndex(People._SYNC_ID);
        mIndexPeopleSyncTime = mPeopleInserter.getColumnIndex(People._SYNC_TIME);
        mIndexPeopleSyncVersion = mPeopleInserter.getColumnIndex(People._SYNC_VERSION);
        mIndexPeopleSyncDirty = mPeopleInserter.getColumnIndex(People._SYNC_DIRTY);
        mIndexPeopleSyncAccount = mPeopleInserter.getColumnIndex(People._SYNC_ACCOUNT);
        mIndexPeopleName = mPeopleInserter.getColumnIndex(People.NAME);
        mIndexPeoplePhoneticName = mPeopleInserter.getColumnIndex(People.PHONETIC_NAME);
        mIndexPeopleNotes = mPeopleInserter.getColumnIndex(People.NOTES);

        mGroupsInserter = new DatabaseUtils.InsertHelper(db, sGroupsTable);

        mPhotosInserter = new DatabaseUtils.InsertHelper(db, sPhotosTable);
        mIndexPhotosPersonId = mPhotosInserter.getColumnIndex(Photos.PERSON_ID);
        mIndexPhotosSyncId = mPhotosInserter.getColumnIndex(Photos._SYNC_ID);
        mIndexPhotosSyncTime = mPhotosInserter.getColumnIndex(Photos._SYNC_TIME);
        mIndexPhotosSyncVersion = mPhotosInserter.getColumnIndex(Photos._SYNC_VERSION);
        mIndexPhotosSyncDirty = mPhotosInserter.getColumnIndex(Photos._SYNC_DIRTY);
        mIndexPhotosSyncAccount = mPhotosInserter.getColumnIndex(Photos._SYNC_ACCOUNT);
        mIndexPhotosSyncError = mPhotosInserter.getColumnIndex(Photos.SYNC_ERROR);
        mIndexPhotosExistsOnServer = mPhotosInserter.getColumnIndex(Photos.EXISTS_ON_SERVER);

        mContactMethodsInserter = new DatabaseUtils.InsertHelper(db, sContactMethodsTable);
        mIndexContactMethodsPersonId = mContactMethodsInserter.getColumnIndex(ContactMethods.PERSON_ID);
        mIndexContactMethodsLabel = mContactMethodsInserter.getColumnIndex(ContactMethods.LABEL);
        mIndexContactMethodsKind = mContactMethodsInserter.getColumnIndex(ContactMethods.KIND);
        mIndexContactMethodsType = mContactMethodsInserter.getColumnIndex(ContactMethods.TYPE);
        mIndexContactMethodsData = mContactMethodsInserter.getColumnIndex(ContactMethods.DATA);
        mIndexContactMethodsAuxData = mContactMethodsInserter.getColumnIndex(ContactMethods.AUX_DATA);
        mIndexContactMethodsIsPrimary = mContactMethodsInserter.getColumnIndex(ContactMethods.ISPRIMARY);

        mOrganizationsInserter = new DatabaseUtils.InsertHelper(db, sOrganizationsTable);
        mIndexOrganizationsPersonId = mOrganizationsInserter.getColumnIndex(Organizations.PERSON_ID);
        mIndexOrganizationsLabel = mOrganizationsInserter.getColumnIndex(Organizations.LABEL);
        mIndexOrganizationsType = mOrganizationsInserter.getColumnIndex(Organizations.TYPE);
        mIndexOrganizationsCompany = mOrganizationsInserter.getColumnIndex(Organizations.COMPANY);
        mIndexOrganizationsTitle = mOrganizationsInserter.getColumnIndex(Organizations.TITLE);
        mIndexOrganizationsIsPrimary = mOrganizationsInserter.getColumnIndex(Organizations.ISPRIMARY);

        mExtensionsInserter = new DatabaseUtils.InsertHelper(db, sExtensionsTable);
        mIndexExtensionsPersonId = mExtensionsInserter.getColumnIndex(Extensions.PERSON_ID);
        mIndexExtensionsName = mExtensionsInserter.getColumnIndex(Extensions.NAME);
        mIndexExtensionsValue = mExtensionsInserter.getColumnIndex(Extensions.VALUE);

        mGroupMembershipInserter = new DatabaseUtils.InsertHelper(db, sGroupmembershipTable);
        mIndexGroupMembershipPersonId = mGroupMembershipInserter.getColumnIndex(GroupMembership.PERSON_ID);
        mIndexGroupMembershipGroupSyncAccount = mGroupMembershipInserter.getColumnIndex(GroupMembership.GROUP_SYNC_ACCOUNT);
        mIndexGroupMembershipGroupSyncId = mGroupMembershipInserter.getColumnIndex(GroupMembership.GROUP_SYNC_ID);

        mCallsInserter = new DatabaseUtils.InsertHelper(db, sCallsTable);

        mPhonesInserter = new DatabaseUtils.InsertHelper(db, sPhonesTable);
        mIndexPhonesPersonId = mPhonesInserter.getColumnIndex(Phones.PERSON_ID);
        mIndexPhonesLabel = mPhonesInserter.getColumnIndex(Phones.LABEL);
        mIndexPhonesType = mPhonesInserter.getColumnIndex(Phones.TYPE);
        mIndexPhonesNumber = mPhonesInserter.getColumnIndex(Phones.NUMBER);
        mIndexPhonesNumberKey = mPhonesInserter.getColumnIndex(Phones.NUMBER_KEY);
        mIndexPhonesIsPrimary = mPhonesInserter.getColumnIndex(Phones.ISPRIMARY);
    
protected voidonLocalChangesForAccount(android.content.ContentResolver resolver, java.lang.String account, boolean groupsModified)
Called when local changes are made, so subclasses have an opportunity to react as they see fit.

param
resolver the content resolver to use
param
account the account the changes are tied to

        // Do nothing
    
public android.os.ParcelFileDescriptoropenFile(android.net.Uri uri, java.lang.String mode)

        int match = sURIMatcher.match(uri);
        switch (match) {
            default:
                throw new UnsupportedOperationException(uri.toString());
        }
    
private android.content.ContentValuesqueryAndroidStarredGroupId(java.lang.String account)

        String whereString;
        String[] whereArgs;
        if (!TextUtils.isEmpty(account)) {
            whereString = "_sync_account=? AND name=?";
            whereArgs = new String[]{account, Groups.GROUP_ANDROID_STARRED};
        } else {
            whereString = "_sync_account is null AND name=?";
            whereArgs = new String[]{Groups.GROUP_ANDROID_STARRED};
        }
        Cursor cursor = getDatabase().query(sGroupsTable,
                new String[]{Groups._ID, Groups._SYNC_ID, Groups._SYNC_ACCOUNT},
                whereString, whereArgs, null, null, null);
        try {
            if (cursor.moveToNext()) {
                ContentValues result = new ContentValues();
                result.put(Groups._ID, cursor.getLong(0));
                result.put(Groups._SYNC_ID, cursor.getString(1));
                result.put(Groups._SYNC_ACCOUNT, cursor.getString(2));
                return result;
            }
            return null;
        } finally {
            cursor.close();
        }
    
private booleanqueryGroupMembershipContainsStarred(long personId)

        // TODO: Part 1 of 2 part hack to work around a bug in reusing SQLiteStatements
        SQLiteStatement mGroupsMembershipQuery = null;

        if (mGroupsMembershipQuery == null) {
            String query =
                "SELECT COUNT(*) FROM groups, groupmembership WHERE "
                + sGroupsJoinString + " AND person=? AND groups.name=?";
            mGroupsMembershipQuery = getDatabase().compileStatement(query);
        }
        long result = DatabaseUtils.longForQuery(mGroupsMembershipQuery,
                new String[]{String.valueOf(personId), Groups.GROUP_ANDROID_STARRED});

        // TODO: Part 2 of 2 part hack to work around a bug in reusing SQLiteStatements
        mGroupsMembershipQuery.close();

        return result != 0;
    
public android.database.CursorqueryInternal(android.net.Uri url, java.lang.String[] projectionIn, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sort)


        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        Uri notificationUri = Contacts.CONTENT_URI;
        StringBuilder whereClause;
        String groupBy = null;

        // Generate the body of the query
        int match = sURIMatcher.match(url);

        if (Config.LOGV) Log.v(TAG, "ContactsProvider.query: url=" + url + ", match is " + match);

        switch (match) {
            case DELETED_GROUPS:
                if (!isTemporary()) {
                    throw new UnsupportedOperationException();
                }

                qb.setTables(sDeletedGroupsTable);
                break;

            case GROUPS_ID:
                qb.appendWhere("_id=");
                qb.appendWhere(url.getPathSegments().get(1));
                // fall through
            case GROUPS:
                qb.setTables(sGroupsTable);
                qb.setProjectionMap(sGroupsProjectionMap);
                break;

            case SETTINGS:
                qb.setTables(sSettingsTable);
                break;

            case PEOPLE_GROUPMEMBERSHIP_ID:
                qb.appendWhere("groupmembership._id=");
                qb.appendWhere(url.getPathSegments().get(3));
                qb.appendWhere(" AND ");
                // fall through
            case PEOPLE_GROUPMEMBERSHIP:
                qb.appendWhere(sGroupsJoinString + " AND ");
                qb.appendWhere("person=" + url.getPathSegments().get(1));
                qb.setTables("groups, groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                break;

            case GROUPMEMBERSHIP_ID:
                qb.appendWhere("groupmembership._id=");
                qb.appendWhere(url.getPathSegments().get(1));
                qb.appendWhere(" AND ");
                // fall through
            case GROUPMEMBERSHIP:
                qb.setTables("groups, groupmembership");
                qb.setProjectionMap(sGroupMembershipProjectionMap);
                qb.appendWhere(sGroupsJoinString);
                break;

            case GROUPMEMBERSHIP_RAW:
                qb.setTables("groupmembership");
                break;

            case GROUP_NAME_MEMBERS_FILTER:
                if (url.getPathSegments().size() > 5) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                    qb.appendWhere(" AND ");
                }
                // fall through
            case GROUP_NAME_MEMBERS:
                qb.setTables(PEOPLE_PHONES_JOIN);
                qb.setProjectionMap(sPeopleProjectionMap);
                qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " +
                        "ON (group_id=groups._id OR " +
                        "(group_sync_id = groups._sync_id AND " +
                            "group_sync_account = groups._sync_account)) "+
                        "WHERE " + Groups.NAME + "="
                        + DatabaseUtils.sqlEscapeString(url.getPathSegments().get(2)) + ")");
                break;
                
            case GROUP_SYSTEM_ID_MEMBERS_FILTER:
                if (url.getPathSegments().size() > 5) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                    qb.appendWhere(" AND ");
                }
                // fall through
            case GROUP_SYSTEM_ID_MEMBERS:
                qb.setTables(PEOPLE_PHONES_JOIN);
                qb.setProjectionMap(sPeopleProjectionMap);
                qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " +
                        "ON (group_id=groups._id OR " +
                        "(group_sync_id = groups._sync_id AND " +
                            "group_sync_account = groups._sync_account)) "+
                        "WHERE " + Groups.SYSTEM_ID + "="
                        + DatabaseUtils.sqlEscapeString(url.getPathSegments().get(2)) + ")");
                break;

            case PEOPLE:
                qb.setTables(PEOPLE_PHONES_JOIN);
                qb.setProjectionMap(sPeopleProjectionMap);
                break;
            case PEOPLE_RAW:
                qb.setTables(sPeopleTable);
                break;

            case PEOPLE_OWNER:
                return queryOwner(projectionIn);

            case PEOPLE_WITH_PHONES_FILTER:

                qb.appendWhere("number IS NOT NULL AND ");

                // Fall through.

            case PEOPLE_FILTER: {
                qb.setTables(PEOPLE_PHONES_JOIN);
                qb.setProjectionMap(sPeopleProjectionMap);
                if (url.getPathSegments().size() > 2) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                }
                break;
            }

            case PEOPLE_WITH_EMAIL_OR_IM_FILTER:
                String email = url.getPathSegments().get(2);
                whereClause = new StringBuilder();
                
                // Match any E-mail or IM contact methods where data exactly
                // matches the provided string.
                whereClause.append(ContactMethods.DATA);
                whereClause.append("=");
                DatabaseUtils.appendEscapedSQLString(whereClause, email);
                whereClause.append(" AND (kind = " + Contacts.KIND_EMAIL +
                        " OR kind = " + Contacts.KIND_IM + ")");
                qb.appendWhere(whereClause.toString());
                
                qb.setTables("people INNER JOIN contact_methods on (people._id = contact_methods.person)");
                qb.setProjectionMap(sPeopleWithEmailOrImProjectionMap);
                
                // Prevent returning the same person for multiple matches
                groupBy = "contact_methods.person";

                qb.setDistinct(true);
                break;
                
            case PHOTOS_ID:
                qb.appendWhere("_id="+url.getPathSegments().get(1));
                // Fall through.
            case PHOTOS:
                qb.setTables(sPhotosTable);
                qb.setProjectionMap(sPhotosProjectionMap);
                break;

            case PEOPLE_PHOTO:
                qb.appendWhere("person="+url.getPathSegments().get(1));
                qb.setTables(sPhotosTable);
                qb.setProjectionMap(sPhotosProjectionMap);
                break;

            case SEARCH_SUGGESTIONS: {
                // Force the default sort order, since the SearchManage doesn't ask for things
                // sorted, though they should be
                if (sort != null && !People.DEFAULT_SORT_ORDER.equals(sort)) {
                    throw new IllegalArgumentException("Sort ordering not allowed for this URI");
                }
                sort = SearchManager.SUGGEST_COLUMN_TEXT_1 + " COLLATE LOCALIZED ASC";

                // This will either setup the query builder so we can run the proper query below
                // and return null, or it will return a cursor with the results already in it.
                Cursor c = handleSearchSuggestionsQuery(url, qb);
                if (c != null) {
                    return c;
                }
                break;
            }
            case PEOPLE_STREQUENT: {
                // Build the first query for starred
                qb.setTables(PEOPLE_PHONES_PHOTOS_JOIN);
                qb.setProjectionMap(sStrequentStarredProjectionMap);
                final String starredQuery = qb.buildQuery(projectionIn, "starred = 1",
                        null, null, null, null,
                        null /* limit */);

                // Build the second query for frequent
                qb = new SQLiteQueryBuilder();
                qb.setTables(PEOPLE_PHONES_PHOTOS_JOIN);
                qb.setProjectionMap(sPeopleWithPhotoProjectionMap);
                final String frequentQuery = qb.buildQuery(projectionIn,
                        "times_contacted > 0 AND starred = 0", null, null, null, null, null);

                // Put them together
                final String query = qb.buildUnionQuery(new String[] {starredQuery, frequentQuery},
                        STREQUENT_ORDER_BY, STREQUENT_LIMIT);
                final SQLiteDatabase db = getDatabase();
                Cursor c = db.rawQueryWithFactory(null, query, null, sPeopleTable);
                if ((c != null) && !isTemporary()) {
                    c.setNotificationUri(getContext().getContentResolver(), notificationUri);
                }
                return c;
            }
            case PEOPLE_STREQUENT_FILTER: {
                // Build the first query for starred
                qb.setTables(PEOPLE_PHONES_PHOTOS_JOIN);
                qb.setProjectionMap(sStrequentStarredProjectionMap);
                if (url.getPathSegments().size() > 3) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                }
                final String starredQuery = qb.buildQuery(projectionIn, "starred = 1",
                        null, null, null, null,
                        null /* limit */);

                // Build the second query for frequent
                qb = new SQLiteQueryBuilder();
                qb.setTables(PEOPLE_PHONES_PHOTOS_JOIN);
                qb.setProjectionMap(sPeopleWithPhotoProjectionMap);
                if (url.getPathSegments().size() > 3) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                }
                final String frequentQuery = qb.buildQuery(projectionIn,
                        "times_contacted > 0 AND starred = 0", null, null, null, null, null);

                // Put them together
                final String query = qb.buildUnionQuery(new String[] {starredQuery, frequentQuery},
                        STREQUENT_ORDER_BY, null);
                final SQLiteDatabase db = getDatabase();
                Cursor c = db.rawQueryWithFactory(null, query, null, sPeopleTable);
                if ((c != null) && !isTemporary()) {
                    c.setNotificationUri(getContext().getContentResolver(), notificationUri);
                }
                return c;
            }
            case DELETED_PEOPLE:
                if (isTemporary()) {
                    qb.setTables("_deleted_people");
                    break;
                }
                throw new UnsupportedOperationException();
            case PEOPLE_ID:
                qb.setTables("people LEFT OUTER JOIN phones ON people.primary_phone=phones._id "
                        + "LEFT OUTER JOIN presence ON (presence." + Presence.PERSON_ID
                        + "=people._id)");
                qb.setProjectionMap(sPeopleProjectionMap);
                qb.appendWhere("people._id=");
                qb.appendWhere(url.getPathSegments().get(1));
                break;
            case PEOPLE_PHONES:
                qb.setTables("phones, people");
                qb.setProjectionMap(sPhonesProjectionMap);
                qb.appendWhere("people._id = phones.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                break;
            case PEOPLE_PHONES_ID:
                qb.setTables("phones, people");
                qb.setProjectionMap(sPhonesProjectionMap);
                qb.appendWhere("people._id = phones.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                qb.appendWhere(" AND phones._id=");
                qb.appendWhere(url.getPathSegments().get(3));
                break;

            case PEOPLE_PHONES_WITH_PRESENCE:
                qb.appendWhere("people._id=?");
                selectionArgs = appendSelectionArg(selectionArgs, url.getPathSegments().get(1));
                // Fall through.

            case PHONES_WITH_PRESENCE:
                qb.setTables("phones JOIN people ON (phones.person = people._id)"
                        + " LEFT OUTER JOIN presence ON (presence.person = people._id)");
                qb.setProjectionMap(sPhonesWithPresenceProjectionMap);
                break;

            case PEOPLE_CONTACTMETHODS:
                qb.setTables("contact_methods, people");
                qb.setProjectionMap(sContactMethodsProjectionMap);
                qb.appendWhere("people._id = contact_methods.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                break;
            case PEOPLE_CONTACTMETHODS_ID:
                qb.setTables("contact_methods, people");
                qb.setProjectionMap(sContactMethodsProjectionMap);
                qb.appendWhere("people._id = contact_methods.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                qb.appendWhere(" AND contact_methods._id=");
                qb.appendWhere(url.getPathSegments().get(3));
                break;
            case PEOPLE_ORGANIZATIONS:
                qb.setTables("organizations, people");
                qb.setProjectionMap(sOrganizationsProjectionMap);
                qb.appendWhere("people._id = organizations.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                break;
            case PEOPLE_ORGANIZATIONS_ID:
                qb.setTables("organizations, people");
                qb.setProjectionMap(sOrganizationsProjectionMap);
                qb.appendWhere("people._id = organizations.person AND person=");
                qb.appendWhere(url.getPathSegments().get(1));
                qb.appendWhere(" AND organizations._id=");
                qb.appendWhere(url.getPathSegments().get(3));
                break;
            case PHONES:
                qb.setTables("phones, people");
                qb.appendWhere("people._id = phones.person");
                qb.setProjectionMap(sPhonesProjectionMap);
                break;
            case PHONES_ID:
                qb.setTables("phones, people");
                qb.appendWhere("people._id = phones.person AND phones._id="
                        + url.getPathSegments().get(1));
                qb.setProjectionMap(sPhonesProjectionMap);
                break;
            case ORGANIZATIONS:
                qb.setTables("organizations, people");
                qb.appendWhere("people._id = organizations.person");
                qb.setProjectionMap(sOrganizationsProjectionMap);
                break;
            case ORGANIZATIONS_ID:
                qb.setTables("organizations, people");
                qb.appendWhere("people._id = organizations.person AND organizations._id="
                        + url.getPathSegments().get(1));
                qb.setProjectionMap(sOrganizationsProjectionMap);
                break;
            case PHONES_MOBILE_FILTER_NAME:
                qb.appendWhere("type=" + Contacts.PhonesColumns.TYPE_MOBILE + " AND ");

                // Fall through.

            case PHONES_FILTER_NAME:
                qb.setTables("phones JOIN people ON (people._id = phones.person)");
                qb.setProjectionMap(sPhonesProjectionMap);
                if (url.getPathSegments().size() > 2) {
                    qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment()));
                }
                break;

            case PHONES_FILTER: {
                String phoneNumber = url.getPathSegments().get(2);
                String indexable = PhoneNumberUtils.toCallerIDMinMatch(phoneNumber);
                StringBuilder subQuery = new StringBuilder();
                if (TextUtils.isEmpty(sort)) {
                    // Default the sort order to something reasonable so we get consistent
                    // results when callers don't request an ordering
                    sort = People.DEFAULT_SORT_ORDER;
                }

                subQuery.append("people, (SELECT * FROM phones WHERE (phones.number_key GLOB '");
                subQuery.append(indexable);
                subQuery.append("*')) AS phones");
                qb.setTables(subQuery.toString());
                qb.appendWhere("phones.person=people._id AND PHONE_NUMBERS_EQUAL(phones.number, ");
                qb.appendWhereEscapeString(phoneNumber);
                qb.appendWhere(")");
                qb.setProjectionMap(sPhonesProjectionMap);
                break;
            }
            case CONTACTMETHODS:
                qb.setTables("contact_methods, people");
                qb.setProjectionMap(sContactMethodsProjectionMap);
                qb.appendWhere("people._id = contact_methods.person");
                break;
            case CONTACTMETHODS_ID:
                qb.setTables("contact_methods LEFT OUTER JOIN people ON contact_methods.person = people._id");
                qb.setProjectionMap(sContactMethodsProjectionMap);
                qb.appendWhere("contact_methods._id=");
                qb.appendWhere(url.getPathSegments().get(1));
                break;
            case CONTACTMETHODS_EMAIL_FILTER:
                String pattern = url.getPathSegments().get(2);
                whereClause = new StringBuilder();

                // TODO This is going to be REALLY slow.  Come up with
                // something faster.
                whereClause.append(ContactMethods.KIND);
                whereClause.append('=");
                whereClause.append('\'");
                whereClause.append(Contacts.KIND_EMAIL);
                whereClause.append("' AND (UPPER(");
                whereClause.append(ContactMethods.NAME);
                whereClause.append(") GLOB ");
                DatabaseUtils.appendEscapedSQLString(whereClause, pattern + "*");
                whereClause.append(" OR UPPER(");
                whereClause.append(ContactMethods.NAME);
                whereClause.append(") GLOB ");
                DatabaseUtils.appendEscapedSQLString(whereClause, "* " + pattern + "*");
                whereClause.append(") AND ");
                qb.appendWhere(whereClause.toString());

                // Fall through.

            case CONTACTMETHODS_EMAIL:
                qb.setTables("contact_methods INNER JOIN people on (contact_methods.person = people._id)");
                qb.setProjectionMap(sEmailSearchProjectionMap);
                qb.appendWhere("kind = " + Contacts.KIND_EMAIL);
                qb.setDistinct(true);
                break;

            case PEOPLE_CONTACTMETHODS_WITH_PRESENCE:
                qb.appendWhere("people._id=?");
                selectionArgs = appendSelectionArg(selectionArgs, url.getPathSegments().get(1));
                // Fall through.

            case CONTACTMETHODS_WITH_PRESENCE:
                qb.setTables("contact_methods JOIN people ON (contact_methods.person = people._id)"
                        + " LEFT OUTER JOIN presence ON "
                        // Match gtalk presence items
                        + "((kind=" + Contacts.KIND_EMAIL +
                            " AND im_protocol='"
                            + ContactMethods.encodePredefinedImProtocol(
                                    ContactMethods.PROTOCOL_GOOGLE_TALK)
                            + "' AND data=im_handle)"
                        + " OR "
                        // Match IM presence items
                        + "(kind=" + Contacts.KIND_IM
                            + " AND data=im_handle AND aux_data=im_protocol))");
                qb.setProjectionMap(sContactMethodsWithPresenceProjectionMap);
                break;

            case CALLS:
                qb.setTables("calls");
                qb.setProjectionMap(sCallsProjectionMap);
                notificationUri = CallLog.CONTENT_URI;
                break;
            case CALLS_ID:
                qb.setTables("calls");
                qb.setProjectionMap(sCallsProjectionMap);
                qb.appendWhere("calls._id=");
                qb.appendWhere(url.getPathSegments().get(1));
                notificationUri = CallLog.CONTENT_URI;
                break;
            case CALLS_FILTER: {
                qb.setTables("calls");
                qb.setProjectionMap(sCallsProjectionMap);

                String phoneNumber = url.getPathSegments().get(2);
                qb.appendWhere("PHONE_NUMBERS_EQUAL(number, ");
                qb.appendWhereEscapeString(phoneNumber);
                qb.appendWhere(")");
                notificationUri = CallLog.CONTENT_URI;
                break;
            }

            case PRESENCE:
                qb.setTables("presence LEFT OUTER JOIN people on (presence." + Presence.PERSON_ID
                        + "= people._id)");
                qb.setProjectionMap(sPresenceProjectionMap);
                break;
            case PRESENCE_ID:
                qb.setTables("presence LEFT OUTER JOIN people on (presence." + Presence.PERSON_ID
                        + "= people._id)");
                qb.appendWhere("presence._id=");
                qb.appendWhere(url.getLastPathSegment());
                break;
            case VOICE_DIALER_TIMESTAMP:
                qb.setTables("voice_dialer_timestamp");
                qb.appendWhere("_id=1");
                break;

            case PEOPLE_EXTENSIONS_ID:
                qb.appendWhere("extensions._id=" + url.getPathSegments().get(3) + " AND ");
                // fall through
            case PEOPLE_EXTENSIONS:
                qb.appendWhere("person=" + url.getPathSegments().get(1));
                qb.setTables(sExtensionsTable);
                qb.setProjectionMap(sExtensionsProjectionMap);
                break;

            case EXTENSIONS_ID:
                qb.appendWhere("extensions._id=" + url.getPathSegments().get(1));
                // fall through
            case EXTENSIONS:
                qb.setTables(sExtensionsTable);
                qb.setProjectionMap(sExtensionsProjectionMap);
                break;

            case LIVE_FOLDERS_PEOPLE:
                qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)");
                qb.setProjectionMap(sLiveFoldersProjectionMap);
                break;
                
            case LIVE_FOLDERS_PEOPLE_WITH_PHONES:
                qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)");
                qb.setProjectionMap(sLiveFoldersProjectionMap);
                qb.appendWhere(People.PRIMARY_PHONE_ID + " IS NOT NULL");
                break;

            case LIVE_FOLDERS_PEOPLE_FAVORITES:
                qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)");
                qb.setProjectionMap(sLiveFoldersProjectionMap);
                qb.appendWhere(People.STARRED + " <> 0");
                break;

            case LIVE_FOLDERS_PEOPLE_GROUP_NAME:
                qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)");
                qb.setProjectionMap(sLiveFoldersProjectionMap);
                qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " +
                        "ON (group_id=groups._id OR " +
                        "(group_sync_id = groups._sync_id AND " +
                            "group_sync_account = groups._sync_account)) "+
                        "WHERE " + Groups.NAME + "="
                        + DatabaseUtils.sqlEscapeString(url.getLastPathSegment()) + ")");
                break;

            default:
                throw new IllegalArgumentException("Unknown URL " + url);
        }

        // run the query
        final SQLiteDatabase db = getDatabase();
        Cursor c = qb.query(db, projectionIn, selection, selectionArgs,
                groupBy, null, sort);
        if ((c != null) && !isTemporary()) {
            c.setNotificationUri(getContext().getContentResolver(), notificationUri);
        }
        return c;
    
private android.database.CursorqueryOwner(java.lang.String[] projection)

        // Check the permissions
        getContext().enforceCallingPermission("android.permission.READ_OWNER_DATA",
                "No permission to access owner info");

        // Read the owner id
        SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME_OWNER,
                Context.MODE_PRIVATE);
        long ownerId = prefs.getLong(PREF_OWNER_ID, 0);

        // Run the query
        return queryInternal(ContentUris.withAppendedId(People.CONTENT_URI, ownerId), projection,
                null, null, null);
    
private voidsetIsPrimary(int kind, long personId, long itemId)

        final String table = kindToTable(kind);
        StringBuilder sb = new StringBuilder();
        sb.append("person=");
        sb.append(personId);

        if (sContactMethodsTable.equals(table)) {
            sb.append(" and ");
            sb.append(ContactMethods.KIND);
            sb.append("=");
            sb.append(kind);
        }

        final String where = sb.toString();
        getDatabase().execSQL(
                "UPDATE " + table + " SET isprimary=(_id=" + itemId + ") WHERE " + where);
    
private intupdateGroups(android.content.ContentValues values, java.lang.String where, java.lang.String[] whereArgs)

        for (Map.Entry<String, Object> entry : values.valueSet()) {
            final String column = entry.getKey();
            if (!Groups.NAME.equals(column) && !Groups.NOTES.equals(column)
                    && !Groups.SYSTEM_ID.equals(column) && !Groups.SHOULD_SYNC.equals(column)) {
                throw new IllegalArgumentException(
                        "you are not allowed to change column " + column);
            }
        }

        Set<String> modifiedAccounts = Sets.newHashSet();
        final SQLiteDatabase db = getDatabase();
        if (values.containsKey(Groups.NAME) || values.containsKey(Groups.SHOULD_SYNC)) {
            String newName = values.getAsString(Groups.NAME);
            Cursor cursor = db.query(sGroupsTable, null, where, whereArgs, null, null, null);
            try {
                final int indexName = cursor.getColumnIndexOrThrow(Groups.NAME);
                final int indexSyncAccount = cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT);
                final int indexSyncId = cursor.getColumnIndexOrThrow(Groups._SYNC_ID);
                final int indexId = cursor.getColumnIndexOrThrow(Groups._ID);
                while (cursor.moveToNext()) {
                    String syncAccount = cursor.getString(indexSyncAccount);
                    if (values.containsKey(Groups.NAME)) {
                        String oldName = cursor.getString(indexName);
                        String syncId = cursor.getString(indexSyncId);
                        long id = cursor.getLong(indexId);
                        fixupPeopleStarredOnGroupRename(oldName, newName, id);
                        if (!TextUtils.isEmpty(syncAccount) && !TextUtils.isEmpty(syncId)) {
                            fixupPeopleStarredOnGroupRename(oldName, newName, syncAccount, syncId);
                        }
                    }
                    if (!TextUtils.isEmpty(syncAccount) && values.containsKey(Groups.SHOULD_SYNC)) {
                        modifiedAccounts.add(syncAccount);
                    }
                }
            } finally {
                cursor.close();
            }
        }

        int numRows = db.update(sGroupsTable, values, where, whereArgs);
        if (numRows > 0) {
            if (!isTemporary()) {
                final ContentResolver cr = getContext().getContentResolver();
                for (String account : modifiedAccounts) {
                    onLocalChangesForAccount(cr, account, true);
                }
            }
        }
        return numRows;
    
public intupdateInternal(android.net.Uri url, android.content.ContentValues values, java.lang.String userWhere, java.lang.String[] whereArgs)

        final SQLiteDatabase db = getDatabase();
        String tableToChange;
        String changedItemId;
        final int matchedUriId = sURIMatcher.match(url);
        switch (matchedUriId) {
            case GROUPS_ID:
                changedItemId = url.getPathSegments().get(1);
                return updateGroups(values,
                        addIdToWhereClause(changedItemId, userWhere), whereArgs);

            case PEOPLE_EXTENSIONS_ID:
                tableToChange = sExtensionsTable;
                changedItemId = url.getPathSegments().get(3);
                break;

            case EXTENSIONS_ID:
                tableToChange = sExtensionsTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case PEOPLE_UPDATE_CONTACT_TIME:
                if (values.size() != 1 || !values.containsKey(People.LAST_TIME_CONTACTED)) {
                    throw new IllegalArgumentException(
                            "You may only use " + url + " to update People.LAST_TIME_CONTACTED");
                }
                tableToChange = sPeopleTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case PEOPLE_ID:
                mValues.clear();
                mValues.putAll(values);
                mValues.put(Photos._SYNC_DIRTY, 1);
                values = mValues;
                tableToChange = sPeopleTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case PEOPLE_PHONES_ID:
                tableToChange = sPhonesTable;
                changedItemId = url.getPathSegments().get(3);
                break;

            case PEOPLE_CONTACTMETHODS_ID:
                tableToChange = sContactMethodsTable;
                changedItemId = url.getPathSegments().get(3);
                break;

            case PHONES_ID:
                tableToChange = sPhonesTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case PEOPLE_PHOTO:
            case PHOTOS_ID:
                mValues.clear();
                mValues.putAll(values);

                // The _SYNC_DIRTY flag should only be set if the data was modified and if
                // it isn't already provided. 
                if (!mValues.containsKey(Photos._SYNC_DIRTY) && mValues.containsKey(Photos.DATA)) {
                    mValues.put(Photos._SYNC_DIRTY, 1);
                }
                StringBuilder where;
                if (matchedUriId == PEOPLE_PHOTO) {
                    where = new StringBuilder("_id=" + url.getPathSegments().get(1));
                } else {
                    where = new StringBuilder("person=" + url.getPathSegments().get(1));
                }
                if (!TextUtils.isEmpty(userWhere)) {
                    where.append(" AND (");
                    where.append(userWhere);
                    where.append(')");
                }
                return db.update(sPhotosTable, mValues, where.toString(), whereArgs);

            case ORGANIZATIONS_ID:
                tableToChange = sOrganizationsTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case CONTACTMETHODS_ID:
                tableToChange = sContactMethodsTable;
                changedItemId = url.getPathSegments().get(1);
                break;

            case SETTINGS:
                if (whereArgs != null) {
                    throw new IllegalArgumentException(
                            "you aren't allowed to specify where args when updating settings");
                }
                if (userWhere != null) {
                    throw new IllegalArgumentException(
                            "you aren't allowed to specify a where string when updating settings");
                }
                return updateSettings(values);

            case CALLS:
                tableToChange = "calls";
                changedItemId = null;
                break;

            case CALLS_ID:
                tableToChange = "calls";
                changedItemId = url.getPathSegments().get(1);
                break;

            default:
                throw new UnsupportedOperationException("Cannot update URL: " + url);
        }

        String where = addIdToWhereClause(changedItemId, userWhere);
        int numRowsUpdated = db.update(tableToChange, values, where, whereArgs);

        if (numRowsUpdated > 0 && changedItemId != null) {
            long itemId = Long.parseLong(changedItemId);
            switch (matchedUriId) {
                case ORGANIZATIONS_ID:
                    fixupPrimaryAfterUpdate(
                            Contacts.KIND_ORGANIZATION, null, itemId,
                            values.getAsInteger(Organizations.ISPRIMARY));
                    break;

                case PHONES_ID:
                case PEOPLE_PHONES_ID:
                    fixupPrimaryAfterUpdate(
                            Contacts.KIND_PHONE, matchedUriId == PEOPLE_PHONES_ID
                                    ? Long.parseLong(url.getPathSegments().get(1))
                                    : null, itemId,
                            values.getAsInteger(Phones.ISPRIMARY));
                    break;

                case CONTACTMETHODS_ID:
                case PEOPLE_CONTACTMETHODS_ID:
                    IsPrimaryInfo isPrimaryInfo = lookupIsPrimaryInfo(sContactMethodsTable,
                            sIsPrimaryProjectionWithKind, where, whereArgs);
                    fixupPrimaryAfterUpdate(
                            isPrimaryInfo.kind, isPrimaryInfo.person, itemId,
                            values.getAsInteger(ContactMethods.ISPRIMARY));
                    break;

                case PEOPLE_ID:
                    boolean hasStarred = values.containsKey(People.STARRED);
                    boolean hasPrimaryPhone = values.containsKey(People.PRIMARY_PHONE_ID);
                    boolean hasPrimaryOrganization =
                            values.containsKey(People.PRIMARY_ORGANIZATION_ID);
                    boolean hasPrimaryEmail = values.containsKey(People.PRIMARY_EMAIL_ID);
                    if (hasStarred || hasPrimaryPhone || hasPrimaryOrganization
                            || hasPrimaryEmail) {
                        Cursor c = mDb.query(sPeopleTable, null,
                                where, whereArgs, null, null, null);
                        try {
                            int indexAccount = c.getColumnIndexOrThrow(People._SYNC_ACCOUNT);
                            int indexId = c.getColumnIndexOrThrow(People._ID);
                            Long starredValue = values.getAsLong(People.STARRED);
                            Long primaryPhone = values.getAsLong(People.PRIMARY_PHONE_ID);
                            Long primaryOrganization =
                                    values.getAsLong(People.PRIMARY_ORGANIZATION_ID);
                            Long primaryEmail = values.getAsLong(People.PRIMARY_EMAIL_ID);
                            while (c.moveToNext()) {
                                final long personId = c.getLong(indexId);
                                if (hasStarred) {
                                    fixupGroupMembershipAfterPeopleUpdate(c.getString(indexAccount),
                                            personId, starredValue != null && starredValue != 0);
                                }

                                if (hasPrimaryPhone) {
                                    if (primaryPhone == null) {
                                        throw new IllegalArgumentException(
                                                "the value of PRIMARY_PHONE_ID must not be null");
                                    }
                                    setIsPrimary(Contacts.KIND_PHONE, personId, primaryPhone);
                                }
                                if (hasPrimaryOrganization) {
                                    if (primaryOrganization == null) {
                                        throw new IllegalArgumentException(
                                                "the value of PRIMARY_ORGANIZATION_ID must "
                                                        + "not be null");
                                    }
                                    setIsPrimary(Contacts.KIND_ORGANIZATION, personId,
                                            primaryOrganization);
                                }
                                if (hasPrimaryEmail) {
                                    if (primaryEmail == null) {
                                        throw new IllegalArgumentException(
                                                "the value of PRIMARY_EMAIL_ID must not be null");
                                    }
                                    setIsPrimary(Contacts.KIND_EMAIL, personId, primaryEmail);
                                }
                            }
                        } finally {
                            c.close();
                        }
                    }
                    break;
            }
        }

        return numRowsUpdated;
    
private voidupdatePeoplePrimary(java.lang.Long personId, java.lang.String column, java.lang.Long primaryId)
Set the specified primary column for the person. This is used to make the people row reflect the isprimary flag in the people or contactmethods tables, which is authoritative.

param
personId the person to modify
param
column the name of the primary column (phone or email)
param
primaryId the new value to write into the primary column

        mValuesLocal.clear();
        mValuesLocal.put(column, primaryId);
        getDatabase().update(sPeopleTable, mValuesLocal, "_id=" + personId, null);
    
private intupdateSettings(android.content.ContentValues values)

        final SQLiteDatabase db = getDatabase();
        final String account = values.getAsString(Contacts.Settings._SYNC_ACCOUNT);
        final String key = values.getAsString(Contacts.Settings.KEY);
        if (key == null) {
            throw new IllegalArgumentException("you must specify the key when updating settings");
        }
        if (account == null) {
            db.delete(sSettingsTable, "_sync_account IS NULL AND key=?", new String[]{key});
        } else {
            if (TextUtils.isEmpty(account)) {
                throw new IllegalArgumentException("account cannot be the empty string, " + values);
            }
            db.delete(sSettingsTable, "_sync_account=? AND key=?", new String[]{account, key});
        }
        long rowId = db.insert(sSettingsTable, Contacts.Settings.KEY, values);
        if (rowId < 0) {
            throw new SQLException("error updating settings with " + values);
        }
        return 1;
    
protected booleanupgradeDatabase(android.database.sqlite.SQLiteDatabase db, int oldVersion, int newVersion)

        boolean upgradeWasLossless = true;
        if (oldVersion < 71) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to " +
                    newVersion + ", which will destroy all old data");
            dropTables(db);
            bootstrapDatabase(db);
            return false; // this was lossy
        }
        if (oldVersion == 71) {
            Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " +
                    newVersion + ", which will preserve existing data");

            db.delete("_sync_state", null, null);
            mValuesLocal.clear();
            mValuesLocal.putNull(Photos._SYNC_VERSION);
            mValuesLocal.putNull(Photos._SYNC_TIME);
            db.update(sPhotosTable, mValuesLocal, null, null);
            getContext().getContentResolver().startSync(Contacts.CONTENT_URI, new Bundle());
            oldVersion = 72;
        }
        if (oldVersion == 72) {
            Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " +
                    newVersion + ", which will preserve existing data");

            // use new token format from 73
            db.execSQL("delete from peopleLookup");
            try {
                DatabaseUtils.longForQuery(db,
                        "SELECT _TOKENIZE('peopleLookup', _id, name, ' ') from people;",
                        null);
            } catch (SQLiteDoneException ex) {
                // it is ok to throw this, 
                // it just means you don't have data in people table
            }
            oldVersion = 73;
        }
        // There was a bug for a while in the upgrade logic where going from 72 to 74 would skip
        // the step from 73 to 74, so 74 to 75 just tries the same steps, and gracefully handles
        // errors in case the device was started freshly at 74.
        if (oldVersion == 73 || oldVersion == 74) {
            Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " +
                    newVersion + ", which will preserve existing data");

            try {
                db.execSQL("ALTER TABLE calls ADD name TEXT;");
                db.execSQL("ALTER TABLE calls ADD numbertype INTEGER;");
                db.execSQL("ALTER TABLE calls ADD numberlabel TEXT;");
            } catch (SQLiteException sqle) {
                // Maybe the table was altered already... Shouldn't be an issue.
            }
            oldVersion = 75;
        }
        // There were some indices added in version 76
        if (oldVersion == 75) {
            Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " +
                    newVersion + ", which will preserve existing data");

            // add the new indices
            db.execSQL("CREATE INDEX IF NOT EXISTS groupsSyncDirtyIndex"
                    + " ON groups (" + Groups._SYNC_DIRTY + ");");
            db.execSQL("CREATE INDEX IF NOT EXISTS photosSyncDirtyIndex"
                    + " ON photos (" + Photos._SYNC_DIRTY + ");");
            db.execSQL("CREATE INDEX IF NOT EXISTS peopleSyncDirtyIndex"
                    + " ON people (" + People._SYNC_DIRTY + ");");
            oldVersion = 76;
        }

        if (oldVersion == 76 || oldVersion == 77) {
            db.execSQL("DELETE FROM people");
            db.execSQL("DELETE FROM groups");
            db.execSQL("DELETE FROM photos");
            db.execSQL("DELETE FROM _deleted_people");
            db.execSQL("DELETE FROM _deleted_groups");
            upgradeWasLossless = false;
            oldVersion = 78;
        }

        if (oldVersion == 78) {
            db.execSQL("UPDATE photos SET _sync_dirty=0 where _sync_dirty is null;");
            oldVersion = 79;
        }

        if (oldVersion == 79) {
            try {
                db.execSQL("ALTER TABLE people ADD phonetic_name TEXT COLLATE LOCALIZED;");
            } catch (SQLiteException sqle) {
                // Maybe the table was altered already... Shouldn't be an issue.
            }
            oldVersion = 80;
        }

        return upgradeWasLossless;