summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/providers/contacts/AccountWithDataSet.java23
-rw-r--r--src/com/android/providers/contacts/CallComposerLocationProvider.java194
-rw-r--r--src/com/android/providers/contacts/CallLogDatabaseHelper.java16
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java407
-rw-r--r--src/com/android/providers/contacts/ContactDirectoryManager.java3
-rw-r--r--src/com/android/providers/contacts/ContactMetadataProvider.java443
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java260
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java448
-rw-r--r--src/com/android/providers/contacts/DataRowHandler.java12
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java5
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForEmail.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForIdentity.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForNickname.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForOrganization.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoto.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredName.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java4
-rw-r--r--src/com/android/providers/contacts/DbModifierWithNotification.java32
-rw-r--r--src/com/android/providers/contacts/MetadataEntryParser.java296
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java4
-rw-r--r--src/com/android/providers/contacts/SearchIndexManager.java6
-rw-r--r--src/com/android/providers/contacts/TransactionContext.java28
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java14
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java9
-rw-r--r--src/com/android/providers/contacts/util/LogFields.java144
-rw-r--r--src/com/android/providers/contacts/util/LogUtils.java97
28 files changed, 1304 insertions, 1173 deletions
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;
+ }
+}
+
+