summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/contacts
diff options
context:
space:
mode:
authorTingting Wang <tingtingw@google.com>2015-04-15 10:52:52 -0700
committerTingting Wang <tingtingw@google.com>2015-04-17 20:08:09 -0700
commit794ef4db9d70441393b756ce6c4cc456b9143b11 (patch)
treedb860cc17ffcb1cba70c8ba769e82ae10cd57f36 /src/com/android/providers/contacts
parentd1f777e7b9003ac5d6bac0d9039acfd05a7bec69 (diff)
downloadpackages_providers_ContactsProvider-794ef4db9d70441393b756ce6c4cc456b9143b11.zip
packages_providers_ContactsProvider-794ef4db9d70441393b756ce6c4cc456b9143b11.tar.gz
packages_providers_ContactsProvider-794ef4db9d70441393b756ce6c4cc456b9143b11.tar.bz2
Update related tables from MetadataSync.data column.
MetadataSync.data column info is parsed to MetadataEntry object. RawContact, Data, DataUsageStats, AggregationException tables need to be updated or inserted for the given rawContactId from MetadataEntry. BUG 20055193 Change-Id: I5bdfe2c44f9c9af8d4067dc136ceb62ab0b274af
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 47dbf2e..03ef037 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -151,6 +151,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;
@@ -4664,6 +4669,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) {
@@ -9039,6 +9200,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
@@ -9209,6 +9416,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);
+ }
+ }
}