diff options
author | Xin Li <delphij@google.com> | 2021-08-14 06:30:58 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2021-08-14 06:30:58 +0000 |
commit | 0ba8ccd088e7e132297e5b56dfa46e0ad66e8caf (patch) | |
tree | 14abe8bdae813b7333ad9a9d2aa271146f2cfd5e | |
parent | c01031b4dd7043ac0ad3bbde41054ed182117a45 (diff) | |
parent | 586fee1e34818160e2cef368cae1ca17acbacba7 (diff) | |
download | ContactsProvider-temp_sam_202323961.tar.gz |
Merge sc-dev-plus-aosp-without-vendor@7634622temp_sam_202323961
Merged-In: I73a2d8b17abcc9f78d055634687e29c5928172a8
Change-Id: I23959c7186eec403c91b0431837a8705e7d07b05
42 files changed, 1341 insertions, 2727 deletions
@@ -19,6 +19,7 @@ license { 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 9136f5da..2b101bef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -21,12 +21,18 @@ <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" @@ -62,13 +68,24 @@ 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.MANAGE_USERS" - android:writePermission="android.permission.MANAGE_USERS"> + android:readPermission="android.permission.INTERACT_ACROSS_USERS" + android:writePermission="android.permission.INTERACT_ACROSS_USERS"> </provider> <!-- Note: While this provider does not declare a permission explicitly, it enforces that @@ -80,12 +97,6 @@ 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"> diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 8ce3b7d0..478e5ce5 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -19,7 +19,7 @@ <string name="sharedUserLabel" msgid="8024311725474286801">"Android Core Apps"</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_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> diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 1073f199..bb60c01f 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -18,11 +18,11 @@ 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="provider_label" msgid="6012150850819899907">"కాంటాక్ట్లు"</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_text" msgid="2581831842693151968">"అప్గ్రేడ్ను పూర్తి చేయడానికి నొక్కండి."</string> - <string name="default_directory" msgid="93961630309570294">"కాంటాక్ట్లు"</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> diff --git a/src/com/android/providers/contacts/AccountWithDataSet.java b/src/com/android/providers/contacts/AccountWithDataSet.java index bfae1128..e1f633ea 100644 --- a/src/com/android/providers/contacts/AccountWithDataSet.java +++ b/src/com/android/providers/contacts/AccountWithDataSet.java @@ -17,10 +17,14 @@ 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. */ @@ -105,4 +109,23 @@ 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 new file mode 100644 index 00000000..568a1899 --- /dev/null +++ b/src/com/android/providers/contacts/CallComposerLocationProvider.java @@ -0,0 +1,194 @@ +/* + * 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 06d3291b..22f1cad4 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 = 9; + private static final int DATABASE_VERSION = 10; private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE @@ -153,6 +153,10 @@ public class CallLogDatabaseHelper { 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,6 +229,10 @@ public class CallLogDatabaseHelper { if (oldVersion < 9) { upgradeToVersion9(db); } + + if (oldVersion < 10) { + upgradeToVersion10(db); + } } } @@ -459,6 +467,12 @@ public class CallLogDatabaseHelper { 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 9fa651cb..c832e9b4 100644 --- a/src/com/android/providers/contacts/CallLogProvider.java +++ b/src/com/android/providers/contacts/CallLogProvider.java @@ -20,6 +20,8 @@ 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; @@ -36,6 +38,10 @@ 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; @@ -43,6 +49,7 @@ 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; @@ -55,11 +62,25 @@ 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. @@ -81,18 +102,57 @@ 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.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.PRIORITY, + Calls.SUBJECT, + Calls.COMPOSER_PHOTO_URI, + // Location is deliberately omitted + Calls.ADD_FOR_ALL_USERS }; static final String[] MINIMAL_PROJECTION = new String[] { Calls._ID }; @@ -103,6 +163,10 @@ 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 + "=?;"; @@ -116,12 +180,20 @@ 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". + // Shadow provider only supports "/calls" and "/call_composer". 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); } - private static final ArrayMap<String, String> sCallsProjectionMap; + public static final ArrayMap<String, String> sCallsProjectionMap; static { // Calls projection map @@ -163,6 +235,10 @@ public class CallLogProvider extends ContentProvider { 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"; @@ -443,6 +519,11 @@ 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); } @@ -494,6 +575,30 @@ 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. @@ -518,6 +623,170 @@ 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) { @@ -537,19 +806,8 @@ public class CallLogProvider extends ContentProvider { SelectionBuilder selectionBuilder = new SelectionBuilder(selection); checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder, false /*isQuery*/); - - final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(Tables.CALLS); - qb.setProjectionMap(sCallsProjectionMap); - qb.setStrict(true); - // If the caller doesn't have READ_VOICEMAIL, make sure they can't - // do any SQL shenanigans to get access to the voicemails. If the caller does have the - // READ_VOICEMAIL permission, then they have sufficient permissions to access any data in - // the database, so the strict check is unnecessary. - if (!mVoicemailPermissions.callerHasReadAccess(getCallingPackage())) { - qb.setStrictGrammar(true); - } - + boolean hasReadVoicemailPermission = mVoicemailPermissions.callerHasReadAccess( + getCallingPackage()); final SQLiteDatabase db = mDbHelper.getWritableDatabase(); final int matchedUriId = sURIMatcher.match(uri); switch (matchedUriId) { @@ -564,11 +822,8 @@ public class CallLogProvider extends ContentProvider { throw new UnsupportedOperationException("Cannot update URL: " + uri); } - int rowsUpdated = qb.update(db, values, selectionBuilder.build(), selectionArgs); - if (rowsUpdated > 0) { - DbModifierWithNotification.notifyCallLogChange(getContext()); - } - return rowsUpdated; + return createDatabaseModifier(db, hasReadVoicemailPermission).update(uri, Tables.CALLS, + values, selectionBuilder.build(), selectionArgs); } private int deleteInternal(Uri uri, String selection, String[] selectionArgs) { @@ -583,29 +838,18 @@ public class CallLogProvider extends ContentProvider { SelectionBuilder selectionBuilder = new SelectionBuilder(selection); checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder, false /*isQuery*/); - final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(Tables.CALLS); - qb.setProjectionMap(sCallsProjectionMap); - qb.setStrict(true); - // If the caller doesn't have READ_VOICEMAIL, make sure they can't - // do any SQL shenanigans to get access to the voicemails. If the caller does have the - // READ_VOICEMAIL permission, then they have sufficient permissions to access any data in - // the database, so the strict check is unnecessary. - if (!mVoicemailPermissions.callerHasReadAccess(getCallingPackage())) { - qb.setStrictGrammar(true); - } - + boolean hasReadVoicemailPermission = + mVoicemailPermissions.callerHasReadAccess(getCallingPackage()); final SQLiteDatabase db = mDbHelper.getWritableDatabase(); final int matchedUriId = sURIMatcher.match(uri); switch (matchedUriId) { case CALLS: - // TODO: Special case - We may want to forward the delete request on user 0 to the - // shadow provider too. - int deletedCount = qb.delete(db, selectionBuilder.build(), selectionArgs); - if (deletedCount > 0) { - DbModifierWithNotification.notifyCallLogChange(getContext()); - } - return deletedCount; + 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); } @@ -619,8 +863,9 @@ public class CallLogProvider extends ContentProvider { * Returns a {@link DatabaseModifier} that takes care of sending necessary notifications * after the operation is performed. */ - private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) { - return new DbModifierWithNotification(Tables.CALLS, db, getContext()); + private DatabaseModifier createDatabaseModifier(SQLiteDatabase db, boolean hasReadVoicemail) { + return new DbModifierWithNotification(Tables.CALLS, db, null, hasReadVoicemail, + getContext()); } /** @@ -762,8 +1007,68 @@ 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 8a1c88bb..09e3ff37 100644 --- a/src/com/android/providers/contacts/ContactDirectoryManager.java +++ b/src/com/android/providers/contacts/ContactDirectoryManager.java @@ -245,8 +245,7 @@ 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, - /* syncToMetadataNetwork =*/false); + mContactsProvider.notifyChange(/* syncToNetwork =*/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 deleted file mode 100644 index 3cf7df2c..00000000 --- a/src/com/android/providers/contacts/ContactMetadataProvider.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * 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 2e5cdacf..7f4188d2 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -16,12 +16,6 @@ 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; @@ -73,14 +67,13 @@ 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; @@ -98,17 +91,23 @@ 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; @@ -143,9 +142,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { * 1200-1299 O * 1300-1399 P * 1400-1499 Q + * 1500-1599 S * </pre> */ - static final int DATABASE_VERSION = 1400; + static final int DATABASE_VERSION = 1501; private static final int MINIMUM_SUPPORTED_VERSION = 700; @VisibleForTesting @@ -189,8 +189,6 @@ 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. @@ -313,15 +311,6 @@ 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 { @@ -712,6 +701,8 @@ 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; @@ -770,22 +761,6 @@ 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; @@ -1247,8 +1222,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + AccountsColumns.ACCOUNT_NAME + " TEXT, " + AccountsColumns.ACCOUNT_TYPE + " TEXT, " + - AccountsColumns.DATA_SET + " TEXT" + - ");"); + AccountsColumns.DATA_SET + " TEXT, " + + AccountsColumns.SIM_SLOT_INDEX + " INTEGER, " + + AccountsColumns.SIM_EF_TYPE + " INTEGER" + + ");"); // 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 @@ -1622,34 +1599,11 @@ 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); @@ -2242,34 +2196,6 @@ 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) { @@ -2655,6 +2581,19 @@ 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. @@ -3317,30 +3256,19 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { } /** - * 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. + * 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. */ 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);"); } /** - * Add new metadata_sync_state table to store the metadata sync state for a set of accounts. + * 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. */ 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) { @@ -3439,6 +3367,14 @@ 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 @@ -3693,9 +3629,6 @@ 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"); @@ -3716,9 +3649,6 @@ 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"); @@ -3980,6 +3910,37 @@ 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) { @@ -4042,6 +4003,69 @@ 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() { @@ -4989,22 +5013,6 @@ 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 aad51c72..0c6e8192 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,6 +34,7 @@ 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; @@ -59,8 +60,11 @@ 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; @@ -94,7 +98,6 @@ 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; @@ -104,6 +107,8 @@ 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; @@ -121,7 +126,6 @@ import android.util.Log; import com.android.common.content.ProjectionMap; import com.android.common.content.SyncStateContentProviderHelper; import com.android.common.io.MoreCloseables; -import com.android.i18n.phonenumbers.Phonenumber; import com.android.internal.util.ArrayUtils; import com.android.providers.contacts.ContactLookupKey.LookupKeySegment; import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns; @@ -135,8 +139,6 @@ 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; @@ -153,11 +155,6 @@ 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; @@ -173,6 +170,8 @@ 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; @@ -219,6 +218,8 @@ 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"; @@ -259,6 +260,9 @@ 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; @@ -1453,13 +1457,14 @@ 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; @@ -1469,9 +1474,6 @@ public class ContactsProvider2 extends AbstractContactsProvider private int mFastScrollingIndexCacheMissCount; private long mTotalTimeFastScrollingIndexGenerate; - // MetadataSync flag. - private boolean mMetadataSyncEnabled; - // Enterprise members private EnterprisePolicyGuard mEnterprisePolicyGuard; @@ -1481,6 +1483,13 @@ 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"); @@ -1515,9 +1524,6 @@ 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); @@ -1971,8 +1977,7 @@ 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, - /* callerIsMetadataSyncAdapter =*/false); + updateValues, null, null, /* callerIsSyncAdapter =*/false); } if (photoFileIdToStreamItemPhotoId.containsKey(missingPhotoId)) { // For missing photos that were in stream item photos, just delete the @@ -2171,49 +2176,106 @@ public class ContactsProvider2 extends AbstractContactsProvider @Override public Uri insert(Uri uri, ContentValues values) { - waitForAccess(mWriteAccessLatch); + 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; - mContactsHelper.validateContentValues(getCallingPackage(), values); + try { + waitForAccess(mWriteAccessLatch); - if (mapsToProfileDbWithInsertedValues(uri, values)) { - switchToProfileMode(); - return mProfileProvider.insert(uri, 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()); } - switchToContactMode(); - return super.insert(uri, values); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - waitForAccess(mWriteAccessLatch); + 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; - mContactsHelper.validateContentValues(getCallingPackage(), values); - mContactsHelper.validateSql(getCallingPackage(), selection); + try { + waitForAccess(mWriteAccessLatch); - if (mapsToProfileDb(uri)) { - switchToProfileMode(); - return mProfileProvider.update(uri, values, selection, selectionArgs); + 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()); } - switchToContactMode(); - return super.update(uri, values, selection, selectionArgs); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - waitForAccess(mWriteAccessLatch); + 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; - mContactsHelper.validateSql(getCallingPackage(), selection); + try { + waitForAccess(mWriteAccessLatch); - if (mapsToProfileDb(uri)) { - switchToProfileMode(); - return mProfileProvider.delete(uri, selection, selectionArgs); + 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()); } - 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)) { @@ -2236,6 +2298,95 @@ 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; } @@ -2455,11 +2606,6 @@ 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 @@ -2474,14 +2620,44 @@ public class ContactsProvider2 extends AbstractContactsProvider @Override protected void notifyChange() { - notifyChange(mSyncToNetwork, mSyncToMetadataNetWork); + notifyChange(mSyncToNetwork); mSyncToNetwork = false; - mSyncToMetadataNetWork = false; } - protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) { + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Runnable mChangeNotifier = () -> { + Log.v(TAG, "Scheduled notifyChange started."); + mLastNotifyChange = System.currentTimeMillis(); 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) { @@ -3039,8 +3215,8 @@ public class ContactsProvider2 extends AbstractContactsProvider Uri dataUri = inProfileMode() ? Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY) : Data.CONTENT_URI; - Cursor c = query(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS, - selection, selectionArgs, null); + Cursor c = queryInternal(dataUri, DataRowHandler.DataDeleteQuery.COLUMNS, + selection, selectionArgs, null, null); try { while(c.moveToNext()) { long rawContactId = c.getLong(DataRowHandler.DataDeleteQuery.RAW_CONTACT_ID); @@ -3067,8 +3243,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 = query(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, Data._ID + "=?", - mSelectionArgs1, null); + Cursor c = queryInternal(Data.CONTENT_URI, DataRowHandler.DataDeleteQuery.COLUMNS, + Data._ID + "=?", mSelectionArgs1, null, null); try { if (!c.moveToFirst()) { @@ -3895,8 +4071,7 @@ 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, - /* callerIsMetadataSyncAdapter =*/false); + return updateRawContact(db, rawContactId, values, callerIsSyncAdapter); } static int deleteDataUsage(SQLiteDatabase db) { @@ -3998,8 +4173,7 @@ public class ContactsProvider2 extends AbstractContactsProvider String selectionWithId = (Data.RAW_CONTACT_ID + "=" + rawContactId + " ") + (selection == null ? "" : " AND " + selection); - count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter, - /* callerIsMetadataSyncAdapter =*/false); + count = updateData(uri, values, selectionWithId, selectionArgs, callerIsSyncAdapter); break; } @@ -4007,8 +4181,7 @@ public class ContactsProvider2 extends AbstractContactsProvider case PROFILE_DATA: { invalidateFastScrollingIndexCache(); count = updateData(uri, values, appendAccountToSelection(uri, selection), - selectionArgs, callerIsSyncAdapter, - /* callerIsMetadataSyncAdapter =*/false); + selectionArgs, callerIsSyncAdapter); if (count > 0) { mSyncToNetwork |= !callerIsSyncAdapter; } @@ -4021,8 +4194,7 @@ public class ContactsProvider2 extends AbstractContactsProvider case CALLABLES_ID: case POSTALS_ID: { invalidateFastScrollingIndexCache(); - count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter, - /* callerIsMetadataSyncAdapter =*/false); + count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter); if (count > 0) { mSyncToNetwork |= !callerIsSyncAdapter; } @@ -4075,8 +4247,7 @@ public class ContactsProvider2 extends AbstractContactsProvider } case AGGREGATION_EXCEPTIONS: { - count = updateAggregationException(db, values, - /* callerIsMetadataSyncAdapter =*/false); + count = updateAggregationException(db, values); invalidateFastScrollingIndexCache(); break; } @@ -4402,8 +4573,7 @@ public class ContactsProvider2 extends AbstractContactsProvider try { while (cursor.moveToNext()) { long rawContactId = cursor.getLong(0); - updateRawContact(db, rawContactId, values, callerIsSyncAdapter, - /* callerIsMetadataSyncAdapter =*/false); + updateRawContact(db, rawContactId, values, callerIsSyncAdapter); count++; } } finally { @@ -4436,7 +4606,7 @@ public class ContactsProvider2 extends AbstractContactsProvider } private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values, - boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) { + boolean callerIsSyncAdapter) { final String selection = RawContactsColumns.CONCRETE_ID + " = ?"; mSelectionArgs1[0] = Long.toString(rawContactId); @@ -4567,8 +4737,7 @@ public class ContactsProvider2 extends AbstractContactsProvider } private int updateData(Uri uri, ContentValues inputValues, String selection, - String[] selectionArgs, boolean callerIsSyncAdapter, - boolean callerIsMetadataSyncAdapter) { + String[] selectionArgs, boolean callerIsSyncAdapter) { final ContentValues values = new ContentValues(inputValues); values.remove(Data._ID); @@ -4594,7 +4763,7 @@ public class ContactsProvider2 extends AbstractContactsProvider selection, selectionArgs, null, -1 /* directory ID */, null); try { while(c.moveToNext()) { - count += updateData(values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter); + count += updateData(values, c, callerIsSyncAdapter); } } finally { c.close(); @@ -4610,8 +4779,7 @@ public class ContactsProvider2 extends AbstractContactsProvider } } - private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter, - boolean callerIsMetadataSyncAdapter) { + private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter) { if (values.size() == 0) { return 0; } @@ -4626,7 +4794,7 @@ public class ContactsProvider2 extends AbstractContactsProvider DataRowHandler rowHandler = getDataRowHandler(mimeType); boolean updated = rowHandler.update(db, mTransactionContext.get(), values, c, - callerIsSyncAdapter, callerIsMetadataSyncAdapter); + callerIsSyncAdapter); if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS); } @@ -4744,8 +4912,7 @@ public class ContactsProvider2 extends AbstractContactsProvider return rslt; } - private int updateAggregationException(SQLiteDatabase db, ContentValues values, - boolean callerIsMetadataSyncAdapter) { + private int updateAggregationException(SQLiteDatabase db, ContentValues values) { Integer exceptionType = values.getAsInteger(AggregationExceptions.TYPE); Long rcId1 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID1); Long rcId2 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID2); @@ -4868,27 +5035,6 @@ 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[] { @@ -4925,80 +5071,6 @@ 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) { @@ -5093,12 +5165,14 @@ 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.inSystemAccounts(systemAccounts) + || knownAccountWithDataSet.inSimAccounts(simAccounts)) { continue; } accountsWithDataSetsToDelete.add(knownAccountWithDataSet); @@ -5108,7 +5182,6 @@ 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 = @@ -5141,14 +5214,6 @@ 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()) { @@ -5349,6 +5414,29 @@ 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) + diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java index 235edfed..a82ce34f 100644 --- a/src/com/android/providers/contacts/DataRowHandler.java +++ b/src/com/android/providers/contacts/DataRowHandler.java @@ -121,7 +121,6 @@ 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 @@ -154,13 +153,11 @@ public abstract class DataRowHandler { * @return true if update changed something */ public boolean update(SQLiteDatabase db, TransactionContext txContext, - ContentValues values, Cursor c, boolean callerIsSyncAdapter, - boolean callerIsMetadataSyncAdapter) { + ContentValues values, Cursor c, boolean callerIsSyncAdapter) { long dataId = c.getLong(DataUpdateQuery._ID); long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId, - callerIsMetadataSyncAdapter); + handlePrimaryAndSuperPrimary(txContext, values, dataId, rawContactId); handleHashIdForUpdate(values, dataId); if (values.size() > 0) { @@ -251,15 +248,13 @@ public abstract class DataRowHandler { * configured correctly */ private void handlePrimaryAndSuperPrimary(TransactionContext txContext, ContentValues values, - long dataId, long rawContactId, boolean callerIsMetadataSyncAdapter) { + long dataId, long rawContactId) { 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 @@ -325,7 +320,6 @@ 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 5ae3a01f..063fcdb2 100644 --- a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java +++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java @@ -49,15 +49,14 @@ public class DataRowHandlerForCommonDataKind extends DataRowHandler { @Override public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, - Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { final long dataId = c.getLong(DataUpdateQuery._ID); final ContentValues augmented = getAugmentedValues(db, dataId, values); if (augmented == null) { // No change return false; } enforceTypeAndLabel(augmented); - return super.update(db, txContext, values, c, callerIsSyncAdapter, - callerIsMetadataSyncAdapter); + return super.update(db, txContext, values, c, callerIsSyncAdapter); } /** diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java index 3c7311f0..539c9596 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, boolean callerIsMetadataSyncAdapter) { - if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { + Cursor c, boolean callerIsSyncAdapter) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { return false; } diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java index b1c40497..3f310b18 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId); resolveGroupSourceIdInValues(txContext, rawContactId, db, values, false); - if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { 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 4d4a33f6..32e9757e 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { - super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter); + super.update(db, txContext, values, c, callerIsSyncAdapter); // 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 cc85c2b1..03b96a3a 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { long dataId = c.getLong(DataUpdateQuery._ID); long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); - if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { return false; } diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java index 5b69fe3c..66a3b1bd 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, boolean callerIsMetadataSyncAdapter) { - if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { + Cursor c, boolean callerIsSyncAdapter) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { return false; } diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java index 7bbac689..052252e1 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { fillNormalizedNumber(values); - if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { return false; } diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java index 3d28b05c..532a8521 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { 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, callerIsMetadataSyncAdapter)) { + if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { return false; } diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java index b80f759d..044e9726 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { 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, callerIsMetadataSyncAdapter); + super.update(db, txContext, values, c, callerIsSyncAdapter); 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 235bbd7c..7fc97b7a 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, boolean callerIsMetadataSyncAdapter) { + Cursor c, boolean callerIsSyncAdapter) { 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, callerIsMetadataSyncAdapter); + super.update(db, txContext, values, c, callerIsSyncAdapter); return true; } diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java index 03ebd1f1..dc74c0ab 100644 --- a/src/com/android/providers/contacts/DbModifierWithNotification.java +++ b/src/com/android/providers/contacts/DbModifierWithNotification.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.database.Cursor; import android.database.DatabaseUtils.InsertHelper; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Binder; import android.provider.CallLog.Calls; @@ -65,6 +66,7 @@ public class DbModifierWithNotification implements DatabaseModifier { Voicemails.DELETED + " == 0"; private final String mTableName; private final SQLiteDatabase mDb; + private final boolean mHasReadVoicemailPermission; private final InsertHelper mInsertHelper; private final Context mContext; private final Uri mBaseUri; @@ -86,8 +88,14 @@ public class DbModifierWithNotification implements DatabaseModifier { private DbModifierWithNotification(String tableName, SQLiteDatabase db, InsertHelper insertHelper, Context context) { + this(tableName, db, insertHelper, true /* hasReadVoicemail */, context); + } + + public DbModifierWithNotification(String tableName, SQLiteDatabase db, + InsertHelper insertHelper, boolean hasReadVoicemailPermission, Context context) { mTableName = tableName; mDb = db; + mHasReadVoicemailPermission = hasReadVoicemailPermission; mInsertHelper = insertHelper; mContext = context; mBaseUri = mTableName.equals(Tables.VOICEMAIL_STATUS) ? @@ -196,7 +204,16 @@ public class DbModifierWithNotification implements DatabaseModifier { if (values.isEmpty()) { return 0; } - int count = mDb.update(table, values, whereClause, whereArgs); + + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(mTableName); + qb.setProjectionMap(CallLogProvider.sCallsProjectionMap); + qb.setStrict(true); + if (!mHasReadVoicemailPermission) { + qb.setStrictGrammar(true); + } + int count = qb.update(mDb, values, whereClause, whereArgs); + if (count > 0 && isVoicemailContent || Tables.VOICEMAIL_STATUS.equals(table)) { notifyVoicemailChange(mBaseUri, packagesModified); } @@ -269,14 +286,23 @@ public class DbModifierWithNotification implements DatabaseModifier { // If the deletion is being made by the package that inserted the voicemail or by // CP2 (cleanup after uninstall), then we don't need to wait for sync, so just delete it. final int count; + + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(mTableName); + qb.setProjectionMap(CallLogProvider.sCallsProjectionMap); + qb.setStrict(true); + if (!mHasReadVoicemailPermission) { + qb.setStrictGrammar(true); + } + if (mIsCallsTable && isVoicemail && !isSelfModifyingOrInternal(packagesModified)) { ContentValues values = new ContentValues(); values.put(VoicemailContract.Voicemails.DIRTY, 1); values.put(VoicemailContract.Voicemails.DELETED, 1); values.put(VoicemailContract.Voicemails.LAST_MODIFIED, getTimeMillis()); - count = mDb.update(table, values, whereClause, whereArgs); + count = qb.update(mDb, values, whereClause, whereArgs); } else { - count = mDb.delete(table, whereClause, whereArgs); + count = qb.delete(mDb, whereClause, whereArgs); } if (count > 0 && isVoicemail) { diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java deleted file mode 100644 index 2fe423a8..00000000 --- a/src/com/android/providers/contacts/MetadataEntryParser.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java index f3b6daf6..d9dc784e 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, boolean syncToMetadataNetWork) { - mDelegate.notifyChange(syncToNetwork, syncToMetadataNetWork); + protected void notifyChange(boolean syncToNetwork) { + mDelegate.notifyChange(syncToNetwork); } protected Locale getLocale() { diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java index e421654c..aeaa0e7d 100644 --- a/src/com/android/providers/contacts/SearchIndexManager.java +++ b/src/com/android/providers/contacts/SearchIndexManager.java @@ -55,7 +55,8 @@ 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 int SEARCH_INDEX_VERSION = 1; + private static final String ROW_ID_KEY = "rowid"; + private static final int SEARCH_INDEX_VERSION = 2; private static final class ContactIndexQuery { public static final String[] COLUMNS = { @@ -327,7 +328,7 @@ public class SearchIndexManager { // Remove affected search_index rows. final SQLiteDatabase db = mDbHelper.getWritableDatabase(); final int deleted = db.delete(Tables.SEARCH_INDEX, - SearchIndexColumns.CONTACT_ID + " IN (SELECT " + + ROW_ID_KEY + " IN (SELECT " + RawContacts.CONTACT_ID + " FROM " + Tables.RAW_CONTACTS + " WHERE " + rawContactsSelection + @@ -400,6 +401,7 @@ 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 dfb6d696..86dae01b 100644 --- a/src/com/android/providers/contacts/TransactionContext.java +++ b/src/com/android/providers/contacts/TransactionContext.java @@ -35,7 +35,6 @@ 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 @@ -78,22 +77,6 @@ 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<>(); @@ -131,16 +114,6 @@ 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; @@ -176,7 +149,6 @@ 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 daecd973..ef7a3758 100644 --- a/src/com/android/providers/contacts/VoicemailContentProvider.java +++ b/src/com/android/providers/contacts/VoicemailContentProvider.java @@ -24,6 +24,7 @@ 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; @@ -119,24 +120,23 @@ public class VoicemailContentProvider extends ContentProvider } @Override - protected int enforceReadPermissionInner(Uri uri, String callingPkg, - @Nullable String featureId, IBinder callerToken) throws SecurityException { + protected int enforceReadPermissionInner(Uri uri, + @NonNull AttributionSource attributionSource) throws SecurityException { // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state. if (mVoicemailPermissions.callerHasCarrierPrivileges()) { return MODE_ALLOWED; } - return super.enforceReadPermissionInner(uri, callingPkg, featureId, callerToken); + return super.enforceReadPermissionInner(uri, attributionSource); } - @Override - protected int enforceWritePermissionInner(Uri uri, String callingPkg, - @Nullable String featureId, IBinder callerToken) throws SecurityException { + protected int enforceWritePermissionInner(Uri uri, + @NonNull AttributionSource attributionSource) throws SecurityException { // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state. if (mVoicemailPermissions.callerHasCarrierPrivileges()) { return MODE_ALLOWED; } - return super.enforceWritePermissionInner(uri, callingPkg, featureId, callerToken); + return super.enforceWritePermissionInner(uri, attributionSource); } @VisibleForTesting diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java index 58e7a146..b38f7f58 100644 --- a/src/com/android/providers/contacts/VoicemailPermissions.java +++ b/src/com/android/providers/contacts/VoicemailPermissions.java @@ -151,7 +151,12 @@ public class VoicemailPermissions { } private static boolean packageHasCarrierPrivileges(TelephonyManager tm, String packageName) { - return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + final long token = Binder.clearCallingIdentity(); + try { + return tm.checkCarrierPrivilegesForPackageAnyPhone(packageName) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } finally { + Binder.restoreCallingIdentity(token); + } } } diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java new file mode 100644 index 00000000..4d07ca4b --- /dev/null +++ b/src/com/android/providers/contacts/util/LogFields.java @@ -0,0 +1,144 @@ +/* + * 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 new file mode 100644 index 00000000..a564a359 --- /dev/null +++ b/src/com/android/providers/contacts/util/LogUtils.java @@ -0,0 +1,97 @@ +/* + * 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/tests/assets/test1/testFileDeviceContactMetadataJSON.txt b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt deleted file mode 100644 index 65e624d1..00000000 --- a/tests/assets/test1/testFileDeviceContactMetadataJSON.txt +++ /dev/null @@ -1,69 +0,0 @@ -{ - "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 91a76a31..816d10d7 100644 --- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java @@ -1331,10 +1331,6 @@ 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 c9d09635..92b4b171 100644 --- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java +++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java @@ -62,7 +62,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 = 36; + private static final int NUM_CALLLOG_FIELDS = 40; private static final int MIN_MATCH = 7; @@ -498,6 +498,7 @@ 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"); @@ -509,6 +510,7 @@ 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"); @@ -520,6 +522,7 @@ 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 deleted file mode 100644 index 3d8b8eb3..00000000 --- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * 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 deleted file mode 100644 index c46643f2..00000000 --- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTestable.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 804e79a9..56a4fc4b 100644 --- a/tests/src/com/android/providers/contacts/ContactsActor.java +++ b/tests/src/com/android/providers/contacts/ContactsActor.java @@ -248,6 +248,11 @@ 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 1832b4e4..d50a2922 100644 --- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java +++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java @@ -30,8 +30,6 @@ 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; @@ -51,8 +49,6 @@ 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; @@ -63,7 +59,6 @@ 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; /** @@ -225,6 +220,8 @@ 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[] { @@ -529,26 +526,12 @@ 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), @@ -592,9 +575,7 @@ 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 7860b63d..7efc2f47 100644 --- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java @@ -55,8 +55,6 @@ 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; @@ -86,11 +84,6 @@ 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; @@ -2894,333 +2887,6 @@ 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"); @@ -5197,10 +4863,6 @@ 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); @@ -5210,14 +4872,12 @@ 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); } @@ -5278,10 +4938,6 @@ 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); @@ -5300,7 +4956,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { assertMetadataDirty(rawContactUri1, false); assertMetadataDirty(rawContactUri2, false); assertNetworkNotified(false); - assertMetadataNetworkNotified(false); } public void testStatusUpdateInsert() { @@ -6766,98 +6421,6 @@ 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); @@ -6891,35 +6454,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { } public void testDirtyWhenRawContactInsert() { - // Enable metadataSync flag. - final ContactsProvider2 cp = (ContactsProvider2) getProvider(); - cp.setMetadataSyncForTest(true); - - // When inserting a rawcontact without metadata. + // When inserting a rawcontact. 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); @@ -6933,9 +6476,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { assertEquals(version, getVersion(uri)); assertDirty(uri, false); - assertNetworkNotified(false); assertMetadataDirty(uri, false); - assertMetadataNetworkNotified(false); + assertNetworkNotified(false); Uri emailUri = insertEmail(rawContactId, "goo@woo.com"); assertDirty(uri, true); @@ -6992,10 +6534,6 @@ 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) @@ -7005,15 +6543,9 @@ 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); @@ -7025,7 +6557,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); assertMetadataDirty(rawContactUri, false); - assertMetadataNetworkNotified(false); clearMetadataDirty(rawContactUri); values = new ContentValues(); @@ -7034,7 +6565,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { assertStoredValue(contactUri, Contacts.PINNED, 1); assertMetadataDirty(rawContactUri, false); - assertMetadataNetworkNotified(false); clearMetadataDirty(rawContactUri); values = new ContentValues(); @@ -7043,14 +6573,9 @@ 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); @@ -7066,14 +6591,9 @@ 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")); @@ -7084,28 +6604,18 @@ 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); @@ -7113,14 +6623,9 @@ 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"); @@ -7133,14 +6638,9 @@ 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); @@ -7148,7 +6648,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { assertMetadataDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), false); - assertMetadataNetworkNotified(false); } public void testDeleteContactWithoutName() { @@ -8809,25 +8308,6 @@ 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(); @@ -9006,10 +8486,6 @@ 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); @@ -9022,7 +8498,6 @@ 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 deleted file mode 100644 index fe6ed132..00000000 --- a/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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 f674dd5b..c2ab74fc 100644 --- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java +++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java @@ -36,7 +36,6 @@ 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; @@ -62,23 +61,17 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 { public void onBegin() { super.onBegin(); mNetworkNotified = false; - mMetadataNetworkNotified = false; } @Override - protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) { + protected void notifyChange(boolean syncToNetwork) { mNetworkNotified |= syncToNetwork; - mMetadataNetworkNotified |= syncToMetadataNetwork; } public boolean isNetworkNotified() { return mNetworkNotified; } - public boolean isMetadataNetworkNotified() { - return mMetadataNetworkNotified; - } - public void setIsPhone(boolean flag) { mIsPhone = flag; } |