aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-08 16:03:37 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-04-08 16:03:37 +0000
commit01bc37691da1a5c62902ddbe881cf52164f51382 (patch)
tree983893b1271bb030ae562c6cce8dd65d53e5fd6a
parent159621a129b6d74a9423adbead833c215acae6b5 (diff)
parent49a88f9bba34225f3bd7532a79dc5ae89b726a1d (diff)
downloadContactsProvider-aml_tz2_305400100.tar.gz
Change-Id: I3c1b823abbe933099659472ffb18c7128d00e6f1
-rw-r--r--Android.bp20
-rw-r--r--AndroidManifest.xml38
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS6
-rw-r--r--res/values-te/strings.xml12
-rw-r--r--src/com/android/providers/contacts/AccountWithDataSet.java23
-rw-r--r--src/com/android/providers/contacts/CallComposerLocationProvider.java194
-rw-r--r--src/com/android/providers/contacts/CallLogDatabaseHelper.java25
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java356
-rw-r--r--src/com/android/providers/contacts/ContactDirectoryManager.java3
-rw-r--r--src/com/android/providers/contacts/ContactMetadataProvider.java443
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java260
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java474
-rw-r--r--src/com/android/providers/contacts/DataRowHandler.java12
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java5
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForEmail.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForIdentity.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForNickname.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForOrganization.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoto.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredName.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java4
-rw-r--r--src/com/android/providers/contacts/MetadataEntryParser.java296
-rw-r--r--src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java33
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java4
-rw-r--r--src/com/android/providers/contacts/SearchIndexManager.java6
-rw-r--r--src/com/android/providers/contacts/TransactionContext.java28
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java14
-rw-r--r--src/com/android/providers/contacts/VoicemailNotifier.java19
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java9
-rw-r--r--src/com/android/providers/contacts/debug/ContactsDumpActivity.java7
-rw-r--r--src/com/android/providers/contacts/util/LogFields.java144
-rw-r--r--src/com/android/providers/contacts/util/LogUtils.java97
-rw-r--r--test_common/Android.bp11
-rw-r--r--tests/Android.bp11
-rw-r--r--tests/assets/test1/testFileDeviceContactMetadataJSON.txt69
-rw-r--r--tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java4
-rw-r--r--tests/src/com/android/providers/contacts/CallLogProviderTest.java10
-rw-r--r--tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java586
-rw-r--r--tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java23
-rw-r--r--tests/src/com/android/providers/contacts/ContactsActor.java5
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java23
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java561
-rw-r--r--tests/src/com/android/providers/contacts/MetadataEntryParserTest.java304
-rw-r--r--tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java9
-rw-r--r--tests2/Android.bp11
48 files changed, 2710 insertions, 1481 deletions
diff --git a/Android.bp b/Android.bp
index 4b345e61..8991628d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,25 +1,5 @@
-package {
- default_applicable_licenses: [
- "packages_providers_ContactsProvider_license",
- ],
-}
-
-// Added automatically by a large-scale-change
-// See: http://go/android-license-faq
-license {
- name: "packages_providers_ContactsProvider_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- ],
- license_text: [
- "NOTICE",
- ],
-}
-
android_app {
name: "ContactsProvider",
- defaults: ["platform_app_defaults"],
// Only compile source java files in this apk.
srcs: [
"src/**/*.java",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2b101bef..9542e6d9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,8 +4,6 @@
android:sharedUserLabel="@string/sharedUserLabel">
<uses-permission android:name="android.permission.BIND_DIRECTORY_SEARCH" />
- <!-- For sending voicemail intents -->
- <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
@@ -20,19 +18,12 @@
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
- <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<permission
android:name="android.permission.SEND_CALL_LOG_CHANGE"
android:label="Broadcast that a change happened to the call log."
android:protectionLevel="signature|system"/>
- <permission
- android:name="android.contacts.permission.MANAGE_SIM_ACCOUNTS"
- android:label="Change known SIM accounts in ContactsProvider."
- android:protectionLevel="signature"/>
-
<application android:process="android.process.acore"
android:label="@string/app_label"
android:allowBackup="false"
@@ -68,24 +59,13 @@
android:writePermission="android.permission.WRITE_CALL_LOG">
</provider>
- <!-- Separate provider for the locations stored in call provider.
- Uses a different db in order to prevent SQL injection attacks from bypassing
- location permission requirements -->
- <provider android:name="CallComposerLocationProvider"
- android:authorities="call_composer_locations"
- android:syncable="false" android:multiprocess="false"
- android:exported="true"
- android:readPermission="android.permission.READ_CALL_LOG"
- android:writePermission="android.permission.WRITE_CALL_LOG">
- </provider>
-
<provider android:name="ShadowCallLogProvider"
android:authorities="call_log_shadow"
android:syncable="false" android:multiprocess="false"
android:exported="true"
android:directBootAware="true"
- android:readPermission="android.permission.INTERACT_ACROSS_USERS"
- android:writePermission="android.permission.INTERACT_ACROSS_USERS">
+ android:readPermission="android.permission.MANAGE_USERS"
+ android:writePermission="android.permission.MANAGE_USERS">
</provider>
<!-- Note: While this provider does not declare a permission explicitly, it enforces that
@@ -97,9 +77,14 @@
android:exported="true">
</provider>
+ <provider android:name="ContactMetadataProvider"
+ android:authorities="com.android.contacts.metadata"
+ android:multiprocess="false"
+ android:exported="true">
+ </provider>
+
<!-- Handles database upgrades after OTAs, then disables itself -->
- <receiver android:name="ContactsUpgradeReceiver"
- android:exported="true">
+ <receiver android:name="ContactsUpgradeReceiver">
<!-- This broadcast is sent after the core system has finished
booting, before the home app is launched or BOOT_COMPLETED
is sent. -->
@@ -109,7 +94,6 @@
</receiver>
<receiver android:name="PhoneAccountRegistrationReceiver"
- android:exported="true"
android:permission="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION">
<!-- Broadcast sent after a phone account is registered in telecom. -->
<intent-filter>
@@ -117,8 +101,7 @@
</intent-filter>
</receiver>
- <receiver android:name="LocaleChangeReceiver"
- android:exported="true">
+ <receiver android:name="LocaleChangeReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter>
@@ -127,7 +110,6 @@
<activity android:name=".debug.ContactsDumpActivity"
android:label="@string/debug_dump_title"
android:theme="@android:style/Theme.Holo.Dialog"
- android:exported="true"
>
<intent-filter>
<action android:name="com.android.providers.contacts.DUMP_DATABASE"/>
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
index 6ee21e68..dc2d0b32 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,8 +1,2 @@
omakoto@google.com
yamasani@google.com
-
-# For calllog provider
-tgunn@google.com
-hallliu@google.com
-breadley@google.com
-rgreenwalt@google.com
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 81a54d9d..14e5d1f3 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -17,19 +17,19 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="sharedUserLabel" msgid="8024311725474286801">"Android ప్రధాన యాప్‌లు"</string>
- <string name="app_label" msgid="3389954322874982620">"కాంటాక్ట్‌ల స్టోరేజ్"</string>
+ <string name="app_label" msgid="3389954322874982620">"పరిచయాల నిల్వ"</string>
<string name="provider_label" msgid="6012150850819899907">"కాంటాక్ట్‌లు"</string>
- <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"కాంటాక్ట్‌ల అప్‌గ్రేడ్‌కు మరింత మెమరీ అవసరం."</string>
- <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"కాంటాక్ట్‌ల కోసం స్టోరేజ్‌ను అప్‌గ్రేడ్ చేస్తోంది"</string>
+ <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"పరిచయాల అప్‌గ్రేడ్‌కు మరింత మెమరీ అవసరం."</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"పరిచయాల కోసం నిల్వను అప్‌గ్రేడ్ చేస్తోంది"</string>
<string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"అప్‌గ్రేడ్‌ను పూర్తి చేయడానికి నొక్కండి."</string>
<string name="default_directory" msgid="93961630309570294">"కాంటాక్ట్‌లు"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ఇతరం"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"దీని నుండి వాయిస్ మెయిల్ "</string>
- <string name="debug_dump_title" msgid="4916885724165570279">"కాంటాక్ట్‌ల డేటాబేస్‌ను కాపీ చేయండి"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"మీరు 1) అన్ని కాంటాక్ట్‌లకు సంబంధించిన సమాచారాన్ని మరియు మొత్తం కాల్ లాగ్‌ను కలిగి ఉండే మీ డేటాబేస్ యొక్క కాపీని అంతర్గత స్టోరేజ్‌లో రూపొందించి 2) దాన్ని ఇమెయిల్ చేయబోతున్నారు. మీరు దాన్ని పరికరం నుండి విజయవంతంగా కాపీ చేసిన తర్వాత లేదా ఈమెయిల్‌ను స్వీకరించిన తర్వాత కాపీని తొలగించడం మర్చిపోవద్దు."</string>
+ <string name="debug_dump_title" msgid="4916885724165570279">"పరిచయాల డేటాబేస్‌ను కాపీ చేయి"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"మీరు 1) అన్ని పరిచయాలకు సంబంధించిన సమాచారాన్ని మరియు మొత్తం కాల్ లాగ్‌ను కలిగి ఉండే మీ డేటాబేస్ యొక్క నకలును అంతర్గత నిల్వలో రూపొందించి 2) దాన్ని ఇమెయిల్ చేయబోతున్నారు. మీరు దాన్ని పరికరం నుండి విజయవంతంగా కాపీ చేసిన తర్వాత లేదా ఇమెయిల్‌ను స్వీకరించిన తర్వాత కాపీని తొలగించడం మర్చిపోవద్దు."</string>
<string name="debug_dump_delete_button" msgid="7832879421132026435">"ఇప్పుడే తొలగించు"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"ప్రారంభించు"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"మీ పైల్‌ను పంపడానికి ప్రోగ్రామ్‌ను ఎంచుకోండి"</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"కాంటాక్ట్‌ల Db జోడించబడింది"</string>
- <string name="debug_dump_email_body" msgid="4577749800871444318">"జోడింపు నా అన్ని కాంటాక్ట్‌ల సమాచారాన్ని కలిగి ఉన్న నా కాంటాక్ట్‌ల డేటాబేస్. జాగ్రత్తగా వ్యవహరించండి."</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"జోడింపు నా అన్ని పరిచయాల సమాచారాన్ని కలిగి ఉన్న నా పరిచయాల డేటాబేస్. జాగ్రత్తగా వ్యవహరించండి."</string>
</resources>
diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java
index e1f633ea..bfae1128 100644
--- a/src/com/android/providers/contacts/AccountWithDataSet.java
+++ b/src/com/android/providers/contacts/AccountWithDataSet.java
@@ -17,14 +17,10 @@
package com.android.providers.contacts;
import android.accounts.Account;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.SimAccount;
import android.text.TextUtils;
import com.google.common.base.Objects;
-import java.util.List;
-
/**
* Account information that includes the data set, if any.
*/
@@ -109,23 +105,4 @@ public class AccountWithDataSet {
}
return false;
}
-
- /**
- * @return {@code true} if there is a {@link SimAccount} with a matching account name and type
- * in the passed list.
- * The list should be obtained from {@link ContactsDatabaseHelper#getAllSimAccounts()} so it
- * will already have valid SIM accounts so only name and type need to be compared.
- */
- public boolean inSimAccounts(List<SimAccount> simAccountList) {
- // Note we don't want to create a new SimAccount object from this instance, as this method
- // does not need to know about the SIM slot or the EF type. It only needs to know whether
- // the name and type match since the passed list will only contain valid SIM accounts.
- for (SimAccount simAccount : simAccountList) {
- if (Objects.equal(simAccount.getAccountName(), getAccountName())
- && Objects.equal(simAccount.getAccountType(), getAccountType())) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/src/com/android/providers/contacts/CallComposerLocationProvider.java b/src/com/android/providers/contacts/CallComposerLocationProvider.java
deleted file mode 100644
index 568a1899..00000000
--- a/src/com/android/providers/contacts/CallComposerLocationProvider.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2021 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 static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentProvider;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Process;
-import android.provider.CallLog;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-
-import com.android.providers.contacts.util.SelectionBuilder;
-
-import java.util.Objects;
-
-public class CallComposerLocationProvider extends ContentProvider {
- private static final String TAG = CallComposerLocationProvider.class.getSimpleName();
- private static final String DB_NAME = "call_composer_locations.db";
- private static final String TABLE_NAME = "locations";
- private static final int VERSION = 1;
-
- private static final int LOCATION = 1;
- private static final int LOCATION_ID = 2;
-
- private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(@Nullable Context context, @Nullable String name,
- @Nullable SQLiteDatabase.CursorFactory factory, int version) {
- super(context, name, factory, version);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_NAME+ " (" +
- CallLog.Locations._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- CallLog.Locations.LATITUDE + " REAL, " +
- CallLog.Locations.LONGITUDE + " REAL" +
- ");");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // Nothing to do here, still on version 1.
- }
- }
-
- private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- static {
- sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "", LOCATION);
- sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "/#", LOCATION_ID);
- }
-
- private OpenHelper mOpenHelper;
-
- @Override
- public boolean onCreate() {
- mOpenHelper = new OpenHelper(getContext(), DB_NAME, null, VERSION);
- return true;
- }
-
- @Nullable
- @Override
- public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
- @Nullable String[] selectionArgs, @Nullable String sortOrder) {
- enforceAccessRestrictions();
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(TABLE_NAME);
- qb.setStrict(true);
- qb.setStrictGrammar(true);
- final int match = sURIMatcher.match(uri);
-
- final SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
- switch (match) {
- case LOCATION_ID: {
- selectionBuilder.addClause(getEqualityClause(CallLog.Locations._ID,
- parseLocationIdFromUri(uri)));
- break;
- }
- default:
- throw new IllegalArgumentException("Provided URI is not supported for query.");
- }
-
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- return qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
- null, sortOrder, null);
- }
-
- @Nullable
- @Override
- public String getType(@NonNull Uri uri) {
- final int match = sURIMatcher.match(uri);
- switch (match) {
- case LOCATION_ID:
- return CallLog.Locations.CONTENT_ITEM_TYPE;
- case LOCATION:
- return CallLog.Locations.CONTENT_TYPE;
- default:
- return null;
- }
- }
-
- @Nullable
- @Override
- public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
- enforceAccessRestrictions();
- long id = mOpenHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
- return ContentUris.withAppendedId(CallLog.Locations.CONTENT_URI, id);
- }
-
- @Override
- public int delete(@NonNull Uri uri, @Nullable String selection,
- @Nullable String[] selectionArgs) {
- enforceAccessRestrictions();
- final int match = sURIMatcher.match(uri);
- switch (match) {
- case LOCATION_ID:
- long id = parseLocationIdFromUri(uri);
- return mOpenHelper.getWritableDatabase().delete(TABLE_NAME,
- CallLog.Locations._ID + " = ?", new String[] {String.valueOf(id)});
- case LOCATION:
- Log.w(TAG, "Deleting entire location table!");
- return mOpenHelper.getWritableDatabase().delete(TABLE_NAME, "1", null);
- default:
- throw new IllegalArgumentException("delete() on the locations"
- + " does not support the uri " + uri.toString());
- }
- }
-
- @Override
- public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
- @Nullable String[] selectionArgs) {
- enforceAccessRestrictions();
- throw new UnsupportedOperationException(
- "Call composer location db does not support updates");
- }
-
- private long parseLocationIdFromUri(Uri uri) {
- try {
- return Long.parseLong(uri.getPathSegments().get(0));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Invalid location id in uri: " + uri, e);
- }
- }
-
- private void enforceAccessRestrictions() {
- int uid = Binder.getCallingUid();
- if (uid == Process.SYSTEM_UID || uid == Process.myUid() || uid == Process.PHONE_UID) {
- return;
- }
- String defaultDialerPackageName = getContext().getSystemService(TelecomManager.class)
- .getDefaultDialerPackage();
- if (TextUtils.isEmpty(defaultDialerPackageName)) {
- throw new SecurityException("Access to call composer locations is only allowed for the"
- + " default dialer, but the default dialer is unset");
- }
- String[] callingPackageCandidates = getContext().getPackageManager().getPackagesForUid(uid);
- for (String packageCandidate : callingPackageCandidates) {
- if (Objects.equals(packageCandidate, defaultDialerPackageName)) {
- return;
- }
- }
- throw new SecurityException("Access to call composer locations is only allowed for the "
- + "default dialer: " + defaultDialerPackageName);
- }
-}
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index 22f1cad4..39c3d5f1 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -39,7 +39,7 @@ import com.android.providers.contacts.util.PropertyUtils;
public class CallLogDatabaseHelper {
private static final String TAG = "CallLogDatabaseHelper";
- private static final int DATABASE_VERSION = 10;
+ private static final int DATABASE_VERSION = 8;
private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE
@@ -152,11 +152,6 @@ public class CallLogDatabaseHelper {
Calls.CALL_SCREENING_COMPONENT_NAME + " TEXT," +
Calls.CALL_SCREENING_APP_NAME + " TEXT," +
Calls.BLOCK_REASON + " INTEGER NOT NULL DEFAULT 0," +
- Calls.MISSED_REASON + " INTEGER NOT NULL DEFAULT 0," +
- Calls.PRIORITY + " INTEGER NOT NULL DEFAULT 0," +
- Calls.SUBJECT + " TEXT," +
- Calls.LOCATION + " TEXT," +
- Calls.COMPOSER_PHOTO_URI + " TEXT," +
Voicemails._DATA + " TEXT," +
Voicemails.HAS_CONTENT + " INTEGER," +
@@ -225,14 +220,6 @@ public class CallLogDatabaseHelper {
if (oldVersion < 8) {
upgradetoVersion8(db);
}
-
- if (oldVersion < 9) {
- upgradeToVersion9(db);
- }
-
- if (oldVersion < 10) {
- upgradeToVersion10(db);
- }
}
}
@@ -463,16 +450,6 @@ public class CallLogDatabaseHelper {
}
}
- private void upgradeToVersion9(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD missed_reason INTEGER NOT NULL DEFAULT 0");
- }
-
- private void upgradeToVersion10(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD priority INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE calls ADD subject TEXT");
- db.execSQL("ALTER TABLE calls ADD location TEXT");
- db.execSQL("ALTER TABLE calls ADD composer_photo_uri TEXT");
- }
/**
* Perform the migration from the contacts2.db (of the latest version) to the current calllog/
* voicemail status tables.
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index c832e9b4..3ee4ae9f 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -20,8 +20,6 @@ import static com.android.providers.contacts.util.DbQueryUtils.checkForSupported
import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
@@ -38,10 +36,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelableException;
-import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.CallLog;
@@ -49,7 +43,6 @@ import android.provider.CallLog.Calls;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -62,25 +55,11 @@ import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.UserUtils;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.UUID;
import java.util.concurrent.CountDownLatch;
-import java.util.stream.Collectors;
/**
* Call log content provider.
@@ -102,57 +81,18 @@ public class CallLogProvider extends ContentProvider {
private static final String EXCLUDE_HIDDEN_SELECTION = getEqualityClause(
Calls.PHONE_ACCOUNT_HIDDEN, 0);
- private static final String CALL_COMPOSER_PICTURE_DIRECTORY_NAME = "call_composer_pics";
- private static final String CALL_COMPOSER_ALL_USERS_DIRECTORY_NAME = "all_users";
-
- // Constants to be used with ContentProvider#call in order to sync call composer pics between
- // users. Defined here because they're for internal use only.
- /**
- * Method name used to get a list of {@link Uri}s for call composer pictures inserted for all
- * users after a certain date
- */
- private static final String GET_CALL_COMPOSER_IMAGE_URIS =
- "com.android.providers.contacts.GET_CALL_COMPOSER_IMAGE_URIS";
-
- /**
- * Long-valued extra containing the date to filter by expressed as milliseconds after the epoch.
- */
- private static final String EXTRA_SINCE_DATE =
- "com.android.providers.contacts.extras.SINCE_DATE";
-
- /**
- * Boolean-valued extra indicating whether to read from the shadow portion of the calllog
- * (i.e. device-encrypted storage rather than credential-encrypted)
- */
- private static final String EXTRA_IS_SHADOW =
- "com.android.providers.contacts.extras.IS_SHADOW";
-
- /**
- * Boolean-valued extra indicating whether to return Uris only for those images that are
- * supposed to be inserted for all users.
- */
- private static final String EXTRA_ALL_USERS_ONLY =
- "com.android.providers.contacts.extras.ALL_USERS_ONLY";
-
- private static final String EXTRA_RESULT_URIS =
- "com.android.provider.contacts.extras.EXTRA_RESULT_URIS";
-
@VisibleForTesting
static final String[] CALL_LOG_SYNC_PROJECTION = new String[] {
- Calls.NUMBER,
- Calls.NUMBER_PRESENTATION,
- Calls.TYPE,
- Calls.FEATURES,
- Calls.DATE,
- Calls.DURATION,
- Calls.DATA_USAGE,
- Calls.PHONE_ACCOUNT_COMPONENT_NAME,
- Calls.PHONE_ACCOUNT_ID,
- Calls.PRIORITY,
- Calls.SUBJECT,
- Calls.COMPOSER_PHOTO_URI,
- // Location is deliberately omitted
- Calls.ADD_FOR_ALL_USERS
+ Calls.NUMBER,
+ Calls.NUMBER_PRESENTATION,
+ Calls.TYPE,
+ Calls.FEATURES,
+ Calls.DATE,
+ Calls.DURATION,
+ Calls.DATA_USAGE,
+ Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+ Calls.PHONE_ACCOUNT_ID,
+ Calls.ADD_FOR_ALL_USERS
};
static final String[] MINIMAL_PROJECTION = new String[] { Calls._ID };
@@ -163,10 +103,6 @@ public class CallLogProvider extends ContentProvider {
private static final int CALLS_FILTER = 3;
- private static final int CALL_COMPOSER_NEW_PICTURE = 4;
-
- private static final int CALL_COMPOSER_PICTURE = 5;
-
private static final String UNHIDE_BY_PHONE_ACCOUNT_QUERY =
"UPDATE " + Tables.CALLS + " SET " + Calls.PHONE_ACCOUNT_HIDDEN + "=0 WHERE " +
Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=? AND " + Calls.PHONE_ACCOUNT_ID + "=?;";
@@ -180,17 +116,9 @@ public class CallLogProvider extends ContentProvider {
sURIMatcher.addURI(CallLog.AUTHORITY, "calls", CALLS);
sURIMatcher.addURI(CallLog.AUTHORITY, "calls/#", CALLS_ID);
sURIMatcher.addURI(CallLog.AUTHORITY, "calls/filter/*", CALLS_FILTER);
- sURIMatcher.addURI(CallLog.AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT,
- CALL_COMPOSER_NEW_PICTURE);
- sURIMatcher.addURI(CallLog.AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT + "/*",
- CALL_COMPOSER_PICTURE);
- // Shadow provider only supports "/calls" and "/call_composer".
+ // Shadow provider only supports "/calls".
sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, "calls", CALLS);
- sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT,
- CALL_COMPOSER_NEW_PICTURE);
- sURIMatcher.addURI(CallLog.SHADOW_AUTHORITY, CallLog.CALL_COMPOSER_SEGMENT + "/*",
- CALL_COMPOSER_PICTURE);
}
public static final ArrayMap<String, String> sCallsProjectionMap;
@@ -234,11 +162,6 @@ public class CallLogProvider extends ContentProvider {
.put(Calls.CALL_SCREENING_COMPONENT_NAME, Calls.CALL_SCREENING_COMPONENT_NAME);
sCallsProjectionMap.put(Calls.CALL_SCREENING_APP_NAME, Calls.CALL_SCREENING_APP_NAME);
sCallsProjectionMap.put(Calls.BLOCK_REASON, Calls.BLOCK_REASON);
- sCallsProjectionMap.put(Calls.MISSED_REASON, Calls.MISSED_REASON);
- sCallsProjectionMap.put(Calls.PRIORITY, Calls.PRIORITY);
- sCallsProjectionMap.put(Calls.COMPOSER_PHOTO_URI, Calls.COMPOSER_PHOTO_URI);
- sCallsProjectionMap.put(Calls.SUBJECT, Calls.SUBJECT);
- sCallsProjectionMap.put(Calls.LOCATION, Calls.LOCATION);
}
private static final String ALLOWED_PACKAGE_FOR_TESTING = "com.android.providers.contacts";
@@ -519,11 +442,6 @@ public class CallLogProvider extends ContentProvider {
return Calls.CONTENT_ITEM_TYPE;
case CALLS_FILTER:
return Calls.CONTENT_TYPE;
- case CALL_COMPOSER_NEW_PICTURE:
- return null; // No type for newly created files
- case CALL_COMPOSER_PICTURE:
- // We don't know the exact image format, so this is as specific as we can be.
- return "application/octet-stream";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
@@ -575,30 +493,6 @@ public class CallLogProvider extends ContentProvider {
" CUID=" + Binder.getCallingUid());
}
waitForAccess(mReadAccessLatch);
- int match = sURIMatcher.match(uri);
- switch (match) {
- case CALL_COMPOSER_PICTURE: {
- String fileName = uri.getLastPathSegment();
- try {
- return allocateNewCallComposerPicture(values,
- CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority()),
- fileName);
- } catch (IOException e) {
- throw new ParcelableException(e);
- }
- }
- case CALL_COMPOSER_NEW_PICTURE: {
- try {
- return allocateNewCallComposerPicture(values,
- CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority()));
- } catch (IOException e) {
- throw new ParcelableException(e);
- }
- }
- default:
- // Fall through and execute the rest of the method for ordinary call log insertions.
- }
-
checkForSupportedColumns(sCallsProjectionMap, values);
// Inserting a voicemail record through call_log requires the voicemail
// permission and also requires the additional voicemail param set.
@@ -623,170 +517,6 @@ public class CallLogProvider extends ContentProvider {
return null;
}
- @Override
- public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
- throws FileNotFoundException {
- int match = sURIMatcher.match(uri);
- if (match != CALL_COMPOSER_PICTURE) {
- throw new UnsupportedOperationException("The call log provider only supports opening"
- + " call composer pictures.");
- }
- int modeInt;
- switch (mode) {
- case "r":
- modeInt = ParcelFileDescriptor.MODE_READ_ONLY;
- break;
- case "w":
- modeInt = ParcelFileDescriptor.MODE_WRITE_ONLY;
- break;
- default:
- throw new UnsupportedOperationException("The call log does not support opening"
- + " a call composer picture with mode " + mode);
- }
-
- try {
- Path callComposerDir = getCallComposerPictureDirectory(getContext(), uri);
- Path pictureFile = callComposerDir.resolve(uri.getLastPathSegment());
- if (Files.notExists(pictureFile)) {
- throw new FileNotFoundException(uri.toString()
- + " does not correspond to a valid file.");
- }
- return ParcelFileDescriptor.open(pictureFile.toFile(), modeInt);
- } catch (IOException e) {
- Log.e(TAG, "IOException while opening call composer file: " + e);
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
- Log.i(TAG, "Fetching list of Uris to sync");
- if (!UserHandle.isSameApp(android.os.Process.myUid(), Binder.getCallingUid())) {
- throw new SecurityException("call() functionality reserved"
- + " for internal use by the call log.");
- }
- if (!GET_CALL_COMPOSER_IMAGE_URIS.equals(method)) {
- throw new UnsupportedOperationException("Invalid method passed to call(): " + method);
- }
- if (!extras.containsKey(EXTRA_SINCE_DATE)) {
- throw new IllegalArgumentException("SINCE_DATE required");
- }
- if (!extras.containsKey(EXTRA_IS_SHADOW)) {
- throw new IllegalArgumentException("IS_SHADOW required");
- }
- if (!extras.containsKey(EXTRA_ALL_USERS_ONLY)) {
- throw new IllegalArgumentException("ALL_USERS_ONLY required");
- }
- boolean isShadow = extras.getBoolean(EXTRA_IS_SHADOW);
- boolean allUsers = extras.getBoolean(EXTRA_ALL_USERS_ONLY);
- long sinceDate = extras.getLong(EXTRA_SINCE_DATE);
-
- try {
- Path queryDir = allUsers
- ? getCallComposerAllUsersPictureDirectory(getContext(), isShadow)
- : getCallComposerPictureDirectory(getContext(), isShadow);
- List<Path> newestPics = new ArrayList<>();
- try (DirectoryStream<Path> dirStream =
- Files.newDirectoryStream(queryDir, entry -> {
- if (Files.isDirectory(entry)) {
- return false;
- }
- FileTime createdAt =
- (FileTime) Files.getAttribute(entry, "creationTime");
- return createdAt.toMillis() > sinceDate;
- })) {
- dirStream.forEach(newestPics::add);
- }
- List<Uri> fileUris = newestPics.stream().map((path) -> {
- String fileName = path.getFileName().toString();
- // We don't need to worry about if it's for all users -- anything that's for
- // all users is also stored in the regular location.
- Uri base = isShadow ? CallLog.SHADOW_CALL_COMPOSER_PICTURE_URI
- : CallLog.CALL_COMPOSER_PICTURE_URI;
- return base.buildUpon().appendPath(fileName).build();
- }).collect(Collectors.toList());
- Bundle result = new Bundle();
- result.putParcelableList(EXTRA_RESULT_URIS, fileUris);
- Log.i(TAG, "Will sync following Uris:" + fileUris);
- return result;
- } catch (IOException e) {
- Log.e(TAG, "IOException while trying to fetch URI list: " + e);
- return null;
- }
- }
-
- private static @NonNull Path getCallComposerPictureDirectory(Context context, Uri uri)
- throws IOException {
- boolean isShadow = CallLog.SHADOW_AUTHORITY.equals(uri.getAuthority());
- return getCallComposerPictureDirectory(context, isShadow);
- }
-
- private static @NonNull Path getCallComposerPictureDirectory(Context context, boolean isShadow)
- throws IOException {
- if (isShadow) {
- context = context.createDeviceProtectedStorageContext();
- }
- Path path = context.getFilesDir().toPath().resolve(CALL_COMPOSER_PICTURE_DIRECTORY_NAME);
- if (!Files.isDirectory(path)) {
- Files.createDirectory(path);
- }
- return path;
- }
-
- private static @NonNull Path getCallComposerAllUsersPictureDirectory(
- Context context, boolean isShadow) throws IOException {
- Path pathToCallComposerDir = getCallComposerPictureDirectory(context, isShadow);
- Path path = pathToCallComposerDir.resolve(CALL_COMPOSER_ALL_USERS_DIRECTORY_NAME);
- if (!Files.isDirectory(path)) {
- Files.createDirectory(path);
- }
- return path;
- }
-
- private Uri allocateNewCallComposerPicture(ContentValues values, boolean isShadow)
- throws IOException {
- return allocateNewCallComposerPicture(values, isShadow, UUID.randomUUID().toString());
- }
-
- private Uri allocateNewCallComposerPicture(ContentValues values,
- boolean isShadow, String fileName) throws IOException {
- Uri baseUri = isShadow ?
- CallLog.CALL_COMPOSER_PICTURE_URI.buildUpon()
- .authority(CallLog.SHADOW_AUTHORITY).build()
- : CallLog.CALL_COMPOSER_PICTURE_URI;
-
- boolean forAllUsers = values.containsKey(Calls.ADD_FOR_ALL_USERS)
- && (values.getAsInteger(Calls.ADD_FOR_ALL_USERS) == 1);
- Path pathToCallComposerDir = getCallComposerPictureDirectory(getContext(), isShadow);
-
- if (new StatFs(pathToCallComposerDir.toString()).getAvailableBytes()
- < TelephonyManager.getMaximumCallComposerPictureSize()) {
- return null;
- }
- Path pathToFile = pathToCallComposerDir.resolve(fileName);
- Files.createFile(pathToFile);
-
- if (forAllUsers) {
- // Create a symlink in a subdirectory for copying later.
- Path allUsersDir = getCallComposerAllUsersPictureDirectory(getContext(), isShadow);
- Files.createSymbolicLink(allUsersDir.resolve(fileName), pathToFile);
- }
- return baseUri.buildUpon().appendPath(fileName).build();
- }
-
- private int deleteCallComposerPicture(Uri uri) {
- try {
- Path pathToCallComposerDir = getCallComposerPictureDirectory(getContext(), uri);
- String fileName = uri.getLastPathSegment();
- boolean successfulDelete =
- Files.deleteIfExists(pathToCallComposerDir.resolve(fileName));
- return successfulDelete ? 1 : 0;
- } catch (IOException e) {
- Log.e(TAG, "IOException encountered deleting the call composer pics dir " + e);
- return 0;
- }
- }
-
private int updateInternal(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
@@ -846,10 +576,6 @@ public class CallLogProvider extends ContentProvider {
case CALLS:
return createDatabaseModifier(db, hasReadVoicemailPermission).delete(Tables.CALLS,
selectionBuilder.build(), selectionArgs);
- case CALL_COMPOSER_PICTURE:
- // TODO(hallliu): implement deletion of file when the corresponding calllog entry
- // gets deleted as well.
- return deleteCallComposerPicture(uri);
default:
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
}
@@ -1007,68 +733,8 @@ public class CallLogProvider extends ContentProvider {
// delete all entries in shadow.
cr.delete(uri, Calls.DATE + "<= ?", new String[] {String.valueOf(newestTimeStamp)});
}
-
- try {
- syncCallComposerPics(sourceUserId, sourceIsShadow, forAllUsersOnly, lastSyncTime);
- } catch (Exception e) {
- // Catch any exceptions to make sure we don't bring down the entire process if something
- // goes wrong
- StringWriter w = new StringWriter();
- PrintWriter pw = new PrintWriter(w);
- e.printStackTrace(pw);
- Log.e(TAG, "Caught exception syncing call composer pics: " + e
- + "\n" + pw.toString());
- }
}
- private void syncCallComposerPics(int sourceUserId, boolean sourceIsShadow,
- boolean forAllUsersOnly, long lastSyncTime) {
- Log.i(TAG, "Syncing call composer pics -- source user=" + sourceUserId + ","
- + " isShadow=" + sourceIsShadow + ", forAllUser=" + forAllUsersOnly);
- ContentResolver contentResolver = getContext().getContentResolver();
- Bundle args = new Bundle();
- args.putLong(EXTRA_SINCE_DATE, lastSyncTime);
- args.putBoolean(EXTRA_ALL_USERS_ONLY, forAllUsersOnly);
- args.putBoolean(EXTRA_IS_SHADOW, sourceIsShadow);
- Uri queryUri = ContentProvider.maybeAddUserId(
- sourceIsShadow
- ? CallLog.SHADOW_CALL_COMPOSER_PICTURE_URI
- : CallLog.CALL_COMPOSER_PICTURE_URI,
- sourceUserId);
- Bundle result = contentResolver.call(queryUri, GET_CALL_COMPOSER_IMAGE_URIS, null, args);
- if (result == null || !result.containsKey(EXTRA_RESULT_URIS)) {
- Log.e(TAG, "Failed to sync call composer pics -- invalid return from call()");
- return;
- }
- List<Uri> urisToCopy = result.getParcelableArrayList(EXTRA_RESULT_URIS);
- Log.i(TAG, "Syncing call composer pics -- got " + urisToCopy);
- for (Uri uri : urisToCopy) {
- try {
- Uri uriWithUser = ContentProvider.maybeAddUserId(uri, sourceUserId);
- Path newFilePath = getCallComposerPictureDirectory(getContext(), false)
- .resolve(uri.getLastPathSegment());
- try (ParcelFileDescriptor remoteFile = contentResolver.openFile(uriWithUser,
- "r", null);
- OutputStream localOut =
- Files.newOutputStream(newFilePath, StandardOpenOption.CREATE_NEW)) {
- FileInputStream input = new FileInputStream(remoteFile.getFileDescriptor());
- byte[] buffer = new byte[1 << 14]; // 16kb
- while (true) {
- int numRead = input.read(buffer);
- if (numRead < 0) {
- break;
- }
- localOut.write(buffer, 0, numRead);
- }
- }
- contentResolver.delete(uriWithUser, null);
- } catch (IOException e) {
- Log.e(TAG, "IOException while syncing call composer pics: " + e);
- // Keep going and get as many as we can.
- }
- }
-
- }
/**
* Un-hides any hidden call log entries that are associated with the specified handle.
*
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
index 09e3ff37..8a1c88bb 100644
--- a/src/com/android/providers/contacts/ContactDirectoryManager.java
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -245,7 +245,8 @@ public class ContactDirectoryManager {
Log.i(TAG, "Discovered " + count + " contact directories in " + (end - start) + "ms");
// Announce the change to listeners of the contacts authority
- mContactsProvider.notifyChange(/* syncToNetwork =*/false);
+ mContactsProvider.notifyChange(/* syncToNetwork =*/false,
+ /* syncToMetadataNetwork =*/false);
// We schedule a rescan if update(DIRECTORIES) is called while we're scanning all packages.
if (mDirectoriesForceUpdated) {
diff --git a/src/com/android/providers/contacts/ContactMetadataProvider.java b/src/com/android/providers/contacts/ContactMetadataProvider.java
new file mode 100644
index 00000000..3cf7df2c
--- /dev/null
+++ b/src/com/android/providers/contacts/ContactMetadataProvider.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2015 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.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.OperationApplicationException;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Binder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.MetadataSync;
+import android.provider.ContactsContract.MetadataSyncState;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.common.content.ProjectionMap;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.ContactsDatabaseHelper.Views;
+import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
+import com.android.providers.contacts.util.SelectionBuilder;
+import com.android.providers.contacts.util.UserUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+import static com.android.providers.contacts.ContactsProvider2.getLimit;
+import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+
+/**
+ * Simple content provider to handle directing contact metadata specific calls.
+ */
+public class ContactMetadataProvider extends ContentProvider {
+ private static final String TAG = "ContactMetadata";
+ private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int METADATA_SYNC = 1;
+ private static final int METADATA_SYNC_ID = 2;
+ private static final int SYNC_STATE = 3;
+
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync", METADATA_SYNC);
+ sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync/#", METADATA_SYNC_ID);
+ sURIMatcher.addURI(MetadataSync.METADATA_AUTHORITY, "metadata_sync_state", SYNC_STATE);
+ }
+
+ private static final Map<String, String> sMetadataProjectionMap = ProjectionMap.builder()
+ .add(MetadataSync._ID)
+ .add(MetadataSync.RAW_CONTACT_BACKUP_ID)
+ .add(MetadataSync.ACCOUNT_TYPE)
+ .add(MetadataSync.ACCOUNT_NAME)
+ .add(MetadataSync.DATA_SET)
+ .add(MetadataSync.DATA)
+ .add(MetadataSync.DELETED)
+ .build();
+
+ private static final Map<String, String> sSyncStateProjectionMap =ProjectionMap.builder()
+ .add(MetadataSyncState._ID)
+ .add(MetadataSyncState.ACCOUNT_TYPE)
+ .add(MetadataSyncState.ACCOUNT_NAME)
+ .add(MetadataSyncState.DATA_SET)
+ .add(MetadataSyncState.STATE)
+ .build();
+
+ private ContactsDatabaseHelper mDbHelper;
+ private ContactsProvider2 mContactsProvider;
+
+ private String mAllowedPackage;
+
+ @Override
+ public boolean onCreate() {
+ final Context context = getContext();
+ mDbHelper = getDatabaseHelper(context);
+ final IContentProvider iContentProvider = context.getContentResolver().acquireProvider(
+ ContactsContract.AUTHORITY);
+ final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(
+ iContentProvider);
+ mContactsProvider = (ContactsProvider2) provider;
+
+ mAllowedPackage = getContext().getResources().getString(R.string.metadata_sync_pacakge);
+ return true;
+ }
+
+ protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+ return ContactsDatabaseHelper.getInstance(context);
+ }
+
+ @VisibleForTesting
+ protected void setDatabaseHelper(final ContactsDatabaseHelper helper) {
+ mDbHelper = helper;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+
+ ensureCaller();
+
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "query: uri=" + uri + " projection=" + Arrays.toString(projection) +
+ " selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
+ " order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
+ final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ String limit = getLimit(uri);
+
+ final SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+
+ final int match = sURIMatcher.match(uri);
+ switch (match) {
+ case METADATA_SYNC:
+ setTablesAndProjectionMapForMetadata(qb);
+ break;
+
+ case METADATA_SYNC_ID: {
+ setTablesAndProjectionMapForMetadata(qb);
+ selectionBuilder.addClause(getEqualityClause(MetadataSync._ID,
+ ContentUris.parseId(uri)));
+ break;
+ }
+
+ case SYNC_STATE:
+ setTablesAndProjectionMapForSyncState(qb);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URL " + uri);
+ }
+
+ final SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ return qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
+ null, sortOrder, limit);
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case METADATA_SYNC:
+ return MetadataSync.CONTENT_TYPE;
+ case METADATA_SYNC_ID:
+ return MetadataSync.CONTENT_ITEM_TYPE;
+ case SYNC_STATE:
+ return MetadataSyncState.CONTENT_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ /**
+ * Insert or update if the raw is already existing.
+ */
+ public Uri insert(Uri uri, ContentValues values) {
+
+ ensureCaller();
+
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.beginTransactionNonExclusive();
+ try {
+ final int matchedUriId = sURIMatcher.match(uri);
+ switch (matchedUriId) {
+ case METADATA_SYNC:
+ // Insert the new entry, and also parse the data column to update related
+ // tables.
+ final long metadataSyncId = updateOrInsertDataToMetadataSync(db, uri, values);
+ db.setTransactionSuccessful();
+ return ContentUris.withAppendedId(uri, metadataSyncId);
+ case SYNC_STATE:
+ replaceAccountInfoByAccountId(uri, values);
+ final Long syncStateId = db.replace(
+ Tables.METADATA_SYNC_STATE, MetadataSyncColumns.ACCOUNT_ID, values);
+ db.setTransactionSuccessful();
+ return ContentUris.withAppendedId(uri, syncStateId);
+ default:
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Calling contact metadata insert on an unknown/invalid URI", uri));
+ }
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+
+ ensureCaller();
+
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.beginTransactionNonExclusive();
+ try {
+ final int matchedUriId = sURIMatcher.match(uri);
+ int numDeletes = 0;
+ switch (matchedUriId) {
+ case METADATA_SYNC:
+ Cursor c = db.query(Views.METADATA_SYNC, new String[]{MetadataSync._ID},
+ selection, selectionArgs, null, null, null);
+ try {
+ while (c.moveToNext()) {
+ final long contactMetadataId = c.getLong(0);
+ numDeletes += db.delete(Tables.METADATA_SYNC,
+ MetadataSync._ID + "=" + contactMetadataId, null);
+ }
+ } finally {
+ c.close();
+ }
+ db.setTransactionSuccessful();
+ return numDeletes;
+ case SYNC_STATE:
+ c = db.query(Views.METADATA_SYNC_STATE, new String[]{MetadataSyncState._ID},
+ selection, selectionArgs, null, null, null);
+ try {
+ while (c.moveToNext()) {
+ final long stateId = c.getLong(0);
+ numDeletes += db.delete(Tables.METADATA_SYNC_STATE,
+ MetadataSyncState._ID + "=" + stateId, null);
+ }
+ } finally {
+ c.close();
+ }
+ db.setTransactionSuccessful();
+ return numDeletes;
+ default:
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Calling contact metadata delete on an unknown/invalid URI", uri));
+ }
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+
+ ensureCaller();
+
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.beginTransactionNonExclusive();
+ try {
+ final int matchedUriId = sURIMatcher.match(uri);
+ switch (matchedUriId) {
+ // Do not support update metadata sync by update() method. Please use insert().
+ case SYNC_STATE:
+ // Only support update by account.
+ final Long accountId = replaceAccountInfoByAccountId(uri, values);
+ if (accountId == null) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Invalid identifier is found for accountId", uri));
+ }
+ values.put(MetadataSyncColumns.ACCOUNT_ID, accountId);
+ // Insert a new row if it doesn't exist.
+ db.replace(Tables.METADATA_SYNC_STATE, null, values);
+ db.setTransactionSuccessful();
+ return 1;
+ default:
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Calling contact metadata update on an unknown/invalid URI", uri));
+ }
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+
+ ensureCaller();
+
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "applyBatch: " + operations.size() + " ops");
+ }
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.beginTransactionNonExclusive();
+ try {
+ ContentProviderResult[] results = super.applyBatch(operations);
+ db.setTransactionSuccessful();
+ return results;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+
+ ensureCaller();
+
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "bulkInsert: " + values.length + " inserts");
+ }
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ db.beginTransactionNonExclusive();
+ try {
+ final int numValues = super.bulkInsert(uri, values);
+ db.setTransactionSuccessful();
+ return numValues;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private void setTablesAndProjectionMapForMetadata(SQLiteQueryBuilder qb){
+ qb.setTables(Views.METADATA_SYNC);
+ qb.setProjectionMap(sMetadataProjectionMap);
+ qb.setStrict(true);
+ }
+
+ private void setTablesAndProjectionMapForSyncState(SQLiteQueryBuilder qb){
+ qb.setTables(Views.METADATA_SYNC_STATE);
+ qb.setProjectionMap(sSyncStateProjectionMap);
+ qb.setStrict(true);
+ }
+
+ /**
+ * Insert or update a non-deleted entry to MetadataSync table, and also parse the data column
+ * to update related tables for the raw contact.
+ * Returns new upserted metadataSyncId.
+ */
+ private long updateOrInsertDataToMetadataSync(SQLiteDatabase db, Uri uri, ContentValues values) {
+ final int matchUri = sURIMatcher.match(uri);
+ if (matchUri != METADATA_SYNC) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Calling contact metadata insert or update on an unknown/invalid URI", uri));
+ }
+
+ // Don't insert or update a deleted metadata.
+ Integer deleted = values.getAsInteger(MetadataSync.DELETED);
+ if (deleted != null && deleted != 0) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Cannot insert or update deleted metadata:" + values.toString(), uri));
+ }
+
+ // Check if data column is empty or null.
+ final String data = values.getAsString(MetadataSync.DATA);
+ if (TextUtils.isEmpty(data)) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Data column cannot be empty.", uri));
+ }
+
+ // Update or insert for backupId and account info.
+ final Long accountId = replaceAccountInfoByAccountId(uri, values);
+ final String rawContactBackupId = values.getAsString(
+ MetadataSync.RAW_CONTACT_BACKUP_ID);
+ // TODO (tingtingw): Consider a corner case: if there's raw with the same accountId and
+ // backupId, but deleted=1, (Deleted should be synced up to server and hard-deleted, but
+ // may be delayed.) In this case, should we not override it with delete=0? or should this
+ // be prevented by sync adapter side?.
+ deleted = 0; // Only insert or update non-deleted metadata
+ if (accountId == null) {
+ // Do nothing, just return.
+ return 0;
+ }
+ if (rawContactBackupId == null) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Invalid identifier is found: accountId=" + accountId + "; " +
+ "rawContactBackupId=" + rawContactBackupId, uri));
+ }
+
+ // Update if it exists, otherwise insert.
+ final long metadataSyncId = mDbHelper.upsertMetadataSync(
+ rawContactBackupId, accountId, data, deleted);
+ if (metadataSyncId <= 0) {
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Metadata upsertion failed. Values= " + values.toString(), uri));
+ }
+
+ // Parse the data column and update other tables.
+ // Data field will never be empty or null, since contacts prefs and usage stats
+ // have default values.
+ final MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(data);
+ mContactsProvider.updateFromMetaDataEntry(db, metadataEntry);
+
+ return metadataSyncId;
+ }
+
+ /**
+ * Replace account_type, account_name and data_set with account_id. If a valid account_id
+ * cannot be found for this combination, return null.
+ */
+ private Long replaceAccountInfoByAccountId(Uri uri, ContentValues values) {
+ String accountName = values.getAsString(MetadataSync.ACCOUNT_NAME);
+ String accountType = values.getAsString(MetadataSync.ACCOUNT_TYPE);
+ String dataSet = values.getAsString(MetadataSync.DATA_SET);
+ final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
+ if (partialUri) {
+ // Throw when either account is incomplete.
+ throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+ "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE", uri));
+ }
+
+ final AccountWithDataSet account = AccountWithDataSet.get(
+ accountName, accountType, dataSet);
+
+ final Long id = mDbHelper.getAccountIdOrNull(account);
+ if (id == null) {
+ return null;
+ }
+
+ values.put(MetadataSyncColumns.ACCOUNT_ID, id);
+ // Only remove the account information once the account ID is extracted (since these
+ // fields are actually used by resolveAccountWithDataSet to extract the relevant ID).
+ values.remove(MetadataSync.ACCOUNT_NAME);
+ values.remove(MetadataSync.ACCOUNT_TYPE);
+ values.remove(MetadataSync.DATA_SET);
+
+ return id;
+ }
+
+ @VisibleForTesting
+ void ensureCaller() {
+ final String caller = getCallingPackage();
+ if (mAllowedPackage.equals(caller)) {
+ return; // Okay.
+ }
+ throw new SecurityException("Caller " + caller + " can't access ContactMetadataProvider");
+ }
+}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 7f4188d2..2e5cdacf 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,6 +16,12 @@
package com.android.providers.contacts;
+import com.android.internal.R.bool;
+import com.android.providers.contacts.sqlite.DatabaseAnalyzer;
+import com.android.providers.contacts.sqlite.SqlChecker;
+import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
+import com.android.providers.contacts.util.PropertyUtils;
+
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -67,13 +73,14 @@ import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.ContactsContract.FullNameStyle;
import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.MetadataSync;
+import android.provider.ContactsContract.MetadataSyncState;
import android.provider.ContactsContract.PhoneticNameStyle;
import android.provider.ContactsContract.PhotoFiles;
import android.provider.ContactsContract.PinnedPositions;
import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.SimAccount;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.StreamItemPhotos;
import android.provider.ContactsContract.StreamItems;
@@ -91,23 +98,17 @@ import android.util.Log;
import android.util.Slog;
import com.android.common.content.SyncStateContentProviderHelper;
-import com.android.internal.R.bool;
import com.android.internal.annotations.VisibleForTesting;
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.sqlite.DatabaseAnalyzer;
-import com.android.providers.contacts.sqlite.SqlChecker;
-import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
import com.android.providers.contacts.util.NeededForTesting;
-import com.android.providers.contacts.util.PropertyUtils;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -142,10 +143,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 1200-1299 O
* 1300-1399 P
* 1400-1499 Q
- * 1500-1599 S
* </pre>
*/
- static final int DATABASE_VERSION = 1501;
+ static final int DATABASE_VERSION = 1400;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
@VisibleForTesting
@@ -189,6 +189,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public static final String DIRECTORIES = "directories";
public static final String DEFAULT_DIRECTORY = "default_directory";
public static final String SEARCH_INDEX = "search_index";
+ public static final String METADATA_SYNC = "metadata_sync";
+ public static final String METADATA_SYNC_STATE = "metadata_sync_state";
public static final String PRE_AUTHORIZED_URIS = "pre_authorized_uris";
// This list of tables contains auto-incremented sequences.
@@ -311,6 +313,15 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ " JOIN " + Tables.ACCOUNTS + " ON ("
+ AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
+ ")";
+
+ public static final String RAW_CONTACTS_JOIN_METADATA_SYNC = Tables.RAW_CONTACTS
+ + " JOIN " + Tables.METADATA_SYNC + " ON ("
+ + RawContactsColumns.CONCRETE_BACKUP_ID + "="
+ + MetadataSyncColumns.CONCRETE_BACKUP_ID
+ + " AND "
+ + RawContactsColumns.CONCRETE_ACCOUNT_ID + "="
+ + MetadataSyncColumns.CONCRETE_ACCOUNT_ID
+ + ")";
}
public interface Joins {
@@ -701,8 +712,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
String ACCOUNT_NAME = RawContacts.ACCOUNT_NAME;
String ACCOUNT_TYPE = RawContacts.ACCOUNT_TYPE;
String DATA_SET = RawContacts.DATA_SET;
- String SIM_SLOT_INDEX = "sim_slot_index";
- String SIM_EF_TYPE = "sim_ef_type";
String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
@@ -761,6 +770,22 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public static final int USAGE_TYPE_INT_SHORT_TEXT = 2;
}
+ public interface MetadataSyncColumns {
+ static final String CONCRETE_ID = Tables.METADATA_SYNC + "._id";
+ static final String ACCOUNT_ID = "account_id";
+ static final String CONCRETE_BACKUP_ID = Tables.METADATA_SYNC + "." +
+ MetadataSync.RAW_CONTACT_BACKUP_ID;
+ static final String CONCRETE_ACCOUNT_ID = Tables.METADATA_SYNC + "." + ACCOUNT_ID;
+ static final String CONCRETE_DELETED = Tables.METADATA_SYNC + "." +
+ MetadataSync.DELETED;
+ }
+
+ public interface MetadataSyncStateColumns {
+ static final String CONCRETE_ID = Tables.METADATA_SYNC_STATE + "._id";
+ static final String ACCOUNT_ID = "account_id";
+ static final String CONCRETE_ACCOUNT_ID = Tables.METADATA_SYNC_STATE + "." + ACCOUNT_ID;
+ }
+
private interface EmailQuery {
public static final String TABLE = Tables.DATA;
@@ -1222,10 +1247,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
AccountsColumns.ACCOUNT_NAME + " TEXT, " +
AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
- AccountsColumns.DATA_SET + " TEXT, " +
- AccountsColumns.SIM_SLOT_INDEX + " INTEGER, " +
- AccountsColumns.SIM_EF_TYPE + " INTEGER" +
- ");");
+ AccountsColumns.DATA_SET + " TEXT" +
+ ");");
// Note, there are two sets of the usage stat columns: LR_* and RAW_*.
// RAW_* contain the real values, which clients can't access. The column names start
@@ -1599,11 +1622,34 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
DataUsageStatColumns.USAGE_TYPE_INT +
");");
+ db.execSQL("CREATE TABLE IF NOT EXISTS "
+ + Tables.METADATA_SYNC + " (" +
+ MetadataSync._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ MetadataSync.RAW_CONTACT_BACKUP_ID + " TEXT NOT NULL," +
+ MetadataSyncColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
+ MetadataSync.DATA + " TEXT," +
+ MetadataSync.DELETED + " INTEGER NOT NULL DEFAULT 0);");
+
+ db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_index ON " +
+ Tables.METADATA_SYNC + " (" +
+ MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
+ MetadataSyncColumns.ACCOUNT_ID +");");
+
db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+
PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
PreAuthorizedUris.URI + " STRING NOT NULL, " +
PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);");
+ db.execSQL("CREATE TABLE IF NOT EXISTS "
+ + Tables.METADATA_SYNC_STATE + " (" +
+ MetadataSyncState._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ MetadataSyncStateColumns.ACCOUNT_ID + " INTEGER NOT NULL," +
+ MetadataSyncState.STATE + " BLOB);");
+
+ db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS metadata_sync_state_index ON " +
+ Tables.METADATA_SYNC_STATE + " (" +
+ MetadataSyncColumns.ACCOUNT_ID +");");
+
// When adding new tables, be sure to also add size-estimates in updateSqliteStats
createContactsViews(db);
createGroupsView(db);
@@ -2196,6 +2242,34 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ RawContactsColumns.CONCRETE_CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + ")";
db.execSQL("CREATE VIEW " + Views.STREAM_ITEMS + " AS " + streamItemSelect);
+
+ String metadataSyncSelect = "SELECT " +
+ MetadataSyncColumns.CONCRETE_ID + ", " +
+ MetadataSync.RAW_CONTACT_BACKUP_ID + ", " +
+ AccountsColumns.ACCOUNT_NAME + ", " +
+ AccountsColumns.ACCOUNT_TYPE + ", " +
+ AccountsColumns.DATA_SET + ", " +
+ MetadataSync.DATA + ", " +
+ MetadataSync.DELETED +
+ " FROM " + Tables.METADATA_SYNC
+ + " JOIN " + Tables.ACCOUNTS + " ON ("
+ + MetadataSyncColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
+ + ")";
+
+ db.execSQL("CREATE VIEW " + Views.METADATA_SYNC + " AS " + metadataSyncSelect);
+
+ String metadataSyncStateSelect = "SELECT " +
+ MetadataSyncStateColumns.CONCRETE_ID + ", " +
+ AccountsColumns.ACCOUNT_NAME + ", " +
+ AccountsColumns.ACCOUNT_TYPE + ", " +
+ AccountsColumns.DATA_SET + ", " +
+ MetadataSyncState.STATE +
+ " FROM " + Tables.METADATA_SYNC_STATE
+ + " JOIN " + Tables.ACCOUNTS + " ON ("
+ + MetadataSyncStateColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
+ + ")";
+
+ db.execSQL("CREATE VIEW " + Views.METADATA_SYNC_STATE + " AS " + metadataSyncStateSelect);
}
private static String buildDisplayPhotoUriAlias(String contactIdColumn, String alias) {
@@ -2581,19 +2655,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1400;
}
- if (isUpgradeRequired(oldVersion, newVersion, 1500)) {
- db.execSQL("DROP TABLE IF EXISTS metadata_sync;");
- db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;");
- upgradeViewsAndTriggers = true;
- oldVersion = 1500;
- }
-
- if (isUpgradeRequired(oldVersion, newVersion, 1501)) {
- upgradeToVersion1501(db);
- upgradeViewsAndTriggers = true;
- oldVersion = 1501;
- }
-
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3256,19 +3317,30 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
- * Used to add new metadata_sync table to cache the meta data on raw contacts level from server
- * before they are merged into other CP2 tables. The table is not used any more.
+ * Add new metadata_sync table to cache the meta data on raw contacts level from server before
+ * they are merged into other CP2 tables. The data column is the blob column containing all
+ * the backed up metadata for this raw_contact. This table should only be used by metadata
+ * sync adapter.
*/
public void upgradeToVersion1104(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS metadata_sync;");
+ db.execSQL("CREATE TABLE metadata_sync (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, raw_contact_backup_id TEXT NOT NULL, " +
+ "account_id INTEGER NOT NULL, data TEXT, deleted INTEGER NOT NULL DEFAULT 0);");
+ db.execSQL("CREATE UNIQUE INDEX metadata_sync_index ON metadata_sync (" +
+ "raw_contact_backup_id, account_id);");
}
/**
- * Used to add new metadata_sync_state table to store the metadata sync state for a set of
- * accounts. The table is not used any more.
+ * Add new metadata_sync_state table to store the metadata sync state for a set of accounts.
*/
public void upgradeToVersion1105(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS metadata_sync_state;");
+ db.execSQL("CREATE TABLE metadata_sync_state (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "account_id INTEGER NOT NULL, state BLOB);");
+ db.execSQL("CREATE UNIQUE INDEX metadata_sync_state_index ON metadata_sync_state (" +
+ "account_id);");
}
public void upgradeToVersion1106(SQLiteDatabase db) {
@@ -3367,14 +3439,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
- private void upgradeToVersion1501(SQLiteDatabase db) {
- try {
- db.execSQL("ALTER TABLE accounts ADD sim_slot_index INTEGER;");
- db.execSQL("ALTER TABLE accounts ADD sim_ef_type INTEGER;");
- } catch (SQLException ignore) {
- }
- }
-
/**
* This method is only used in upgradeToVersion1101 method, and should not be used in other
* places now. Because data15 is not used to generate hash_id for photo, and the new generating
@@ -3629,6 +3693,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
updateIndexStats(db, Tables.DATA_USAGE_STAT,
"data_usage_stat_index", "20 2 1");
+ updateIndexStats(db, Tables.METADATA_SYNC,
+ "metadata_sync_index", "10000 1 1");
+
// Tiny tables
updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
null, "10");
@@ -3649,6 +3716,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
updateIndexStats(db, "properties",
"sqlite_autoindex_properties_1", "4 1");
+ updateIndexStats(db, Tables.METADATA_SYNC_STATE,
+ "metadata_sync_state_index", "2 1 1");
+
// Search index
updateIndexStats(db, "search_index_docsize",
null, "9000");
@@ -3910,37 +3980,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
- * Gets all SIM accounts in the accounts table.
- */
- public List<SimAccount> getAllSimAccounts() {
- final List<SimAccount> result = new ArrayList<>();
- final Cursor c = getReadableDatabase().rawQuery(
- "SELECT DISTINCT " + AccountsColumns._ID + ","
- + AccountsColumns.ACCOUNT_NAME + ","
- + AccountsColumns.ACCOUNT_TYPE + ","
- + AccountsColumns.SIM_SLOT_INDEX + ","
- + AccountsColumns.SIM_EF_TYPE + " FROM " + Tables.ACCOUNTS, null);
- try {
- while (c.moveToNext()) {
- if (c.isNull(3) || c.isNull(4)) {
- // Invalid slot index or ef type
- continue;
- }
- final int simSlot = c.getInt(3);
- final int efType = c.getInt(4);
- if (simSlot < 0 || !SimAccount.getValidEfTypes().contains(efType)) {
- // Invalid slot index or ef type
- continue;
- }
- result.add(new SimAccount(c.getString(1), c.getString(2), simSlot, efType));
- }
- } finally {
- c.close();
- }
- return result;
- }
-
- /**
* @return ID of the specified account, or null if the account doesn't exist.
*/
public Long getAccountIdOrNull(AccountWithDataSet accountWithDataSet) {
@@ -4003,69 +4042,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
- * This method will create a record in the accounts table.
- *
- * This must be used in a transaction, so there's no need for synchronization.
- *
- * @param simSlot Sim slot index of the account. Must be 0 or greater
- * @param efType EF type of the account. Must be a value contained in {@link
- * SimAccount#getValidEfTypes()}
- * @throws IllegalArgumentException if the account name/type pair is already within the table.
- * SIM accounts should have distinct names and types.
- * And if simSlot is negative, or efType is not in {@link
- * SimAccount#getValidEfTypes()}
- */
- public long createSimAccountIdInTransaction(AccountWithDataSet accountWithDataSet,
- int simSlot, int efType) {
- if (simSlot < 0) {
- throw new IllegalArgumentException("Sim slot is negative");
- }
- if (!SimAccount.getValidEfTypes().contains(efType)) {
- throw new IllegalArgumentException("Invalid EF type");
- }
- if (accountWithDataSet == null || TextUtils.isEmpty(accountWithDataSet.getAccountName())
- || TextUtils.isEmpty(accountWithDataSet.getAccountType())) {
- throw new IllegalArgumentException("Account is null or the name/type is empty");
- }
-
- Long id = getAccountIdOrNull(accountWithDataSet);
- if (id != null) {
- throw new IllegalArgumentException("Account already exists in the table");
- }
- final SQLiteStatement insert = getWritableDatabase().compileStatement(
- "INSERT INTO " + Tables.ACCOUNTS +
- " (" + AccountsColumns.ACCOUNT_NAME + ", " +
- AccountsColumns.ACCOUNT_TYPE + ", " +
- AccountsColumns.DATA_SET + ", " +
- AccountsColumns.SIM_SLOT_INDEX + ", " +
- AccountsColumns.SIM_EF_TYPE + ") VALUES (?, ?, ?, ?, ?)");
- try {
- DatabaseUtils.bindObjectToProgram(insert, 1, accountWithDataSet.getAccountName());
- DatabaseUtils.bindObjectToProgram(insert, 2, accountWithDataSet.getAccountType());
- DatabaseUtils.bindObjectToProgram(insert, 3, accountWithDataSet.getDataSet());
- DatabaseUtils.bindObjectToProgram(insert, 4, simSlot);
- DatabaseUtils.bindObjectToProgram(insert, 5, efType);
- id = insert.executeInsert();
- } finally {
- insert.close();
- }
-
- return id;
- }
-
- /**
- * Deletes all rows in the accounts table with the given sim slot index
- *
- * @param simSlot Sim slot to remove accounts
- * @return how many rows were deleted
- */
- public int removeSimAccounts(int simSlot) {
- final SQLiteDatabase db = getWritableDatabase();
- return db.delete(Tables.ACCOUNTS, AccountsColumns.SIM_SLOT_INDEX + "=?",
- new String[]{String.valueOf(simSlot)});
- }
-
- /**
* Update {@link Contacts#IN_VISIBLE_GROUP} for all contacts.
*/
public void updateAllVisible() {
@@ -5013,6 +4989,22 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
new String[] {String.valueOf(contactId)});
}
+ public long upsertMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
+ final SQLiteStatement metadataSyncInsert = getWritableDatabase().compileStatement(
+ "INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "("
+ + MetadataSync.RAW_CONTACT_BACKUP_ID + ", "
+ + MetadataSyncColumns.ACCOUNT_ID + ", "
+ + MetadataSync.DATA + ","
+ + MetadataSync.DELETED + ")" +
+ " VALUES (?,?,?,?)");
+ metadataSyncInsert.bindString(1, backupId);
+ metadataSyncInsert.bindLong(2, accountId);
+ data = (data == null) ? "" : data;
+ metadataSyncInsert.bindString(3, data);
+ metadataSyncInsert.bindLong(4, deleted);
+ return metadataSyncInsert.executeInsert();
+ }
+
public static void notifyProviderStatusChange(Context context) {
context.getContentResolver().notifyChange(ProviderStatus.CONTENT_URI,
/* observer= */ null, /* syncToNetwork= */ false);
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 0c6e8192..5159fb94 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -16,9 +16,9 @@
package com.android.providers.contacts;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -34,7 +34,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.IContentService;
-import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncAdapterType;
@@ -60,11 +59,8 @@ import android.net.Uri;
import android.net.Uri.Builder;
import android.os.AsyncTask;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.RemoteException;
@@ -98,6 +94,7 @@ import android.provider.ContactsContract.DeletedContacts;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.MetadataSync;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.PhotoFiles;
import android.provider.ContactsContract.PinnedPositions;
@@ -107,8 +104,6 @@ import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract.SearchSnippets;
import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.SimAccount;
-import android.provider.ContactsContract.SimContacts;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.StreamItemPhotos;
import android.provider.ContactsContract.StreamItems;
@@ -139,6 +134,8 @@ import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumn
import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Joins;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
@@ -155,6 +152,11 @@ import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Views;
+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.SearchIndexManager.FtsQueryBuilder;
import com.android.providers.contacts.aggregation.AbstractContactAggregator;
import com.android.providers.contacts.aggregation.AbstractContactAggregator.AggregationSuggestionParameter;
@@ -170,8 +172,6 @@ import com.android.providers.contacts.enterprise.EnterprisePolicyGuard;
import com.android.providers.contacts.util.Clock;
import com.android.providers.contacts.util.ContactsPermissions;
import com.android.providers.contacts.util.DbQueryUtils;
-import com.android.providers.contacts.util.LogFields;
-import com.android.providers.contacts.util.LogUtils;
import com.android.providers.contacts.util.NeededForTesting;
import com.android.providers.contacts.util.UserUtils;
import com.android.vcard.VCardComposer;
@@ -218,8 +218,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final String READ_PERMISSION = "android.permission.READ_CONTACTS";
private static final String WRITE_PERMISSION = "android.permission.WRITE_CONTACTS";
- private static final String MANAGE_SIM_ACCOUNTS_PERMISSION =
- "android.contacts.permission.MANAGE_SIM_ACCOUNTS";
/* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -260,9 +258,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
/** Limit for the maximum number of social stream items to store under a raw contact. */
private static final int MAX_STREAM_ITEMS_PER_RAW_CONTACT = 5;
- /** Rate limit (in milliseconds) for notify change. Do it as most once every 5 seconds. */
- private static final int NOTIFY_CHANGE_RATE_LIMIT = 5 * 1000;
-
/** Rate limit (in milliseconds) for photo cleanup. Do it at most once per day. */
private static final int PHOTO_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
@@ -1457,14 +1452,13 @@ public class ContactsProvider2 extends AbstractContactsProvider
private boolean mVisibleTouched = false;
private boolean mSyncToNetwork;
+ private boolean mSyncToMetadataNetWork;
private LocaleSet mCurrentLocales;
private int mContactsAccountCount;
private ContactsTaskScheduler mTaskScheduler;
- private long mLastNotifyChange = 0;
-
private long mLastPhotoCleanup = 0;
private FastScrollingIndexCache mFastScrollingIndexCache;
@@ -1474,6 +1468,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
private int mFastScrollingIndexCacheMissCount;
private long mTotalTimeFastScrollingIndexGenerate;
+ // MetadataSync flag.
+ private boolean mMetadataSyncEnabled;
+
// Enterprise members
private EnterprisePolicyGuard mEnterprisePolicyGuard;
@@ -1483,13 +1480,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
Log.v(TAG, "onCreate user="
+ android.os.Process.myUserHandle().getIdentifier());
}
- if (Build.IS_DEBUGGABLE) {
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectLeakedSqlLiteObjects() // for SqlLiteCursor
- .detectLeakedClosableObjects() // for any Cursor
- .penaltyLog()
- .build());
- }
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "ContactsProvider2.onCreate start");
@@ -1524,6 +1514,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
+ mMetadataSyncEnabled = android.provider.Settings.Global.getInt(
+ getContext().getContentResolver(), Global.CONTACT_METADATA_SYNC_ENABLED, 0) == 1;
+
mContactsHelper = getDatabaseHelper();
mDbHelper.set(mContactsHelper);
@@ -1977,7 +1970,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
ContentValues updateValues = new ContentValues();
updateValues.putNull(Photo.PHOTO_FILE_ID);
updateData(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
- updateValues, null, null, /* callerIsSyncAdapter =*/false);
+ updateValues, null, null, /* callerIsSyncAdapter =*/false,
+ /* callerIsMetadataSyncAdapter =*/false);
}
if (photoFileIdToStreamItemPhotoId.containsKey(missingPhotoId)) {
// For missing photos that were in stream item photos, just delete the
@@ -2176,106 +2170,49 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public Uri insert(Uri uri, ContentValues values) {
- LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
- .setApiType(LogUtils.ApiType.INSERT)
- .setUriType(sUriMatcher.match(uri))
- .setCallerIsSyncAdapter(readBooleanQueryParameter(
- uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
- Uri resultUri = null;
-
- try {
- waitForAccess(mWriteAccessLatch);
+ waitForAccess(mWriteAccessLatch);
- mContactsHelper.validateContentValues(getCallingPackage(), values);
+ mContactsHelper.validateContentValues(getCallingPackage(), values);
- if (mapsToProfileDbWithInsertedValues(uri, values)) {
- switchToProfileMode();
- resultUri = mProfileProvider.insert(uri, values);
- return resultUri;
- }
- switchToContactMode();
- resultUri = super.insert(uri, values);
- return resultUri;
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(
- logBuilder.setResultUri(resultUri).setResultCount(resultUri == null ? 0 : 1)
- .build());
+ if (mapsToProfileDbWithInsertedValues(uri, values)) {
+ switchToProfileMode();
+ return mProfileProvider.insert(uri, values);
}
+ switchToContactMode();
+ return super.insert(uri, values);
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
- .setApiType(LogUtils.ApiType.UPDATE)
- .setUriType(sUriMatcher.match(uri))
- .setCallerIsSyncAdapter(readBooleanQueryParameter(
- uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
- int updates = 0;
-
- try {
- waitForAccess(mWriteAccessLatch);
+ waitForAccess(mWriteAccessLatch);
- mContactsHelper.validateContentValues(getCallingPackage(), values);
- mContactsHelper.validateSql(getCallingPackage(), selection);
+ mContactsHelper.validateContentValues(getCallingPackage(), values);
+ mContactsHelper.validateSql(getCallingPackage(), selection);
- if (mapsToProfileDb(uri)) {
- switchToProfileMode();
- updates = mProfileProvider.update(uri, values, selection, selectionArgs);
- return updates;
- }
- switchToContactMode();
- updates = super.update(uri, values, selection, selectionArgs);
- return updates;
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(logBuilder.setResultCount(updates).build());
+ if (mapsToProfileDb(uri)) {
+ switchToProfileMode();
+ return mProfileProvider.update(uri, values, selection, selectionArgs);
}
+ switchToContactMode();
+ return super.update(uri, values, selection, selectionArgs);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
- .setApiType(LogUtils.ApiType.DELETE)
- .setUriType(sUriMatcher.match(uri))
- .setCallerIsSyncAdapter(readBooleanQueryParameter(
- uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
- int deletes = 0;
-
- try {
- waitForAccess(mWriteAccessLatch);
+ waitForAccess(mWriteAccessLatch);
- mContactsHelper.validateSql(getCallingPackage(), selection);
+ mContactsHelper.validateSql(getCallingPackage(), selection);
- if (mapsToProfileDb(uri)) {
- switchToProfileMode();
- deletes = mProfileProvider.delete(uri, selection, selectionArgs);
- return deletes;
- }
- switchToContactMode();
- deletes = super.delete(uri, selection, selectionArgs);
- return deletes;
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(logBuilder.setResultCount(deletes).build());
+ if (mapsToProfileDb(uri)) {
+ switchToProfileMode();
+ return mProfileProvider.delete(uri, selection, selectionArgs);
}
+ switchToContactMode();
+ return super.delete(uri, selection, selectionArgs);
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
- LogFields.Builder logBuilder =
- LogFields.Builder.aLogFields()
- .setApiType(LogUtils.ApiType.CALL)
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
waitForAccess(mReadAccessLatch);
switchToContactMode();
if (Authorization.AUTHORIZATION_METHOD.equals(method)) {
@@ -2298,95 +2235,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
undemoteContact(mDbHelper.get().getWritableDatabase(), id);
return null;
- } else if (SimContacts.ADD_SIM_ACCOUNT_METHOD.equals(method)) {
- ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
- MANAGE_SIM_ACCOUNTS_PERMISSION);
-
- final String accountName = extras.getString(SimContacts.KEY_ACCOUNT_NAME);
- final String accountType = extras.getString(SimContacts.KEY_ACCOUNT_TYPE);
- final int simSlot = extras.getInt(SimContacts.KEY_SIM_SLOT_INDEX, -1);
- final int efType = extras.getInt(SimContacts.KEY_SIM_EF_TYPE, -1);
- if (simSlot < 0) {
- throw new IllegalArgumentException("Sim slot is negative");
- }
- if (!SimAccount.getValidEfTypes().contains(efType)) {
- throw new IllegalArgumentException("Invalid EF type");
- }
- if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
- throw new IllegalArgumentException("Account name or type is empty");
- }
-
- long resultId = -1;
- final Bundle response = new Bundle();
- final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- db.beginTransaction();
- try {
- resultId = mDbHelper.get().createSimAccountIdInTransaction(
- AccountWithDataSet.get(accountName, accountType, null), simSlot, efType);
- db.setTransactionSuccessful();
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(
- logBuilder
- .setMethodCall(LogUtils.MethodCall.ADD_SIM_ACCOUNTS)
- .setResultCount(resultId > -1 ? 1 : 0)
- .build());
- db.endTransaction();
- }
-
- getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
- return response;
- } else if (SimContacts.REMOVE_SIM_ACCOUNT_METHOD.equals(method)) {
- ContactsPermissions.enforceCallingOrSelfPermission(
- getContext(), MANAGE_SIM_ACCOUNTS_PERMISSION);
-
- final int simSlot = extras.getInt(SimContacts.KEY_SIM_SLOT_INDEX, -1);
- if (simSlot < 0) {
- throw new IllegalArgumentException("Sim slot is negative");
- }
-
- int removedCount = 0;
- final Bundle response = new Bundle();
- final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- db.beginTransaction();
- try {
- removedCount = mDbHelper.get().removeSimAccounts(simSlot);
- scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
- db.setTransactionSuccessful();
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(
- logBuilder
- .setMethodCall(LogUtils.MethodCall.REMOVE_SIM_ACCOUNTS)
- .setResultCount(removedCount)
- .build());
- db.endTransaction();
- }
- getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
- return response;
- } else if (SimContacts.QUERY_SIM_ACCOUNTS_METHOD.equals(method)) {
- ContactsPermissions.enforceCallingOrSelfPermission(getContext(), READ_PERMISSION);
- final Bundle response = new Bundle();
- int accountsCount = 0;
- try {
- final List<SimAccount> simAccounts = mDbHelper.get().getAllSimAccounts();
- response.putParcelableList(SimContacts.KEY_SIM_ACCOUNTS, simAccounts);
- accountsCount = simAccounts.size();
- return response;
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(
- logBuilder
- .setMethodCall(LogUtils.MethodCall.GET_SIM_ACCOUNTS)
- .setResultCount(accountsCount)
- .build());
- }
}
return null;
}
@@ -2606,6 +2454,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
mTransactionContext.get().clearExceptSearchIndexUpdates();
}
+ @VisibleForTesting
+ void setMetadataSyncForTest(boolean enabled) {
+ mMetadataSyncEnabled = enabled;
+ }
+
/**
* Appends comma separated IDs.
* @param ids Should not be empty
@@ -2620,44 +2473,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
protected void notifyChange() {
- notifyChange(mSyncToNetwork);
+ notifyChange(mSyncToNetwork, mSyncToMetadataNetWork);
mSyncToNetwork = false;
+ mSyncToMetadataNetWork = false;
}
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Runnable mChangeNotifier = () -> {
- Log.v(TAG, "Scheduled notifyChange started.");
- mLastNotifyChange = System.currentTimeMillis();
+ protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
- false);
- };
-
- protected void notifyChange(boolean syncToNetwork) {
- if (syncToNetwork) {
- // Changes to sync to network won't be rate limited.
- getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
syncToNetwork);
- } else {
- // Rate limit the changes which are not to sync to network.
- long currentTimeMillis = System.currentTimeMillis();
-
- mHandler.removeCallbacks(mChangeNotifier);
- if (currentTimeMillis > mLastNotifyChange + NOTIFY_CHANGE_RATE_LIMIT) {
- // Notify change immediately, since it has been a while since last notify.
- mLastNotifyChange = currentTimeMillis;
- getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
- false);
- } else {
- // Schedule a delayed notification, to ensure the very last notifyChange will be
- // executed.
- // Delay is set to two-fold of rate limit, and the subsequent notifyChange called
- // (if ever) between the (NOTIFY_CHANGE_RATE_LIMIT, 2 * NOTIFY_CHANGE_RATE_LIMIT)
- // time window, will cancel this delayed notification.
- // The delayed notification is only expected to run if notifyChange is not invoked
- // between the above time window.
- mHandler.postDelayed(mChangeNotifier, NOTIFY_CHANGE_RATE_LIMIT * 2);
- }
- }
}
protected void setProviderStatus(int status) {
@@ -3215,8 +3038,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
Uri dataUri = inProfileMode()
? Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY)
: Data.CONTENT_URI;
- Cursor c = queryInternal(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS,
- selection, selectionArgs, null, null);
+ Cursor c = query(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS,
+ selection, selectionArgs, null);
try {
while(c.moveToNext()) {
long rawContactId = c.getLong(DataRowHandler.DataDeleteQuery.RAW_CONTACT_ID);
@@ -3243,8 +3066,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 = queryInternal(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS,
- Data._ID + "=?", mSelectionArgs1, null, null);
+ Cursor c = query(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, Data._ID + "=?",
+ mSelectionArgs1, null);
try {
if (!c.moveToFirst()) {
@@ -4071,7 +3894,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
values.put(RawContactsColumns.AGGREGATION_NEEDED, 1);
values.putNull(RawContacts.CONTACT_ID);
values.put(RawContacts.DIRTY, 1);
- return updateRawContact(db, rawContactId, values, callerIsSyncAdapter);
+ return updateRawContact(db, rawContactId, values, callerIsSyncAdapter,
+ /* callerIsMetadataSyncAdapter =*/false);
}
static int deleteDataUsage(SQLiteDatabase db) {
@@ -4173,7 +3997,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
String selectionWithId = (Data.RAW_CONTACT_ID + "=" + rawContactId + " ")
+ (selection == null ? "" : " AND " + selection);
- count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter);
+ count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter,
+ /* callerIsMetadataSyncAdapter =*/false);
break;
}
@@ -4181,7 +4006,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
case PROFILE_DATA: {
invalidateFastScrollingIndexCache();
count = updateData(uri, values, appendAccountToSelection(uri, selection),
- selectionArgs, callerIsSyncAdapter);
+ selectionArgs, callerIsSyncAdapter,
+ /* callerIsMetadataSyncAdapter =*/false);
if (count > 0) {
mSyncToNetwork |= !callerIsSyncAdapter;
}
@@ -4194,7 +4020,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
case CALLABLES_ID:
case POSTALS_ID: {
invalidateFastScrollingIndexCache();
- count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+ count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter,
+ /* callerIsMetadataSyncAdapter =*/false);
if (count > 0) {
mSyncToNetwork |= !callerIsSyncAdapter;
}
@@ -4247,7 +4074,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
case AGGREGATION_EXCEPTIONS: {
- count = updateAggregationException(db, values);
+ count = updateAggregationException(db, values,
+ /* callerIsMetadataSyncAdapter =*/false);
invalidateFastScrollingIndexCache();
break;
}
@@ -4573,7 +4401,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
try {
while (cursor.moveToNext()) {
long rawContactId = cursor.getLong(0);
- updateRawContact(db, rawContactId, values, callerIsSyncAdapter);
+ updateRawContact(db, rawContactId, values, callerIsSyncAdapter,
+ /* callerIsMetadataSyncAdapter =*/false);
count++;
}
} finally {
@@ -4606,7 +4435,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values,
- boolean callerIsSyncAdapter) {
+ boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
final String selection = RawContactsColumns.CONCRETE_ID + " = ?";
mSelectionArgs1[0] = Long.toString(rawContactId);
@@ -4737,7 +4566,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
private int updateData(Uri uri, ContentValues inputValues, String selection,
- String[] selectionArgs, boolean callerIsSyncAdapter) {
+ String[] selectionArgs, boolean callerIsSyncAdapter,
+ boolean callerIsMetadataSyncAdapter) {
final ContentValues values = new ContentValues(inputValues);
values.remove(Data._ID);
@@ -4763,7 +4593,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
selection, selectionArgs, null, -1 /* directory ID */, null);
try {
while(c.moveToNext()) {
- count += updateData(values, c, callerIsSyncAdapter);
+ count += updateData(values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
}
} finally {
c.close();
@@ -4779,7 +4609,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
- private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
+ private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter,
+ boolean callerIsMetadataSyncAdapter) {
if (values.size() == 0) {
return 0;
}
@@ -4794,7 +4625,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
DataRowHandler rowHandler = getDataRowHandler(mimeType);
boolean updated =
rowHandler.update(db, mTransactionContext.get(), values, c,
- callerIsSyncAdapter);
+ callerIsSyncAdapter, callerIsMetadataSyncAdapter);
if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
}
@@ -4912,7 +4743,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
return rslt;
}
- private int updateAggregationException(SQLiteDatabase db, ContentValues values) {
+ private int updateAggregationException(SQLiteDatabase db, ContentValues values,
+ boolean callerIsMetadataSyncAdapter) {
Integer exceptionType = values.getAsInteger(AggregationExceptions.TYPE);
Long rcId1 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID1);
Long rcId2 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID2);
@@ -5035,6 +4867,27 @@ public class ContactsProvider2 extends AbstractContactsProvider
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;
+ }
+
interface AggregationExceptionQuery {
String TABLE = Tables.AGGREGATION_EXCEPTIONS;
String[] COLUMNS = new String[] {
@@ -5071,6 +4924,80 @@ public class ContactsProvider2 extends AbstractContactsProvider
return aggregationRawContactIds;
}
+ /**
+ * 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, /* callerIsSyncAdapter =*/true,
+ /* callerIsMetadataSyncAdapter =*/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, rawContactId, dataHashId);
+
+ for (long dataId : dataIds) {
+ // Update is_primary and is_super_primary.
+ ContentValues dataValues = new ContentValues();
+ dataValues.put(Data.IS_PRIMARY, fieldData.mIsPrimary ? 1 : 0);
+ dataValues.put(Data.IS_SUPER_PRIMARY, fieldData.mIsSuperPrimary ? 1 : 0);
+ updateData(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
+ dataValues, null, null, /* callerIsSyncAdapter =*/true,
+ /* callerIsMetadataSyncAdapter =*/true);
+
+ }
+ }
+
+ // Update AggregationException table.
+ final Set<Long> aggregationRawContactIdsInServer = new ArraySet<>();
+ 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, /* callerIsMetadataSyncAdapter =*/true);
+ if (rawContactId1 != rawContactId) {
+ aggregationRawContactIdsInServer.add(rawContactId1);
+ }
+ if (rawContactId2 != rawContactId) {
+ aggregationRawContactIdsInServer.add(rawContactId2);
+ }
+ }
+
+ // Delete AggregationExceptions from CP2 if it doesn't exist in server side.
+ Set<Long> aggregationRawContactIdsInLocal = queryAggregationRawContactIds(db, rawContactId);
+ Set<Long> rawContactIdsToBeDeleted = com.google.common.collect.Sets.difference(
+ aggregationRawContactIdsInLocal, aggregationRawContactIdsInServer);
+ for (Long deleteRawContactId : rawContactIdsToBeDeleted) {
+ ContentValues values = new ContentValues();
+ values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
+ values.put(AggregationExceptions.RAW_CONTACT_ID2, deleteRawContactId);
+ values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_AUTOMATIC);
+ updateAggregationException(db, values, /* callerIsMetadataSyncAdapter =*/true);
+ }
+ }
+
/** return serialized version of {@code accounts} */
@VisibleForTesting
static String accountsToString(Set<Account> accounts) {
@@ -5165,14 +5092,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
// All accounts that are used in raw_contacts and/or groups.
final Set<AccountWithDataSet> knownAccountsWithDataSets
= dbHelper.getAllAccountsWithDataSets();
- // All known SIM accounts
- final List<SimAccount> simAccounts = getDatabaseHelper().getAllSimAccounts();
+
// Find the accounts that have been removed.
final List<AccountWithDataSet> accountsWithDataSetsToDelete = Lists.newArrayList();
for (AccountWithDataSet knownAccountWithDataSet : knownAccountsWithDataSets) {
if (knownAccountWithDataSet.isLocalAccount()
- || knownAccountWithDataSet.inSystemAccounts(systemAccounts)
- || knownAccountWithDataSet.inSimAccounts(simAccounts)) {
+ || knownAccountWithDataSet.inSystemAccounts(systemAccounts)) {
continue;
}
accountsWithDataSetsToDelete.add(knownAccountWithDataSet);
@@ -5182,6 +5107,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
for (AccountWithDataSet accountWithDataSet : accountsWithDataSetsToDelete) {
final Long accountIdOrNull = dbHelper.getAccountIdOrNull(accountWithDataSet);
+ // getAccountIdOrNull() really shouldn't return null here, but just in case...
if (accountIdOrNull != null) {
final String accountId = Long.toString(accountIdOrNull);
final String[] accountIdParams =
@@ -5214,6 +5140,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + RawContactsColumns.ACCOUNT_ID + " = ?)",
accountIdParams);
+ db.execSQL(
+ "DELETE FROM " + Tables.METADATA_SYNC +
+ " WHERE " + MetadataSyncColumns.ACCOUNT_ID + " = ?",
+ accountIdParams);
+ db.execSQL(
+ "DELETE FROM " + Tables.METADATA_SYNC_STATE +
+ " WHERE " + MetadataSyncStateColumns.ACCOUNT_ID + " = ?",
+ accountIdParams);
// Delta API is only needed for regular contacts.
if (!inProfileMode()) {
@@ -5414,29 +5348,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder, CancellationSignal cancellationSignal) {
- LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
- .setApiType(LogUtils.ApiType.QUERY)
- .setUriType(sUriMatcher.match(uri))
- .setCallerIsSyncAdapter(readBooleanQueryParameter(
- uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
-
- Cursor cursor = null;
- try {
- cursor = queryInternal(uri, projection, selection, selectionArgs, sortOrder,
- cancellationSignal);
- return cursor;
- } catch (Exception e) {
- logBuilder.setException(e);
- throw e;
- } finally {
- LogUtils.log(
- logBuilder.setResultCount(cursor == null ? 0 : cursor.getCount()).build());
- }
- }
-
- private Cursor queryInternal(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "query: uri=" + uri + " projection=" + Arrays.toString(projection) +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -5637,17 +5548,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
return null;
}
- if (cursor.getCount() > 0) {
- final int callingUid = Binder.getCallingUid();
- final String directoryAuthority = directoryInfo.authority;
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "Making authority " + directoryAuthority
- + " visible to UID " + callingUid);
- }
- getContext().getPackageManager().grantImplicitAccess(
- callingUid, directoryAuthority);
- }
-
// Load the cursor contents into a memory cursor (backed by a cursor window) and close the
// underlying cursor.
try {
@@ -6796,17 +6696,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
boolean foundResult = false;
Cursor cursor = doQuery(db, qb, projectionWithNumber, selection, selectionArgs,
sortOrder, groupBy, null, limit, cancellationSignal);
-
try {
if (cursor.getCount() > 0) {
foundResult = true;
- cursor = PhoneLookupWithStarPrefix
+ return PhoneLookupWithStarPrefix
.removeNonStarMatchesFromCursor(number, cursor);
- if (!mDbHelper.get().getUseStrictPhoneNumberComparisonForTest()) {
- cursor = PhoneLookupWithStarPrefix.removeNoMatchPhoneNumber(number,
- cursor, mDbHelper.get().getCurrentCountryIso());
- }
- return cursor;
}
// Use the fall-back lookup method.
@@ -6819,13 +6713,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
// numbers
mDbHelper.get().buildFallbackPhoneLookupAndContactQuery(qb, number);
- Cursor fallbackCursor = doQuery(db, qb, projectionWithNumber,
+ final Cursor fallbackCursor = doQuery(db, qb, projectionWithNumber,
selection, selectionArgs, sortOrder, groupBy, having, limit,
cancellationSignal);
- fallbackCursor = PhoneLookupWithStarPrefix.removeNonStarMatchesFromCursor(
+ return PhoneLookupWithStarPrefix.removeNonStarMatchesFromCursor(
number, fallbackCursor);
- return PhoneLookupWithStarPrefix.removeNoMatchPhoneNumber(number,
- fallbackCursor, mDbHelper.get().getCurrentCountryIso());
} finally {
if (!foundResult) {
// We'll be returning a different cursor, so close this one.
@@ -8736,7 +8628,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
case RAW_CONTACTS_ID_DISPLAY_PHOTO: {
long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
- boolean writeable = mode.contains("w");
+ boolean writeable = !mode.equals("r");
// Find the primary photo data record for this raw contact.
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java
index a82ce34f..235edfed 100644
--- a/src/com/android/providers/contacts/DataRowHandler.java
+++ b/src/com/android/providers/contacts/DataRowHandler.java
@@ -121,6 +121,7 @@ public abstract class DataRowHandler {
if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) {
final long mimeTypeId = getMimeTypeId();
mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId);
+ txContext.markRawContactMetadataDirty(rawContactId, /* isMetadataSyncAdapter =*/false);
// We also have to make sure that no other data item on this raw_contact is
// configured super primary
@@ -153,11 +154,13 @@ public abstract class DataRowHandler {
* @return true if update changed something
*/
public boolean update(SQLiteDatabase db, TransactionContext txContext,
- ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
+ ContentValues values, Cursor c, boolean callerIsSyncAdapter,
+ boolean callerIsMetadataSyncAdapter) {
long dataId = c.getLong(DataUpdateQuery._ID);
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
- handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId);
+ handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId,
+ callerIsMetadataSyncAdapter);
handleHashIdForUpdate(values, dataId);
if (values.size() > 0) {
@@ -248,13 +251,15 @@ public abstract class DataRowHandler {
* configured correctly
*/
private void handlePrimaryAndSuperPrimary(TransactionContext txContext, ContentValues values,
- long dataId, long rawContactId) {
+ long dataId, long rawContactId, boolean callerIsMetadataSyncAdapter) {
final boolean hasPrimary = values.getAsInteger(Data.IS_PRIMARY) != null;
final boolean hasSuperPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY) != null;
// Nothing to do? Bail out early
if (!hasPrimary && !hasSuperPrimary) return;
+ txContext.markRawContactMetadataDirty(rawContactId, callerIsMetadataSyncAdapter);
+
final long mimeTypeId = getMimeTypeId();
// Check if we want to clear values
@@ -320,6 +325,7 @@ public abstract class DataRowHandler {
db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1);
if (count != 0 && primary) {
fixPrimary(db, rawContactId);
+ txContext.markRawContactMetadataDirty(rawContactId, /* isMetadataSyncAdapter =*/false);
}
if (hasSearchableData()) {
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
index 063fcdb2..5ae3a01f 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
@@ -49,14 +49,15 @@ public class DataRowHandlerForCommonDataKind extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
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);
+ return super.update(db, txContext, values, c, callerIsSyncAdapter,
+ callerIsMetadataSyncAdapter);
}
/**
diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
index 539c9596..3c7311f0 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForEmail.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
@@ -50,8 +50,8 @@ public class DataRowHandlerForEmail extends DataRowHandlerForCommonDataKind {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
- if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
index 3f310b18..b1c40497 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
@@ -86,11 +86,11 @@ public class DataRowHandlerForGroupMembership extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
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)) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
index 32e9757e..4d4a33f6 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
@@ -46,9 +46,9 @@ public class DataRowHandlerForIdentity extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
- super.update(db, txContext, values, c, callerIsSyncAdapter);
+ super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
// Identity affects aggregation.
final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index 03b96a3a..cc85c2b1 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -52,11 +52,11 @@ public class DataRowHandlerForNickname extends DataRowHandlerForCommonDataKind {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
long dataId = c.getLong(DataUpdateQuery._ID);
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
- if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 66a3b1bd..5b69fe3c 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -51,8 +51,8 @@ public class DataRowHandlerForOrganization extends DataRowHandlerForCommonDataKi
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
- if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
index 052252e1..7bbac689 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
@@ -58,10 +58,10 @@ public class DataRowHandlerForPhoneNumber extends DataRowHandlerForCommonDataKin
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
fillNormalizedNumber(values);
- if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index 532a8521..3d28b05c 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -76,7 +76,7 @@ public class DataRowHandlerForPhoto extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
if (values.containsKey(SKIP_PROCESSING_KEY)) {
@@ -89,7 +89,7 @@ public class DataRowHandlerForPhoto extends DataRowHandler {
}
// Do the actual update.
- if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
+ if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
index 044e9726..b80f759d 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
@@ -63,7 +63,7 @@ public class DataRowHandlerForStructuredName extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
final long dataId = c.getLong(DataUpdateQuery._ID);
final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
@@ -74,7 +74,7 @@ public class DataRowHandlerForStructuredName extends DataRowHandler {
fixStructuredNameComponents(augmented, values);
- super.update(db, txContext, values, c, callerIsSyncAdapter);
+ super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
if (values.containsKey(StructuredName.DISPLAY_NAME)) {
augmented.putAll(values);
String name = augmented.getAsString(StructuredName.DISPLAY_NAME);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
index 7fc97b7a..235bbd7c 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
@@ -60,7 +60,7 @@ public class DataRowHandlerForStructuredPostal extends DataRowHandler {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
- Cursor c, boolean callerIsSyncAdapter) {
+ Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
final long dataId = c.getLong(DataUpdateQuery._ID);
final ContentValues augmented = getAugmentedValues(db, dataId, values);
if (augmented == null) { // No change
@@ -68,7 +68,7 @@ public class DataRowHandlerForStructuredPostal extends DataRowHandler {
}
fixStructuredPostalComponents(augmented, values);
- super.update(db, txContext, values, c, callerIsSyncAdapter);
+ super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter);
return true;
}
diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java
new file mode 100644
index 00000000..2fe423a8
--- /dev/null
+++ b/src/com/android/providers/contacts/MetadataEntryParser.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2015 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.text.TextUtils;
+import com.android.providers.contacts.util.NeededForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+@NeededForTesting
+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";
+ private final static String STARRED = "starred";
+ private final static String PINNED = "pinned";
+ private final static String AGGREGATION_DATA = "aggregation_data";
+ private final static String CONTACT_IDS = "contact_ids";
+ private final static String TYPE = "type";
+ private final static String FIELD_DATA = "field_data";
+ private final static String FIELD_DATA_ID = "field_data_id";
+ private final static String FIELD_DATA_PREFS = "field_data_prefs";
+ private final static String IS_PRIMARY = "is_primary";
+ private final static String IS_SUPER_PRIMARY = "is_super_primary";
+ private final static String USAGE_STATS = "usage_stats";
+ private final static String USAGE_TYPE = "usage_type";
+ private final static String LAST_TIME_USED = "last_time_used";
+ private final static String USAGE_COUNT = "usage_count";
+
+ @NeededForTesting
+ public static class UsageStats {
+ @NeededForTesting
+ final String mUsageType;
+ @NeededForTesting
+ final long mLastTimeUsed;
+ @NeededForTesting
+ final int mTimesUsed;
+
+ @NeededForTesting
+ public UsageStats(String usageType, long lastTimeUsed, int timesUsed) {
+ this.mUsageType = usageType;
+ this.mLastTimeUsed = lastTimeUsed;
+ this.mTimesUsed = timesUsed;
+ }
+ }
+
+ @NeededForTesting
+ public static class FieldData {
+ @NeededForTesting
+ final String mDataHashId;
+ @NeededForTesting
+ final boolean mIsPrimary;
+ @NeededForTesting
+ final boolean mIsSuperPrimary;
+ @NeededForTesting
+ final ArrayList<UsageStats> mUsageStatsList;
+
+ @NeededForTesting
+ public FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary,
+ ArrayList<UsageStats> usageStatsList) {
+ this.mDataHashId = dataHashId;
+ this.mIsPrimary = isPrimary;
+ this.mIsSuperPrimary = isSuperPrimary;
+ this.mUsageStatsList = usageStatsList;
+ }
+ }
+
+ @NeededForTesting
+ public static class RawContactInfo {
+ @NeededForTesting
+ final String mBackupId;
+ @NeededForTesting
+ final String mAccountType;
+ @NeededForTesting
+ final String mAccountName;
+ @NeededForTesting
+ 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 {
+ @NeededForTesting
+ final RawContactInfo mRawContactInfo1;
+ @NeededForTesting
+ final RawContactInfo mRawContactInfo2;
+ @NeededForTesting
+ final String mType;
+
+ @NeededForTesting
+ public AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2,
+ String type) {
+ this.mRawContactInfo1 = rawContactInfo1;
+ this.mRawContactInfo2 = rawContactInfo2;
+ this.mType = type;
+ }
+ }
+
+ @NeededForTesting
+ public static class MetadataEntry {
+ @NeededForTesting
+ final RawContactInfo mRawContactInfo;
+ @NeededForTesting
+ final int mSendToVoicemail;
+ @NeededForTesting
+ final int mStarred;
+ @NeededForTesting
+ final int mPinned;
+ @NeededForTesting
+ final ArrayList<FieldData> mFieldDatas;
+ @NeededForTesting
+ final ArrayList<AggregationData> mAggregationDatas;
+
+ @NeededForTesting
+ public MetadataEntry(RawContactInfo rawContactInfo,
+ int sendToVoicemail, int starred, int pinned,
+ ArrayList<FieldData> fieldDatas,
+ ArrayList<AggregationData> aggregationDatas) {
+ this.mRawContactInfo = rawContactInfo;
+ this.mSendToVoicemail = sendToVoicemail;
+ this.mStarred = starred;
+ this.mPinned = pinned;
+ this.mFieldDatas = fieldDatas;
+ this.mAggregationDatas = aggregationDatas;
+ }
+ }
+
+ @NeededForTesting
+ static MetadataEntry parseDataToMetaDataEntry(String inputData) {
+ if (TextUtils.isEmpty(inputData)) {
+ throw new IllegalArgumentException("Input cannot be empty.");
+ }
+
+ try {
+ final JSONObject root = new JSONObject(inputData);
+ // Parse to get rawContactId and account info.
+ 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);
+ final boolean sendToVoicemail = contactPrefs.has(SEND_TO_VOICEMAIL)
+ ? contactPrefs.getBoolean(SEND_TO_VOICEMAIL) : false;
+ final boolean starred = contactPrefs.has(STARRED)
+ ? contactPrefs.getBoolean(STARRED) : false;
+ final int pinned = contactPrefs.has(PINNED) ? contactPrefs.getInt(PINNED) : 0;
+
+ // Parse aggregationDatas
+ final ArrayList<AggregationData> aggregationsList = new ArrayList<AggregationData>();
+ if (root.has(AGGREGATION_DATA)) {
+ final JSONArray aggregationDatas = root.getJSONArray(AGGREGATION_DATA);
+
+ for (int i = 0; i < aggregationDatas.length(); i++) {
+ final JSONObject aggregationData = aggregationDatas.getJSONObject(i);
+ final JSONArray contacts = aggregationData.getJSONArray(CONTACT_IDS);
+
+ if (contacts.length() != 2) {
+ throw new IllegalArgumentException(
+ "There should be two contacts for each aggregation.");
+ }
+ final JSONObject rawContact1 = contacts.getJSONObject(0);
+ final RawContactInfo aggregationContact1 = parseUniqueContact(rawContact1);
+ final JSONObject rawContact2 = contacts.getJSONObject(1);
+ 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(
+ aggregationContact1, aggregationContact2, type);
+ aggregationsList.add(aggregation);
+ }
+ }
+
+ // Parse fieldDatas
+ final ArrayList<FieldData> fieldDatasList = new ArrayList<FieldData>();
+ if (root.has(FIELD_DATA)) {
+ final JSONArray fieldDatas = root.getJSONArray(FIELD_DATA);
+
+ for (int i = 0; i < fieldDatas.length(); i++) {
+ final JSONObject fieldData = fieldDatas.getJSONObject(i);
+ 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);
+
+ final ArrayList<UsageStats> usageStatsList = new ArrayList<UsageStats>();
+ if (fieldData.has(USAGE_STATS)) {
+ final JSONArray usageStats = fieldData.getJSONArray(USAGE_STATS);
+ for (int j = 0; j < usageStats.length(); j++) {
+ final JSONObject usageStat = usageStats.getJSONObject(j);
+ final String usageType = usageStat.getString(USAGE_TYPE);
+ if (TextUtils.isEmpty(usageType)) {
+ throw new IllegalArgumentException("Usage type cannot be empty.");
+ }
+ final long lastTimeUsed = usageStat.getLong(LAST_TIME_USED);
+ final int usageCount = usageStat.getInt(USAGE_COUNT);
+
+ final UsageStats usageStatsParsed = new UsageStats(
+ usageType, lastTimeUsed, usageCount);
+ usageStatsList.add(usageStatsParsed);
+ }
+ }
+
+ final FieldData fieldDataParse = new FieldData(dataHashId, isPrimary,
+ isSuperPrimary, usageStatsList);
+ fieldDatasList.add(fieldDataParse);
+ }
+ }
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java b/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
index ca29edc6..7efc8910 100644
--- a/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
+++ b/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
@@ -15,7 +15,6 @@
*/
package com.android.providers.contacts;
-import com.android.i18n.phonenumbers.Phonenumber;
import com.android.internal.annotations.VisibleForTesting;
import android.database.Cursor;
@@ -149,36 +148,4 @@ import android.util.Log;
return null;
}
}
-
- /**
- * Check each phone number in the given cursor to detemine if it's a match with the given phone
- * number. Return the matching ones in a new cursor.
- * @param number phone number to be match
- * @param cursor contains a series of number s to be match
- * @param defaultCountryIso The lowercase two letter ISO 3166-1 country code. It is recommended
- * to pass in {@link TelephonyManager#getNetworkCountryIso()}.
- * @return A new cursor with all matching phone numbers.
- */
- public static Cursor removeNoMatchPhoneNumber(String number, Cursor cursor,
- String defaultCountryIso) {
- if (number == null) {
- return cursor;
- }
-
- final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames());
-
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
- final String numberToMatch = cursor.getString(numberIndex);
- if (PhoneNumberUtils.areSamePhoneNumber(number, numberToMatch, defaultCountryIso)) {
- final MatrixCursor.RowBuilder b = matrixCursor.newRow();
- for (int column = 0; column < cursor.getColumnCount(); column++) {
- b.add(cursor.getColumnName(column), cursorValue(cursor, column));
- }
- }
- }
-
- return matrixCursor;
- }
}
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index d9dc784e..f3b6daf6 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -116,8 +116,8 @@ public class ProfileProvider extends AbstractContactsProvider {
mDelegate.notifyChange();
}
- protected void notifyChange(boolean syncToNetwork) {
- mDelegate.notifyChange(syncToNetwork);
+ protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetWork) {
+ mDelegate.notifyChange(syncToNetwork, syncToMetadataNetWork);
}
protected Locale getLocale() {
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index aeaa0e7d..e421654c 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -55,8 +55,7 @@ public class SearchIndexManager {
private static final int MAX_STRING_BUILDER_SIZE = 1024 * 10;
public static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index";
- private static final String ROW_ID_KEY = "rowid";
- private static final int SEARCH_INDEX_VERSION = 2;
+ private static final int SEARCH_INDEX_VERSION = 1;
private static final class ContactIndexQuery {
public static final String[] COLUMNS = {
@@ -328,7 +327,7 @@ public class SearchIndexManager {
// Remove affected search_index rows.
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
final int deleted = db.delete(Tables.SEARCH_INDEX,
- ROW_ID_KEY + " IN (SELECT " +
+ SearchIndexColumns.CONTACT_ID + " IN (SELECT " +
RawContacts.CONTACT_ID +
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + rawContactsSelection +
@@ -401,7 +400,6 @@ public class SearchIndexManager {
mValues.put(SearchIndexColumns.NAME, builder.getName());
mValues.put(SearchIndexColumns.TOKENS, builder.getTokens());
mValues.put(SearchIndexColumns.CONTACT_ID, contactId);
- mValues.put(ROW_ID_KEY, contactId);
db.insert(Tables.SEARCH_INDEX, null, mValues);
}
private int getSearchIndexVersion() {
diff --git a/src/com/android/providers/contacts/TransactionContext.java b/src/com/android/providers/contacts/TransactionContext.java
index 86dae01b..dfb6d696 100644
--- a/src/com/android/providers/contacts/TransactionContext.java
+++ b/src/com/android/providers/contacts/TransactionContext.java
@@ -35,6 +35,7 @@ public class TransactionContext {
/** Map from raw contact id to account Id */
private ArrayMap<Long, Long> mInsertedRawContactsAccounts;
private ArraySet<Long> mUpdatedRawContacts;
+ private ArraySet<Long> mMetadataDirtyRawContacts;
private ArraySet<Long> mBackupIdChangedRawContacts;
private ArraySet<Long> mDirtyRawContacts;
// Set used to track what has been changed and deleted. This is needed so we can update the
@@ -77,6 +78,22 @@ public class TransactionContext {
markRawContactChangedOrDeletedOrInserted(rawContactId);
}
+ public void markRawContactMetadataDirty(long rawContactId, boolean isMetadataSyncAdapter) {
+ if (!isMetadataSyncAdapter) {
+ if (mMetadataDirtyRawContacts == null) {
+ mMetadataDirtyRawContacts = new ArraySet<>();
+ }
+ mMetadataDirtyRawContacts.add(rawContactId);
+ }
+ }
+
+ public void markBackupIdChangedRawContact(long rawContactId) {
+ if (mBackupIdChangedRawContacts == null) {
+ mBackupIdChangedRawContacts = new ArraySet<>();
+ }
+ mBackupIdChangedRawContacts.add(rawContactId);
+ }
+
public void markRawContactChangedOrDeletedOrInserted(long rawContactId) {
if (mChangedRawContacts == null) {
mChangedRawContacts = new ArraySet<>();
@@ -114,6 +131,16 @@ public class TransactionContext {
return mDirtyRawContacts;
}
+ public Set<Long> getMetadataDirtyRawContactIds() {
+ if (mMetadataDirtyRawContacts == null) mMetadataDirtyRawContacts = new ArraySet<>();
+ return mMetadataDirtyRawContacts;
+ }
+
+ public Set<Long> getBackupIdChangedRawContacts() {
+ if (mBackupIdChangedRawContacts == null) mBackupIdChangedRawContacts = new ArraySet<>();
+ return mBackupIdChangedRawContacts;
+ }
+
public Set<Long> getChangedRawContactIds() {
if (mChangedRawContacts == null) mChangedRawContacts = new ArraySet<>();
return mChangedRawContacts;
@@ -149,6 +176,7 @@ public class TransactionContext {
mUpdatedRawContacts = null;
mUpdatedSyncStates = null;
mDirtyRawContacts = null;
+ mMetadataDirtyRawContacts = null;
mChangedRawContacts = null;
mBackupIdChangedRawContacts = null;
}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index ef7a3758..daecd973 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -24,7 +24,6 @@ import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.content.AttributionSource;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -120,23 +119,24 @@ public class VoicemailContentProvider extends ContentProvider
}
@Override
- protected int enforceReadPermissionInner(Uri uri,
- @NonNull AttributionSource attributionSource) throws SecurityException {
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg,
+ @Nullable String featureId, IBinder callerToken) throws SecurityException {
// Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
return MODE_ALLOWED;
}
- return super.enforceReadPermissionInner(uri, attributionSource);
+ return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken);
}
+
@Override
- protected int enforceWritePermissionInner(Uri uri,
- @NonNull AttributionSource attributionSource) throws SecurityException {
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg,
+ @Nullable String featureId, IBinder callerToken) throws SecurityException {
// Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
return MODE_ALLOWED;
}
- return super.enforceWritePermissionInner(uri, attributionSource);
+ return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken);
}
@VisibleForTesting
diff --git a/src/com/android/providers/contacts/VoicemailNotifier.java b/src/com/android/providers/contacts/VoicemailNotifier.java
index b4033eac..159cec73 100644
--- a/src/com/android/providers/contacts/VoicemailNotifier.java
+++ b/src/com/android/providers/contacts/VoicemailNotifier.java
@@ -1,6 +1,5 @@
package com.android.providers.contacts;
-import android.app.BroadcastOptions;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -28,12 +27,6 @@ public class VoicemailNotifier {
private final String TAG = "VoicemailNotifier";
- /**
- * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background
- * service to do VVM processing.
- */
- private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000;
-
private final Context mContext;
private final Uri mBaseUri;
@@ -92,17 +85,7 @@ public class VoicemailNotifier {
intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
callingPackages.contains(component.getPackageName()));
}
- if (intentAction.equals(VoicemailContract.ACTION_NEW_VOICEMAIL)) {
- BroadcastOptions bopts = BroadcastOptions.makeBasic();
- bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS);
- Log.i(TAG, String.format("sendNotification: allowMillis=%d, pkg=%s",
- VOICEMAIL_ALLOW_LIST_DURATION_MILLIS, component.getPackageName()));
- mContext.sendBroadcast(intent, android.Manifest.permission.READ_VOICEMAIL,
- bopts.toBundle());
- } else {
- mContext.sendBroadcast(intent);
- }
-
+ mContext.sendBroadcast(intent);
Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," +
" self_change:%s", intent.getAction(), intent.getData(),
component.getClassName(),
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
index b38f7f58..58e7a146 100644
--- a/src/com/android/providers/contacts/VoicemailPermissions.java
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -151,12 +151,7 @@ public class VoicemailPermissions {
}
private static boolean packageHasCarrierPrivileges(TelephonyManager tm, String packageName) {
- final long token = Binder.clearCallingIdentity();
- try {
- return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
}
diff --git a/src/com/android/providers/contacts/debug/ContactsDumpActivity.java b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
index 725b5bfc..359f3f8b 100644
--- a/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
+++ b/src/com/android/providers/contacts/debug/ContactsDumpActivity.java
@@ -66,13 +66,6 @@ public class ContactsDumpActivity extends Activity implements OnClickListener {
}
@Override
- protected void onStart() {
- super.onStart();
- getWindow().addSystemFlags(android.view.WindowManager.LayoutParams
- .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- }
-
- @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.confirm:
diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java
deleted file mode 100644
index 4d07ca4b..00000000
--- a/src/com/android/providers/contacts/util/LogFields.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 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.util;
-
-import android.net.Uri;
-
-public final class LogFields {
-
- private final int apiType;
-
- private final int uriType;
-
- private final boolean callerIsSyncAdapter;
-
- private final long startNanos;
-
- private Exception exception;
-
- private Uri resultUri;
-
- private int resultCount;
-
- private int methodCall;
-
- public LogFields(int apiType, int uriType, boolean callerIsSyncAdapter, long startNanos) {
- this.apiType = apiType;
- this.uriType = uriType;
- this.callerIsSyncAdapter = callerIsSyncAdapter;
- this.startNanos = startNanos;
- }
-
- public int getApiType() {
- return apiType;
- }
-
- public int getUriType() {
- return uriType;
- }
-
- public boolean isCallerIsSyncAdapter() {
- return callerIsSyncAdapter;
- }
-
- public long getStartNanos() {
- return startNanos;
- }
-
- public Exception getException() {
- return exception;
- }
-
- public Uri getResultUri() {
- return resultUri;
- }
-
- public int getResultCount() {
- return resultCount;
- }
-
- public int getMethodCall() {
- return methodCall;
- }
-
- public static final class Builder {
- private int apiType;
- private int uriType;
- private boolean callerIsSyncAdapter;
- private long startNanos;
- private Exception exception;
- private Uri resultUri;
- private int resultCount;
- private int methodCall;
-
- private Builder() {
- }
-
- public static Builder aLogFields() {
- return new Builder();
- }
-
- public Builder setApiType(int apiType) {
- this.apiType = apiType;
- return this;
- }
-
- public Builder setUriType(int uriType) {
- this.uriType = uriType;
- return this;
- }
-
- public Builder setCallerIsSyncAdapter(boolean callerIsSyncAdapter) {
- this.callerIsSyncAdapter = callerIsSyncAdapter;
- return this;
- }
-
- public Builder setStartNanos(long startNanos) {
- this.startNanos = startNanos;
- return this;
- }
-
- public Builder setException(Exception exception) {
- this.exception = exception;
- return this;
- }
-
- public Builder setResultUri(Uri resultUri) {
- this.resultUri = resultUri;
- return this;
- }
-
- public Builder setResultCount(int resultCount) {
- this.resultCount = resultCount;
- return this;
- }
-
- public Builder setMethodCall(int methodCall) {
- this.methodCall = methodCall;
- return this;
- }
-
- public LogFields build() {
- LogFields logFields = new LogFields(apiType, uriType, callerIsSyncAdapter, startNanos);
- logFields.resultCount = this.resultCount;
- logFields.exception = this.exception;
- logFields.resultUri = this.resultUri;
- logFields.methodCall = this.methodCall;
- return logFields;
- }
- }
-}
diff --git a/src/com/android/providers/contacts/util/LogUtils.java b/src/com/android/providers/contacts/util/LogUtils.java
deleted file mode 100644
index a564a359..00000000
--- a/src/com/android/providers/contacts/util/LogUtils.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 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.util;
-
-import android.os.SystemClock;
-import android.util.StatsEvent;
-import android.util.StatsLog;
-
-public class LogUtils {
- // Keep in sync with ContactsProviderStatus#ResultType in
- // frameworks/proto_logging/stats/atoms.proto file.
- public interface ResultType {
- int SUCCESS = 1;
- int FAIL = 2;
- int ILLEGAL_ARGUMENT = 3;
- int UNSUPPORTED_OPERATION = 4;
- }
-
- // Keep in sync with ContactsProviderStatus#ApiType in
- // frameworks/proto_logging/stats/atoms.proto file.
- public interface ApiType {
- int QUERY = 1;
- int INSERT = 2;
- int UPDATE = 3;
- int DELETE = 4;
- int CALL = 5;
- }
-
- // Keep in sync with ContactsProviderStatus#CallerType in
- // frameworks/proto_logging/stats/atoms.proto file.
- public interface CallerType {
- int CALLER_IS_SYNC_ADAPTER = 1;
- int CALLER_IS_NOT_SYNC_ADAPTER = 2;
- }
-
- // Keep in sync with ContactsProviderStatus#MethodCall in
- // frameworks/proto_logging/stats/atoms.proto file.
- public interface MethodCall {
- int ADD_SIM_ACCOUNTS = 1;
- int REMOVE_SIM_ACCOUNTS = 2;
- int GET_SIM_ACCOUNTS = 3;
- }
-
- private static final int STATSD_LOG_ATOM_ID = 301;
-
- public static void log(LogFields logFields) {
- StatsLog.write(StatsEvent.newBuilder()
- .setAtomId(STATSD_LOG_ATOM_ID)
- .writeInt(logFields.getApiType())
- .writeInt(logFields.getUriType())
- .writeInt(getCallerType(logFields.isCallerIsSyncAdapter()))
- .writeInt(getResultType(logFields.getException()))
- .writeInt(logFields.getResultCount())
- .writeLong(getLatencyMicros(logFields.getStartNanos()))
- .writeInt(0) // Empty value for TaskType
- .writeInt(logFields.getMethodCall())
- .usePooledBuffer()
- .build());
- }
-
- private static int getCallerType(boolean callerIsSyncAdapter) {
- return callerIsSyncAdapter
- ? CallerType.CALLER_IS_SYNC_ADAPTER : CallerType.CALLER_IS_NOT_SYNC_ADAPTER;
- }
-
- private static int getResultType(Exception exception) {
- if (exception == null) {
- return ResultType.SUCCESS;
- } else if (exception instanceof IllegalArgumentException) {
- return ResultType.ILLEGAL_ARGUMENT;
- } else if (exception instanceof UnsupportedOperationException) {
- return ResultType.UNSUPPORTED_OPERATION;
- } else {
- return ResultType.FAIL;
- }
- }
-
- private static long getLatencyMicros(long startNanos) {
- return (SystemClock.elapsedRealtimeNanos() - startNanos) / 1000;
- }
-}
-
-
diff --git a/test_common/Android.bp b/test_common/Android.bp
index 207b1db4..b30e2e33 100644
--- a/test_common/Android.bp
+++ b/test_common/Android.bp
@@ -12,17 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: [
- "packages_providers_ContactsProvider_license",
- ],
-}
-
java_library {
name: "ContactsProviderTestUtils",
srcs: ["src/**/*.java"],
diff --git a/tests/Android.bp b/tests/Android.bp
index 6fc1d179..b15ded47 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1,14 +1,3 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: [
- "packages_providers_ContactsProvider_license",
- ],
-}
-
android_test {
name: "ContactsProviderTests",
static_libs: [
diff --git a/tests/assets/test1/testFileDeviceContactMetadataJSON.txt b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt
new file mode 100644
index 00000000..65e624d1
--- /dev/null
+++ b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt
@@ -0,0 +1,69 @@
+{
+ "unique_contact_id": {
+ "account_type": "CUSTOM_ACCOUNT",
+ "custom_account_type": "facebook",
+ "account_name": "android-test",
+ "contact_id": "1111111",
+ "data_set": "FOCUS"
+ },
+ "contact_prefs": {
+ "send_to_voicemail": true,
+ "starred": false,
+ "pinned": 2
+ },
+ "aggregation_data": [
+ {
+ "type": "TOGETHER",
+ "contact_ids": [
+ {
+ "account_type": "GOOGLE_ACCOUNT",
+ "account_name": "android-test2",
+ "contact_id": "2222222",
+ "data_set": "GOOGLE_PLUS"
+ },
+ {
+ "account_type": "GOOGLE_ACCOUNT",
+ "account_name": "android-test3",
+ "contact_id": "3333333",
+ "data_set": "CUSTOM",
+ "custom_data_set": "custom type"
+ }
+ ]
+ }
+ ],
+ "field_data": [
+ {
+ "field_data_id": "1001",
+ "field_data_prefs": {
+ "is_primary": true,
+ "is_super_primary": true
+ },
+ "usage_stats": [
+ {
+ "usage_type": "CALL",
+ "last_time_used": 10000001,
+ "usage_count": 10
+ },
+ {
+ "usage_type": "SHORT_TEXT",
+ "last_time_used": 20000002,
+ "usage_count": 20
+ }
+ ]
+ },
+ {
+ "field_data_id": "1002",
+ "field_data_prefs": {
+ "is_primary": false,
+ "is_super_primary": false
+ },
+ "usage_stats": [
+ {
+ "usage_type": "LONG_TEXT",
+ "last_time_used": 30000003,
+ "usage_count": 30
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 816d10d7..91a76a31 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -1331,6 +1331,10 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
assertEquals(expected, (getContactsProvider()).isNetworkNotified());
}
+ protected void assertMetadataNetworkNotified(boolean expected) {
+ assertEquals(expected, (getContactsProvider()).isMetadataNetworkNotified());
+ }
+
protected void assertProjection(Uri uri, String[] expectedProjection) {
Cursor cursor = mResolver.query(uri, null, "0", null, null);
String[] actualProjection = cursor.getColumnNames();
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 92b4b171..9baf1e41 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -16,8 +16,6 @@
package com.android.providers.contacts;
-import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
-
import android.telecom.CallerInfo;
import com.android.providers.contacts.testutil.CommonDatabaseUtils;
import com.android.providers.contacts.util.ContactsPermissions;
@@ -62,7 +60,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
Voicemails.DIRTY,
Voicemails.DELETED};
/** Total number of columns exposed by call_log provider. */
- private static final int NUM_CALLLOG_FIELDS = 40;
+ private static final int NUM_CALLLOG_FIELDS = 35;
private static final int MIN_MATCH = 7;
@@ -201,7 +199,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
ContactsPermissions.ALLOW_SELF_CALL = true;
Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643",
Calls.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000,
- 40, null, MISSED_REASON_NOT_MISSED);
+ 40, null);
ContactsPermissions.ALLOW_SELF_CALL = false;
assertNotNull(uri);
assertEquals("0@" + CallLog.AUTHORITY, uri.getAuthority());
@@ -224,7 +222,6 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
// Casting null to Long as there are many forms of "put" which have nullable second
// parameters and the compiler needs a hint as to which form is correct.
values.put(Calls.DATA_USAGE, (Long) null);
- values.put(Calls.MISSED_REASON, 0);
assertStoredValues(uri, values);
}
@@ -498,7 +495,6 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
values.put(Calls.DATA_USAGE, 1000);
values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
- values.put(Calls.PRIORITY, Calls.PRIORITY_NORMAL);
break;
case 1:
values.put(Calls.NUMBER, "654321");
@@ -510,7 +506,6 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
values.put(Calls.DATA_USAGE, 0);
values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
- values.put(Calls.PRIORITY, Calls.PRIORITY_NORMAL);
break;
case 2:
values.put(Calls.NUMBER, "123456");
@@ -522,7 +517,6 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
values.put(Calls.DATA_USAGE, 2000);
values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null);
values.put(Calls.PHONE_ACCOUNT_ID, (Long) null);
- values.put(Calls.PRIORITY, Calls.PRIORITY_URGENT);
break;
}
return values;
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
new file mode 100644
index 00000000..3d8b8eb3
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2015 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.ContentProviderOperation;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.MetadataSync;
+import android.provider.ContactsContract.MetadataSyncState;
+import android.provider.ContactsContract.RawContacts;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.MediumTest;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
+import com.android.providers.contacts.testutil.RawContactUtil;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.providers.contacts.ContactMetadataProvider}.
+ * <p/>
+ * Run the test like this:
+ * <code>
+ * adb shell am instrument -e class com.android.providers.contacts.ContactMetadataProviderTest -w \
+ * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
+ * </code>
+ */
+@MediumTest
+public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
+ private static String TEST_ACCOUNT_TYPE1 = "test_account_type1";
+ private static String TEST_ACCOUNT_NAME1 = "test_account_name1";
+ private static String TEST_DATA_SET1 = "plus";
+ private static String TEST_BACKUP_ID1 = "1001";
+ private static String TEST_DATA1 = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 2\n" +
+ " }\n" +
+ " }";
+ private static byte[] TEST_SYNC_STATE1 = "sync state1".getBytes();
+ private static String TEST_ACCOUNT_TYPE2 = "test_account_type2";
+ private static String TEST_ACCOUNT_NAME2 = "test_account_name2";
+ private static String TEST_DATA_SET2 = null;
+ private static String TEST_BACKUP_ID2 = "1002";
+ private static String TEST_DATA2 = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE2 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME2 + ",\n" +
+ " \"contact_id\": " + TEST_BACKUP_ID2 + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 2\n" +
+ " }\n" +
+ " }";
+ private static byte[] TEST_SYNC_STATE2 = "sync state2".getBytes();
+ private static String SELECTION_BY_TEST_ACCOUNT1 = MetadataSync.ACCOUNT_NAME + "='" +
+ TEST_ACCOUNT_NAME1 + "' AND " + MetadataSync.ACCOUNT_TYPE + "='" + TEST_ACCOUNT_TYPE1 +
+ "' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET1 + "'";
+
+ private static String SELECTION_BY_TEST_ACCOUNT2 = MetadataSync.ACCOUNT_NAME + "='" +
+ TEST_ACCOUNT_NAME2 + "' AND " + MetadataSync.ACCOUNT_TYPE + "='" + TEST_ACCOUNT_TYPE2 +
+ "' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET2 + "'";
+
+ private ContactMetadataProvider mContactMetadataProvider;
+ private ContentValues defaultValues;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContactMetadataProvider = addProvider(
+ ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
+ // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
+ // are using different dbHelpers.
+ mContactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
+ mActor.provider).getDatabaseHelper());
+ setupData();
+ }
+
+ public void testInsertWithInvalidUri() {
+ try {
+ mResolver.insert(Uri.withAppendedPath(MetadataSync.METADATA_AUTHORITY_URI,
+ "metadata"), getDefaultValues());
+ fail("the insert was expected to fail, but it succeeded");
+ } catch (IllegalArgumentException e) {
+ // this was expected
+ }
+ }
+
+ public void testUpdateWithInvalidUri() {
+ try {
+ mResolver.update(Uri.withAppendedPath(MetadataSync.METADATA_AUTHORITY_URI,
+ "metadata"), getDefaultValues(), null, null);
+ fail("the update was expected to fail, but it succeeded");
+ } catch (IllegalArgumentException e) {
+ // this was expected
+ }
+ }
+
+ public void testGetMetadataByAccount() {
+ Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
+ null, null);
+ assertEquals(1, c.getCount());
+
+ ContentValues expectedValues = defaultValues;
+ expectedValues.remove(MetadataSyncColumns.ACCOUNT_ID);
+ c.moveToFirst();
+ assertCursorValues(c, expectedValues);
+ c.close();
+ }
+
+ public void testFailOnInsertMetadataForSameAccountIdAndBackupId() {
+ // Insert a new metadata with same account and backupId as defaultValues should fail.
+ String newData = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": false,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+
+ ContentValues newValues = new ContentValues();
+ newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1);
+ newValues.put(MetadataSync.DATA, newData);
+ newValues.put(MetadataSync.DELETED, 0);
+ try {
+ mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+ } catch (Exception e) {
+ // Expected.
+ }
+ }
+
+ public void testInsertAndUpdateMetadataSync() {
+ // Create a raw contact with backupId.
+ String backupId = "backupId10001";
+ long rawContactId = RawContactUtil.createRawContactWithAccountDataSet(
+ mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ ContentValues values = new ContentValues();
+ values.put(RawContacts.BACKUP_ID, backupId);
+ assertEquals(1, mResolver.update(rawContactUri, values, null, null));
+
+ assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
+ assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
+
+ String deleted = "0";
+ String insertJson = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 2\n" +
+ " }\n" +
+ " }";
+
+ // Insert to MetadataSync table.
+ ContentValues insertedValues = new ContentValues();
+ insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ insertedValues.put(MetadataSync.DATA, insertJson);
+ insertedValues.put(MetadataSync.DELETED, deleted);
+ Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+ long metadataId = ContentUris.parseId(metadataUri);
+ assertEquals(true, metadataId > 0);
+
+ // Check if RawContact table is updated after inserting metadata.
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
+ assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "2");
+
+ // Update the MetadataSync table.
+ String updatedJson = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": false,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+ ContentValues updatedValues = new ContentValues();
+ updatedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ updatedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ updatedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ updatedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ updatedValues.put(MetadataSync.DATA, updatedJson);
+ updatedValues.put(MetadataSync.DELETED, deleted);
+ mResolver.insert(MetadataSync.CONTENT_URI, updatedValues);
+
+ // Check if the insert (actually update) is correct.
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET1);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
+ }
+
+ public void testInsertMetadata() {
+ String backupId = "newBackupId";
+ String deleted = "0";
+ String insertJson = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 2\n" +
+ " }\n" +
+ " }";
+
+ // Insert to MetadataSync table.
+ ContentValues insertedValues = new ContentValues();
+ insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ insertedValues.put(MetadataSync.DATA, insertJson);
+ insertedValues.put(MetadataSync.DELETED, deleted);
+ Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+ long metadataId = ContentUris.parseId(metadataUri);
+ assertEquals(true, metadataId > 0);
+ }
+
+ public void testFailUpdateDeletedMetadata() {
+ String backupId = "backupId001";
+ String newData = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": false,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+
+ ContentValues newValues = new ContentValues();
+ newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ newValues.put(MetadataSync.DATA, newData);
+ newValues.put(MetadataSync.DELETED, 1);
+
+ try {
+ mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+ fail("the update was expected to fail, but it succeeded");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ public void testInsertWithNullData() {
+ ContentValues newValues = new ContentValues();
+ String data = null;
+ String backupId = "backupId002";
+ newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ newValues.put(MetadataSync.DATA, data);
+ newValues.put(MetadataSync.DELETED, 0);
+
+ try {
+ mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testDeleteMetadata() {
+ //insert another metadata for TEST_ACCOUNT
+ insertMetadata(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "2", TEST_DATA1, 0);
+ Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
+ null, null);
+ assertEquals(2, c.getCount());
+ int numOfDeletion = mResolver.delete(MetadataSync.CONTENT_URI, SELECTION_BY_TEST_ACCOUNT1,
+ null);
+ assertEquals(2, numOfDeletion);
+ c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT1,
+ null, null);
+ assertEquals(0, c.getCount());
+ }
+
+ public void testBulkInsert() {
+ Cursor c = mResolver.query(MetadataSync.CONTENT_URI, new String[]{MetadataSync._ID},
+ SELECTION_BY_TEST_ACCOUNT1, null, null);
+ assertEquals(1, c.getCount());
+
+ ContentValues values1 = getMetadataContentValues(
+ TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "123", TEST_DATA1, 0);
+ ContentValues values2 = getMetadataContentValues(
+ TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, "456", TEST_DATA1, 0);
+ ContentValues[] values = new ContentValues[] {values1, values2};
+
+ mResolver.bulkInsert(MetadataSync.CONTENT_URI, values);
+ c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
+ SELECTION_BY_TEST_ACCOUNT1, null, null);
+ assertEquals(3, c.getCount());
+ }
+
+ public void testBatchOperations() throws Exception {
+ // Two mentadata_sync entries in the beginning, one for TEST_ACCOUNT1 and another for
+ // TEST_ACCOUNT2
+ Cursor c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
+ null, null, null);
+ assertEquals(2, c.getCount());
+
+ String updatedData = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + TEST_BACKUP_ID1 + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 5\n" +
+ " }\n" +
+ " }";
+
+ String newBackupId = "2222";
+ String newData = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + TEST_ACCOUNT_TYPE1 + ",\n" +
+ " \"account_name\": " + TEST_ACCOUNT_NAME1 + ",\n" +
+ " \"contact_id\": " + newBackupId + ",\n" +
+ " \"data_set\": \"GOOGLE_PLUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 5\n" +
+ " }\n" +
+ " }";
+
+ ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
+ ops.add(ContentProviderOperation.newInsert(MetadataSync.CONTENT_URI)
+ .withValue(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1)
+ .withValue(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1)
+ .withValue(MetadataSync.DATA_SET, TEST_DATA_SET1)
+ .withValue(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1)
+ .withValue(MetadataSync.DATA, updatedData)
+ .withValue(MetadataSync.DELETED, 0)
+ .build());
+
+ ops.add(ContentProviderOperation.newInsert(MetadataSync.CONTENT_URI)
+ .withValue(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1)
+ .withValue(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1)
+ .withValue(MetadataSync.DATA_SET, TEST_DATA_SET1)
+ .withValue(MetadataSync.RAW_CONTACT_BACKUP_ID, newBackupId)
+ .withValue(MetadataSync.DATA, newData)
+ .withValue(MetadataSync.DELETED, 0)
+ .build());
+
+ ops.add(ContentProviderOperation.newDelete(MetadataSync.CONTENT_URI)
+ .withSelection(SELECTION_BY_TEST_ACCOUNT2, null)
+ .build());
+
+ // Batch three operations: update the metadata_entry of TEST_ACCOUNT1; insert one new
+ // metadata_entry for TEST_ACCOUNT1; delete metadata_entry of TEST_ACCOUNT2
+ mResolver.applyBatch(MetadataSync.METADATA_AUTHORITY, ops);
+
+ // After the batch operations, there should be two metadata_entry for TEST_ACCOUNT1 with
+ // new data value and no metadata_entry for TEST_ACCOUNT2.
+ c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync.DATA},
+ SELECTION_BY_TEST_ACCOUNT1, null, null);
+ assertEquals(2, c.getCount());
+ Set<String> actualData = new HashSet<>();
+ while (c.moveToNext()) {
+ actualData.add(c.getString(0));
+ }
+ c.close();
+ MoreAsserts.assertContentsInAnyOrder(actualData, updatedData, newData);
+
+ c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
+ SELECTION_BY_TEST_ACCOUNT2, null, null);
+ assertEquals(0, c.getCount());
+ }
+
+ public void testQueryMetadataSyncState() {
+ String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
+ MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
+ final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
+ final String[] projection = new String[]{MetadataSyncState.STATE};
+ Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertTrue(Arrays.equals(TEST_SYNC_STATE1, c.getBlob(0)));
+ c.close();
+ }
+
+ public void testUpdateMetadataSyncState() {
+ mResolver.update(MetadataSyncState.CONTENT_URI, getSyncStateValues(TEST_ACCOUNT_NAME1,
+ TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, TEST_SYNC_STATE2), null, null);
+ String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
+ MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
+ final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
+ final String[] projection = new String[] {MetadataSyncState.STATE};
+ Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertTrue(Arrays.equals(TEST_SYNC_STATE2, c.getBlob(0)));
+ c.close();
+ }
+
+ public void testUpdateMetadataSyncState_NonExisting() {
+ // Delete the existing one first.
+ String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
+ MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
+ final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
+
+ mResolver.delete(MetadataSyncState.CONTENT_URI, selection, args);
+
+ mResolver.update(MetadataSyncState.CONTENT_URI, getSyncStateValues(TEST_ACCOUNT_NAME1,
+ TEST_ACCOUNT_TYPE1, TEST_DATA_SET1, TEST_SYNC_STATE2), null, null);
+ final String[] projection = new String[] {MetadataSyncState.STATE};
+ Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertTrue(Arrays.equals(TEST_SYNC_STATE2, c.getBlob(0)));
+ c.close();
+ }
+
+ public void testDeleteMetadataSyncState() {
+ String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND " +
+ MetadataSyncState.ACCOUNT_TYPE + "=?2 AND " + MetadataSyncState.DATA_SET + "=?3";
+ final String[] args = new String[]{TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1};
+ final String[] projection = new String[]{MetadataSyncState.STATE};
+ Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+ assertEquals(1, c.getCount());
+ c.close();
+
+ mResolver.delete(MetadataSyncState.CONTENT_URI, selection, args);
+ c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+ assertEquals(0, c.getCount());
+ c.close();
+ }
+
+ private void setupData() {
+ long rawContactId1 = RawContactUtil.createRawContactWithAccountDataSet(
+ mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
+ createAccount(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
+ insertMetadata(getDefaultValues());
+ insertMetadataSyncState(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1,
+ TEST_SYNC_STATE1);
+
+ // Insert another entry for another account
+ createAccount(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2);
+ insertMetadata(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2, TEST_BACKUP_ID2,
+ TEST_DATA2, 0);
+ insertMetadataSyncState(TEST_ACCOUNT_NAME2, TEST_ACCOUNT_TYPE2, TEST_DATA_SET2,
+ TEST_SYNC_STATE2);
+ }
+
+ private ContentValues getDefaultValues() {
+ defaultValues = new ContentValues();
+ defaultValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME1);
+ defaultValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE1);
+ defaultValues.put(MetadataSync.DATA_SET, TEST_DATA_SET1);
+ defaultValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID1);
+ defaultValues.put(MetadataSync.DATA, TEST_DATA1);
+ defaultValues.put(MetadataSync.DELETED, 0);
+ return defaultValues;
+ }
+
+ private long insertMetadata(String accountName, String accountType, String dataSet,
+ String backupId, String data, int deleted) {
+ return insertMetadata(getMetadataContentValues(
+ accountName, accountType, dataSet, backupId, data, deleted));
+ }
+
+ private ContentValues getMetadataContentValues(String accountName, String accountType,
+ String dataSet, String backupId, String data, int deleted) {
+ ContentValues values = new ContentValues();
+ values.put(MetadataSync.ACCOUNT_NAME, accountName);
+ values.put(MetadataSync.ACCOUNT_TYPE, accountType);
+ values.put(MetadataSync.DATA_SET, dataSet);
+ values.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ values.put(MetadataSync.DATA, data);
+ values.put(MetadataSync.DELETED, deleted);
+ return values;
+ }
+
+ private long insertMetadata(ContentValues values) {
+ return ContentUris.parseId(mResolver.insert(MetadataSync.CONTENT_URI, values));
+ }
+
+ private long insertMetadataSyncState(String accountName, String accountType,
+ String dataSet, byte[] state) {
+ return ContentUris.parseId(mResolver.insert(MetadataSyncState.CONTENT_URI,
+ getSyncStateValues(accountName, accountType, dataSet, state)));
+ }
+
+ private ContentValues getSyncStateValues(String accountName, String accountType,
+ String dataSet, byte[] state) {
+ ContentValues values = new ContentValues();
+ values.put(MetadataSyncState.ACCOUNT_NAME, accountName);
+ values.put(MetadataSyncState.ACCOUNT_TYPE, accountType);
+ values.put(MetadataSyncState.DATA_SET, dataSet);
+ values.put(MetadataSyncState.STATE, state);
+ return values;
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java
new file mode 100644
index 00000000..c46643f2
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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 ContactMetadataProviderTestable extends ContactMetadataProvider {
+ @Override
+ void ensureCaller() {
+ // Not testable, skip.
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 56a4fc4b..804e79a9 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -248,11 +248,6 @@ public class ContactsActor {
public boolean isUserUnlocked(int userId) {
return true; // Just make it always unlocked for now.
}
-
- @Override
- public boolean isUserRunning(int userId) {
- return true;
- }
}
private MockTelephonyManager mMockTelephonyManager;
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index d50a2922..1832b4e4 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -30,6 +30,8 @@ import android.provider.ContactsContract.DeletedContacts;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.MetadataSync;
+import android.provider.ContactsContract.MetadataSyncState;
import android.provider.ContactsContract.PhotoFiles;
import android.provider.ContactsContract.PinnedPositions;
import android.provider.ContactsContract.RawContacts;
@@ -49,6 +51,8 @@ import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncStateColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
@@ -59,6 +63,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.testutil.TestUtil;
import com.android.providers.contacts.util.PropertyUtils;
/**
@@ -220,8 +225,6 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
new TableColumn(AccountsColumns.ACCOUNT_NAME, TEXT, false, null),
new TableColumn(AccountsColumns.ACCOUNT_TYPE, TEXT, false, null),
new TableColumn(AccountsColumns.DATA_SET, TEXT, false, null),
- new TableColumn(AccountsColumns.SIM_SLOT_INDEX, INTEGER, false, null),
- new TableColumn(AccountsColumns.SIM_EF_TYPE, INTEGER, false, null),
};
private static final TableColumn[] CONTACTS_COLUMNS = new TableColumn[] {
@@ -526,12 +529,26 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
new TableColumn(DataUsageStatColumns.LR_LAST_TIME_USED, INTEGER, true, "0"),
};
+ private static final TableColumn[] METADATA_SYNC_COLUMNS = new TableColumn[] {
+ new TableColumn(MetadataSync._ID, INTEGER, false, null),
+ new TableColumn(MetadataSync.RAW_CONTACT_BACKUP_ID, TEXT, true, null),
+ new TableColumn(MetadataSyncColumns.ACCOUNT_ID, INTEGER, true, null),
+ new TableColumn(MetadataSync.DATA, TEXT, false, null),
+ new TableColumn(MetadataSync.DELETED, INTEGER, true, "0"),
+ };
+
private static final TableColumn[] PRE_AUTHORIZED_URIS_COLUMNS = new TableColumn[] {
new TableColumn(PreAuthorizedUris._ID, INTEGER, false, null),
new TableColumn(PreAuthorizedUris.URI, STRING, true, null),
new TableColumn(PreAuthorizedUris.EXPIRATION, INTEGER, true, "0"),
};
+ private static final TableColumn[] METADATA_SYNC_STATE_COLUMNS = new TableColumn[] {
+ new TableColumn(MetadataSyncState._ID, INTEGER, false, null),
+ new TableColumn(MetadataSyncStateColumns.ACCOUNT_ID, INTEGER, true, null),
+ new TableColumn(MetadataSyncState.STATE, BLOB, false, null),
+ };
+
private static final TableColumn[] PRESENCE_COLUMNS = new TableColumn[] {
new TableColumn(StatusUpdates.DATA_ID, INTEGER, false, null),
new TableColumn(StatusUpdates.PROTOCOL, INTEGER, true, null),
@@ -575,7 +592,9 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
new TableListEntry(Tables.STATUS_UPDATES, STATUS_UPDATES_COLUMNS),
new TableListEntry(Tables.DIRECTORIES, DIRECTORIES_COLUMNS),
new TableListEntry(Tables.DATA_USAGE_STAT, DATA_USAGE_STAT_COLUMNS),
+ new TableListEntry(Tables.METADATA_SYNC, METADATA_SYNC_COLUMNS),
new TableListEntry(Tables.PRE_AUTHORIZED_URIS, PRE_AUTHORIZED_URIS_COLUMNS),
+ new TableListEntry(Tables.METADATA_SYNC_STATE, METADATA_SYNC_STATE_COLUMNS),
new TableListEntry(Tables.PRESENCE, PRESENCE_COLUMNS),
new TableListEntry(Tables.AGGREGATED_PRESENCE, AGGREGATED_PRESENCE_COLUMNS)
};
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 7efc2f47..d5643d25 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -55,6 +55,8 @@ import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.ContactsContract.FullNameStyle;
import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.MetadataSync;
+import android.provider.ContactsContract.MetadataSyncState;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.PhoneticNameStyle;
import android.provider.ContactsContract.PinnedPositions;
@@ -84,6 +86,11 @@ import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+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.testutil.CommonDatabaseUtils;
import com.android.providers.contacts.testutil.ContactUtil;
import com.android.providers.contacts.testutil.DataUtil;
@@ -1678,9 +1685,9 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValues(lookupUri1, null, null, new ContentValues[] {values, values});
// In the context that 8004664411 is a valid number, "4664411" as a
- // call id should not match to either "8004664411" or "+18004664411".
+ // call id should match to both "8004664411" and "+18004664411".
Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "4664411");
- assertEquals(0, getCount(lookupUri2, null, null));
+ assertEquals(2, getCount(lookupUri2, null, null));
// A wrong area code 799 vs 800 should not be matched
lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "7994664411");
@@ -1911,13 +1918,13 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.clear();
- // No match with international format
+ // match with international format
lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0002");
- assertEquals(0, getCount(lookupUri2, null, null));
+ assertEquals(1, getCount(lookupUri2, null, null));
- // No match with national format
+ // match with national format
lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0002");
- assertEquals(0, getCount(lookupUri2, null, null));
+ assertEquals(1, getCount(lookupUri2, null, null));
}
public void testIntlPhoneLookupUseCases() {
@@ -1936,20 +1943,20 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertEquals(2, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, fullNumber), null, null));
- // Shorter (local) number with 0 prefix should not match.
- assertEquals(0, getCount(Uri.withAppendedPath(
+ // Shorter (local) number with 0 prefix should also match.
+ assertEquals(2, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "097427289"), null, null));
// Number with international (+972) prefix should also match.
assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "+97297427289"), null, null));
- // Same shorter number with dashes should not match.
- assertEquals(0, getCount(Uri.withAppendedPath(
+ // Same shorter number with dashes should match.
+ assertEquals(2, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "09-742-7289"), null, null));
- // Same shorter number with spaces should not match.
- assertEquals(0, getCount(Uri.withAppendedPath(
+ // Same shorter number with spaces should match.
+ assertEquals(2, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "09 742 7289"), null, null));
// Some other number should not match.
@@ -1971,16 +1978,16 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "0796010101"), null, null));
- assertEquals(0, getCount(Uri.withAppendedPath(
+ assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "+48796010101"), null, null));
- assertEquals(0, getCount(Uri.withAppendedPath(
+ assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "48796010101"), null, null));
- assertEquals(0, getCount(Uri.withAppendedPath(
+ assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "4-879-601-0101"), null, null));
- assertEquals(0, getCount(Uri.withAppendedPath(
+ assertEquals(1, getCount(Uri.withAppendedPath(
PhoneLookup.CONTENT_FILTER_URI, "4 879 601 0101"), null, null));
}
@@ -2887,6 +2894,333 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
}
+ public void testUpdateFromMetadataEntry() {
+ String accountType1 = "accountType1";
+ String accountName1 = "accountName1";
+ String dataSet1 = "plus";
+ Account account1 = new Account(accountName1, accountType1);
+ long rawContactId = RawContactUtil.createRawContactWithName(mResolver, account1);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ // Add backup_id for the raw contact.
+ String backupId = "backupId100001";
+ ContentValues values = new ContentValues();
+ values.put(RawContacts.BACKUP_ID, backupId);
+ assertEquals(1, mResolver.update(rawContactUri, values, null, null));
+
+ String emailAddress = "address@email.com";
+ Uri dataUri = insertEmail(rawContactId, emailAddress);
+ String hashId = getStoredValue(dataUri, Data.HASH_ID);
+
+ // Another data that should not be updated.
+ String phoneNumber = "111-111-1111";
+ Uri dataUri2 = insertPhoneNumber(rawContactId, phoneNumber);
+
+ // Aggregation should be deleted from local since it doesn't exist in server.
+ long toBeDeletedAggRawContactId = RawContactUtil.createRawContactWithName(
+ mResolver, account1);
+ setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
+ rawContactId, toBeDeletedAggRawContactId);
+
+ // Check if AggregationException table has one value.
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID1,
+ rawContactId);
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID2,
+ toBeDeletedAggRawContactId);
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.TYPE,
+ AggregationExceptions.TYPE_KEEP_SEPARATE);
+
+ String accountType2 = "accountType2";
+ String accountName2 = "accountName2";
+ Account account2 = new Account(accountName2, accountType2);
+ long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, account2);
+ Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
+ String backupId2 = "backupId100003";
+ ContentValues values2 = new ContentValues();
+ values2.put(RawContacts.BACKUP_ID, backupId2);
+ assertEquals(1, mResolver.update(rawContactUri2, values2, null, null));
+
+ String usageTypeString = "CALL";
+ int lastTimeUsed = 1111111;
+ int timesUsed = 5;
+ String aggregationTypeString = "TOGETHER";
+ int aggregationType = AggregationExceptions.TYPE_KEEP_TOGETHER;
+
+ RawContactInfo rawContactInfo = new RawContactInfo(
+ backupId, accountType1, accountName1, null);
+ UsageStats usageStats = new UsageStats(usageTypeString, lastTimeUsed, timesUsed);
+ ArrayList<UsageStats> usageStatsList = new ArrayList<>();
+ usageStatsList.add(usageStats);
+ FieldData fieldData = new FieldData(hashId, true, true, usageStatsList);
+ ArrayList<FieldData> fieldDataList = new ArrayList<>();
+ fieldDataList.add(fieldData);
+ ArrayList<AggregationData> aggregationDataList = new ArrayList<>();
+ MetadataEntry metadataEntry = new MetadataEntry(rawContactInfo,
+ 1, 1, 1, fieldDataList, aggregationDataList);
+
+ ContactsProvider2 provider = (ContactsProvider2) getProvider();
+ final ContactsDatabaseHelper helper =
+ ((ContactsDatabaseHelper) provider.getDatabaseHelper());
+ SQLiteDatabase db = helper.getWritableDatabase();
+
+ // Before updating tables from MetadataEntry.
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType1);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName1);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+ assertStoredValue(dataUri, Data.IS_PRIMARY, 0);
+ assertStoredValue(dataUri, Data.IS_SUPER_PRIMARY, 0);
+
+ // Update tables without aggregation first, since aggregator will affect pinned value.
+ provider.updateFromMetaDataEntry(db, metadataEntry);
+
+ // After updating tables from MetadataEntry.
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType1);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName1);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
+ assertStoredValue(dataUri, Data.IS_PRIMARY, 1);
+ assertStoredValue(dataUri, Data.IS_SUPER_PRIMARY, 1);
+ assertStoredValue(dataUri2, Data.IS_PRIMARY, 0);
+ assertStoredValue(dataUri2, Data.IS_SUPER_PRIMARY, 0);
+ final Uri dataUriWithUsageType = Data.CONTENT_URI.buildUpon().appendQueryParameter(
+ DataUsageFeedback.USAGE_TYPE, usageTypeString).build();
+ assertDataUsageZero(dataUriWithUsageType, emailAddress);
+
+ // Update AggregationException table.
+ RawContactInfo aggregationContact = new RawContactInfo(
+ backupId2, accountType2, accountName2, null);
+ AggregationData aggregationData = new AggregationData(
+ rawContactInfo, aggregationContact, aggregationTypeString);
+ aggregationDataList.add(aggregationData);
+ metadataEntry = new MetadataEntry(rawContactInfo,
+ 1, 1, 1, fieldDataList, aggregationDataList);
+ provider.updateFromMetaDataEntry(db, metadataEntry);
+
+ // Check if AggregationException table is updated.
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID1,
+ rawContactId);
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.RAW_CONTACT_ID2,
+ rawContactId2);
+ assertStoredValue(AggregationExceptions.CONTENT_URI, AggregationExceptions.TYPE,
+ aggregationType);
+
+ // After aggregation, check if rawContacts.starred/send_to_voicemail
+ // were copied to contacts table.
+ final long contactId = queryContactId(rawContactId);
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ // The merged contact should be starred if any of the rawcontact is starred.
+ assertStoredValue(contactUri, Contacts.STARRED, 1);
+ // The merged contact should be send_to_voicemail
+ // if all of the rawcontact is send_to_voicemail.
+ assertStoredValue(contactUri, Contacts.SEND_TO_VOICEMAIL, 0);
+ }
+
+ public void testUpdateMetadataOnRawContactInsert() throws Exception {
+ ContactMetadataProvider contactMetadataProvider = addProvider(
+ ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
+ // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
+ // are using different dbHelpers.
+ contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
+ mActor.provider).getDatabaseHelper());
+ // Create an account first.
+ String backupId = "backupId001";
+ String accountType = "accountType";
+ String accountName = "accountName";
+ Account account = new Account(accountName, accountType);
+ createAccount(accountName, accountType, null);
+
+ // Insert a metadata to MetadataSync table.
+ String data = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + accountType + ",\n" +
+ " \"account_name\": " + accountName + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"FOCUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+
+ ContentValues insertedValues = new ContentValues();
+ insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
+ insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
+ insertedValues.put(MetadataSync.DATA, data);
+ mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+ // Insert a raw contact.
+ long rawContactId = RawContactUtil.createRawContactWithBackupId(mResolver, backupId,
+ account);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ // Check if the raw contact is not updated since Lychee is removed.
+ assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
+ assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+ // No metadata network notify.
+ assertMetadataNetworkNotified(false);
+ }
+
+ public void testUpdateMetadataOnRawContactBackupIdChange() throws Exception {
+ ContactMetadataProvider contactMetadataProvider = addProvider(
+ ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
+ // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
+ // are using different dbHelpers.
+ contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
+ mActor.provider).getDatabaseHelper());
+ // Create an account first.
+ String backupId = "backupId001";
+ String accountType = "accountType";
+ String accountName = "accountName";
+ Account account = new Account(accountName, accountType);
+ createAccount(accountName, accountType, null);
+
+ // Insert a metadata to MetadataSync table.
+ String data = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + accountType + ",\n" +
+ " \"account_name\": " + accountName + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"FOCUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+
+ ContentValues insertedValues = new ContentValues();
+ insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
+ insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
+ insertedValues.put(MetadataSync.DATA, data);
+ mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+ // Insert a raw contact without backup_id.
+ long rawContactId = RawContactUtil.createRawContact(mResolver, account);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ // Check if the raw contact is not updated because of no backup_id.
+ assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+
+ // Update the raw contact with backup_id.
+ ContentValues updatedValues = new ContentValues();
+ updatedValues.put(RawContacts.BACKUP_ID, backupId);
+ mResolver.update(RawContacts.CONTENT_URI, updatedValues, null, null);
+ // Check if the raw contact is still not updated.
+ assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, accountType);
+ assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, accountName);
+ assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+ assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+ assertStoredValue(rawContactUri, RawContacts.PINNED, "0");
+ // No metadata network notify.
+ assertMetadataNetworkNotified(false);
+ }
+
+ public void testDeleteMetadataOnRawContactDelete() throws Exception {
+ ContactMetadataProvider contactMetadataProvider = addProvider(
+ ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
+ // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
+ // are using different dbHelpers.
+ contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
+ mActor.provider).getDatabaseHelper());
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+ // Create an account first.
+ String backupId = "backupId001";
+ String accountType = "accountType";
+ String accountName = "accountName";
+ Account account = new Account(accountName, accountType);
+ createAccount(accountName, accountType, null);
+
+ // Insert a raw contact.
+ long rawContactId = RawContactUtil.createRawContactWithBackupId(mResolver, backupId,
+ account);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+
+ // Insert a metadata to MetadataSync table.
+ String data = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + accountType + ",\n" +
+ " \"account_name\": " + accountName + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"FOCUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+
+ ContentValues insertedValues = new ContentValues();
+ insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ insertedValues.put(MetadataSync.ACCOUNT_TYPE, accountType);
+ insertedValues.put(MetadataSync.ACCOUNT_NAME, accountName);
+ insertedValues.put(MetadataSync.DATA, data);
+ Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+ // Delete raw contact.
+ mResolver.delete(rawContactUri, null, null);
+ // Check if the metadata is not deleted.
+ assertStoredValue(metadataUri, MetadataSync.DELETED, "0");
+ // check raw contact metadata_dirty column is not changed on raw contact deletion
+ assertMetadataDirty(rawContactUri, false);
+ // Lychee removed. Will not notify it.
+ assertMetadataNetworkNotified(false);
+
+ // Add another rawcontact and metadata, and don't delete them.
+ // Insert a raw contact.
+ String backupId2 = "newBackupId";
+ long rawContactId2 = RawContactUtil.createRawContactWithBackupId(mResolver, backupId2,
+ account);
+ Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+
+ // Insert a metadata to MetadataSync table.
+ ContentValues insertedValues2 = new ContentValues();
+ insertedValues2.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
+ insertedValues2.put(MetadataSync.ACCOUNT_TYPE, accountType);
+ insertedValues2.put(MetadataSync.ACCOUNT_NAME, accountName);
+ insertedValues2.put(MetadataSync.DATA, data);
+ Uri metadataUri2 = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues2);
+
+ // Update raw contact but not delete.
+ ContentValues values = new ContentValues();
+ values.put(RawContacts.STARRED, "1");
+ mResolver.update(rawContactUri2, values, null, null);
+
+ // Check if the metadata is not marked as deleted.
+ assertStoredValue(metadataUri2, MetadataSync.DELETED, "0");
+ // Will not set metadata_dirty since Lychee is removed.
+ assertMetadataDirty(rawContactUri2, false);
+ // Will not notify Lychee since it's removed.
+ assertMetadataNetworkNotified(false);
+ }
+
public void testPostalsQuery() {
long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Alice", "Nextore");
Uri dataUri = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View");
@@ -4863,6 +5197,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
public void testSetSendToVoicemailAndRingtone() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContactWithName(mResolver);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
assertDirty(rawContactUri, true);
@@ -4872,12 +5210,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
updateSendToVoicemailAndRingtone(contactId, true, "foo");
assertSendToVoicemailAndRingtone(contactId, true, "foo");
assertNetworkNotified(false);
+ assertMetadataNetworkNotified(false);
assertDirty(rawContactUri, false);
assertMetadataDirty(rawContactUri, false);
updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar");
assertSendToVoicemailAndRingtone(contactId, false, "bar");
assertNetworkNotified(false);
+ assertMetadataNetworkNotified(false);
assertDirty(rawContactUri, false);
assertMetadataDirty(rawContactUri, false);
}
@@ -4938,6 +5278,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
public void testMarkMetadataDirtyAfterAggregation() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "i", "j");
long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "k", "l");
Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
@@ -4956,6 +5300,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertMetadataDirty(rawContactUri1, false);
assertMetadataDirty(rawContactUri2, false);
assertNetworkNotified(false);
+ assertMetadataNetworkNotified(false);
}
public void testStatusUpdateInsert() {
@@ -6421,6 +6766,98 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValue(safeStreamItemPhotoUri, StreamItemPhotos._ID, safeStreamItemPhotoId);
}
+ public void testMetadataSyncCleanedUpOnAccountRemoval() throws Exception {
+ Account doomedAccount = new Account("doom", "doom");
+ createAccount(doomedAccount.name, doomedAccount.type, null);
+ Account safeAccount = new Account("safe", "safe");
+ createAccount(safeAccount.name, safeAccount.type, null);
+ ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ mActor.setAccounts(new Account[]{doomedAccount, safeAccount});
+ cp.onAccountsUpdated(new Account[]{doomedAccount, safeAccount});
+
+ ContactMetadataProvider contactMetadataProvider = addProvider(
+ ContactMetadataProviderTestable.class, MetadataSync.METADATA_AUTHORITY);
+ // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
+ // are using different dbHelpers.
+ contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
+ mActor.provider).getDatabaseHelper());
+
+ // Create a doomed metadata.
+ String backupId = "backupIdForDoomed";
+ ContentValues metadataValues = new ContentValues();
+ metadataValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+ metadataValues.put(MetadataSync.ACCOUNT_TYPE, doomedAccount.type);
+ metadataValues.put(MetadataSync.ACCOUNT_NAME, doomedAccount.name);
+ metadataValues.put(MetadataSync.DATA,
+ getDefaultMetadataJSONString(doomedAccount.type, doomedAccount.name, backupId));
+ Uri doomedMetadataUri = mResolver.insert(MetadataSync.CONTENT_URI, metadataValues);
+ // Create a doomed metadata sync state.
+ ContentValues syncStateValues = new ContentValues();
+ syncStateValues.put(MetadataSyncState.ACCOUNT_TYPE, doomedAccount.type);
+ syncStateValues.put(MetadataSyncState.ACCOUNT_NAME, doomedAccount.name);
+ syncStateValues.put(MetadataSyncState.STATE, "syncState");
+ mResolver.insert(MetadataSyncState.CONTENT_URI, syncStateValues);
+
+ // Create a safe metadata.
+ String backupId2 = "backupIdForSafe";
+ ContentValues insertedValues2 = new ContentValues();
+ insertedValues2.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
+ insertedValues2.put(MetadataSync.ACCOUNT_TYPE, safeAccount.type);
+ insertedValues2.put(MetadataSync.ACCOUNT_NAME, safeAccount.name);
+ insertedValues2.put(MetadataSync.DATA,
+ getDefaultMetadataJSONString(safeAccount.type, safeAccount.name, backupId2));
+ Uri safeMetadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues2);
+ // Create a safe metadata sync state.
+ ContentValues syncStateValues2 = new ContentValues();
+ syncStateValues2.put(MetadataSyncState.ACCOUNT_TYPE, safeAccount.type);
+ syncStateValues2.put(MetadataSyncState.ACCOUNT_NAME, safeAccount.name);
+ syncStateValues2.put(MetadataSyncState.STATE, "syncState2");
+ mResolver.insert(MetadataSyncState.CONTENT_URI, syncStateValues2);
+
+ // Remove the doomed account.
+ mActor.setAccounts(new Account[]{safeAccount});
+ cp.onAccountsUpdated(new Account[]{safeAccount});
+
+ // Check that the doomed stuff has all been nuked.
+ ContentValues[] noValues = new ContentValues[0];
+ assertStoredValues(doomedMetadataUri, noValues);
+ String selection = MetadataSyncState.ACCOUNT_NAME + "=?1 AND "
+ + MetadataSyncState.ACCOUNT_TYPE + "=?2";
+ String[] args = new String[]{doomedAccount.name, doomedAccount.type};
+ final String[] projection = new String[]{MetadataSyncState.STATE};
+ Cursor c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+ assertEquals(0, c.getCount());
+
+ // Check that the safe stuff lives on.
+ assertStoredValue(safeMetadataUri, MetadataSync.RAW_CONTACT_BACKUP_ID, backupId2);
+ args = new String[]{safeAccount.name, safeAccount.type};
+ c = mResolver.query(MetadataSyncState.CONTENT_URI, projection, selection, args,
+ null);
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ assertEquals("syncState2", c.getString(0));
+ c.close();
+ }
+
+ private String getDefaultMetadataJSONString(
+ String accountType, String accountName, String backupId) {
+ return "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": " + accountType + ",\n" +
+ " \"account_name\": " + accountName + ",\n" +
+ " \"contact_id\": " + backupId + ",\n" +
+ " \"data_set\": \"FOCUS\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": true,\n" +
+ " \"pinned\": 1\n" +
+ " }\n" +
+ " }";
+ }
+
public void testContactDeletion() {
long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
TestUtil.ACCOUNT_1);
@@ -6454,15 +6891,35 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
public void testDirtyWhenRawContactInsert() {
- // When inserting a rawcontact.
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
+ // When inserting a rawcontact without metadata.
long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
assertDirty(rawContactUri, false);
assertMetadataDirty(rawContactUri, false);
assertNetworkNotified(true);
+ assertMetadataNetworkNotified(false);
+
+ // When inserting a rawcontact with metadata.
+ ContentValues values = new ContentValues();
+ values.put(ContactsContract.RawContacts.STARRED, 1);
+ values.put(ContactsContract.RawContacts.ACCOUNT_NAME, mAccount.name);
+ values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type);
+ Uri rawContactId2Uri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ assertDirty(rawContactId2Uri, false);
+ assertMetadataDirty(rawContactId2Uri, false);
+ assertNetworkNotified(true);
+ assertMetadataNetworkNotified(false);
}
public void testRawContactDirtyAndVersion() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
final long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
assertDirty(uri, false);
@@ -6476,8 +6933,9 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertEquals(version, getVersion(uri));
assertDirty(uri, false);
- assertMetadataDirty(uri, false);
assertNetworkNotified(false);
+ assertMetadataDirty(uri, false);
+ assertMetadataNetworkNotified(false);
Uri emailUri = insertEmail(rawContactId, "goo@woo.com");
assertDirty(uri, true);
@@ -6534,6 +6992,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
public void testNotifyMetadataChangeForRawContactInsertBySyncAdapter() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
Uri uri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, mAccount.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, mAccount.type)
@@ -6543,9 +7005,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
long rawContactId = ContentUris.parseId(mResolver.insert(uri, new ContentValues()));
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
assertMetadataDirty(rawContactUri, false);
+ // Will not notify Lychee since it's removed.
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForRawContactMetadataChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
long contactId = queryContactId(rawContactId);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
@@ -6557,6 +7025,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
assertMetadataDirty(rawContactUri, false);
+ assertMetadataNetworkNotified(false);
clearMetadataDirty(rawContactUri);
values = new ContentValues();
@@ -6565,6 +7034,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValue(contactUri, Contacts.PINNED, 1);
assertMetadataDirty(rawContactUri, false);
+ assertMetadataNetworkNotified(false);
clearMetadataDirty(rawContactUri);
values = new ContentValues();
@@ -6573,9 +7043,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValue(contactUri, Contacts.SEND_TO_VOICEMAIL, 1);
assertMetadataDirty(rawContactUri, false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForRawContactBackupIdChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
@@ -6591,9 +7066,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
mResolver.update(rawContactUri, values, null, null);
assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, "newBackupId");
assertMetadataDirty(rawContactUri, false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForAggregationExceptionChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
long rawContactId2 = RawContactUtil.createRawContact(mResolver, new Account("b", "b"));
@@ -6604,18 +7084,28 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
false);
assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2),
false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataNotDirtyForUsageStatsChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
final long rid1 = RawContactUtil.createRawContactWithName(mResolver, "contact", "a");
final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com"));
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a);
// Usage feedback no longer works, so "false".
assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1), false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForDataPrimarySettingInsert() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
Uri mailUri11 = insertEmail(rawContactId1, "test1@domain1.com", true, true);
@@ -6623,9 +7113,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 1);
assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1),
false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForDataPrimarySettingUpdate() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com");
@@ -6638,9 +7133,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
false);
+ assertMetadataNetworkNotified(false);
}
public void testMarkAsMetadataDirtyForDataDelete() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContact(mResolver, new Account("a", "a"));
Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com", true, true);
@@ -6648,6 +7148,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
false);
+ assertMetadataNetworkNotified(false);
}
public void testDeleteContactWithoutName() {
@@ -8308,6 +8809,25 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
}
+ public void testMarkMetadataNotDirtyWhenDataUsageUpdate() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
+ final long rid1 = RawContactUtil.createRawContactWithName(mResolver, "contact", "a");
+ final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com"));
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1);
+ assertDirty(rawContactUri, true);
+ clearDirty(rawContactUri);
+ updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a);
+
+ assertDirty(rawContactUri, false);
+ // Usage feedback no longer works, so "false".
+ assertMetadataDirty(rawContactUri, false);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(false);
+ }
+
public void testDataUsageFeedbackAndDelete() {
sMockClock.install();
@@ -8486,6 +9006,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
public void testContactUpdate_metadataChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, ids.mRawContactId);
assertDirty(rawContactUri, true);
@@ -8498,6 +9022,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertDirty(rawContactUri, false);
assertMetadataDirty(rawContactUri, false);
assertNetworkNotified(false);
+ assertMetadataNetworkNotified(false);
}
public void testContactUpdate_updatesContactUpdatedTimestamp() {
diff --git a/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java b/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java
new file mode 100644
index 00000000..fe6ed132
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+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 org.json.JSONException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+/**
+ * Unit tests for {@link MetadataEntryParser}.
+ *
+ * Run the test like this:
+ * <code>
+ adb shell am instrument -e class com.android.providers.contacts.MetadataEntryParserTest -w \
+ com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
+ * </code>
+ */
+@SmallTest
+public class MetadataEntryParserTest extends FixedAndroidTestCase {
+
+ public void testErrorForEmptyInput() {
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry("");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testParseDataToMetadataEntry() throws IOException {
+ String contactBackupId = "1111111";
+ String accountType = "facebook";
+ String accountName = "android-test";
+ String dataSet = null;
+ int sendToVoicemail = 1;
+ int starred = 0;
+ int pinned = 2;
+ String dataHashId1 = "1001";
+ String usageType1_1 = "CALL";
+ long lastTimeUsed1_1 = 10000001;
+ int timesUsed1_1 = 10;
+ String usageType1_2 = "SHORT_TEXT";
+ long lastTimeUsed1_2 = 20000002;
+ int timesUsed1_2 = 20;
+ String dataHashId2 = "1002";
+ String usageType2 = "LONG_TEXT";
+ long lastTimeUsed2 = 30000003;
+ int timesUsed2 = 30;
+ String aggregationContactBackupId1 = "2222222";
+ String aggregationAccountType1 = "com.google";
+ String aggregationAccountName1 = "android-test2";
+ String aggregationDataSet1 = "plus";
+ String aggregationContactBackupId2 = "3333333";
+ String aggregationAccountType2 = "com.google";
+ String aggregationAccountName2 = "android-test3";
+ String aggregationDataSet2 = "custom type";
+ String type = "TOGETHER";
+ String inputFile = "test1/testFileDeviceContactMetadataJSON.txt";
+
+ RawContactInfo rawContactInfo = new RawContactInfo(
+ contactBackupId, accountType, accountName, dataSet);
+ RawContactInfo aggregationContact1 = new RawContactInfo(aggregationContactBackupId1,
+ aggregationAccountType1, aggregationAccountName1, aggregationDataSet1);
+ RawContactInfo aggregationContact2 = new RawContactInfo(aggregationContactBackupId2,
+ aggregationAccountType2, aggregationAccountName2, aggregationDataSet2);
+ AggregationData aggregationData = new AggregationData(
+ aggregationContact1, aggregationContact2, type);
+ ArrayList<AggregationData> aggregationDataList = new ArrayList<>();
+ aggregationDataList.add(aggregationData);
+
+ UsageStats usageStats1_1 = new UsageStats(usageType1_1, lastTimeUsed1_1, timesUsed1_1);
+ UsageStats usageStats1_2 = new UsageStats(usageType1_2, lastTimeUsed1_2, timesUsed1_2);
+ UsageStats usageStats2 = new UsageStats(usageType2, lastTimeUsed2, timesUsed2);
+
+ ArrayList<UsageStats> usageStats1List = new ArrayList<>();
+ usageStats1List.add(usageStats1_1);
+ usageStats1List.add(usageStats1_2);
+ FieldData fieldData1 = new FieldData(dataHashId1, true, true, usageStats1List);
+
+ ArrayList<UsageStats> usageStats2List = new ArrayList<>();
+ usageStats2List.add(usageStats2);
+ FieldData fieldData2 = new FieldData(dataHashId2, false, false, usageStats2List);
+
+ ArrayList<FieldData> fieldDataList = new ArrayList<>();
+ fieldDataList.add(fieldData1);
+ fieldDataList.add(fieldData2);
+
+ MetadataEntry expectedResult = new MetadataEntry(rawContactInfo,
+ sendToVoicemail, starred, pinned, fieldDataList, aggregationDataList);
+
+ String inputJson = readAssetAsString(inputFile);
+ MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(
+ inputJson.toString());
+ assertMetaDataEntry(expectedResult, metadataEntry);
+ }
+
+ public void testErrorForMissingContactId() {
+ String input = "{\"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"android-test\"\n" +
+ " }}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testErrorForNullContactId() throws JSONException {
+ String input = "{\"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"android-test\",\n" +
+ " \"contact_id\": \"\"\n" +
+ " }}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testErrorForNullAccountType() throws JSONException {
+ String input = "{\"unique_contact_id\": {\n" +
+ " \"account_type\": \"\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"android-test\",\n" +
+ " \"contact_id\": \"\"\n" +
+ " }}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testErrorForNullAccountName() throws JSONException {
+ String input = "{\"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"\",\n" +
+ " \"contact_id\": \"1111111\"\n" +
+ " }}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testErrorForNullFieldDataId() throws JSONException {
+ String input = "{\"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"android-test\",\n" +
+ " \"contact_id\": \"1111111\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 2\n" +
+ " }," +
+ " \"field_data\": [{\n" +
+ " \"field_data_id\": \"\"}]" +
+ "}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testErrorForNullAggregationType() throws JSONException {
+ String input = "{\n" +
+ " \"unique_contact_id\": {\n" +
+ " \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+ " \"custom_account_type\": \"facebook\",\n" +
+ " \"account_name\": \"android-test\",\n" +
+ " \"contact_id\": \"1111111\"\n" +
+ " },\n" +
+ " \"contact_prefs\": {\n" +
+ " \"send_to_voicemail\": true,\n" +
+ " \"starred\": false,\n" +
+ " \"pinned\": 2\n" +
+ " },\n" +
+ " \"aggregation_data\": [\n" +
+ " {\n" +
+ " \"type\": \"\",\n" +
+ " \"contact_ids\": [\n" +
+ " {\n" +
+ " \"contact_id\": \"2222222\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"contact_id\": \"3333333\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]}";
+ try {
+ MetadataEntryParser.parseDataToMetaDataEntry(input);
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ private String readAssetAsString(String fileName) throws IOException {
+ Context context = getTestContext();
+ InputStream input = context.getAssets().open(fileName);
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = input.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+
+ private void assertMetaDataEntry(MetadataEntry entry1, MetadataEntry entry2) {
+ assertRawContactInfoEquals(entry1.mRawContactInfo, entry2.mRawContactInfo);
+ assertEquals(entry1.mSendToVoicemail, entry2.mSendToVoicemail);
+ assertEquals(entry1.mStarred, entry2.mStarred);
+ assertEquals(entry1.mPinned, entry2.mPinned);
+ assertAggregationDataListEquals(entry1.mAggregationDatas, entry2.mAggregationDatas);
+ assertFieldDataListEquals(entry1.mFieldDatas, entry2.mFieldDatas);
+ }
+
+ private void assertRawContactInfoEquals(RawContactInfo contact1, RawContactInfo contact2) {
+ assertEquals(contact1.mBackupId, contact2.mBackupId);
+ assertEquals(contact1.mAccountType, contact2.mAccountType);
+ assertEquals(contact1.mAccountName, contact2.mAccountName);
+ assertEquals(contact1.mDataSet, contact2.mDataSet);
+ }
+
+ private void assertAggregationDataListEquals(ArrayList<AggregationData> aggregationList1,
+ ArrayList<AggregationData> aggregationList2) {
+ assertEquals(aggregationList1.size(), aggregationList2.size());
+ for (int i = 0; i < aggregationList1.size(); i++) {
+ assertAggregationDataEquals(aggregationList1.get(i), aggregationList2.get(i));
+ }
+ }
+
+ private void assertAggregationDataEquals(AggregationData aggregationData1,
+ AggregationData aggregationData2) {
+ assertRawContactInfoEquals(aggregationData1.mRawContactInfo1,
+ aggregationData2.mRawContactInfo1);
+ assertRawContactInfoEquals(aggregationData1.mRawContactInfo2,
+ aggregationData2.mRawContactInfo2);
+ assertEquals(aggregationData1.mType, aggregationData2.mType);
+ }
+
+ private void assertFieldDataListEquals(ArrayList<FieldData> fieldDataList1,
+ ArrayList<FieldData> fieldDataList2) {
+ assertEquals(fieldDataList1.size(), fieldDataList2.size());
+ for (int i = 0; i < fieldDataList1.size(); i++) {
+ assertFieldDataEquals(fieldDataList1.get(i), fieldDataList2.get(i));
+ }
+ }
+
+ private void assertFieldDataEquals(FieldData fieldData1, FieldData fieldData2) {
+ assertEquals(fieldData1.mDataHashId, fieldData2.mDataHashId);
+ assertEquals(fieldData1.mIsPrimary, fieldData2.mIsPrimary);
+ assertEquals(fieldData1.mIsSuperPrimary, fieldData2.mIsSuperPrimary);
+ assertUsageStatsListEquals(fieldData1.mUsageStatsList, fieldData2.mUsageStatsList);
+ }
+
+ private void assertUsageStatsListEquals(ArrayList<UsageStats> usageStatsList1,
+ ArrayList<UsageStats> usageStatsList2) {
+ assertEquals(usageStatsList1.size(), usageStatsList2.size());
+ for (int i = 0; i < usageStatsList1.size(); i++) {
+ assertUsageStatsEquals(usageStatsList1.get(i), usageStatsList2.get(i));
+ }
+ }
+
+ private void assertUsageStatsEquals(UsageStats usageStats1, UsageStats usageStats2) {
+ assertEquals(usageStats1.mUsageType, usageStats2.mUsageType);
+ assertEquals(usageStats1.mLastTimeUsed, usageStats2.mLastTimeUsed);
+ assertEquals(usageStats1.mTimesUsed, usageStats2.mTimesUsed);
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index c2ab74fc..f674dd5b 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -36,6 +36,7 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
private static ContactsDatabaseHelper sDbHelper;
private Account mAccount;
private boolean mNetworkNotified;
+ private boolean mMetadataNetworkNotified;
private boolean mIsPhone = true;
private boolean mIsVoiceCapable = true;
@@ -61,17 +62,23 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
public void onBegin() {
super.onBegin();
mNetworkNotified = false;
+ mMetadataNetworkNotified = false;
}
@Override
- protected void notifyChange(boolean syncToNetwork) {
+ protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
mNetworkNotified |= syncToNetwork;
+ mMetadataNetworkNotified |= syncToMetadataNetwork;
}
public boolean isNetworkNotified() {
return mNetworkNotified;
}
+ public boolean isMetadataNetworkNotified() {
+ return mMetadataNetworkNotified;
+ }
+
public void setIsPhone(boolean flag) {
mIsPhone = flag;
}
diff --git a/tests2/Android.bp b/tests2/Android.bp
index 3dcdebbc..9a2351aa 100644
--- a/tests2/Android.bp
+++ b/tests2/Android.bp
@@ -14,17 +14,6 @@
// limitations under the License.
//
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "packages_providers_ContactsProvider_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: [
- "packages_providers_ContactsProvider_license",
- ],
-}
-
android_test {
name: "ContactsProviderTests2",
static_libs: [