diff options
Diffstat (limited to 'src/com/android/providers/contacts')
12 files changed, 1492 insertions, 1176 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 7fcadf5..57e421a 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -82,7 +82,6 @@ import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.provider.ContactsContract.AggregationExceptions; -import android.provider.ContactsContract.CommonDataKinds.BaseTypes; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Im; @@ -97,7 +96,6 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts.AggregationSuggestions; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Directory; -import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.Intents; import android.provider.ContactsContract.PhoneLookup; @@ -296,42 +294,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun public static final int CONTACT_ID = 2; } - private interface DataDeleteQuery { - public static final String TABLE = Tables.DATA_JOIN_MIMETYPES; - - public static final String[] CONCRETE_COLUMNS = new String[] { - DataColumns.CONCRETE_ID, - MimetypesColumns.MIMETYPE, - Data.RAW_CONTACT_ID, - Data.IS_PRIMARY, - Data.DATA1, - }; - - public static final String[] COLUMNS = new String[] { - Data._ID, - MimetypesColumns.MIMETYPE, - Data.RAW_CONTACT_ID, - Data.IS_PRIMARY, - Data.DATA1, - }; - - public static final int _ID = 0; - public static final int MIMETYPE = 1; - public static final int RAW_CONTACT_ID = 2; - public static final int IS_PRIMARY = 3; - public static final int DATA1 = 4; - } - - private interface DataUpdateQuery { - String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE }; - - int _ID = 0; - int RAW_CONTACT_ID = 1; - int MIMETYPE = 2; - } - - - private interface RawContactsQuery { + interface RawContactsQuery { String TABLE = Tables.RAW_CONTACTS; String[] COLUMNS = new String[] { @@ -939,1099 +902,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private boolean mDirectoryCacheValid = false; /** - * Handles inserts and update for a specific Data type. - */ - private static abstract class DataRowHandler { - protected final ContactsDatabaseHelper mDbHelper; - protected final ContactAggregator mContactAggregator; - protected String[] mSelectionArgs1 = new String[1]; - protected final String mMimetype; - protected long mMimetypeId; - - @SuppressWarnings("all") - public DataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, - String mimetype) { - mDbHelper = dbHelper; - mContactAggregator = aggregator; - mMimetype = mimetype; - - // To ensure the data column position. This is dead code if properly configured. - if (StructuredName.DISPLAY_NAME != Data.DATA1 || Nickname.NAME != Data.DATA1 - || Organization.COMPANY != Data.DATA1 || Phone.NUMBER != Data.DATA1 - || Email.DATA != Data.DATA1) { - throw new AssertionError("Some of ContactsContract.CommonDataKinds class primary" - + " data is not in DATA1 column"); - } - } - - protected long getMimeTypeId() { - if (mMimetypeId == 0) { - mMimetypeId = mDbHelper.getMimeTypeId(mMimetype); - } - return mMimetypeId; - } - - /** - * Inserts a row into the {@link Data} table. - */ - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - final long dataId = db.insert(Tables.DATA, null, values); - - final Integer primary = values.getAsInteger(Data.IS_PRIMARY); - final Integer superPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY); - if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) { - final long mimeTypeId = getMimeTypeId(); - mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); - - // We also have to make sure that no other data item on this raw_contact is - // configured super primary - if (superPrimary != null) { - if (superPrimary != 0) { - mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); - } else { - mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); - } - } else { - // if there is already another data item configured as super-primary, - // take over the flag (which will automatically remove it from the other item) - if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { - mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); - } - } - } - - return dataId; - } - - /** - * Validates data and updates a {@link Data} row using the cursor, which contains - * the current data. - * - * @return true if update changed something - */ - public boolean update(SQLiteDatabase db, TransactionContext txContext, - ContentValues values, Cursor c, boolean callerIsSyncAdapter) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - - handlePrimaryAndSuperPrimary(values, dataId, rawContactId); - - if (values.size() > 0) { - mSelectionArgs1[0] = String.valueOf(dataId); - db.update(Tables.DATA, values, Data._ID + " =?", mSelectionArgs1); - } - - if (!callerIsSyncAdapter) { - txContext.markRawContactDirty(rawContactId); - } - - return true; - } - - /** - * Ensures that all super-primary and primary flags of this raw_contact are - * configured correctly - */ - private void handlePrimaryAndSuperPrimary(ContentValues values, long dataId, - long rawContactId) { - final boolean hasPrimary = values.containsKey(Data.IS_PRIMARY); - final boolean hasSuperPrimary = values.containsKey(Data.IS_SUPER_PRIMARY); - - // Nothing to do? Bail out early - if (!hasPrimary && !hasSuperPrimary) return; - - final long mimeTypeId = getMimeTypeId(); - - // Check if we want to clear values - final boolean clearPrimary = hasPrimary && - values.getAsInteger(Data.IS_PRIMARY) == 0; - final boolean clearSuperPrimary = hasSuperPrimary && - values.getAsInteger(Data.IS_SUPER_PRIMARY) == 0; - - if (clearPrimary || clearSuperPrimary) { - // Test whether these values are currently set - mSelectionArgs1[0] = String.valueOf(dataId); - final String[] cols = new String[] { Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY }; - final Cursor c = mDbHelper.getReadableDatabase().query(Tables.DATA, - cols, Data._ID + "=?", mSelectionArgs1, null, null, null); - try { - if (c.moveToFirst()) { - final boolean isPrimary = c.getInt(0) != 0; - final boolean isSuperPrimary = c.getInt(1) != 0; - // Clear values if they are currently set - if (isSuperPrimary) { - mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); - } - if (clearPrimary && isPrimary) { - mDbHelper.setIsPrimary(rawContactId, -1, mimeTypeId); - } - } - } finally { - c.close(); - } - } else { - // Check if we want to set values - final boolean setPrimary = hasPrimary && - values.getAsInteger(Data.IS_PRIMARY) != 0; - final boolean setSuperPrimary = hasSuperPrimary && - values.getAsInteger(Data.IS_SUPER_PRIMARY) != 0; - if (setSuperPrimary) { - // Set both super primary and primary - mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); - mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); - } else if (setPrimary) { - // Primary was explicitly set, but super-primary was not. - // In this case we set super-primary on this data item, if - // any data item of the same raw-contact already is super-primary - if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { - mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); - } - mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); - } - } - - // Now that we've taken care of clearing this, remove it from "values". - values.remove(Data.IS_SUPER_PRIMARY); - values.remove(Data.IS_PRIMARY); - } - - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataDeleteQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - boolean primary = c.getInt(DataDeleteQuery.IS_PRIMARY) != 0; - mSelectionArgs1[0] = String.valueOf(dataId); - int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1); - mSelectionArgs1[0] = String.valueOf(rawContactId); - db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1); - if (count != 0 && primary) { - fixPrimary(db, rawContactId); - } - return count; - } - - private void fixPrimary(SQLiteDatabase db, long rawContactId) { - long mimeTypeId = getMimeTypeId(); - long primaryId = -1; - int primaryType = -1; - mSelectionArgs1[0] = String.valueOf(rawContactId); - Cursor c = db.query(DataDeleteQuery.TABLE, - DataDeleteQuery.CONCRETE_COLUMNS, - Data.RAW_CONTACT_ID + "=?" + - " AND " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId, - mSelectionArgs1, null, null, null); - try { - while (c.moveToNext()) { - long dataId = c.getLong(DataDeleteQuery._ID); - int type = c.getInt(DataDeleteQuery.DATA1); - if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) { - primaryId = dataId; - primaryType = type; - } - } - } finally { - c.close(); - } - if (primaryId != -1) { - mDbHelper.setIsPrimary(rawContactId, primaryId, mimeTypeId); - } - } - - /** - * Returns the rank of a specific record type to be used in determining the primary - * row. Lower number represents higher priority. - */ - protected int getTypeRank(int type) { - return 0; - } - - protected void fixRawContactDisplayName(SQLiteDatabase db, TransactionContext txContext, - long rawContactId) { - if (!isNewRawContact(txContext, rawContactId)) { - mContactAggregator.updateRawContactDisplayName(db, rawContactId); - mContactAggregator.updateDisplayNameForRawContact(db, rawContactId); - } - } - - private boolean isNewRawContact(TransactionContext txContext, long rawContactId) { - return txContext.isNewRawContact(rawContactId); - } - - /** - * Return set of values, using current values at given {@link Data#_ID} - * as baseline, but augmented with any updates. Returns null if there is - * no change. - */ - public ContentValues getAugmentedValues(SQLiteDatabase db, long dataId, - ContentValues update) { - boolean changing = false; - final ContentValues values = new ContentValues(); - mSelectionArgs1[0] = String.valueOf(dataId); - final Cursor cursor = db.query(Tables.DATA, null, Data._ID + "=?", - mSelectionArgs1, null, null, null); - try { - if (cursor.moveToFirst()) { - for (int i = 0; i < cursor.getColumnCount(); i++) { - final String key = cursor.getColumnName(i); - final String value = cursor.getString(i); - if (!changing && update.containsKey(key)) { - Object newValue = update.get(key); - String newString = newValue == null ? null : newValue.toString(); - changing |= !TextUtils.equals(newString, value); - } - values.put(key, value); - } - } - } finally { - cursor.close(); - } - if (!changing) { - return null; - } - - values.putAll(update); - return values; - } - - public void triggerAggregation(long rawContactId) { - mContactAggregator.triggerAggregation(rawContactId); - } - } - - public static class CustomDataRowHandler extends DataRowHandler { - - public CustomDataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, - String mimetype) { - super(dbHelper, aggregator, mimetype); - } - } - - public static class StructuredNameRowHandler extends DataRowHandler { - private final NameSplitter mSplitter; - private final NameLookupBuilder mNameLookupBuilder; - - public StructuredNameRowHandler(ContactsDatabaseHelper dbHelper, - ContactAggregator aggregator, NameSplitter splitter, - NameLookupBuilder nameLookupBuilder) { - super(dbHelper, aggregator, StructuredName.CONTENT_ITEM_TYPE); - mSplitter = splitter; - mNameLookupBuilder = nameLookupBuilder; - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - fixStructuredNameComponents(values, values); - - long dataId = super.insert(db, txContext, rawContactId, values); - - String name = values.getAsString(StructuredName.DISPLAY_NAME); - Integer fullNameStyle = values.getAsInteger(StructuredName.FULL_NAME_STYLE); - mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name, - fullNameStyle != null - ? mSplitter.getAdjustedFullNameStyle(fullNameStyle) - : FullNameStyle.UNDEFINED); - insertNameLookupForPhoneticName(rawContactId, dataId, values); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - final long dataId = c.getLong(DataUpdateQuery._ID); - final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - - final ContentValues augmented = getAugmentedValues(db, dataId, values); - if (augmented == null) { // No change - return false; - } - - fixStructuredNameComponents(augmented, values); - - super.update(db, txContext, values, c, callerIsSyncAdapter); - if (values.containsKey(StructuredName.DISPLAY_NAME) || - values.containsKey(StructuredName.PHONETIC_FAMILY_NAME) || - values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME) || - values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)) { - augmented.putAll(values); - String name = augmented.getAsString(StructuredName.DISPLAY_NAME); - mDbHelper.deleteNameLookup(dataId); - Integer fullNameStyle = augmented.getAsInteger(StructuredName.FULL_NAME_STYLE); - mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name, - fullNameStyle != null - ? mSplitter.getAdjustedFullNameStyle(fullNameStyle) - : FullNameStyle.UNDEFINED); - insertNameLookupForPhoneticName(rawContactId, dataId, augmented); - } - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataDeleteQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - - int count = super.delete(db, txContext, c); - - mDbHelper.deleteNameLookup(dataId); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return count; - } - - /** - * Specific list of structured fields. - */ - private final String[] STRUCTURED_FIELDS = new String[] { - StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME, - StructuredName.FAMILY_NAME, StructuredName.SUFFIX - }; - - /** - * Parses the supplied display name, but only if the incoming values do - * not already contain structured name parts. Also, if the display name - * is not provided, generate one by concatenating first name and last - * name. - */ - public void fixStructuredNameComponents(ContentValues augmented, ContentValues update) { - final String unstruct = update.getAsString(StructuredName.DISPLAY_NAME); - - final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct); - final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS); - - if (touchedUnstruct && !touchedStruct) { - NameSplitter.Name name = new NameSplitter.Name(); - mSplitter.split(name, unstruct); - name.toValues(update); - } else if (!touchedUnstruct - && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) { - // We need to update the display name when any structured components - // are specified, even when they are null, which is why we are checking - // areAnySpecified. The touchedStruct in the condition is an optimization: - // if there are non-null values, we know for a fact that some values are present. - NameSplitter.Name name = new NameSplitter.Name(); - name.fromValues(augmented); - // As the name could be changed, let's guess the name style again. - name.fullNameStyle = FullNameStyle.UNDEFINED; - mSplitter.guessNameStyle(name); - int unadjustedFullNameStyle = name.fullNameStyle; - name.fullNameStyle = mSplitter.getAdjustedFullNameStyle(name.fullNameStyle); - final String joined = mSplitter.join(name, true); - update.put(StructuredName.DISPLAY_NAME, joined); - - update.put(StructuredName.FULL_NAME_STYLE, unadjustedFullNameStyle); - update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle); - } else if (touchedUnstruct && touchedStruct){ - if (!update.containsKey(StructuredName.FULL_NAME_STYLE)) { - update.put(StructuredName.FULL_NAME_STYLE, - mSplitter.guessFullNameStyle(unstruct)); - } - if (!update.containsKey(StructuredName.PHONETIC_NAME_STYLE)) { - update.put(StructuredName.PHONETIC_NAME_STYLE, - mSplitter.guessPhoneticNameStyle(unstruct)); - } - } - } - - public void insertNameLookupForPhoneticName(long rawContactId, long dataId, - ContentValues values) { - if (values.containsKey(StructuredName.PHONETIC_FAMILY_NAME) - || values.containsKey(StructuredName.PHONETIC_GIVEN_NAME) - || values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME)) { - mDbHelper.insertNameLookupForPhoneticName(rawContactId, dataId, - values.getAsString(StructuredName.PHONETIC_FAMILY_NAME), - values.getAsString(StructuredName.PHONETIC_MIDDLE_NAME), - values.getAsString(StructuredName.PHONETIC_GIVEN_NAME)); - } - } - } - - public static class StructuredPostalRowHandler extends DataRowHandler { - private PostalSplitter mSplitter; - - public StructuredPostalRowHandler(ContactsDatabaseHelper dbHelper, - ContactAggregator aggregator, PostalSplitter splitter) { - super(dbHelper, aggregator, StructuredPostal.CONTENT_ITEM_TYPE); - mSplitter = splitter; - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - fixStructuredPostalComponents(values, values); - return super.insert(db, txContext, rawContactId, values); - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - final long dataId = c.getLong(DataUpdateQuery._ID); - final ContentValues augmented = getAugmentedValues(db, dataId, values); - if (augmented == null) { // No change - return false; - } - - fixStructuredPostalComponents(augmented, values); - super.update(db, txContext, values, c, callerIsSyncAdapter); - return true; - } - - /** - * Specific list of structured fields. - */ - private final String[] STRUCTURED_FIELDS = new String[] { - StructuredPostal.STREET, StructuredPostal.POBOX, StructuredPostal.NEIGHBORHOOD, - StructuredPostal.CITY, StructuredPostal.REGION, StructuredPostal.POSTCODE, - StructuredPostal.COUNTRY, - }; - - /** - * Prepares the given {@link StructuredPostal} row, building - * {@link StructuredPostal#FORMATTED_ADDRESS} to match the structured - * values when missing. When structured components are missing, the - * unstructured value is assigned to {@link StructuredPostal#STREET}. - */ - private void fixStructuredPostalComponents(ContentValues augmented, ContentValues update) { - final String unstruct = update.getAsString(StructuredPostal.FORMATTED_ADDRESS); - - final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct); - final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS); - - final PostalSplitter.Postal postal = new PostalSplitter.Postal(); - - if (touchedUnstruct && !touchedStruct) { - mSplitter.split(postal, unstruct); - postal.toValues(update); - } else if (!touchedUnstruct - && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) { - // See comment in - postal.fromValues(augmented); - final String joined = mSplitter.join(postal); - update.put(StructuredPostal.FORMATTED_ADDRESS, joined); - } - } - } - - public static class CommonDataRowHandler extends DataRowHandler { - - private final String mTypeColumn; - private final String mLabelColumn; - - public CommonDataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, - String mimetype, String typeColumn, String labelColumn) { - super(dbHelper, aggregator, mimetype); - mTypeColumn = typeColumn; - mLabelColumn = labelColumn; - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - enforceTypeAndLabel(values, values); - return super.insert(db, txContext, rawContactId, values); - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - final long dataId = c.getLong(DataUpdateQuery._ID); - final ContentValues augmented = getAugmentedValues(db, dataId, values); - if (augmented == null) { // No change - return false; - } - enforceTypeAndLabel(augmented, values); - return super.update(db, txContext, values, c, callerIsSyncAdapter); - } - - /** - * If the given {@link ContentValues} defines {@link #mTypeColumn}, - * enforce that {@link #mLabelColumn} only appears when type is - * {@link BaseTypes#TYPE_CUSTOM}. Exception is thrown otherwise. - */ - private void enforceTypeAndLabel(ContentValues augmented, ContentValues update) { - final boolean hasType = !TextUtils.isEmpty(augmented.getAsString(mTypeColumn)); - final boolean hasLabel = !TextUtils.isEmpty(augmented.getAsString(mLabelColumn)); - - if (hasLabel && !hasType) { - // When label exists, assert that some type is defined - throw new IllegalArgumentException(mTypeColumn + " must be specified when " - + mLabelColumn + " is defined."); - } - } - } - - public static class OrganizationDataRowHandler extends CommonDataRowHandler { - - public OrganizationDataRowHandler(ContactsDatabaseHelper dbHelper, - ContactAggregator aggregator) { - super(dbHelper, aggregator, - Organization.CONTENT_ITEM_TYPE, Organization.TYPE, Organization.LABEL); - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - String company = values.getAsString(Organization.COMPANY); - String title = values.getAsString(Organization.TITLE); - - long dataId = super.insert(db, txContext, rawContactId, values); - - fixRawContactDisplayName(db, txContext, rawContactId); - mDbHelper.insertNameLookupForOrganization(rawContactId, dataId, company, title); - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - - boolean containsCompany = values.containsKey(Organization.COMPANY); - boolean containsTitle = values.containsKey(Organization.TITLE); - if (containsCompany || containsTitle) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - - String company; - - if (containsCompany) { - company = values.getAsString(Organization.COMPANY); - } else { - mSelectionArgs1[0] = String.valueOf(dataId); - company = DatabaseUtils.stringForQuery(db, - "SELECT " + Organization.COMPANY + - " FROM " + Tables.DATA + - " WHERE " + Data._ID + "=?", mSelectionArgs1); - } - - String title; - if (containsTitle) { - title = values.getAsString(Organization.TITLE); - } else { - mSelectionArgs1[0] = String.valueOf(dataId); - title = DatabaseUtils.stringForQuery(db, - "SELECT " + Organization.TITLE + - " FROM " + Tables.DATA + - " WHERE " + Data._ID + "=?", mSelectionArgs1); - } - - mDbHelper.deleteNameLookup(dataId); - mDbHelper.insertNameLookupForOrganization(rawContactId, dataId, company, title); - - fixRawContactDisplayName(db, txContext, rawContactId); - } - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - - int count = super.delete(db, txContext, c); - fixRawContactDisplayName(db, txContext, rawContactId); - mDbHelper.deleteNameLookup(dataId); - return count; - } - - @Override - protected int getTypeRank(int type) { - switch (type) { - case Organization.TYPE_WORK: return 0; - case Organization.TYPE_CUSTOM: return 1; - case Organization.TYPE_OTHER: return 2; - default: return 1000; - } - } - } - - public static class EmailDataRowHandler extends CommonDataRowHandler { - - public EmailDataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { - super(dbHelper, aggregator, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL); - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - String email = values.getAsString(Email.DATA); - - long dataId = super.insert(db, txContext, rawContactId, values); - - fixRawContactDisplayName(db, txContext, rawContactId); - String address = mDbHelper.insertNameLookupForEmail(rawContactId, dataId, email); - if (address != null) { - triggerAggregation(rawContactId); - } - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - - if (values.containsKey(Email.DATA)) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - - String address = values.getAsString(Email.DATA); - mDbHelper.deleteNameLookup(dataId); - mDbHelper.insertNameLookupForEmail(rawContactId, dataId, address); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - } - - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataDeleteQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - - int count = super.delete(db, txContext, c); - - mDbHelper.deleteNameLookup(dataId); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return count; - } - - @Override - protected int getTypeRank(int type) { - switch (type) { - case Email.TYPE_HOME: return 0; - case Email.TYPE_WORK: return 1; - case Email.TYPE_CUSTOM: return 2; - case Email.TYPE_OTHER: return 3; - default: return 1000; - } - } - } - - public static class NicknameDataRowHandler extends CommonDataRowHandler { - - public NicknameDataRowHandler(ContactsDatabaseHelper dbHelper, - ContactAggregator aggregator) { - super(dbHelper, aggregator, Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE, Nickname.LABEL); - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - String nickname = values.getAsString(Nickname.NAME); - - long dataId = super.insert(db, txContext, rawContactId, values); - - if (!TextUtils.isEmpty(nickname)) { - fixRawContactDisplayName(db, txContext, rawContactId); - mDbHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); - triggerAggregation(rawContactId); - } - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - - if (values.containsKey(Nickname.NAME)) { - String nickname = values.getAsString(Nickname.NAME); - mDbHelper.deleteNameLookup(dataId); - mDbHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - } - - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataDeleteQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - - int count = super.delete(db, txContext, c); - - mDbHelper.deleteNameLookup(dataId); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return count; - } - } - - public static class PhoneDataRowHandler extends CommonDataRowHandler { - - public PhoneDataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { - super(dbHelper, aggregator, Phone.CONTENT_ITEM_TYPE, Phone.TYPE, Phone.LABEL); - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - long dataId; - if (values.containsKey(Phone.NUMBER)) { - String number = values.getAsString(Phone.NUMBER); - - String numberE164 = - PhoneNumberUtils.formatNumberToE164(number, mDbHelper.getCurrentCountryIso()); - if (numberE164 != null) { - values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164); - } - dataId = super.insert(db, txContext, rawContactId, values); - - updatePhoneLookup(db, rawContactId, dataId, number, numberE164); - mContactAggregator.updateHasPhoneNumber(db, rawContactId); - fixRawContactDisplayName(db, txContext, rawContactId); - if (numberE164 != null) { - triggerAggregation(rawContactId); - } - } else { - dataId = super.insert(db, txContext, rawContactId, values); - } - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - String number = null; - String numberE164 = null; - if (values.containsKey(Phone.NUMBER)) { - number = values.getAsString(Phone.NUMBER); - if (number != null) { - numberE164 = PhoneNumberUtils.formatNumberToE164(number, - mDbHelper.getCurrentCountryIso()); - } - if (numberE164 != null) { - values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164); - } - } - - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - - if (values.containsKey(Phone.NUMBER)) { - long dataId = c.getLong(DataUpdateQuery._ID); - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - updatePhoneLookup(db, rawContactId, dataId, number, numberE164); - mContactAggregator.updateHasPhoneNumber(db, rawContactId); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - } - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long dataId = c.getLong(DataDeleteQuery._ID); - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - - int count = super.delete(db, txContext, c); - - updatePhoneLookup(db, rawContactId, dataId, null, null); - mContactAggregator.updateHasPhoneNumber(db, rawContactId); - fixRawContactDisplayName(db, txContext, rawContactId); - triggerAggregation(rawContactId); - return count; - } - - private void updatePhoneLookup(SQLiteDatabase db, long rawContactId, long dataId, - String number, String numberE164) { - mSelectionArgs1[0] = String.valueOf(dataId); - db.delete(Tables.PHONE_LOOKUP, PhoneLookupColumns.DATA_ID + "=?", mSelectionArgs1); - if (number != null) { - String normalizedNumber = PhoneNumberUtils.normalizeNumber(number); - if (!TextUtils.isEmpty(normalizedNumber)) { - ContentValues phoneValues = new ContentValues(); - phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactId); - phoneValues.put(PhoneLookupColumns.DATA_ID, dataId); - phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber); - phoneValues.put(PhoneLookupColumns.MIN_MATCH, - PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber)); - db.insert(Tables.PHONE_LOOKUP, null, phoneValues); - - if (numberE164 != null && !numberE164.equals(normalizedNumber)) { - phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, numberE164); - phoneValues.put(PhoneLookupColumns.MIN_MATCH, - PhoneNumberUtils.toCallerIDMinMatch(numberE164)); - db.insert(Tables.PHONE_LOOKUP, null, phoneValues); - } - } - } - } - - @Override - protected int getTypeRank(int type) { - switch (type) { - case Phone.TYPE_MOBILE: return 0; - case Phone.TYPE_WORK: return 1; - case Phone.TYPE_HOME: return 2; - case Phone.TYPE_PAGER: return 3; - case Phone.TYPE_CUSTOM: return 4; - case Phone.TYPE_OTHER: return 5; - case Phone.TYPE_FAX_WORK: return 6; - case Phone.TYPE_FAX_HOME: return 7; - default: return 1000; - } - } - } - - public static class GroupMembershipRowHandler extends DataRowHandler { - - private static final String SELECTION_RAW_CONTACT_ID = RawContacts._ID + "=?"; - - private static final String QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID = - "SELECT COUNT(*) FROM " + Tables.DATA + " LEFT OUTER JOIN " + Tables .GROUPS - + " ON " + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID - + "=" + GroupsColumns.CONCRETE_ID - + " WHERE " + DataColumns.MIMETYPE_ID + "=?" - + " AND " + Tables.DATA + "." + GroupMembership.RAW_CONTACT_ID + "=?" - + " AND " + Groups.FAVORITES + "!=0"; - - private final HashMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache; - - public GroupMembershipRowHandler(ContactsDatabaseHelper dbHelper, - ContactAggregator aggregator, - HashMap<String, ArrayList<GroupIdCacheEntry>> groupIdCache) { - super(dbHelper, aggregator, GroupMembership.CONTENT_ITEM_TYPE); - mGroupIdCache = groupIdCache; - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - resolveGroupSourceIdInValues(txContext, rawContactId, db, values, true); - long dataId = super.insert(db, txContext, rawContactId, values); - if (hasFavoritesGroupMembership(db, rawContactId)) { - updateRawContactsStar(db, rawContactId, true /* starred */); - } - updateVisibility(rawContactId); - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId); - resolveGroupSourceIdInValues(txContext, rawContactId, db, values, false); - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - boolean isStarred = hasFavoritesGroupMembership(db, rawContactId); - if (wasStarred != isStarred) { - updateRawContactsStar(db, rawContactId, isStarred); - } - updateVisibility(rawContactId); - return true; - } - - private void updateRawContactsStar(SQLiteDatabase db, long rawContactId, boolean starred) { - ContentValues rawContactValues = new ContentValues(); - rawContactValues.put(RawContacts.STARRED, starred ? 1 : 0); - if (db.update(Tables.RAW_CONTACTS, rawContactValues, SELECTION_RAW_CONTACT_ID, - new String[]{Long.toString(rawContactId)}) > 0) { - mContactAggregator.updateStarred(rawContactId); - } - } - - private boolean hasFavoritesGroupMembership(SQLiteDatabase db, long rawContactId) { - final long groupMembershipMimetypeId = mDbHelper - .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); - boolean isStarred = 0 < DatabaseUtils - .longForQuery(db, QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID, - new String[]{Long.toString(groupMembershipMimetypeId), Long.toString(rawContactId)}); - return isStarred; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId); - int count = super.delete(db, txContext, c); - boolean isStarred = hasFavoritesGroupMembership(db, rawContactId); - if (wasStarred && !isStarred) { - updateRawContactsStar(db, rawContactId, false /* starred */); - } - updateVisibility(rawContactId); - return count; - } - - private void updateVisibility(long rawContactId) { - long contactId = mDbHelper.getContactId(rawContactId); - if (contactId != 0) { - mDbHelper.updateContactVisible(contactId); - } - } - - private void resolveGroupSourceIdInValues(TransactionContext txContext, - long rawContactId, SQLiteDatabase db, ContentValues values, boolean isInsert) { - boolean containsGroupSourceId = values.containsKey(GroupMembership.GROUP_SOURCE_ID); - boolean containsGroupId = values.containsKey(GroupMembership.GROUP_ROW_ID); - if (containsGroupSourceId && containsGroupId) { - throw new IllegalArgumentException( - "you are not allowed to set both the GroupMembership.GROUP_SOURCE_ID " - + "and GroupMembership.GROUP_ROW_ID"); - } - - if (!containsGroupSourceId && !containsGroupId) { - if (isInsert) { - throw new IllegalArgumentException( - "you must set exactly one of GroupMembership.GROUP_SOURCE_ID " - + "and GroupMembership.GROUP_ROW_ID"); - } else { - return; - } - } - - if (containsGroupSourceId) { - final String sourceId = values.getAsString(GroupMembership.GROUP_SOURCE_ID); - final long groupId = getOrMakeGroup(db, rawContactId, sourceId, - txContext.getAccountForRawContact(rawContactId)); - values.remove(GroupMembership.GROUP_SOURCE_ID); - values.put(GroupMembership.GROUP_ROW_ID, groupId); - } - } - - /** - * Returns the group id of the group with sourceId and the same account as rawContactId. - * If the group doesn't already exist then it is first created, - * @param db SQLiteDatabase to use for this operation - * @param rawContactId the contact this group is associated with - * @param sourceId the sourceIf of the group to query or create - * @return the group id of the existing or created group - * @throws IllegalArgumentException if the contact is not associated with an account - * @throws IllegalStateException if a group needs to be created but the creation failed - */ - private long getOrMakeGroup(SQLiteDatabase db, long rawContactId, String sourceId, - Account account) { - - if (account == null) { - mSelectionArgs1[0] = String.valueOf(rawContactId); - Cursor c = db.query(RawContactsQuery.TABLE, RawContactsQuery.COLUMNS, - RawContacts._ID + "=?", mSelectionArgs1, null, null, null); - try { - if (c.moveToFirst()) { - String accountName = c.getString(RawContactsQuery.ACCOUNT_NAME); - String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE); - if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { - account = new Account(accountName, accountType); - } - } - } finally { - c.close(); - } - } - - if (account == null) { - throw new IllegalArgumentException("if the groupmembership only " - + "has a sourceid the the contact must be associated with " - + "an account"); - } - - ArrayList<GroupIdCacheEntry> entries = mGroupIdCache.get(sourceId); - if (entries == null) { - entries = new ArrayList<GroupIdCacheEntry>(1); - mGroupIdCache.put(sourceId, entries); - } - - int count = entries.size(); - for (int i = 0; i < count; i++) { - GroupIdCacheEntry entry = entries.get(i); - if (entry.accountName.equals(account.name) && entry.accountType.equals(account.type)) { - return entry.groupId; - } - } - - GroupIdCacheEntry entry = new GroupIdCacheEntry(); - entry.accountName = account.name; - entry.accountType = account.type; - entry.sourceId = sourceId; - entries.add(0, entry); - - // look up the group that contains this sourceId and has the same account name and type - // as the contact refered to by rawContactId - Cursor c = db.query(Tables.GROUPS, new String[]{RawContacts._ID}, - Clauses.GROUP_HAS_ACCOUNT_AND_SOURCE_ID, - new String[]{sourceId, account.name, account.type}, null, null, null); - try { - if (c.moveToFirst()) { - entry.groupId = c.getLong(0); - } else { - ContentValues groupValues = new ContentValues(); - groupValues.put(Groups.ACCOUNT_NAME, account.name); - groupValues.put(Groups.ACCOUNT_TYPE, account.type); - groupValues.put(Groups.SOURCE_ID, sourceId); - long groupId = db.insert(Tables.GROUPS, Groups.ACCOUNT_NAME, groupValues); - if (groupId < 0) { - throw new IllegalStateException("unable to create a new group with " - + "this sourceid: " + groupValues); - } - entry.groupId = groupId; - } - } finally { - c.close(); - } - - return entry.groupId; - } - } - - public static class PhotoDataRowHandler extends DataRowHandler { - - public PhotoDataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { - super(dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE); - } - - @Override - public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, - ContentValues values) { - long dataId = super.insert(db, txContext, rawContactId, values); - if (!txContext.isNewRawContact(rawContactId)) { - mContactAggregator.updatePhotoId(db, rawContactId); - } - return dataId; - } - - @Override - public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter) { - long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { - return false; - } - - mContactAggregator.updatePhotoId(db, rawContactId); - return true; - } - - @Override - public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - int count = super.delete(db, txContext, c); - mContactAggregator.updatePhotoId(db, rawContactId); - return count; - } - } - - /** * An entry in group id cache. It maps the combination of (account type, account name * and source id) to group row id. */ @@ -2078,7 +948,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private Locale mCurrentLocale; private int mContactsAccountCount; - @Override public boolean onCreate() { super.onCreate(); @@ -2122,29 +991,31 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun mDataRowHandlers = new HashMap<String, DataRowHandler>(); mDataRowHandlers.put(Email.CONTENT_ITEM_TYPE, - new EmailDataRowHandler(mDbHelper, mContactAggregator)); + new DataRowHandlerForEmail(mDbHelper, mContactAggregator)); mDataRowHandlers.put(Im.CONTENT_ITEM_TYPE, - new CommonDataRowHandler(mDbHelper, mContactAggregator, + new DataRowHandlerForCommonDataKind(mDbHelper, mContactAggregator, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL)); mDataRowHandlers.put(Nickname.CONTENT_ITEM_TYPE, - new CommonDataRowHandler(mDbHelper, mContactAggregator, + new DataRowHandlerForCommonDataKind(mDbHelper, mContactAggregator, StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE, StructuredPostal.LABEL)); mDataRowHandlers.put(Organization.CONTENT_ITEM_TYPE, - new OrganizationDataRowHandler(mDbHelper, mContactAggregator)); + new DataRowHandlerForOrganization(mDbHelper, mContactAggregator)); mDataRowHandlers.put(Phone.CONTENT_ITEM_TYPE, - new PhoneDataRowHandler(mDbHelper, mContactAggregator)); + new DataRowHandlerForPhoneNumber(mDbHelper, mContactAggregator)); mDataRowHandlers.put(Nickname.CONTENT_ITEM_TYPE, - new NicknameDataRowHandler(mDbHelper, mContactAggregator)); + new DataRowHandlerForNickname(mDbHelper, mContactAggregator)); mDataRowHandlers.put(StructuredName.CONTENT_ITEM_TYPE, - new StructuredNameRowHandler(mDbHelper, mContactAggregator, + new DataRowHandlerForStructuredName(mDbHelper, mContactAggregator, mNameSplitter, mNameLookupBuilder)); mDataRowHandlers.put(StructuredPostal.CONTENT_ITEM_TYPE, - new StructuredPostalRowHandler(mDbHelper, mContactAggregator, mPostalSplitter)); + new DataRowHandlerForStructuredPostal(mDbHelper, mContactAggregator, + mPostalSplitter)); mDataRowHandlers.put(GroupMembership.CONTENT_ITEM_TYPE, - new GroupMembershipRowHandler(mDbHelper, mContactAggregator, mGroupIdCache)); + new DataRowHandlerForGroupMembership(mDbHelper, mContactAggregator, + mGroupIdCache)); mDataRowHandlers.put(Photo.CONTENT_ITEM_TYPE, - new PhotoDataRowHandler(mDbHelper, mContactAggregator)); + new DataRowHandlerForPhoto(mDbHelper, mContactAggregator)); } /** @@ -2546,7 +1417,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private DataRowHandler getDataRowHandler(final String mimeType) { DataRowHandler handler = mDataRowHandlers.get(mimeType); if (handler == null) { - handler = new CustomDataRowHandler(mDbHelper, mContactAggregator, mimeType); + handler = new DataRowHandlerForCustomMimetype(mDbHelper, mContactAggregator, mimeType); mDataRowHandlers.put(mimeType, handler); } return handler; @@ -2841,11 +1712,12 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun // Note that the query will return data according to the access restrictions, // so we don't need to worry about deleting data we don't have permission to read. - Cursor c = query(Data.CONTENT_URI, DataDeleteQuery.COLUMNS, selection, selectionArgs, null); + Cursor c = query(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, + selection, selectionArgs, null); try { while(c.moveToNext()) { - long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); - String mimeType = c.getString(DataDeleteQuery.MIMETYPE); + long rawContactId = c.getLong(DataRowHandler.DataDeleteQuery.RAW_CONTACT_ID); + String mimeType = c.getString(DataRowHandler.DataDeleteQuery.MIMETYPE); DataRowHandler rowHandler = getDataRowHandler(mimeType); count += rowHandler.delete(mDb, mTransactionContext, c); if (!callerIsSyncAdapter) { @@ -2867,7 +1739,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun // Note that the query will return data according to the access restrictions, // so we don't need to worry about deleting data we don't have permission to read. mSelectionArgs1[0] = String.valueOf(dataId); - Cursor c = query(Data.CONTENT_URI, DataDeleteQuery.COLUMNS, Data._ID + "=?", + Cursor c = query(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, Data._ID + "=?", mSelectionArgs1, null); try { @@ -2875,7 +1747,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun return 0; } - String mimeType = c.getString(DataDeleteQuery.MIMETYPE); + String mimeType = c.getString(DataRowHandler.DataDeleteQuery.MIMETYPE); boolean valid = false; for (int i = 0; i < allowedMimeTypes.length; i++) { if (TextUtils.equals(mimeType, allowedMimeTypes[i])) { @@ -3771,7 +2643,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun // Note that the query will return data according to the access restrictions, // so we don't need to worry about updating data we don't have permission to read. - Cursor c = query(uri, DataUpdateQuery.COLUMNS, selection, selectionArgs, null); + Cursor c = query(uri, DataRowHandler.DataUpdateQuery.COLUMNS, + selection, selectionArgs, null); try { while(c.moveToNext()) { count += updateData(mValues, c, callerIsSyncAdapter); @@ -3788,7 +2661,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun return 0; } - final String mimeType = c.getString(DataUpdateQuery.MIMETYPE); + final String mimeType = c.getString(DataRowHandler.DataUpdateQuery.MIMETYPE); DataRowHandler rowHandler = getDataRowHandler(mimeType); if (rowHandler.update(mDb, mTransactionContext, values, c, callerIsSyncAdapter)) { return 1; @@ -4090,30 +2963,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun } } - /** - * Test all against {@link TextUtils#isEmpty(CharSequence)}. - */ - private static boolean areAllEmpty(ContentValues values, String[] keys) { - for (String key : keys) { - if (!TextUtils.isEmpty(values.getAsString(key))) { - return false; - } - } - return true; - } - - /** - * Returns true if a value (possibly null) is specified for at least one of the supplied keys. - */ - private static boolean areAnySpecified(ContentValues values, String[] keys) { - for (String key : keys) { - if (values.containsKey(key)) { - return true; - } - } - return false; - } - @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { @@ -5938,8 +4787,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun } ContentValues values = new ContentValues(); - StructuredNameRowHandler handler = - (StructuredNameRowHandler) getDataRowHandler(StructuredName.CONTENT_ITEM_TYPE); + DataRowHandlerForStructuredName handler = (DataRowHandlerForStructuredName) + getDataRowHandler(StructuredName.CONTENT_ITEM_TYPE); copyQueryParamsToContentValues(values, uri, StructuredName.DISPLAY_NAME, diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java new file mode 100644 index 0000000..32cef19 --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandler.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.Tables; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.Data; +import android.text.TextUtils; + +/** + * Handles inserts and update for a specific Data type. + */ +public abstract class DataRowHandler { + + public interface DataDeleteQuery { + public static final String TABLE = Tables.DATA_JOIN_MIMETYPES; + + public static final String[] CONCRETE_COLUMNS = new String[] { + DataColumns.CONCRETE_ID, + MimetypesColumns.MIMETYPE, + Data.RAW_CONTACT_ID, + Data.IS_PRIMARY, + Data.DATA1, + }; + + public static final String[] COLUMNS = new String[] { + Data._ID, + MimetypesColumns.MIMETYPE, + Data.RAW_CONTACT_ID, + Data.IS_PRIMARY, + Data.DATA1, + }; + + public static final int _ID = 0; + public static final int MIMETYPE = 1; + public static final int RAW_CONTACT_ID = 2; + public static final int IS_PRIMARY = 3; + public static final int DATA1 = 4; + } + + public interface DataUpdateQuery { + String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE }; + + int _ID = 0; + int RAW_CONTACT_ID = 1; + int MIMETYPE = 2; + } + + protected final ContactsDatabaseHelper mDbHelper; + protected final ContactAggregator mContactAggregator; + protected String[] mSelectionArgs1 = new String[1]; + protected final String mMimetype; + protected long mMimetypeId; + + @SuppressWarnings("all") + public DataRowHandler(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, + String mimetype) { + mDbHelper = dbHelper; + mContactAggregator = aggregator; + mMimetype = mimetype; + + // To ensure the data column position. This is dead code if properly configured. + if (StructuredName.DISPLAY_NAME != Data.DATA1 || Nickname.NAME != Data.DATA1 + || Organization.COMPANY != Data.DATA1 || Phone.NUMBER != Data.DATA1 + || Email.DATA != Data.DATA1) { + throw new AssertionError("Some of ContactsContract.CommonDataKinds class primary" + + " data is not in DATA1 column"); + } + } + + protected long getMimeTypeId() { + if (mMimetypeId == 0) { + mMimetypeId = mDbHelper.getMimeTypeId(mMimetype); + } + return mMimetypeId; + } + + /** + * Inserts a row into the {@link Data} table. + */ + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + final long dataId = db.insert(Tables.DATA, null, values); + + final Integer primary = values.getAsInteger(Data.IS_PRIMARY); + final Integer superPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY); + if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) { + final long mimeTypeId = getMimeTypeId(); + mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); + + // We also have to make sure that no other data item on this raw_contact is + // configured super primary + if (superPrimary != null) { + if (superPrimary != 0) { + mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); + } else { + mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); + } + } else { + // if there is already another data item configured as super-primary, + // take over the flag (which will automatically remove it from the other item) + if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { + mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); + } + } + } + + return dataId; + } + + /** + * Validates data and updates a {@link Data} row using the cursor, which contains + * the current data. + * + * @return true if update changed something + */ + public boolean update(SQLiteDatabase db, TransactionContext txContext, + ContentValues values, Cursor c, boolean callerIsSyncAdapter) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + + handlePrimaryAndSuperPrimary(values, dataId, rawContactId); + + if (values.size() > 0) { + mSelectionArgs1[0] = String.valueOf(dataId); + db.update(Tables.DATA, values, Data._ID + " =?", mSelectionArgs1); + } + + if (!callerIsSyncAdapter) { + txContext.markRawContactDirty(rawContactId); + } + + return true; + } + + /** + * Ensures that all super-primary and primary flags of this raw_contact are + * configured correctly + */ + private void handlePrimaryAndSuperPrimary(ContentValues values, long dataId, + long rawContactId) { + final boolean hasPrimary = values.containsKey(Data.IS_PRIMARY); + final boolean hasSuperPrimary = values.containsKey(Data.IS_SUPER_PRIMARY); + + // Nothing to do? Bail out early + if (!hasPrimary && !hasSuperPrimary) return; + + final long mimeTypeId = getMimeTypeId(); + + // Check if we want to clear values + final boolean clearPrimary = hasPrimary && + values.getAsInteger(Data.IS_PRIMARY) == 0; + final boolean clearSuperPrimary = hasSuperPrimary && + values.getAsInteger(Data.IS_SUPER_PRIMARY) == 0; + + if (clearPrimary || clearSuperPrimary) { + // Test whether these values are currently set + mSelectionArgs1[0] = String.valueOf(dataId); + final String[] cols = new String[] { Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY }; + final Cursor c = mDbHelper.getReadableDatabase().query(Tables.DATA, + cols, Data._ID + "=?", mSelectionArgs1, null, null, null); + try { + if (c.moveToFirst()) { + final boolean isPrimary = c.getInt(0) != 0; + final boolean isSuperPrimary = c.getInt(1) != 0; + // Clear values if they are currently set + if (isSuperPrimary) { + mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); + } + if (clearPrimary && isPrimary) { + mDbHelper.setIsPrimary(rawContactId, -1, mimeTypeId); + } + } + } finally { + c.close(); + } + } else { + // Check if we want to set values + final boolean setPrimary = hasPrimary && + values.getAsInteger(Data.IS_PRIMARY) != 0; + final boolean setSuperPrimary = hasSuperPrimary && + values.getAsInteger(Data.IS_SUPER_PRIMARY) != 0; + if (setSuperPrimary) { + // Set both super primary and primary + mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); + mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); + } else if (setPrimary) { + // Primary was explicitly set, but super-primary was not. + // In this case we set super-primary on this data item, if + // any data item of the same raw-contact already is super-primary + if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { + mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); + } + mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); + } + } + + // Now that we've taken care of clearing this, remove it from "values". + values.remove(Data.IS_SUPER_PRIMARY); + values.remove(Data.IS_PRIMARY); + } + + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataDeleteQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + boolean primary = c.getInt(DataDeleteQuery.IS_PRIMARY) != 0; + mSelectionArgs1[0] = String.valueOf(dataId); + int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1); + mSelectionArgs1[0] = String.valueOf(rawContactId); + db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1); + if (count != 0 && primary) { + fixPrimary(db, rawContactId); + } + return count; + } + + private void fixPrimary(SQLiteDatabase db, long rawContactId) { + long mimeTypeId = getMimeTypeId(); + long primaryId = -1; + int primaryType = -1; + mSelectionArgs1[0] = String.valueOf(rawContactId); + Cursor c = db.query(DataDeleteQuery.TABLE, + DataDeleteQuery.CONCRETE_COLUMNS, + Data.RAW_CONTACT_ID + "=?" + + " AND " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId, + mSelectionArgs1, null, null, null); + try { + while (c.moveToNext()) { + long dataId = c.getLong(DataDeleteQuery._ID); + int type = c.getInt(DataDeleteQuery.DATA1); + if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) { + primaryId = dataId; + primaryType = type; + } + } + } finally { + c.close(); + } + if (primaryId != -1) { + mDbHelper.setIsPrimary(rawContactId, primaryId, mimeTypeId); + } + } + + /** + * Returns the rank of a specific record type to be used in determining the primary + * row. Lower number represents higher priority. + */ + protected int getTypeRank(int type) { + return 0; + } + + protected void fixRawContactDisplayName(SQLiteDatabase db, TransactionContext txContext, + long rawContactId) { + if (!isNewRawContact(txContext, rawContactId)) { + mContactAggregator.updateRawContactDisplayName(db, rawContactId); + mContactAggregator.updateDisplayNameForRawContact(db, rawContactId); + } + } + + private boolean isNewRawContact(TransactionContext txContext, long rawContactId) { + return txContext.isNewRawContact(rawContactId); + } + + /** + * Return set of values, using current values at given {@link Data#_ID} + * as baseline, but augmented with any updates. Returns null if there is + * no change. + */ + public ContentValues getAugmentedValues(SQLiteDatabase db, long dataId, + ContentValues update) { + boolean changing = false; + final ContentValues values = new ContentValues(); + mSelectionArgs1[0] = String.valueOf(dataId); + final Cursor cursor = db.query(Tables.DATA, null, Data._ID + "=?", + mSelectionArgs1, null, null, null); + try { + if (cursor.moveToFirst()) { + for (int i = 0; i < cursor.getColumnCount(); i++) { + final String key = cursor.getColumnName(i); + final String value = cursor.getString(i); + if (!changing && update.containsKey(key)) { + Object newValue = update.get(key); + String newString = newValue == null ? null : newValue.toString(); + changing |= !TextUtils.equals(newString, value); + } + values.put(key, value); + } + } + } finally { + cursor.close(); + } + if (!changing) { + return null; + } + + values.putAll(update); + return values; + } + + public void triggerAggregation(long rawContactId) { + mContactAggregator.triggerAggregation(rawContactId); + } + + /** + * Test all against {@link TextUtils#isEmpty(CharSequence)}. + */ + public boolean areAllEmpty(ContentValues values, String[] keys) { + for (String key : keys) { + if (!TextUtils.isEmpty(values.getAsString(key))) { + return false; + } + } + return true; + } + + /** + * Returns true if a value (possibly null) is specified for at least one of the supplied keys. + */ + public boolean areAnySpecified(ContentValues values, String[] keys) { + for (String key : keys) { + if (values.containsKey(key)) { + return true; + } + } + return false; + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java new file mode 100644 index 0000000..327f2bf --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.BaseTypes; +import android.text.TextUtils; + +/** + * Superclass for data row handlers that deal with types (e.g. Home, Work, Other) and + * labels, which are custom types. + */ +public abstract class DataRowHandlerForCommonDataKind extends DataRowHandler { + + private final String mTypeColumn; + private final String mLabelColumn; + + public DataRowHandlerForCommonDataKind(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator, String mimetype, String typeColumn, String labelColumn) { + super(dbHelper, aggregator, mimetype); + mTypeColumn = typeColumn; + mLabelColumn = labelColumn; + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + enforceTypeAndLabel(values); + return super.insert(db, txContext, rawContactId, values); + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + final long dataId = c.getLong(DataUpdateQuery._ID); + final ContentValues augmented = getAugmentedValues(db, dataId, values); + if (augmented == null) { // No change + return false; + } + enforceTypeAndLabel(augmented); + return super.update(db, txContext, values, c, callerIsSyncAdapter); + } + + /** + * If the given {@link ContentValues} defines {@link #mTypeColumn}, + * enforce that {@link #mLabelColumn} only appears when type is + * {@link BaseTypes#TYPE_CUSTOM}. Exception is thrown otherwise. + */ + private void enforceTypeAndLabel(ContentValues augmented) { + final boolean hasType = !TextUtils.isEmpty(augmented.getAsString(mTypeColumn)); + final boolean hasLabel = !TextUtils.isEmpty(augmented.getAsString(mLabelColumn)); + + if (hasLabel && !hasType) { + // When label exists, assert that some type is defined + throw new IllegalArgumentException(mTypeColumn + " must be specified when " + + mLabelColumn + " is defined."); + } + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java new file mode 100644 index 0000000..49ad302 --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +public class DataRowHandlerForCustomMimetype extends DataRowHandler { + + public DataRowHandlerForCustomMimetype( + ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, String mimetype) { + super(dbHelper, aggregator, mimetype); + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java new file mode 100644 index 0000000..334e7fa --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Email; + +/** + * Handler for email address data rows. + */ +public class DataRowHandlerForEmail extends DataRowHandlerForCommonDataKind { + + public DataRowHandlerForEmail(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { + super(dbHelper, aggregator, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL); + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + String email = values.getAsString(Email.DATA); + + long dataId = super.insert(db, txContext, rawContactId, values); + + fixRawContactDisplayName(db, txContext, rawContactId); + String address = mDbHelper.insertNameLookupForEmail(rawContactId, dataId, email); + if (address != null) { + triggerAggregation(rawContactId); + } + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + + if (values.containsKey(Email.DATA)) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + + String address = values.getAsString(Email.DATA); + mDbHelper.deleteNameLookup(dataId); + mDbHelper.insertNameLookupForEmail(rawContactId, dataId, address); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + } + + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataDeleteQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + + int count = super.delete(db, txContext, c); + + mDbHelper.deleteNameLookup(dataId); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return count; + } + + @Override + protected int getTypeRank(int type) { + switch (type) { + case Email.TYPE_HOME: return 0; + case Email.TYPE_WORK: return 1; + case Email.TYPE_CUSTOM: return 2; + case Email.TYPE_OTHER: return 3; + default: return 1000; + } + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java new file mode 100644 index 0000000..eedca80 --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import com.android.providers.contacts.ContactsDatabaseHelper.Clauses; +import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.Tables; +import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry; + +import android.accounts.Account; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; +import android.provider.ContactsContract.Groups; +import android.provider.ContactsContract.RawContacts; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Handler for group membership data rows. + */ +public class DataRowHandlerForGroupMembership extends DataRowHandler { + + interface RawContactsQuery { + String TABLE = Tables.RAW_CONTACTS; + + String[] COLUMNS = new String[] { + RawContacts.DELETED, + RawContacts.ACCOUNT_TYPE, + RawContacts.ACCOUNT_NAME, + }; + + int DELETED = 0; + int ACCOUNT_TYPE = 1; + int ACCOUNT_NAME = 2; + } + + private static final String SELECTION_RAW_CONTACT_ID = RawContacts._ID + "=?"; + + private static final String QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID = + "SELECT COUNT(*) FROM " + Tables.DATA + " LEFT OUTER JOIN " + Tables .GROUPS + + " ON " + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + + "=" + GroupsColumns.CONCRETE_ID + + " WHERE " + DataColumns.MIMETYPE_ID + "=?" + + " AND " + Tables.DATA + "." + GroupMembership.RAW_CONTACT_ID + "=?" + + " AND " + Groups.FAVORITES + "!=0"; + + private final HashMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache; + + public DataRowHandlerForGroupMembership(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator, + HashMap<String, ArrayList<GroupIdCacheEntry>> groupIdCache) { + super(dbHelper, aggregator, GroupMembership.CONTENT_ITEM_TYPE); + mGroupIdCache = groupIdCache; + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + resolveGroupSourceIdInValues(txContext, rawContactId, db, values, true); + long dataId = super.insert(db, txContext, rawContactId, values); + if (hasFavoritesGroupMembership(db, rawContactId)) { + updateRawContactsStar(db, rawContactId, true /* starred */); + } + updateVisibility(rawContactId); + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId); + resolveGroupSourceIdInValues(txContext, rawContactId, db, values, false); + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + boolean isStarred = hasFavoritesGroupMembership(db, rawContactId); + if (wasStarred != isStarred) { + updateRawContactsStar(db, rawContactId, isStarred); + } + updateVisibility(rawContactId); + return true; + } + + private void updateRawContactsStar(SQLiteDatabase db, long rawContactId, boolean starred) { + ContentValues rawContactValues = new ContentValues(); + rawContactValues.put(RawContacts.STARRED, starred ? 1 : 0); + if (db.update(Tables.RAW_CONTACTS, rawContactValues, SELECTION_RAW_CONTACT_ID, + new String[]{Long.toString(rawContactId)}) > 0) { + mContactAggregator.updateStarred(rawContactId); + } + } + + private boolean hasFavoritesGroupMembership(SQLiteDatabase db, long rawContactId) { + // TODO compiled SQL statement + final long groupMembershipMimetypeId = mDbHelper + .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE); + boolean isStarred = 0 < DatabaseUtils + .longForQuery(db, QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID, + new String[]{Long.toString(groupMembershipMimetypeId), Long.toString(rawContactId)}); + return isStarred; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId); + int count = super.delete(db, txContext, c); + boolean isStarred = hasFavoritesGroupMembership(db, rawContactId); + if (wasStarred && !isStarred) { + updateRawContactsStar(db, rawContactId, false /* starred */); + } + updateVisibility(rawContactId); + return count; + } + + private void updateVisibility(long rawContactId) { + long contactId = mDbHelper.getContactId(rawContactId); + if (contactId != 0) { + mDbHelper.updateContactVisible(contactId); + } + } + + private void resolveGroupSourceIdInValues(TransactionContext txContext, + long rawContactId, SQLiteDatabase db, ContentValues values, boolean isInsert) { + boolean containsGroupSourceId = values.containsKey(GroupMembership.GROUP_SOURCE_ID); + boolean containsGroupId = values.containsKey(GroupMembership.GROUP_ROW_ID); + if (containsGroupSourceId && containsGroupId) { + throw new IllegalArgumentException( + "you are not allowed to set both the GroupMembership.GROUP_SOURCE_ID " + + "and GroupMembership.GROUP_ROW_ID"); + } + + if (!containsGroupSourceId && !containsGroupId) { + if (isInsert) { + throw new IllegalArgumentException( + "you must set exactly one of GroupMembership.GROUP_SOURCE_ID " + + "and GroupMembership.GROUP_ROW_ID"); + } else { + return; + } + } + + if (containsGroupSourceId) { + final String sourceId = values.getAsString(GroupMembership.GROUP_SOURCE_ID); + final long groupId = getOrMakeGroup(db, rawContactId, sourceId, + txContext.getAccountForRawContact(rawContactId)); + values.remove(GroupMembership.GROUP_SOURCE_ID); + values.put(GroupMembership.GROUP_ROW_ID, groupId); + } + } + + /** + * Returns the group id of the group with sourceId and the same account as rawContactId. + * If the group doesn't already exist then it is first created, + * @param db SQLiteDatabase to use for this operation + * @param rawContactId the contact this group is associated with + * @param sourceId the sourceIf of the group to query or create + * @return the group id of the existing or created group + * @throws IllegalArgumentException if the contact is not associated with an account + * @throws IllegalStateException if a group needs to be created but the creation failed + */ + private long getOrMakeGroup(SQLiteDatabase db, long rawContactId, String sourceId, + Account account) { + + if (account == null) { + mSelectionArgs1[0] = String.valueOf(rawContactId); + Cursor c = db.query(RawContactsQuery.TABLE, RawContactsQuery.COLUMNS, + RawContacts._ID + "=?", mSelectionArgs1, null, null, null); + try { + if (c.moveToFirst()) { + String accountName = c.getString(RawContactsQuery.ACCOUNT_NAME); + String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE); + if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { + account = new Account(accountName, accountType); + } + } + } finally { + c.close(); + } + } + + if (account == null) { + throw new IllegalArgumentException("if the groupmembership only " + + "has a sourceid the the contact must be associated with " + + "an account"); + } + + ArrayList<GroupIdCacheEntry> entries = mGroupIdCache.get(sourceId); + if (entries == null) { + entries = new ArrayList<GroupIdCacheEntry>(1); + mGroupIdCache.put(sourceId, entries); + } + + int count = entries.size(); + for (int i = 0; i < count; i++) { + GroupIdCacheEntry entry = entries.get(i); + if (entry.accountName.equals(account.name) && entry.accountType.equals(account.type)) { + return entry.groupId; + } + } + + GroupIdCacheEntry entry = new GroupIdCacheEntry(); + entry.accountName = account.name; + entry.accountType = account.type; + entry.sourceId = sourceId; + entries.add(0, entry); + + // look up the group that contains this sourceId and has the same account name and type + // as the contact refered to by rawContactId + Cursor c = db.query(Tables.GROUPS, new String[]{RawContacts._ID}, + Clauses.GROUP_HAS_ACCOUNT_AND_SOURCE_ID, + new String[]{sourceId, account.name, account.type}, null, null, null); + try { + if (c.moveToFirst()) { + entry.groupId = c.getLong(0); + } else { + ContentValues groupValues = new ContentValues(); + groupValues.put(Groups.ACCOUNT_NAME, account.name); + groupValues.put(Groups.ACCOUNT_TYPE, account.type); + groupValues.put(Groups.SOURCE_ID, sourceId); + long groupId = db.insert(Tables.GROUPS, Groups.ACCOUNT_NAME, groupValues); + if (groupId < 0) { + throw new IllegalStateException("unable to create a new group with " + + "this sourceid: " + groupValues); + } + entry.groupId = groupId; + } + } finally { + c.close(); + } + + return entry.groupId; + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java new file mode 100644 index 0000000..f2c828f --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.text.TextUtils; + +/** + * Handler for nickname data rows. + */ +public class DataRowHandlerForNickname extends DataRowHandlerForCommonDataKind { + + public DataRowHandlerForNickname(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator) { + super(dbHelper, aggregator, Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE, Nickname.LABEL); + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + String nickname = values.getAsString(Nickname.NAME); + + long dataId = super.insert(db, txContext, rawContactId, values); + + if (!TextUtils.isEmpty(nickname)) { + fixRawContactDisplayName(db, txContext, rawContactId); + mDbHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); + triggerAggregation(rawContactId); + } + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + + if (values.containsKey(Nickname.NAME)) { + String nickname = values.getAsString(Nickname.NAME); + mDbHelper.deleteNameLookup(dataId); + mDbHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + } + + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataDeleteQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + + int count = super.delete(db, txContext, c); + + mDbHelper.deleteNameLookup(dataId); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return count; + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java new file mode 100644 index 0000000..1382f1f --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import com.android.providers.contacts.ContactsDatabaseHelper.Tables; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.Data; + +/** + * Handler for organization data rows. + */ +public class DataRowHandlerForOrganization extends DataRowHandlerForCommonDataKind { + + public DataRowHandlerForOrganization(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator) { + super(dbHelper, aggregator, + Organization.CONTENT_ITEM_TYPE, Organization.TYPE, Organization.LABEL); + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + String company = values.getAsString(Organization.COMPANY); + String title = values.getAsString(Organization.TITLE); + + long dataId = super.insert(db, txContext, rawContactId, values); + + fixRawContactDisplayName(db, txContext, rawContactId); + mDbHelper.insertNameLookupForOrganization(rawContactId, dataId, company, title); + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + + boolean containsCompany = values.containsKey(Organization.COMPANY); + boolean containsTitle = values.containsKey(Organization.TITLE); + if (containsCompany || containsTitle) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + + String company; + + if (containsCompany) { + company = values.getAsString(Organization.COMPANY); + } else { + mSelectionArgs1[0] = String.valueOf(dataId); + company = DatabaseUtils.stringForQuery(db, + "SELECT " + Organization.COMPANY + + " FROM " + Tables.DATA + + " WHERE " + Data._ID + "=?", mSelectionArgs1); + } + + String title; + if (containsTitle) { + title = values.getAsString(Organization.TITLE); + } else { + mSelectionArgs1[0] = String.valueOf(dataId); + title = DatabaseUtils.stringForQuery(db, + "SELECT " + Organization.TITLE + + " FROM " + Tables.DATA + + " WHERE " + Data._ID + "=?", mSelectionArgs1); + } + + mDbHelper.deleteNameLookup(dataId); + mDbHelper.insertNameLookupForOrganization(rawContactId, dataId, company, title); + + fixRawContactDisplayName(db, txContext, rawContactId); + } + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + + int count = super.delete(db, txContext, c); + fixRawContactDisplayName(db, txContext, rawContactId); + mDbHelper.deleteNameLookup(dataId); + return count; + } + + @Override + protected int getTypeRank(int type) { + switch (type) { + case Organization.TYPE_WORK: return 0; + case Organization.TYPE_CUSTOM: return 1; + case Organization.TYPE_OTHER: return 2; + default: return 1000; + } + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java new file mode 100644 index 0000000..84f05c0 --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import com.android.providers.contacts.ContactsDatabaseHelper.PhoneColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.Tables; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; + +/** + * Handler for phone number data rows. + */ +public class DataRowHandlerForPhoneNumber extends DataRowHandlerForCommonDataKind { + + public DataRowHandlerForPhoneNumber( + ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { + super(dbHelper, aggregator, Phone.CONTENT_ITEM_TYPE, Phone.TYPE, Phone.LABEL); + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + long dataId; + if (values.containsKey(Phone.NUMBER)) { + String number = values.getAsString(Phone.NUMBER); + + String numberE164 = + PhoneNumberUtils.formatNumberToE164(number, mDbHelper.getCurrentCountryIso()); + if (numberE164 != null) { + values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164); + } + dataId = super.insert(db, txContext, rawContactId, values); + + updatePhoneLookup(db, rawContactId, dataId, number, numberE164); + mContactAggregator.updateHasPhoneNumber(db, rawContactId); + fixRawContactDisplayName(db, txContext, rawContactId); + if (numberE164 != null) { + triggerAggregation(rawContactId); + } + } else { + dataId = super.insert(db, txContext, rawContactId, values); + } + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + String number = null; + String numberE164 = null; + if (values.containsKey(Phone.NUMBER)) { + number = values.getAsString(Phone.NUMBER); + if (number != null) { + numberE164 = PhoneNumberUtils.formatNumberToE164(number, + mDbHelper.getCurrentCountryIso()); + } + if (numberE164 != null) { + values.put(PhoneColumns.NORMALIZED_NUMBER, numberE164); + } + } + + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + + if (values.containsKey(Phone.NUMBER)) { + long dataId = c.getLong(DataUpdateQuery._ID); + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + updatePhoneLookup(db, rawContactId, dataId, number, numberE164); + mContactAggregator.updateHasPhoneNumber(db, rawContactId); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + } + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataDeleteQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + + int count = super.delete(db, txContext, c); + + updatePhoneLookup(db, rawContactId, dataId, null, null); + mContactAggregator.updateHasPhoneNumber(db, rawContactId); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return count; + } + + private void updatePhoneLookup(SQLiteDatabase db, long rawContactId, long dataId, + String number, String numberE164) { + mSelectionArgs1[0] = String.valueOf(dataId); + db.delete(Tables.PHONE_LOOKUP, PhoneLookupColumns.DATA_ID + "=?", mSelectionArgs1); + if (number != null) { + String normalizedNumber = PhoneNumberUtils.normalizeNumber(number); + if (!TextUtils.isEmpty(normalizedNumber)) { + ContentValues phoneValues = new ContentValues(); + phoneValues.put(PhoneLookupColumns.RAW_CONTACT_ID, rawContactId); + phoneValues.put(PhoneLookupColumns.DATA_ID, dataId); + phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, normalizedNumber); + phoneValues.put(PhoneLookupColumns.MIN_MATCH, + PhoneNumberUtils.toCallerIDMinMatch(normalizedNumber)); + db.insert(Tables.PHONE_LOOKUP, null, phoneValues); + + if (numberE164 != null && !numberE164.equals(normalizedNumber)) { + phoneValues.put(PhoneLookupColumns.NORMALIZED_NUMBER, numberE164); + phoneValues.put(PhoneLookupColumns.MIN_MATCH, + PhoneNumberUtils.toCallerIDMinMatch(numberE164)); + db.insert(Tables.PHONE_LOOKUP, null, phoneValues); + } + } + } + } + + @Override + protected int getTypeRank(int type) { + switch (type) { + case Phone.TYPE_MOBILE: return 0; + case Phone.TYPE_WORK: return 1; + case Phone.TYPE_HOME: return 2; + case Phone.TYPE_PAGER: return 3; + case Phone.TYPE_CUSTOM: return 4; + case Phone.TYPE_OTHER: return 5; + case Phone.TYPE_FAX_WORK: return 6; + case Phone.TYPE_FAX_HOME: return 7; + default: return 1000; + } + } +}
\ No newline at end of file diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java new file mode 100644 index 0000000..c737c8b --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.Photo; + +/** + * Handler for photo data rows. + */ +public class DataRowHandlerForPhoto extends DataRowHandler { + + public DataRowHandlerForPhoto(ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) { + super(dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE); + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + long dataId = super.insert(db, txContext, rawContactId, values); + if (!txContext.isNewRawContact(rawContactId)) { + mContactAggregator.updatePhotoId(db, rawContactId); + } + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { + return false; + } + + mContactAggregator.updatePhotoId(db, rawContactId); + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + int count = super.delete(db, txContext, c); + mContactAggregator.updatePhotoId(db, rawContactId); + return count; + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java new file mode 100644 index 0000000..db8caee --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.FullNameStyle; +import android.text.TextUtils; + +/** + * Handler for email address data rows. + */ +public class DataRowHandlerForStructuredName extends DataRowHandler { + private final NameSplitter mSplitter; + private final NameLookupBuilder mNameLookupBuilder; + + public DataRowHandlerForStructuredName(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator, NameSplitter splitter, + NameLookupBuilder nameLookupBuilder) { + super(dbHelper, aggregator, StructuredName.CONTENT_ITEM_TYPE); + mSplitter = splitter; + mNameLookupBuilder = nameLookupBuilder; + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + fixStructuredNameComponents(values, values); + + long dataId = super.insert(db, txContext, rawContactId, values); + + String name = values.getAsString(StructuredName.DISPLAY_NAME); + Integer fullNameStyle = values.getAsInteger(StructuredName.FULL_NAME_STYLE); + mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name, + fullNameStyle != null + ? mSplitter.getAdjustedFullNameStyle(fullNameStyle) + : FullNameStyle.UNDEFINED); + insertNameLookupForPhoneticName(rawContactId, dataId, values); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return dataId; + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + final long dataId = c.getLong(DataUpdateQuery._ID); + final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); + + final ContentValues augmented = getAugmentedValues(db, dataId, values); + if (augmented == null) { // No change + return false; + } + + fixStructuredNameComponents(augmented, values); + + super.update(db, txContext, values, c, callerIsSyncAdapter); + if (values.containsKey(StructuredName.DISPLAY_NAME) || + values.containsKey(StructuredName.PHONETIC_FAMILY_NAME) || + values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME) || + values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)) { + augmented.putAll(values); + String name = augmented.getAsString(StructuredName.DISPLAY_NAME); + mDbHelper.deleteNameLookup(dataId); + Integer fullNameStyle = augmented.getAsInteger(StructuredName.FULL_NAME_STYLE); + mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name, + fullNameStyle != null + ? mSplitter.getAdjustedFullNameStyle(fullNameStyle) + : FullNameStyle.UNDEFINED); + insertNameLookupForPhoneticName(rawContactId, dataId, augmented); + } + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return true; + } + + @Override + public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { + long dataId = c.getLong(DataDeleteQuery._ID); + long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); + + int count = super.delete(db, txContext, c); + + mDbHelper.deleteNameLookup(dataId); + fixRawContactDisplayName(db, txContext, rawContactId); + triggerAggregation(rawContactId); + return count; + } + + /** + * Specific list of structured fields. + */ + private final String[] STRUCTURED_FIELDS = new String[] { + StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME, + StructuredName.FAMILY_NAME, StructuredName.SUFFIX + }; + + /** + * Parses the supplied display name, but only if the incoming values do + * not already contain structured name parts. Also, if the display name + * is not provided, generate one by concatenating first name and last + * name. + */ + public void fixStructuredNameComponents(ContentValues augmented, ContentValues update) { + final String unstruct = update.getAsString(StructuredName.DISPLAY_NAME); + + final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct); + final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS); + + if (touchedUnstruct && !touchedStruct) { + NameSplitter.Name name = new NameSplitter.Name(); + mSplitter.split(name, unstruct); + name.toValues(update); + } else if (!touchedUnstruct + && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) { + // We need to update the display name when any structured components + // are specified, even when they are null, which is why we are checking + // areAnySpecified. The touchedStruct in the condition is an optimization: + // if there are non-null values, we know for a fact that some values are present. + NameSplitter.Name name = new NameSplitter.Name(); + name.fromValues(augmented); + // As the name could be changed, let's guess the name style again. + name.fullNameStyle = FullNameStyle.UNDEFINED; + mSplitter.guessNameStyle(name); + int unadjustedFullNameStyle = name.fullNameStyle; + name.fullNameStyle = mSplitter.getAdjustedFullNameStyle(name.fullNameStyle); + final String joined = mSplitter.join(name, true); + update.put(StructuredName.DISPLAY_NAME, joined); + + update.put(StructuredName.FULL_NAME_STYLE, unadjustedFullNameStyle); + update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle); + } else if (touchedUnstruct && touchedStruct){ + if (!update.containsKey(StructuredName.FULL_NAME_STYLE)) { + update.put(StructuredName.FULL_NAME_STYLE, + mSplitter.guessFullNameStyle(unstruct)); + } + if (!update.containsKey(StructuredName.PHONETIC_NAME_STYLE)) { + update.put(StructuredName.PHONETIC_NAME_STYLE, + mSplitter.guessPhoneticNameStyle(unstruct)); + } + } + } + + public void insertNameLookupForPhoneticName(long rawContactId, long dataId, + ContentValues values) { + if (values.containsKey(StructuredName.PHONETIC_FAMILY_NAME) + || values.containsKey(StructuredName.PHONETIC_GIVEN_NAME) + || values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME)) { + mDbHelper.insertNameLookupForPhoneticName(rawContactId, dataId, + values.getAsString(StructuredName.PHONETIC_FAMILY_NAME), + values.getAsString(StructuredName.PHONETIC_MIDDLE_NAME), + values.getAsString(StructuredName.PHONETIC_GIVEN_NAME)); + } + } +} diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java new file mode 100644 index 0000000..af60e48 --- /dev/null +++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License + */ +package com.android.providers.contacts; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.text.TextUtils; + +/** + * Handler for postal address data rows. + */ +public class DataRowHandlerForStructuredPostal extends DataRowHandler { + + /** + * Specific list of structured fields. + */ + private final String[] STRUCTURED_FIELDS = new String[] { + StructuredPostal.STREET, + StructuredPostal.POBOX, + StructuredPostal.NEIGHBORHOOD, + StructuredPostal.CITY, + StructuredPostal.REGION, + StructuredPostal.POSTCODE, + StructuredPostal.COUNTRY, + }; + + private final PostalSplitter mSplitter; + + public DataRowHandlerForStructuredPostal(ContactsDatabaseHelper dbHelper, + ContactAggregator aggregator, PostalSplitter splitter) { + super(dbHelper, aggregator, StructuredPostal.CONTENT_ITEM_TYPE); + mSplitter = splitter; + } + + @Override + public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, + ContentValues values) { + fixStructuredPostalComponents(values, values); + return super.insert(db, txContext, rawContactId, values); + } + + @Override + public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, + Cursor c, boolean callerIsSyncAdapter) { + final long dataId = c.getLong(DataUpdateQuery._ID); + final ContentValues augmented = getAugmentedValues(db, dataId, values); + if (augmented == null) { // No change + return false; + } + + fixStructuredPostalComponents(augmented, values); + super.update(db, txContext, values, c, callerIsSyncAdapter); + return true; + } + + /** + * Prepares the given {@link StructuredPostal} row, building + * {@link StructuredPostal#FORMATTED_ADDRESS} to match the structured + * values when missing. When structured components are missing, the + * unstructured value is assigned to {@link StructuredPostal#STREET}. + */ + private void fixStructuredPostalComponents(ContentValues augmented, ContentValues update) { + final String unstruct = update.getAsString(StructuredPostal.FORMATTED_ADDRESS); + + final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct); + final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS); + + final PostalSplitter.Postal postal = new PostalSplitter.Postal(); + + if (touchedUnstruct && !touchedStruct) { + mSplitter.split(postal, unstruct); + postal.toValues(update); + } else if (!touchedUnstruct + && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) { + // See comment in + postal.fromValues(augmented); + final String joined = mSplitter.join(postal); + update.put(StructuredPostal.FORMATTED_ADDRESS, joined); + } + } +} |