diff options
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/providers/contacts/ContactsProvider2.java | 223 | ||||
-rw-r--r-- | src/com/android/providers/contacts/MetadataEntryParser.java | 121 |
2 files changed, 308 insertions, 36 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 5154900..a181f06 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -153,6 +153,11 @@ import com.android.providers.contacts.aggregation.util.CommonNicknameCache; import com.android.providers.contacts.database.ContactsTableUtil; import com.android.providers.contacts.database.DeletedContactsTableUtil; import com.android.providers.contacts.database.MoreDatabaseUtils; +import com.android.providers.contacts.MetadataEntryParser.AggregationData; +import com.android.providers.contacts.MetadataEntryParser.FieldData; +import com.android.providers.contacts.MetadataEntryParser.MetadataEntry; +import com.android.providers.contacts.MetadataEntryParser.RawContactInfo; +import com.android.providers.contacts.MetadataEntryParser.UsageStats; import com.android.providers.contacts.util.Clock; import com.android.providers.contacts.util.DbQueryUtils; import com.android.providers.contacts.util.NeededForTesting; @@ -4673,6 +4678,162 @@ public class ContactsProvider2 extends AbstractContactsProvider scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS); } + interface RawContactsBackupQuery { + String TABLE = Tables.RAW_CONTACTS; + String[] COLUMNS = new String[] { + RawContacts._ID, + }; + int RAW_CONTACT_ID = 0; + String SELECTION = RawContacts.DELETED + "=0 AND " + + RawContacts.BACKUP_ID + "=? AND " + + RawContactsColumns.ACCOUNT_ID + "=?"; + } + + /** + * Fetch rawContactId related to the given backupId. + * Return 0 if there's no such rawContact or it's deleted. + */ + private long queryRawContactId(SQLiteDatabase db, String backupId, long accountId) { + if (TextUtils.isEmpty(backupId)) { + return 0; + } + mSelectionArgs2[0] = backupId; + mSelectionArgs2[1] = String.valueOf(accountId); + long rawContactId = 0; + final Cursor cursor = db.query(RawContactsBackupQuery.TABLE, + RawContactsBackupQuery.COLUMNS, RawContactsBackupQuery.SELECTION, + mSelectionArgs2, null, null, null); + try { + if (cursor.moveToFirst()) { + rawContactId = cursor.getLong(RawContactsBackupQuery.RAW_CONTACT_ID); + } + } finally { + cursor.close(); + } + return rawContactId; + } + + interface DataHashQuery { + String TABLE = Tables.DATA; + String[] COLUMNS = new String[] { + Data._ID, + }; + int DATA_ID = 0; + String SELECTION = Data.HASH_ID + "=?"; + } + + /** + * Fetch a list of dataId related to the given hashId. + * Return empty list if there's no such data. + */ + private ArrayList<Long> queryDataId(SQLiteDatabase db, String hashId) { + if (TextUtils.isEmpty(hashId)) { + return new ArrayList<>(); + } + mSelectionArgs1[0] = hashId; + ArrayList<Long> result = new ArrayList<>(); + long dataId = 0; + final Cursor c = db.query(DataHashQuery.TABLE, DataHashQuery.COLUMNS, + DataHashQuery.SELECTION, mSelectionArgs1, null, null, null); + try { + while (c.moveToNext()) { + dataId = c.getLong(DataHashQuery.DATA_ID); + result.add(dataId); + } + } finally { + c.close(); + } + return result; + } + + private long searchRawContactIdForRawContactInfo(SQLiteDatabase db, + RawContactInfo rawContactInfo) { + if (rawContactInfo == null) { + return 0; + } + final String backupId = rawContactInfo.mBackupId; + final String accountType = rawContactInfo.mAccountType; + final String accountName = rawContactInfo.mAccountName; + final String dataSet = rawContactInfo.mDataSet; + ContentValues values = new ContentValues(); + values.put(AccountsColumns.ACCOUNT_TYPE, accountType); + values.put(AccountsColumns.ACCOUNT_NAME, accountName); + if (dataSet != null) { + values.put(AccountsColumns.DATA_SET, dataSet); + } + + final long accountId = replaceAccountInfoByAccountId(RawContacts.CONTENT_URI, values); + final long rawContactId = queryRawContactId(db, backupId, accountId); + return rawContactId; + } + + /** + * Update RawContact, Data, DataUsageStats, AggregationException tables from MetadataEntry. + */ + @NeededForTesting + void updateFromMetaDataEntry(SQLiteDatabase db, MetadataEntry metadataEntry) { + final RawContactInfo rawContactInfo = metadataEntry.mRawContactInfo; + final long rawContactId = searchRawContactIdForRawContactInfo(db, rawContactInfo); + if (rawContactId == 0) { + return; + } + + ContentValues rawContactValues = new ContentValues(); + rawContactValues.put(RawContacts.SEND_TO_VOICEMAIL, metadataEntry.mSendToVoicemail); + rawContactValues.put(RawContacts.STARRED, metadataEntry.mStarred); + rawContactValues.put(RawContacts.PINNED, metadataEntry.mPinned); + updateRawContact(db, rawContactId, rawContactValues, true); + + // Update Data and DataUsageStats table. + for (int i = 0; i < metadataEntry.mFieldDatas.size(); i++) { + final FieldData fieldData = metadataEntry.mFieldDatas.get(i); + final String dataHashId = fieldData.mDataHashId; + final ArrayList<Long> dataIds = queryDataId(db, dataHashId); + + for (long dataId : dataIds) { + // Update is_primary and is_super_primary. + ContentValues dataValues = new ContentValues(); + dataValues.put(Data.IS_PRIMARY, fieldData.mIsPrimary); + dataValues.put(Data.IS_SUPER_PRIMARY, fieldData.mIsSuperPrimary); + updateData(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), + dataValues, null, null, true); + + // Update UsageStats. + for (int j = 0; j < fieldData.mUsageStatsList.size(); j++) { + final UsageStats usageStats = fieldData.mUsageStatsList.get(j); + final String usageType = usageStats.mUsageType; + final int typeInt = getDataUsageFeedbackType(usageType.toLowerCase(), null); + final long lastTimeUsed = usageStats.mLastTimeUsed; + final int timesUsed = usageStats.mTimesUsed; + ContentValues usageStatsValues = new ContentValues(); + usageStatsValues.put(DataUsageStatColumns.DATA_ID, dataId); + usageStatsValues.put(DataUsageStatColumns.USAGE_TYPE_INT, typeInt); + usageStatsValues.put(DataUsageStatColumns.LAST_TIME_USED, lastTimeUsed); + usageStatsValues.put(DataUsageStatColumns.TIMES_USED, timesUsed); + updateDataUsageStats(db, usageStatsValues); + } + } + } + + // Update AggregationException table. + for (int i = 0; i < metadataEntry.mAggregationDatas.size(); i++) { + final AggregationData aggregationData = metadataEntry.mAggregationDatas.get(i); + final int typeInt = getAggregationType(aggregationData.mType, null); + final RawContactInfo aggregationContact1 = aggregationData.mRawContactInfo1; + final RawContactInfo aggregationContact2 = aggregationData.mRawContactInfo2; + final long rawContactId1 = searchRawContactIdForRawContactInfo(db, aggregationContact1); + final long rawContactId2 = searchRawContactIdForRawContactInfo(db, aggregationContact2); + if (rawContactId1 == 0 || rawContactId2 == 0) { + continue; + } + ContentValues values = new ContentValues(); + values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); + values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); + values.put(AggregationExceptions.TYPE, typeInt); + updateAggregationException(db, values); + } + } + /** return serialized version of {@code accounts} */ @VisibleForTesting static String accountsToString(Set<Account> accounts) { @@ -9061,6 +9222,52 @@ public class ContactsProvider2 extends AbstractContactsProvider } /** + * Update {@link Tables#DATA_USAGE_STAT}. + * Update or insert usageType, lastTimeUsed, and timesUsed for specific dataId. + */ + private void updateDataUsageStats(SQLiteDatabase db, ContentValues values) { + final String dataId = values.getAsString(DataUsageStatColumns.DATA_ID); + final String type = values.getAsString(DataUsageStatColumns.USAGE_TYPE_INT); + final String lastTimeUsed = values.getAsString(DataUsageStatColumns.LAST_TIME_USED); + final String timesUsed = values.getAsString(DataUsageStatColumns.TIMES_USED); + + mSelectionArgs2[0] = dataId; + mSelectionArgs2[1] = type; + final Cursor cursor = db.query(DataUsageStatQuery.TABLE, + DataUsageStatQuery.COLUMNS, DataUsageStatQuery.SELECTION, + mSelectionArgs2, null, null, null); + + try { + if (cursor.moveToFirst()) { + final long id = cursor.getLong(DataUsageStatQuery.ID); + + mSelectionArgs3[0] = lastTimeUsed; + mSelectionArgs3[1] = timesUsed; + mSelectionArgs3[2] = String.valueOf(id); + db.execSQL("UPDATE " + Tables.DATA_USAGE_STAT + + " SET " + DataUsageStatColumns.LAST_TIME_USED + "=?" + + "," + DataUsageStatColumns.TIMES_USED + "=?" + + " WHERE " + DataUsageStatColumns._ID + "=?", + mSelectionArgs3); + } else { + mSelectionArgs4[0] = dataId; + mSelectionArgs4[1] = type; + mSelectionArgs4[2] = timesUsed; + mSelectionArgs4[3] = lastTimeUsed; + db.execSQL("INSERT INTO " + Tables.DATA_USAGE_STAT + + "(" + DataUsageStatColumns.DATA_ID + + "," + DataUsageStatColumns.USAGE_TYPE_INT + + "," + DataUsageStatColumns.TIMES_USED + + "," + DataUsageStatColumns.LAST_TIME_USED + + ") VALUES (?,?,?,?)", + mSelectionArgs4); + } + } finally { + cursor.close(); + } + } + + /** * Returns a sort order String for promoting data rows (email addresses, phone numbers, etc.) * associated with a primary account. The primary account should be supplied from applications * with {@link ContactsContract#PRIMARY_ACCOUNT_NAME} and @@ -9234,6 +9441,22 @@ public class ContactsProvider2 extends AbstractContactsProvider throw new IllegalArgumentException("Invalid usage type " + type); } + private static final int getAggregationType(String type, Integer defaultType) { + if ("TOGETHER".equalsIgnoreCase(type)) { + return AggregationExceptions.TYPE_KEEP_TOGETHER; // 1 + } + if ("SEPARATE".equalsIgnoreCase(type)) { + return AggregationExceptions.TYPE_KEEP_SEPARATE; // 2 + } + if ("UNSET".equalsIgnoreCase(type)) { + return AggregationExceptions.TYPE_AUTOMATIC; // 0 + } + if (defaultType != null) { + return defaultType; + } + throw new IllegalArgumentException("Invalid aggregation type " + type); + } + /** Use only for debug logging */ @Override public String toString() { diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java index a276cae..b90f938 100644 --- a/src/com/android/providers/contacts/MetadataEntryParser.java +++ b/src/com/android/providers/contacts/MetadataEntryParser.java @@ -31,8 +31,15 @@ public class MetadataEntryParser { private final static String UNIQUE_CONTACT_ID = "unique_contact_id"; private final static String ACCOUNT_TYPE = "account_type"; private final static String CUSTOM_ACCOUNT_TYPE = "custom_account_type"; + private final static String ENUM_VALUE_FOR_GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT"; + private final static String GOOGLE_ACCOUNT_TYPE = "com.google"; private final static String ENUM_VALUE_FOR_CUSTOM_ACCOUNT = "CUSTOM_ACCOUNT"; private final static String ACCOUNT_NAME = "account_name"; + private final static String DATA_SET = "data_set"; + private final static String ENUM_FOR_PLUS_DATA_SET = "GOOGLE_PLUS"; + private final static String ENUM_FOR_CUSTOM_DATA_SET = "CUSTOM"; + private final static String PLUS_DATA_SET_TYPE = "plus"; + private final static String CUSTOM_DATA_SET = "custom_data_set"; private final static String CONTACT_ID = "contact_id"; private final static String CONTACT_PREFS = "contact_prefs"; private final static String SEND_TO_VOICEMAIL = "send_to_voicemail"; @@ -67,15 +74,15 @@ public class MetadataEntryParser { @NeededForTesting public static class FieldData { - final long mFieldDataId; + final String mDataHashId; final boolean mIsPrimary; final boolean mIsSuperPrimary; final ArrayList<UsageStats> mUsageStatsList; @NeededForTesting - public FieldData(long fieldDataId, boolean isPrimary, boolean isSuperPrimary, + public FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary, ArrayList<UsageStats> usageStatsList) { - this.mFieldDataId = fieldDataId; + this.mDataHashId = dataHashId; this.mIsPrimary = isPrimary; this.mIsSuperPrimary = isSuperPrimary; this.mUsageStatsList = usageStatsList; @@ -83,24 +90,40 @@ public class MetadataEntryParser { } @NeededForTesting + public static class RawContactInfo { + final String mBackupId; + final String mAccountType; + final String mAccountName; + final String mDataSet; + + @NeededForTesting + public RawContactInfo(String backupId, String accountType, String accountName, + String dataSet) { + this.mBackupId = backupId; + this.mAccountType = accountType; + this.mAccountName = accountName; + mDataSet = dataSet; + } + } + + @NeededForTesting public static class AggregationData { - final long mRawContactId1; - final long mRawContactId2; + final RawContactInfo mRawContactInfo1; + final RawContactInfo mRawContactInfo2; final String mType; @NeededForTesting - public AggregationData(long rawContactId1, long rawContactId2, String type) { - this.mRawContactId1 = rawContactId1; - this.mRawContactId2 = rawContactId2; + public AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2, + String type) { + this.mRawContactInfo1 = rawContactInfo1; + this.mRawContactInfo2 = rawContactInfo2; this.mType = type; } } @NeededForTesting public static class MetadataEntry { - final long mRawContactId; - final String mAccountType; - final String mAccountName; + final RawContactInfo mRawContactInfo; final int mSendToVoicemail; final int mStarred; final int mPinned; @@ -108,13 +131,11 @@ public class MetadataEntryParser { final ArrayList<AggregationData> mAggregationDatas; @NeededForTesting - public MetadataEntry(long rawContactId, String accountType, String accountName, + public MetadataEntry(RawContactInfo rawContactInfo, int sendToVoicemail, int starred, int pinned, - ArrayList<FieldData> fieldDatas, - ArrayList<AggregationData> aggregationDatas) { - this.mRawContactId = rawContactId; - this.mAccountType = accountType; - this.mAccountName = accountName; + ArrayList<FieldData> fieldDatas, + ArrayList<AggregationData> aggregationDatas) { + this.mRawContactInfo = rawContactInfo; this.mSendToVoicemail = sendToVoicemail; this.mStarred = starred; this.mPinned = pinned; @@ -132,18 +153,8 @@ public class MetadataEntryParser { try { final JSONObject root = new JSONObject(inputData); // Parse to get rawContactId and account info. - final JSONObject uniqueContact = root.getJSONObject(UNIQUE_CONTACT_ID); - final String rawContactId = uniqueContact.getString(CONTACT_ID); - String accountType = uniqueContact.getString(ACCOUNT_TYPE); - if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) { - accountType = uniqueContact.getString(CUSTOM_ACCOUNT_TYPE); - } - final String accountName = uniqueContact.getString(ACCOUNT_NAME); - if (TextUtils.isEmpty(rawContactId) || TextUtils.isEmpty(accountType) - || TextUtils.isEmpty(accountName)) { - throw new IllegalArgumentException( - "contact_id, account_type, account_name cannot be empty."); - } + final JSONObject uniqueContactJSON = root.getJSONObject(UNIQUE_CONTACT_ID); + final RawContactInfo rawContactInfo = parseUniqueContact(uniqueContactJSON); // Parse contactPrefs to get sendToVoicemail, starred, pinned. final JSONObject contactPrefs = root.getJSONObject(CONTACT_PREFS); @@ -165,16 +176,16 @@ public class MetadataEntryParser { "There should be two contacts for each aggregation."); } final JSONObject rawContact1 = contacts.getJSONObject(0); - final long rawContactId1 = rawContact1.getLong(CONTACT_ID); + final RawContactInfo aggregationContact1 = parseUniqueContact(rawContact1); final JSONObject rawContact2 = contacts.getJSONObject(1); - final long rawContactId2 = rawContact2.getLong(CONTACT_ID); + final RawContactInfo aggregationContact2 = parseUniqueContact(rawContact2); final String type = aggregationData.getString(TYPE); if (TextUtils.isEmpty(type)) { throw new IllegalArgumentException("Aggregation type cannot be empty."); } final AggregationData aggregation = new AggregationData( - rawContactId1, rawContactId2, type); + aggregationContact1, aggregationContact2, type); aggregationsList.add(aggregation); } } @@ -186,7 +197,10 @@ public class MetadataEntryParser { for (int i = 0; i < fieldDatas.length(); i++) { final JSONObject fieldData = fieldDatas.getJSONObject(i); - final long fieldDataId = fieldData.getLong(FIELD_DATA_ID); + final String dataHashId = fieldData.getString(FIELD_DATA_ID); + if (TextUtils.isEmpty(dataHashId)) { + throw new IllegalArgumentException("Field data hash id cannot be empty."); + } final JSONObject fieldDataPrefs = fieldData.getJSONObject(FIELD_DATA_PREFS); final boolean isPrimary = fieldDataPrefs.getBoolean(IS_PRIMARY); final boolean isSuperPrimary = fieldDataPrefs.getBoolean(IS_SUPER_PRIMARY); @@ -209,17 +223,52 @@ public class MetadataEntryParser { } } - final FieldData fieldDataParse = new FieldData(fieldDataId, isPrimary, + final FieldData fieldDataParse = new FieldData(dataHashId, isPrimary, isSuperPrimary, usageStatsList); fieldDatasList.add(fieldDataParse); } } - final MetadataEntry metaDataEntry = new MetadataEntry(Long.parseLong(rawContactId), - accountType, accountName, sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned, + final MetadataEntry metaDataEntry = new MetadataEntry(rawContactInfo, + sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned, fieldDatasList, aggregationsList); return metaDataEntry; } catch (JSONException e) { throw new IllegalArgumentException("JSON Exception.", e); } } + + private static RawContactInfo parseUniqueContact(JSONObject uniqueContactJSON) { + try { + final String backupId = uniqueContactJSON.getString(CONTACT_ID); + final String accountName = uniqueContactJSON.getString(ACCOUNT_NAME); + String accountType = uniqueContactJSON.getString(ACCOUNT_TYPE); + if (ENUM_VALUE_FOR_GOOGLE_ACCOUNT.equals(accountType)) { + accountType = GOOGLE_ACCOUNT_TYPE; + } else if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) { + accountType = uniqueContactJSON.getString(CUSTOM_ACCOUNT_TYPE); + } else { + throw new IllegalArgumentException("Unknown account type."); + } + + String dataSet = null; + switch (uniqueContactJSON.getString(DATA_SET)) { + case ENUM_FOR_PLUS_DATA_SET: + dataSet = PLUS_DATA_SET_TYPE; + break; + case ENUM_FOR_CUSTOM_DATA_SET: + dataSet = uniqueContactJSON.getString(CUSTOM_DATA_SET); + break; + } + if (TextUtils.isEmpty(backupId) || TextUtils.isEmpty(accountType) + || TextUtils.isEmpty(accountName)) { + throw new IllegalArgumentException( + "Contact backup id, account type, account name cannot be empty."); + } + final RawContactInfo rawContactInfo = new RawContactInfo( + backupId, accountType, accountName, dataSet); + return rawContactInfo; + } catch (JSONException e) { + throw new IllegalArgumentException("JSON Exception.", e); + } + } } |