Client access stats for calllog

This prints # of requests made by clients, per calling UID, with total
time spent for their requests.

Sample output:

PROVIDER ContentProviderRecord{a16090d u0 com.android.providers.contacts/.CallLogProvider} pid=2417
    Client:
        Process uptime: 4 minutes

        Client activities:
          UID        Query  Insert Update Delete   Batch Insert Update Delete          Sec
          1000           1       4      0      0       0      0      0      0        0.161
          10022          1       0      0      0       0      0      0      0        0.068
          10062        336       2      0      0       0      0      0      0        5.361

Change-Id: I4b6f7e58dd8dc971408220edaae11ce6f24c43f5
Fix: 76037330
Bug: 76037330
Test: build & boot, dumpsys activity provider com.android.providers.contacts/.CallLogProvider
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 76e207a..f71a750 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -22,10 +22,13 @@
 
 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.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 @@
     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 @@
         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 @@
 
     @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());
@@ -390,8 +488,8 @@
         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) +
@@ -427,8 +525,7 @@
                 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) +
@@ -753,4 +850,9 @@
     public void shutdown() {
         mTaskScheduler.shutdownForTest();
     }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mStats.dump(writer, "  ");
+    }
 }