Merge "Initial implementation of ContentCaptureService.setContentCaptureWhitelist()"
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index ae70775..b60fbc5 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -34,6 +34,7 @@
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.view.contentcapture.ContentCaptureContext;
@@ -171,16 +172,11 @@
     @Deprecated
     public final void setContentCaptureWhitelist(@Nullable List<String> packages,
             @Nullable List<ComponentName> activities) {
-        final IContentCaptureServiceCallback callback = mCallback;
-        if (callback == null) {
-            Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
-            return;
-        }
-        try {
-            callback.setContentCaptureWhitelist(packages, activities);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
+        setContentCaptureWhitelist(toSet(packages), toSet(activities));
+    }
+
+    private <T> ArraySet<T> toSet(@Nullable List<T> set) {
+        return set == null ? null : new ArraySet<T>(set);
     }
 
     /**
@@ -197,7 +193,17 @@
      */
     public final void setContentCaptureWhitelist(@Nullable Set<String> packages,
             @Nullable Set<ComponentName> activities) {
-        setContentCaptureWhitelist(toList(packages), toList(activities));
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
+            return;
+        }
+
+        try {
+            callback.setContentCaptureWhitelist(toList(packages), toList(activities));
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     private <T> ArrayList<T> toList(@Nullable Set<T> set) {
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index ec3b44a..1e051a4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -120,6 +120,13 @@
      */
     public static final int STATE_INTERNAL_ERROR = 0x100;
 
+    /**
+     * Session is disabled because service didn't whitelist package.
+     *
+     * @hide
+     */
+    public static final int STATE_PACKAGE_NOT_WHITELISTED = 0x200;
+
     private static final int INITIAL_CHILDREN_CAPACITY = 5;
 
     /** @hide */
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 7150264..360f064 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -21,6 +21,7 @@
 import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
 import static android.view.contentcapture.ContentCaptureSession.STATE_INTERNAL_ERROR;
 import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;
+import static android.view.contentcapture.ContentCaptureSession.STATE_PACKAGE_NOT_WHITELISTED;
 
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
@@ -47,7 +48,7 @@
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
-import android.util.Log;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.contentcapture.UserDataRemovalRequest;
 
@@ -87,6 +88,12 @@
     private final ContentCaptureServiceRemoteCallback mRemoteServiceCallback =
             new ContentCaptureServiceRemoteCallback();
 
+    /**
+     * List of packages that are whitelisted to be content captured.
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<String> mWhitelistedPackages = new ArraySet<>();
+
     // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
 
     ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
@@ -185,6 +192,7 @@
         final int taskId = activityPresentationInfo.taskId;
         final int displayId = activityPresentationInfo.displayId;
         final ComponentName componentName = activityPresentationInfo.componentName;
+        final boolean whitelisted = isWhitelistedLocked(componentName);
         final ComponentName serviceComponentName = getServiceComponentName();
         final boolean enabled = isEnabledLocked();
         if (mMaster.mRequestsHistory != null) {
@@ -193,7 +201,8 @@
                     + " a=" + ComponentName.flattenToShortString(componentName)
                     + " t=" + taskId + " d=" + displayId
                     + " s=" + ComponentName.flattenToShortString(serviceComponentName)
-                    + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)");
+                    + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)")
+                    + " w=" + whitelisted;
             mMaster.mRequestsHistory.log(historyItem);
         }
 
@@ -214,6 +223,16 @@
             return;
         }
 
+        if (!whitelisted) {
+            if (mMaster.debug) {
+                Slog.d(TAG, "startSession(" + componentName + "): not whitelisted");
+            }
+            // TODO(b/122595322): need to return STATE_ACTIVITY_NOT_WHITELISTED as well
+            setClientState(clientReceiver, STATE_DISABLED | STATE_PACKAGE_NOT_WHITELISTED,
+                    /* binder= */ null);
+            return;
+        }
+
         final ContentCaptureServerSession existingSession = mSessions.get(sessionId);
         if (existingSession != null) {
             Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
@@ -247,6 +266,26 @@
         newSession.notifySessionStartedLocked(clientReceiver);
     }
 
+    @GuardedBy("mLock")
+    private boolean isWhitelistedLocked(@NonNull ComponentName componentName) {
+        // TODO(b/122595322): need to check whitelisted activities as well.
+        final String packageName = componentName.getPackageName();
+        return mWhitelistedPackages.contains(packageName);
+    }
+
+    private void whitelistPackages(@NonNull List<String> packages) {
+        // TODO(b/122595322): add CTS test for when it's null
+        synchronized (mLock) {
+            if (packages == null) {
+                if (mMaster.verbose) Slog.v(TAG, "clearing all whitelisted packages");
+                mWhitelistedPackages.clear();
+            } else {
+                if (mMaster.verbose) Slog.v(TAG, "whitelisting packages: " + packages);
+                mWhitelistedPackages.addAll(packages);
+            }
+        }
+    }
+
     // TODO(b/119613670): log metrics
     @GuardedBy("mLock")
     public void finishSessionLocked(@NonNull String sessionId) {
@@ -378,15 +417,23 @@
             mRemoteService.dump(prefix2, pw);
         }
 
+        final int whitelistSize = mWhitelistedPackages.size();
+        pw.print(prefix); pw.print("Whitelisted packages: "); pw.println(whitelistSize);
+        for (int i = 0; i < whitelistSize; i++) {
+            final String whitelistedPkg = mWhitelistedPackages.valueAt(i);
+            pw.print(prefix2); pw.print(i + 1); pw.print(": "); pw.println(whitelistedPkg);
+        }
+
         if (mSessions.isEmpty()) {
             pw.print(prefix); pw.println("no sessions");
         } else {
-            final int size = mSessions.size();
-            pw.print(prefix); pw.print("number sessions: "); pw.println(size);
-            for (int i = 0; i < size; i++) {
-                pw.print(prefix); pw.print("session@"); pw.println(i);
+            final int sessionsSize = mSessions.size();
+            pw.print(prefix); pw.print("number sessions: "); pw.println(sessionsSize);
+            for (int i = 0; i < sessionsSize; i++) {
+                pw.print(prefix); pw.print("#"); pw.println(i);
                 final ContentCaptureServerSession session = mSessions.valueAt(i);
                 session.dumpLocked(prefix2, pw);
+                pw.println();
             }
         }
     }
@@ -412,10 +459,12 @@
         public void setContentCaptureWhitelist(List<String> packages,
                 List<ComponentName> activities) {
             if (mMaster.verbose) {
-                Log.v(TAG, "setContentCaptureWhitelist(packages=" + packages + ", activities="
+                Slog.v(TAG, "setContentCaptureWhitelist(packages=" + packages + ", activities="
                         + activities + ")");
             }
-            // TODO(b/122595322): implement
+            whitelistPackages(packages);
+
+            // TODO(b/122595322): whitelist activities as well
             // TODO(b/119613670): log metrics
         }
     }