summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/contacts
diff options
context:
space:
mode:
authorTingting Wang <tingtingw@google.com>2015-04-18 03:23:33 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-04-18 03:23:33 +0000
commit9019dfd748d0a5e39cc041269d8d42e8ee8eb3ef (patch)
tree505b61700e46df4fffff852335091d234d41513b /src/com/android/providers/contacts
parent9231b1c4cec7179dfde777c3088535a9bb022781 (diff)
parent794ef4db9d70441393b756ce6c4cc456b9143b11 (diff)
downloadpackages_providers_ContactsProvider-9019dfd748d0a5e39cc041269d8d42e8ee8eb3ef.tar.gz
packages_providers_ContactsProvider-9019dfd748d0a5e39cc041269d8d42e8ee8eb3ef.tar.bz2
packages_providers_ContactsProvider-9019dfd748d0a5e39cc041269d8d42e8ee8eb3ef.zip
Merge "Update related tables from MetadataSync.data column."
Diffstat (limited to 'src/com/android/providers/contacts')
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java223
-rw-r--r--src/com/android/providers/contacts/MetadataEntryParser.java121
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 5154900a..a181f069 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 a276cae2..b90f9381 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);
+ }
+ }
}