aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/providers
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers')
-rw-r--r--src/com/android/providers/contacts/AbstractContactsProvider.java117
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java122
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java24
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java14
-rw-r--r--src/com/android/providers/contacts/DatabaseModifier.java6
-rw-r--r--src/com/android/providers/contacts/DbModifierWithNotification.java197
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java6
-rw-r--r--src/com/android/providers/contacts/SearchIndexManager.java24
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java43
-rw-r--r--src/com/android/providers/contacts/VoicemailContentTable.java45
-rw-r--r--src/com/android/providers/contacts/VoicemailNotifier.java128
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java35
-rw-r--r--src/com/android/providers/contacts/VoicemailStatusTable.java24
-rw-r--r--src/com/android/providers/contacts/VoicemailTable.java2
-rw-r--r--src/com/android/providers/contacts/util/CappedStringBuilder.java69
15 files changed, 619 insertions, 237 deletions
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index ebf0a1b9..e00bd862 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -16,10 +16,6 @@
package com.android.providers.contacts;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -38,8 +34,11 @@ import android.provider.BaseColumns;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.util.SparseLongArray;
+
+import com.android.internal.util.ProviderAccessStats;
+import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -116,23 +115,8 @@ public abstract class AbstractContactsProvider extends ContentProvider
*/
private SQLiteTransactionListener mSerializedDbTransactionListener;
- private final long mStartTime = SystemClock.elapsedRealtime();
-
- private final Object mStatsLock = new Object();
- protected final SparseBooleanArray mAllCallingUids = new SparseBooleanArray();
- protected final SparseLongArray mQueryStats = new SparseLongArray();
- protected final SparseLongArray mBatchStats = new SparseLongArray();
- protected final SparseLongArray mInsertStats = new SparseLongArray();
- protected final SparseLongArray mUpdateStats = new SparseLongArray();
- protected final SparseLongArray mDeleteStats = new SparseLongArray();
- protected final SparseLongArray mInsertInBatchStats = new SparseLongArray();
- protected final SparseLongArray mUpdateInBatchStats = new SparseLongArray();
- protected final SparseLongArray mDeleteInBatchStats = new SparseLongArray();
- private final SparseLongArray mOperationDurationMicroStats = new SparseLongArray();
-
- private final ThreadLocal<Integer> mOperationNest = ThreadLocal.withInitial(() -> 0);
- private final ThreadLocal<Long> mOperationStartNs = ThreadLocal.withInitial(() -> 0L);
+ protected final ProviderAccessStats mStats = new ProviderAccessStats();
@Override
public boolean onCreate() {
@@ -158,47 +142,19 @@ public abstract class AbstractContactsProvider extends ContentProvider
mSerializedDbTransactionListener = listener;
}
- protected final void incrementStats(SparseLongArray stats) {
- final int callingUid = Binder.getCallingUid();
- synchronized (mStatsLock) {
- stats.put(callingUid, stats.get(callingUid) + 1);
- mAllCallingUids.put(callingUid, true);
-
- final int nest = mOperationNest.get();
- mOperationNest.set(nest + 1);
- if (nest == 0) {
- mOperationStartNs.set(SystemClock.elapsedRealtimeNanos());
- }
- }
+ public ContactsTransaction getCurrentTransaction() {
+ return mTransactionHolder.get();
}
- protected final void incrementStats(SparseLongArray statsNonBatch,
- SparseLongArray statsInBatch) {
+ private boolean isInBatch() {
final ContactsTransaction t = mTransactionHolder.get();
- final boolean inBatch = t != null && t.isBatch();
- incrementStats(inBatch ? statsInBatch : statsNonBatch);
- }
-
- protected void finishOperation() {
- final int callingUid = Binder.getCallingUid();
- synchronized (mStatsLock) {
- final int nest = mOperationNest.get();
- mOperationNest.set(nest - 1);
- if (nest == 1) {
- final long duration = SystemClock.elapsedRealtimeNanos() - mOperationStartNs.get();
- mOperationDurationMicroStats.put(callingUid,
- mOperationDurationMicroStats.get(callingUid) + duration / 1000L);
- }
- }
- }
-
- public ContactsTransaction getCurrentTransaction() {
- return mTransactionHolder.get();
+ return t != null && t.isBatch();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
- incrementStats(mInsertStats, mInsertInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementInsertStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -212,13 +168,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- incrementStats(mDeleteStats, mDeleteInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementDeleteStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -232,13 +189,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- incrementStats(mUpdateStats, mUpdateInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementUpdateStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -252,13 +210,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
- incrementStats(mBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementBatchStats(callingUid);
try {
ContactsTransaction transaction = startTransaction(true);
int numValues = values.length;
@@ -282,14 +241,15 @@ public abstract class AbstractContactsProvider extends ContentProvider
}
return numValues;
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
- incrementStats(mBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementBatchStats(callingUid);
try {
if (VERBOSE_LOGGING) {
Log.v(TAG, "applyBatch: " + operations.size() + " ops");
@@ -331,7 +291,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(true);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@@ -462,32 +422,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
pw.print("Database: ");
pw.println(dbName);
- pw.print(" Uptime: ");
- pw.print((SystemClock.elapsedRealtime() - mStartTime) / (60 * 1000));
- pw.println(" minutes");
-
- synchronized (mStatsLock) {
- pw.println();
- pw.println(" Client activities:");
- pw.println(" UID Query Insert Update Delete Batch Insert Update Delete"
- + " Sec");
- for (int i = 0; i < mAllCallingUids.size(); i++) {
- final int uid = mAllCallingUids.keyAt(i);
- pw.println(String.format(
- " %-9d %6d %6d %6d %6d %6d %6d %6d %6d %12.3f",
- uid,
- mQueryStats.get(uid),
- mInsertStats.get(uid),
- mUpdateStats.get(uid),
- mDeleteStats.get(uid),
- mBatchStats.get(uid),
- mInsertInBatchStats.get(uid),
- mUpdateInBatchStats.get(uid),
- mDeleteInBatchStats.get(uid),
- (mOperationDurationMicroStats.get(uid) / 1000000.0)
- ));
- }
- }
+ mStats.dump(pw, " ");
if (mDbHelper == null) {
pw.println("mDbHelper is null");
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 59e9b147..f71a7503 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -22,10 +22,13 @@ import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClau
import android.app.AppOpsManager;
import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -45,11 +48,15 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ProviderAccessStats;
import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties;
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.UserUtils;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -175,6 +182,10 @@ public class CallLogProvider extends ContentProvider {
private VoicemailPermissions mVoicemailPermissions;
private CallLogInsertionHelper mCallLogInsertionHelper;
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<>();
+ private final ThreadLocal<Integer> mCallingUid = new ThreadLocal<>();
+ private final ProviderAccessStats mStats = new ProviderAccessStats();
+
protected boolean isShadow() {
return false;
}
@@ -228,9 +239,58 @@ public class CallLogProvider extends ContentProvider {
return CallLogDatabaseHelper.getInstance(context);
}
+ protected boolean applyingBatch() {
+ final Boolean applying = mApplyingBatch.get();
+ return applying != null && applying;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ final int callingUid = Binder.getCallingUid();
+ mCallingUid.set(callingUid);
+
+ mStats.incrementBatchStats(callingUid);
+ mApplyingBatch.set(true);
+ try {
+ return super.applyBatch(operations);
+ } finally {
+ mApplyingBatch.set(false);
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ final int callingUid = Binder.getCallingUid();
+ mCallingUid.set(callingUid);
+
+ mStats.incrementBatchStats(callingUid);
+ mApplyingBatch.set(true);
+ try {
+ return super.bulkInsert(uri, values);
+ } finally {
+ mApplyingBatch.set(false);
+ mStats.finishOperation(callingUid);
+ }
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ // Note don't use mCallingUid here. That's only used by mutation functions.
+ final int callingUid = Binder.getCallingUid();
+
+ mStats.incrementQueryStats(callingUid);
+ try {
+ return queryInternal(uri, projection, selection, selectionArgs, sortOrder);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ private Cursor queryInternal(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "query: uri=" + uri + " projection=" + Arrays.toString(projection) +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -361,6 +421,44 @@ public class CallLogProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return insertInternal(uri, values);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return updateInternal(uri, values, selection, selectionArgs);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return deleteInternal(uri, selection, selectionArgs);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ private Uri insertInternal(Uri uri, ContentValues values) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "insert: uri=" + uri + " values=[" + values + "]" +
" CPID=" + Binder.getCallingPid());
@@ -383,15 +481,15 @@ public class CallLogProvider extends ContentProvider {
// Add the computed fields to the copied values.
mCallLogInsertionHelper.addComputedValues(copiedValues);
- long rowId = getDatabaseModifier(mCallsInserter).insert(copiedValues);
+ long rowId = createDatabaseModifier(mCallsInserter).insert(copiedValues);
if (rowId > 0) {
return ContentUris.withAppendedId(uri, rowId);
}
return null;
}
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ private int updateInternal(Uri uri, ContentValues values,
+ String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "update: uri=" + uri +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -423,12 +521,11 @@ public class CallLogProvider extends ContentProvider {
throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
- return getDatabaseModifier(db).update(uri, Tables.CALLS, values, selectionBuilder.build(),
+ return createDatabaseModifier(db).update(uri, Tables.CALLS, values, selectionBuilder.build(),
selectionArgs);
}
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
+ private int deleteInternal(Uri uri, String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "delete: uri=" + uri +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -445,7 +542,7 @@ public class CallLogProvider extends ContentProvider {
case CALLS:
// TODO: Special case - We may want to forward the delete request on user 0 to the
// shadow provider too.
- return getDatabaseModifier(db).delete(Tables.CALLS,
+ return createDatabaseModifier(db).delete(Tables.CALLS,
selectionBuilder.build(), selectionArgs);
default:
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
@@ -460,15 +557,15 @@ public class CallLogProvider extends ContentProvider {
* Returns a {@link DatabaseModifier} that takes care of sending necessary notifications
* after the operation is performed.
*/
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(Tables.CALLS, db, getContext());
}
/**
- * Same as {@link #getDatabaseModifier(SQLiteDatabase)} but used for insert helper operations
+ * Same as {@link #createDatabaseModifier(SQLiteDatabase)} but used for insert helper operations
* only.
*/
- private DatabaseModifier getDatabaseModifier(DatabaseUtils.InsertHelper insertHelper) {
+ private DatabaseModifier createDatabaseModifier(DatabaseUtils.InsertHelper insertHelper) {
return new DbModifierWithNotification(Tables.CALLS, insertHelper, getContext());
}
@@ -753,4 +850,9 @@ public class CallLogProvider extends ContentProvider {
public void shutdown() {
mTaskScheduler.shutdownForTest();
}
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mStats.dump(writer, " ");
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 76fe173d..9414ece8 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -136,9 +136,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 1000-1099 M
* 1100-1199 N
* 1200-1299 O
+ * 1300-1399 P
* </pre>
*/
- static final int DATABASE_VERSION = 1202;
+ static final int DATABASE_VERSION = 1300;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
@VisibleForTesting
@@ -1440,7 +1441,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
Data.SYNC2 + " TEXT, " +
Data.SYNC3 + " TEXT, " +
Data.SYNC4 + " TEXT, " +
- Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0 " +
+ Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0, " +
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME + " TEXT, " +
+ Data.PREFERRED_PHONE_ACCOUNT_ID + " TEXT " +
");");
db.execSQL("CREATE INDEX data_raw_contact_id ON " + Tables.DATA + " (" +
@@ -1924,6 +1927,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ Data.DATA14 + ", "
+ Data.DATA15 + ", "
+ Data.CARRIER_PRESENCE + ", "
+ + Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME + ", "
+ + Data.PREFERRED_PHONE_ACCOUNT_ID + ", "
+ Data.SYNC1 + ", "
+ Data.SYNC2 + ", "
+ Data.SYNC3 + ", "
@@ -2666,6 +2671,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1202;
}
+ if (isUpgradeRequired(oldVersion,newVersion, 1300)) {
+ upgradeToVersion1300(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1300;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3438,6 +3449,15 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ "last_time_used = 0");
}
+ public void upgradeToVersion1300(SQLiteDatabase db) {
+ try {
+ db.execSQL("ALTER TABLE data ADD preferred_phone_account_component_name "
+ + "TEXT;");
+ db.execSQL("ALTER TABLE data ADD preferred_phone_account_id TEXT;");
+ } catch (SQLiteException 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
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index a5af51fe..54b4a27d 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -754,6 +754,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.add(Data.DATA14)
.add(Data.DATA15)
.add(Data.CARRIER_PRESENCE)
+ .add(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME)
+ .add(Data.PREFERRED_PHONE_ACCOUNT_ID)
.add(Data.DATA_VERSION)
.add(Data.IS_PRIMARY)
.add(Data.IS_SUPER_PRIMARY)
@@ -4649,6 +4651,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (flagExists(values, RawContacts.STARRED)) {
if (!callerIsSyncAdapter) {
updateFavoritesMembership(rawContactId, flagIsSet(values, RawContacts.STARRED));
+ mTransactionContext.get().markRawContactDirtyAndChanged(
+ rawContactId, callerIsSyncAdapter);
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
aggregator.updateStarred(rawContactId);
aggregator.updatePinned(rawContactId);
@@ -4662,6 +4667,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
SELECTION_STARRED_FROM_RAW_CONTACTS,
new String[] {Long.toString(rawContactId)});
updateFavoritesMembership(rawContactId, starred);
+ mTransactionContext.get().markRawContactDirtyAndChanged(
+ rawContactId, callerIsSyncAdapter);
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
}
if (flagExists(values, RawContacts.SEND_TO_VOICEMAIL)) {
@@ -4832,6 +4840,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (hasStarredValue) {
updateFavoritesMembership(rawContactId,
flagIsSet(values, RawContacts.STARRED));
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
if (hasStarredValue || hasPinnedValue || hasVoiceMailValue) {
@@ -5536,7 +5545,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
return mProfileProvider.query(uri, projection, selection, selectionArgs, sortOrder,
cancellationSignal);
}
- incrementStats(mQueryStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementQueryStats(callingUid);
try {
// Otherwise proceed with a normal query against the contacts DB.
switchToContactMode();
@@ -5544,7 +5554,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
return queryDirectoryIfNecessary(uri, projection, selection, selectionArgs, sortOrder,
cancellationSignal);
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
diff --git a/src/com/android/providers/contacts/DatabaseModifier.java b/src/com/android/providers/contacts/DatabaseModifier.java
index b11605b4..60f9c7f1 100644
--- a/src/com/android/providers/contacts/DatabaseModifier.java
+++ b/src/com/android/providers/contacts/DatabaseModifier.java
@@ -49,4 +49,10 @@ public interface DatabaseModifier {
* {@link SQLiteDatabase#delete(String, String, String[])} method.
*/
public abstract int delete(String table, String whereClause, String[] whereArgs);
+
+ void startBulkOperation();
+
+ void yieldBulkOperation();
+
+ void finishBulkOperation();
}
diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java
index 7e7b3e17..852301d3 100644
--- a/src/com/android/providers/contacts/DbModifierWithNotification.java
+++ b/src/com/android/providers/contacts/DbModifierWithNotification.java
@@ -17,7 +17,6 @@
package com.android.providers.contacts;
-import static android.Manifest.permission.ADD_VOICEMAIL;
import static android.Manifest.permission.READ_VOICEMAIL;
import android.content.ComponentName;
@@ -25,8 +24,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.DatabaseUtils.InsertHelper;
import android.database.sqlite.SQLiteDatabase;
@@ -37,17 +34,15 @@ import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Status;
import android.provider.VoicemailContract.Voicemails;
import android.util.ArraySet;
-import android.util.Log;
import com.android.common.io.MoreCloseables;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
import com.android.providers.contacts.util.DbQueryUtils;
import com.google.android.collect.Lists;
import com.google.common.collect.Iterables;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Set;
/**
@@ -57,6 +52,7 @@ import java.util.Set;
* of then got affected by the change.
*/
public class DbModifierWithNotification implements DatabaseModifier {
+
private static final String TAG = "DbModifierWithNotify";
private static final String[] PROJECTION = new String[] {
@@ -73,8 +69,11 @@ public class DbModifierWithNotification implements DatabaseModifier {
private final Context mContext;
private final Uri mBaseUri;
private final boolean mIsCallsTable;
- private final VoicemailPermissions mVoicemailPermissions;
+ private final VoicemailNotifier mVoicemailNotifier;
+
+ private boolean mIsBulkOperation = false;
+ private static VoicemailNotifier sVoicemailNotifierForTest;
public DbModifierWithNotification(String tableName, SQLiteDatabase db, Context context) {
this(tableName, db, null, context);
@@ -94,7 +93,8 @@ public class DbModifierWithNotification implements DatabaseModifier {
mBaseUri = mTableName.equals(Tables.VOICEMAIL_STATUS) ?
Status.CONTENT_URI : Voicemails.CONTENT_URI;
mIsCallsTable = mTableName.equals(Tables.CALLS);
- mVoicemailPermissions = new VoicemailPermissions(mContext);
+ mVoicemailNotifier = sVoicemailNotifierForTest != null ? sVoicemailNotifierForTest
+ : new VoicemailNotifier(mContext, mBaseUri);
}
@Override
@@ -143,13 +143,21 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
}
- private void notifyVoicemailChangeOnInsert(Uri notificationUri, Set<String> packagesModified) {
+ private void notifyVoicemailChangeOnInsert(
+ Uri notificationUri, Set<String> packagesModified) {
if (mIsCallsTable) {
- notifyVoicemailChange(notificationUri, packagesModified,
- VoicemailContract.ACTION_NEW_VOICEMAIL, Intent.ACTION_PROVIDER_CHANGED);
- } else {
- notifyVoicemailChange(notificationUri, packagesModified,
- Intent.ACTION_PROVIDER_CHANGED);
+ mVoicemailNotifier.addIntentActions(VoicemailContract.ACTION_NEW_VOICEMAIL);
+ }
+ notifyVoicemailChange(notificationUri, packagesModified);
+ }
+
+ private void notifyVoicemailChange(Uri notificationUri,
+ Set<String> modifiedPackages) {
+ mVoicemailNotifier.addUri(notificationUri);
+ mVoicemailNotifier.addModifiedPackages(modifiedPackages);
+ mVoicemailNotifier.addIntentActions(Intent.ACTION_PROVIDER_CHANGED);
+ if (!mIsBulkOperation) {
+ mVoicemailNotifier.sendNotification();
}
}
@@ -159,7 +167,8 @@ public class DbModifierWithNotification implements DatabaseModifier {
Set<String> packagesModified = getModifiedPackages(whereClause, whereArgs);
packagesModified.addAll(getModifiedPackages(values));
- boolean isVoicemail = packagesModified.size() != 0;
+ boolean isVoicemailContent =
+ packagesModified.size() != 0 && isUpdatingVoicemailColumns(values);
boolean hasMarkedRead = false;
if (mIsCallsTable) {
@@ -169,35 +178,27 @@ public class DbModifierWithNotification implements DatabaseModifier {
} else {
updateLastModified(table, whereClause, whereArgs);
}
- if (isVoicemail) {
- // If a calling package is modifying its own entries, it means that the change came
- // from the server and thus is synced or "clean". Otherwise, it means that a local
- // change is being made to the database, so the entries should be marked as "dirty"
- // so that the corresponding sync adapter knows they need to be synced.
- int isDirty;
- Integer callerSetDirty = values.getAsInteger(Voicemails.DIRTY);
- if (callerSetDirty != null) {
- // Respect the calling package if it sets the dirty flag
- isDirty = callerSetDirty == 0 ? 0 : 1;
- } else {
- isDirty = isSelfModifyingOrInternal(packagesModified) ? 0 : 1;
- }
- values.put(VoicemailContract.Voicemails.DIRTY, isDirty);
-
- if (isDirty == 0 && values.containsKey(Calls.IS_READ) && getAsBoolean(values,
- Calls.IS_READ)) {
- // If the server has set the IS_READ, it should also unset the new flag
- if (!values.containsKey(Calls.NEW)) {
- values.put(Calls.NEW, 0);
- hasMarkedRead = true;
+ if (isVoicemailContent) {
+ if (updateDirtyFlag(values, packagesModified)) {
+ if (values.containsKey(Calls.IS_READ)
+ && getAsBoolean(values,
+ Calls.IS_READ)) {
+ // If the server has set the IS_READ, it should also unset the new flag
+ if (!values.containsKey(Calls.NEW)) {
+ values.put(Calls.NEW, 0);
+ hasMarkedRead = true;
+ }
}
}
}
}
-
+ // updateDirtyFlag might remove the value and leave values empty.
+ if (values.isEmpty()) {
+ return 0;
+ }
int count = mDb.update(table, values, whereClause, whereArgs);
- if (count > 0 && isVoicemail) {
- notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ if (count > 0 && isVoicemailContent || Tables.VOICEMAIL_STATUS.equals(table)) {
+ notifyVoicemailChange(mBaseUri, packagesModified);
}
if (count > 0 && mIsCallsTable) {
notifyCallLogChange();
@@ -213,6 +214,38 @@ public class DbModifierWithNotification implements DatabaseModifier {
return count;
}
+ private boolean updateDirtyFlag(ContentValues values, Set<String> packagesModified) {
+ // If a calling package is modifying its own entries, it means that the change came
+ // from the server and thus is synced or "clean". Otherwise, it means that a local
+ // change is being made to the database, so the entries should be marked as "dirty"
+ // so that the corresponding sync adapter knows they need to be synced.
+ int isDirty;
+ Integer callerSetDirty = values.getAsInteger(Voicemails.DIRTY);
+ if (callerSetDirty != null) {
+ // Respect the calling package if it sets the dirty flag
+ if (callerSetDirty == Voicemails.DIRTY_RETAIN) {
+ values.remove(Voicemails.DIRTY);
+ return false;
+ } else {
+ isDirty = callerSetDirty == 0 ? 0 : 1;
+ }
+ } else {
+ isDirty = isSelfModifyingOrInternal(packagesModified) ? 0 : 1;
+ }
+
+ values.put(Voicemails.DIRTY, isDirty);
+ return isDirty == 0;
+ }
+
+ private boolean isUpdatingVoicemailColumns(ContentValues values) {
+ for (String key : values.keySet()) {
+ if (VoicemailContentTable.ALLOWED_COLUMNS.contains(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void updateLastModified(String table, String whereClause, String[] whereArgs) {
ContentValues values = new ContentValues();
values.put(Calls.LAST_MODIFIED, getTimeMillis());
@@ -247,7 +280,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
if (count > 0 && isVoicemail) {
- notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ notifyVoicemailChange(mBaseUri, packagesModified);
}
if (count > 0 && mIsCallsTable) {
notifyCallLogChange();
@@ -255,6 +288,25 @@ public class DbModifierWithNotification implements DatabaseModifier {
return count;
}
+ @Override
+ public void startBulkOperation() {
+ mIsBulkOperation = true;
+ mDb.beginTransaction();
+ }
+
+ @Override
+ public void yieldBulkOperation() {
+ mDb.yieldIfContendedSafely();
+ }
+
+ @Override
+ public void finishBulkOperation() {
+ mDb.setTransactionSuccessful();
+ mDb.endTransaction();
+ mIsBulkOperation = false;
+ mVoicemailNotifier.sendNotification();
+ }
+
/**
* Returns the set of packages affected when a modify operation is run for the specified
* where clause. When called from an insert operation an empty set returned by this method
@@ -266,7 +318,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
Cursor cursor = mDb.query(mTableName, PROJECTION,
DbQueryUtils.concatenateClauses(NON_NULL_SOURCE_PACKAGE_SELECTION, whereClause),
whereArgs, null, null, null);
- while(cursor.moveToNext()) {
+ while (cursor.moveToNext()) {
modifiedPackages.add(cursor.getString(SOURCE_PACKAGE_COLUMN_INDEX));
}
MoreCloseables.closeQuietly(cursor);
@@ -281,7 +333,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
*/
private Set<String> getModifiedPackages(ContentValues values) {
Set<String> impactedPackages = new ArraySet<>();
- if(values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) {
+ if (values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) {
impactedPackages.add(values.getAsString(VoicemailContract.SOURCE_PACKAGE_FIELD));
}
return impactedPackages;
@@ -290,7 +342,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
/**
* @param packagesModified source packages that inserted the voicemail that is being modified
* @return {@code true} if the caller is modifying its own voicemail, or this is an internal
- * transaction, {@code false} otherwise.
+ * transaction, {@code false} otherwise.
*/
private boolean isSelfModifyingOrInternal(Set<String> packagesModified) {
final Collection<String> callingPackages = getCallingPackages();
@@ -301,59 +353,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
// but allows us to mock the results for testing.
return packagesModified.size() == 1 && (callingPackages.contains(
Iterables.getOnlyElement(packagesModified))
- || callingPackages.contains(mContext.getPackageName()));
- }
-
- private void notifyVoicemailChange(Uri notificationUri, Set<String> modifiedPackages,
- String... intentActions) {
- // Notify the observers.
- // Must be done only once, even if there are multiple broadcast intents.
- mContext.getContentResolver().notifyChange(notificationUri, null, true);
- Collection<String> callingPackages = getCallingPackages();
- // Now fire individual intents.
- for (String intentAction : intentActions) {
- // self_change extra should be included only for provider_changed events.
- boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
- for (ComponentName component :
- getBroadcastReceiverComponents(intentAction, notificationUri)) {
- // Ignore any package that is not affected by the change and don't have full access
- // either.
- if (!modifiedPackages.contains(component.getPackageName()) &&
- !mVoicemailPermissions.packageHasReadAccess(
- component.getPackageName())) {
- continue;
- }
-
- Intent intent = new Intent(intentAction, notificationUri);
- intent.setComponent(component);
- if (includeSelfChangeExtra && callingPackages != null) {
- intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
- callingPackages.contains(component.getPackageName()));
- }
- String permissionNeeded = modifiedPackages.contains(component.getPackageName()) ?
- ADD_VOICEMAIL : READ_VOICEMAIL;
- mContext.sendBroadcast(intent, permissionNeeded);
- Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s, perm:%s," +
- " self_change:%s", intent.getAction(), intent.getData(),
- component.getClassName(), permissionNeeded,
- intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
- intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
- null));
- }
- }
- }
-
- /** Determines the components that can possibly receive the specified intent. */
- private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
- Intent intent = new Intent(intentAction, uri);
- List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
- // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
- for (ResolveInfo resolveInfo :
- mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) {
- ActivityInfo activityInfo = resolveInfo.activityInfo;
- receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
- }
- return receiverComponents;
+ || callingPackages.contains(mContext.getPackageName()));
}
/**
@@ -393,4 +393,9 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
return CallLogProvider.getTimeForTestMillis();
}
+
+ @VisibleForTesting
+ static void setVoicemailNotifierForTest(VoicemailNotifier notifier) {
+ sVoicemailNotifierForTest = notifier;
+ }
}
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index 6c84e4b0..f3b6daf6 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -24,6 +24,7 @@ import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.provider.ContactsContract.Intents;
@@ -70,12 +71,13 @@ public class ProfileProvider extends AbstractContactsProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder, CancellationSignal cancellationSignal) {
- incrementStats(mQueryStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementQueryStats(callingUid);
try {
return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
cancellationSignal);
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index 14c78a77..e421654c 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -24,7 +24,6 @@ import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -35,6 +34,8 @@ import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.CappedStringBuilder;
+
import com.google.android.collect.Lists;
import com.google.common.annotations.VisibleForTesting;
@@ -51,6 +52,8 @@ public class SearchIndexManager {
private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+ 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;
@@ -72,10 +75,11 @@ public class SearchIndexManager {
public static final int SEPARATOR_SLASH = 2;
public static final int SEPARATOR_COMMA = 3;
- private StringBuilder mSbContent = new StringBuilder();
- private StringBuilder mSbName = new StringBuilder();
- private StringBuilder mSbTokens = new StringBuilder();
- private StringBuilder mSbElementContent = new StringBuilder();
+ private CappedStringBuilder mSbContent = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbName = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbTokens = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbElementContent = new CappedStringBuilder(
+ MAX_STRING_BUILDER_SIZE);
private ArraySet<String> mUniqueElements = new ArraySet<>();
private Cursor mCursor;
@@ -84,10 +88,10 @@ public class SearchIndexManager {
}
void reset() {
- mSbContent.setLength(0);
- mSbTokens.setLength(0);
- mSbName.setLength(0);
- mSbElementContent.setLength(0);
+ mSbContent.clear();
+ mSbTokens.clear();
+ mSbName.clear();
+ mSbElementContent.clear();
mUniqueElements.clear();
}
@@ -126,7 +130,7 @@ public class SearchIndexManager {
mSbContent.append(content);
mUniqueElements.add(content);
}
- mSbElementContent.setLength(0);
+ mSbElementContent.clear();
}
}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 160a1a99..1ced1be6 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -15,20 +15,22 @@
*/
package com.android.providers.contacts;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.provider.VoicemailContract.SOURCE_PACKAGE_FIELD;
import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.VoicemailContract;
@@ -49,7 +51,6 @@ import com.google.common.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.List;
-import java.util.Set;
/**
* An implementation of the Voicemail content provider. This class in the entry point for both
@@ -82,8 +83,15 @@ public class VoicemailContentProvider extends ContentProvider
}
Context context = context();
- // ADD_VOICEMAIL permission guards read and write. We do the same with app ops.
- // The permission name doesn't reflect its function but we cannot rename it.
+ // Read and write permission requires ADD_VOICEMAIL or carrier privileges. We can't declare
+ // any permission entries in the manifest because carrier-privileged apps without
+ // ADD_VOICEMAIL would be blocked by the platform without even reaching our custom
+ // enforce{Read,Write}PermissionInner functions. These overrides are what allow carrier-
+ // privileged apps to bypass these runtime-configured permissions.
+ // TODO(b/74245334): See if these can be removed since individual operations perform their
+ // own checks.
+ setReadPermission(android.Manifest.permission.ADD_VOICEMAIL);
+ setWritePermission(android.Manifest.permission.ADD_VOICEMAIL);
setAppOps(AppOpsManager.OP_ADD_VOICEMAIL, AppOpsManager.OP_ADD_VOICEMAIL);
mVoicemailPermissions = new VoicemailPermissions(context);
@@ -109,6 +117,27 @@ public class VoicemailContentProvider extends ContentProvider
return true;
}
+ @Override
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+ throws SecurityException {
+ // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
+ if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
+ return MODE_ALLOWED;
+ }
+ return super.enforceReadPermissionInner(uri, callingPkg, callerToken);
+ }
+
+
+ @Override
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+ throws SecurityException {
+ // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
+ if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
+ return MODE_ALLOWED;
+ }
+ return super.enforceWritePermissionInner(uri, callingPkg, callerToken);
+ }
+
@VisibleForTesting
void scheduleScanStalePackages() {
scheduleTask(BACKGROUND_TASK_SCAN_STALE_PACKAGES, null);
@@ -157,6 +186,12 @@ public class VoicemailContentProvider extends ContentProvider
}
@Override
+ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
+ UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
+ return getTableDelegate(uriData).bulkInsert(uriData, values);
+ }
+
+ @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (VERBOSE_LOGGING) {
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
index 09a8c1f0..67dda926 100644
--- a/src/com/android/providers/contacts/VoicemailContentTable.java
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -39,7 +39,6 @@ import com.android.providers.contacts.VoicemailContentProvider.UriData;
import com.android.providers.contacts.util.CloseUtils;
import com.google.common.collect.ImmutableSet;
-
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -52,16 +51,19 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
private static final String TAG = "VmContentProvider";
private final ProjectionMap mVoicemailProjectionMap;
- /** The private directory in which to store the data associated with the voicemail. */
+ /**
+ * The private directory in which to store the data associated with the voicemail.
+ */
private static final String DATA_DIRECTORY = "voicemail-data";
- private static final String[] FILENAME_ONLY_PROJECTION = new String[] { Voicemails._DATA };
+ private static final String[] FILENAME_ONLY_PROJECTION = new String[] {Voicemails._DATA};
- private static final ImmutableSet<String> ALLOWED_COLUMNS = new ImmutableSet.Builder<String>()
+ public static final ImmutableSet<String> ALLOWED_COLUMNS = new ImmutableSet.Builder<String>()
.add(Voicemails._ID)
.add(Voicemails.NUMBER)
.add(Voicemails.DATE)
.add(Voicemails.DURATION)
+ .add(Voicemails.NEW)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
.add(Voicemails.TRANSCRIPTION_STATE)
@@ -83,6 +85,8 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(OpenableColumns.SIZE)
.build();
+ private static final int BULK_INSERTS_PER_YIELD_POINT = 50;
+
private final String mTableName;
private final CallLogDatabaseHelper mDbHelper;
private final Context mContext;
@@ -101,6 +105,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(Voicemails.NUMBER)
.add(Voicemails.DATE)
.add(Voicemails.DURATION)
+ .add(Voicemails.NEW)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
.add(Voicemails.TRANSCRIPTION_STATE)
@@ -138,6 +143,30 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
@Override
public Uri insert(UriData uriData, ContentValues values) {
+ DatabaseModifier modifier = createDatabaseModifier(mDbHelper.getWritableDatabase());
+ Uri uri = insertRow(modifier, uriData, values);
+ return uri;
+ }
+
+ @Override
+ public int bulkInsert(UriData uriData, ContentValues[] values) {
+ DatabaseModifier modifier = createDatabaseModifier(mDbHelper.getWritableDatabase());
+ modifier.startBulkOperation();
+ int count = 0;
+ for (ContentValues value : values) {
+ Uri uri = insertRow(modifier, uriData, value);
+ if (uri != null) {
+ count++;
+ }
+ if((count % BULK_INSERTS_PER_YIELD_POINT) == 0){
+ modifier.yieldBulkOperation();
+ }
+ }
+ modifier.finishBulkOperation();
+ return count;
+ }
+
+ private Uri insertRow(DatabaseModifier modifier, UriData uriData, ContentValues values) {
checkForSupportedColumns(mVoicemailProjectionMap, values);
ContentValues copiedValues = new ContentValues(values);
checkInsertSupported(uriData);
@@ -160,7 +189,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
}
SQLiteDatabase db = mDbHelper.getWritableDatabase();
- long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues);
+ long rowId = modifier.insert(mTableName, null, copiedValues);
if (rowId > 0) {
Uri newUri = ContentUris.withAppendedId(uriData.getUri(), rowId);
// Populate the 'voicemail_uri' field to be used by the call_log provider.
@@ -228,7 +257,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
}
// Now delete the rows themselves.
- return getDatabaseModifier(db).delete(mTableName, combinedClause,
+ return createDatabaseModifier(db).delete(mTableName, combinedClause,
selectionArgs);
}
@@ -262,7 +291,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
// URI that include message Id. I think we do want to support bulk update.
String combinedClause = concatenateClauses(selection, uriData.getWhereClause(),
getCallTypeClause());
- return getDatabaseModifier(db).update(uriData.getUri(), mTableName, values, combinedClause,
+ return createDatabaseModifier(db).update(uriData.getUri(), mTableName, values, combinedClause,
selectionArgs);
}
@@ -298,7 +327,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
return getEqualityClause(Calls.TYPE, Calls.VOICEMAIL_TYPE);
}
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(mTableName, db, mContext);
}
diff --git a/src/com/android/providers/contacts/VoicemailNotifier.java b/src/com/android/providers/contacts/VoicemailNotifier.java
new file mode 100644
index 00000000..159cec73
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailNotifier.java
@@ -0,0 +1,128 @@
+package com.android.providers.contacts;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.provider.VoicemailContract;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Aggregates voicemail broadcasts from multiple operations in to a single one. The URIs will be
+ * {@link VoicemailContract.Voicemails#DIR_TYPE} instead of {@link
+ * VoicemailContract.Voicemails#ITEM_TYPE} if multiple URIs is notified.
+ */
+public class VoicemailNotifier {
+
+ private final String TAG = "VoicemailNotifier";
+
+ private final Context mContext;
+ private final Uri mBaseUri;
+
+ private final VoicemailPermissions mVoicemailPermissions;
+
+ private final Set<String> mIntentActions = new ArraySet<>();
+ private final Set<String> mModifiedPackages = new ArraySet<>();
+ private final Set<Uri> mUris = new ArraySet<>();
+
+ public VoicemailNotifier(Context context, Uri baseUri) {
+ mContext = context;
+ mBaseUri = baseUri;
+ mVoicemailPermissions = new VoicemailPermissions(mContext);
+ }
+
+ public void addIntentActions(String action) {
+ mIntentActions.add(action);
+ }
+
+ public void addModifiedPackages(Collection<String> packages) {
+ mModifiedPackages.addAll(packages);
+ }
+
+ public void addUri(Uri uri) {
+ mUris.add(uri);
+ }
+
+ public void sendNotification() {
+ Uri uri = mUris.size() == 1 ? mUris.iterator().next() : mBaseUri;
+ mContext.getContentResolver().notifyChange(uri, null, true);
+ Collection<String> callingPackages = getCallingPackages();
+ // Now fire individual intents.
+ for (String intentAction : mIntentActions) {
+ // self_change extra should be included only for provider_changed events.
+ boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
+ Log.i(TAG, "receivers for " + intentAction + " :" + getBroadcastReceiverComponents(
+ intentAction, uri));
+ for (ComponentName component :
+ getBroadcastReceiverComponents(intentAction, uri)) {
+ boolean hasFullReadAccess =
+ mVoicemailPermissions.packageHasReadAccess(component.getPackageName());
+ boolean hasOwnAccess =
+ mVoicemailPermissions.packageHasOwnVoicemailAccess(
+ component.getPackageName());
+ // If we don't have full access, ignore the broadcast if the package isn't affected
+ // by the change or doesn't have access to its own messages.
+ if (!hasFullReadAccess
+ && (!mModifiedPackages.contains(component.getPackageName())
+ || !hasOwnAccess)) {
+ continue;
+ }
+
+ Intent intent = new Intent(intentAction, uri);
+ intent.setComponent(component);
+ if (includeSelfChangeExtra && callingPackages != null) {
+ intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
+ callingPackages.contains(component.getPackageName()));
+ }
+ mContext.sendBroadcast(intent);
+ Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," +
+ " self_change:%s", intent.getAction(), intent.getData(),
+ component.getClassName(),
+ intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
+ intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
+ null));
+ }
+ }
+ mIntentActions.clear();
+ mModifiedPackages.clear();
+ mUris.clear();
+ }
+
+ /**
+ * Returns the package names of the calling process. If the calling process has more than
+ * one packages, this returns them all
+ */
+ private Collection<String> getCallingPackages() {
+ int caller = Binder.getCallingUid();
+ if (caller == 0) {
+ return null;
+ }
+ return Lists.newArrayList(mContext.getPackageManager().getPackagesForUid(caller));
+ }
+
+ /**
+ * Determines the components that can possibly receive the specified intent.
+ */
+ private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
+ Intent intent = new Intent(intentAction, uri);
+ List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
+ // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
+ for (ResolveInfo resolveInfo :
+ mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
+ }
+ return receiverComponents;
+ }
+}
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
index 50f2447a..ed3815dc 100644
--- a/src/com/android/providers/contacts/VoicemailPermissions.java
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -16,10 +16,12 @@
package com.android.providers.contacts;
-import com.android.providers.contacts.util.ContactsPermissions;
-
import android.content.Context;
+import android.os.Binder;
import android.telecom.DefaultDialerManager;
+import android.telephony.TelephonyManager;
+
+import com.android.providers.contacts.util.ContactsPermissions;
/**
* Provides method related to check various voicemail permissions under the
@@ -35,7 +37,8 @@ public class VoicemailPermissions {
/** Determines if the calling process has access to its own voicemails. */
public boolean callerHasOwnVoicemailAccess() {
- return callerHasPermission(android.Manifest.permission.ADD_VOICEMAIL);
+ return callerHasPermission(android.Manifest.permission.ADD_VOICEMAIL)
+ || callerHasCarrierPrivileges();
}
/** Determine if the calling process has full read access to all voicemails. */
@@ -63,7 +66,7 @@ public class VoicemailPermissions {
public void checkCallerHasOwnVoicemailAccess() {
if (!callerHasOwnVoicemailAccess()) {
throw new SecurityException("The caller must have permission: " +
- android.Manifest.permission.ADD_VOICEMAIL);
+ android.Manifest.permission.ADD_VOICEMAIL + " or carrier privileges");
}
}
@@ -91,7 +94,8 @@ public class VoicemailPermissions {
/** Determines if the given package has access to its own voicemails. */
public boolean packageHasOwnVoicemailAccess(String packageName) {
return packageHasPermission(packageName,
- android.Manifest.permission.ADD_VOICEMAIL);
+ android.Manifest.permission.ADD_VOICEMAIL)
+ || packageHasCarrierPrivileges(packageName);
}
/** Determines if the given package has read access. */
@@ -113,4 +117,25 @@ public class VoicemailPermissions {
private boolean callerHasPermission(String permission) {
return ContactsPermissions.hasCallerOrSelfPermission(mContext, permission);
}
+
+ /** Determines if the calling process has carrier privileges. */
+ public boolean callerHasCarrierPrivileges() {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ String[] packages = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ for (String packageName : packages) {
+ if (tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Determines if the given package has carrier privileges. */
+ private boolean packageHasCarrierPrivileges(String packageName) {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return tm.getPackagesWithCarrierPrivileges().contains(packageName);
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java
index f3008c0e..5b03c8a6 100644
--- a/src/com/android/providers/contacts/VoicemailStatusTable.java
+++ b/src/com/android/providers/contacts/VoicemailStatusTable.java
@@ -32,8 +32,6 @@ import android.util.ArraySet;
import com.android.common.content.ProjectionMap;
import com.android.providers.contacts.VoicemailContentProvider.UriData;
-import java.util.Set;
-
/**
* Implementation of {@link VoicemailTable.Delegate} for the voicemail status table.
*
@@ -78,7 +76,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Try to update before insert.
String combinedClause = uriData.getWhereClause();
- int rowsChanged = getDatabaseModifier(db)
+ int rowsChanged = createDatabaseModifier(db)
.update(uriData.getUri(), mTableName, values, combinedClause, null);
if (rowsChanged != 0) {
final String[] selection = new String[] {Status._ID};
@@ -90,7 +88,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
}
ContentValues copiedValues = new ContentValues(values);
mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues);
- long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues);
+ long rowId = createDatabaseModifier(db).insert(mTableName, null, copiedValues);
if (rowId > 0) {
return ContentUris.withAppendedId(uriData.getUri(), rowId);
} else {
@@ -100,11 +98,23 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
}
@Override
+ public int bulkInsert(UriData uriData, ContentValues[] values) {
+ int count = 0;
+ for (ContentValues value : values) {
+ Uri uri = insert(uriData, value);
+ if (uri != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
public int delete(UriData uriData, String selection, String[] selectionArgs) {
synchronized (DATABASE_LOCK) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
String combinedClause = concatenateClauses(selection, uriData.getWhereClause());
- return getDatabaseModifier(db).delete(mTableName, combinedClause,
+ return createDatabaseModifier(db).delete(mTableName, combinedClause,
selectionArgs);
}
}
@@ -135,7 +145,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
synchronized (DATABASE_LOCK) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
String combinedClause = concatenateClauses(selection, uriData.getWhereClause());
- return getDatabaseModifier(db)
+ return createDatabaseModifier(db)
.update(uriData.getUri(), mTableName, values, combinedClause, selectionArgs);
}
}
@@ -154,7 +164,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
throw new UnsupportedOperationException("File operation is not supported for status table");
}
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(mTableName, db, mContext);
}
diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java
index fcb653ce..f71b50de 100644
--- a/src/com/android/providers/contacts/VoicemailTable.java
+++ b/src/com/android/providers/contacts/VoicemailTable.java
@@ -46,6 +46,8 @@ public interface VoicemailTable {
public ParcelFileDescriptor openFile(UriData uriData, String mode)
throws FileNotFoundException;
public ArraySet<String> getSourcePackages();
+
+ int bulkInsert(UriData uriData, ContentValues[] values);
}
/**
diff --git a/src/com/android/providers/contacts/util/CappedStringBuilder.java b/src/com/android/providers/contacts/util/CappedStringBuilder.java
new file mode 100644
index 00000000..74b70cf5
--- /dev/null
+++ b/src/com/android/providers/contacts/util/CappedStringBuilder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 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.util.Log;
+
+import com.android.providers.contacts.AbstractContactsProvider;
+
+public class CappedStringBuilder {
+ private final int mCapSize;
+ private boolean mOver;
+ private final StringBuilder mStringBuilder = new StringBuilder();
+
+ public CappedStringBuilder(int capSize) {
+ mCapSize = capSize;
+ }
+
+ public void clear() {
+ mOver = false;
+ mStringBuilder.setLength(0);
+ }
+
+ public int length() {
+ return mStringBuilder.length();
+ }
+
+ @Override
+ public String toString() {
+ return mStringBuilder.toString();
+ }
+
+ public CappedStringBuilder append(char ch) {
+ if (canAppend(mStringBuilder.length() + 1)) {
+ mStringBuilder.append(ch);
+ }
+ return this;
+ }
+
+ public CappedStringBuilder append(String s) {
+ if (canAppend(mStringBuilder.length() + s.length())) {
+ mStringBuilder.append(s);
+ }
+ return this;
+ }
+
+ private boolean canAppend(int length) {
+ if (mOver || length > mCapSize) {
+ if (!mOver && AbstractContactsProvider.VERBOSE_LOGGING) {
+ Log.w(AbstractContactsProvider.TAG, "String too long! new length=" + length);
+ }
+ mOver = true;
+ return false;
+ }
+ return true;
+ }
+}