Fixed ContentCapture and AugmentedAutofill methods that should not hold the main lock...

...otherwise they could ANR the ActivityManagerService and crash the system

Test: manual verification
Test: atest AugmentedLoginActivityTest
Test: atest CtsContentCaptureServiceTestCases # sanity check (minus usual flakiness)

Fixes: 129410913
Bug: 126266412

Change-Id: I4b0b0c389dd2c34928c6fffeec2854518a5e7da1
diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java
new file mode 100644
index 0000000..dfa59b7
--- /dev/null
+++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 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.internal.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Helper class used to manage a {@link WhitelistHelper} per user instance when the main service
+ * cannot hold a lock when external entities (typically {@code ActivityManagerService}) needs to
+ * get whitelist info.
+ *
+ * <p>This class is thread safe.
+ */
+public class GlobalWhitelistState {
+
+    // Uses full-name to avoid collision with service-provided mLock
+    protected final Object mGlobalWhitelistStateLock = new Object();
+
+    @Nullable
+    @GuardedBy("mGlobalWhitelistStateLock")
+    protected SparseArray<WhitelistHelper> mWhitelisterHelpers;
+
+    /**
+     * Sets the whitelist for the given user.
+     */
+    public void setWhitelist(@UserIdInt int userId, @Nullable List<String> packageNames,
+            @Nullable List<ComponentName> components) {
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) {
+                mWhitelisterHelpers = new SparseArray<>(1);
+            }
+            WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+            if (helper == null) {
+                helper = new WhitelistHelper();
+                mWhitelisterHelpers.put(userId, helper);
+            }
+            helper.setWhitelist(packageNames, components);
+        }
+    }
+
+    /**
+     * Checks if the given package is whitelisted for the given user.
+     */
+    public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) return false;
+            final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+            return helper == null ? false : helper.isWhitelisted(packageName);
+        }
+    }
+
+    /**
+     * Checks if the given component is whitelisted for the given user.
+     */
+    public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) return false;
+            final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+            return helper == null ? false : helper.isWhitelisted(componentName);
+        }
+    }
+
+    /**
+     * Gets the whitelisted components for the given package and user.
+     */
+    public ArraySet<ComponentName> getWhitelistedComponents(@UserIdInt int userId,
+            @NonNull String packageName) {
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) return null;
+            final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+            return helper == null ? null : helper.getWhitelistedComponents(packageName);
+        }
+    }
+
+    /**
+     * Resets the whitelist for the given user.
+     */
+    public void resetWhitelist(@NonNull int userId) {
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) return;
+            mWhitelisterHelpers.remove(userId);
+            if (mWhitelisterHelpers.size() == 0) {
+                mWhitelisterHelpers = null;
+            }
+        }
+    }
+
+    /**
+     * Dumps it!
+     */
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        pw.print(prefix); pw.print("State: ");
+        synchronized (mGlobalWhitelistStateLock) {
+            if (mWhitelisterHelpers == null) {
+                pw.println("empty");
+                return;
+            }
+            pw.print(mWhitelisterHelpers.size()); pw.println(" services");
+            final String prefix2 = prefix + "  ";
+            for (int i = 0; i < mWhitelisterHelpers.size(); i++) {
+                final int userId  = mWhitelisterHelpers.keyAt(i);
+                final WhitelistHelper helper = mWhitelisterHelpers.valueAt(i);
+                helper.dump(prefix2, "Whitelist for userId " + userId, pw);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java
index 183b465..d7753db 100644
--- a/core/java/com/android/internal/infra/WhitelistHelper.java
+++ b/core/java/com/android/internal/infra/WhitelistHelper.java
@@ -31,6 +31,7 @@
 /**
  * Helper class for keeping track of whitelisted packages/activities.
  *
+ * <p><b>NOTE: </b>this class is not thread safe.
  * @hide
  */
 public final class WhitelistHelper {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 7020e7e..fdc3567 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -42,6 +42,7 @@
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
@@ -61,6 +62,7 @@
 import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -72,6 +74,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.GlobalWhitelistState;
+import com.android.internal.infra.WhitelistHelper;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -146,6 +150,7 @@
     private final LocalLog mWtfHistory = new LocalLog(50);
 
     private final AutofillCompatState mAutofillCompatState = new AutofillCompatState();
+
     private final LocalService mLocalService = new LocalService();
     private final ActivityManagerInternal mAm;
 
@@ -178,6 +183,8 @@
     @GuardedBy("mLock")
     int mAugmentedServiceRequestTimeoutMs;
 
+    final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState();
+
     public AutofillManagerService(Context context) {
         super(context,
                 new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
@@ -187,7 +194,7 @@
 
         DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_AUTOFILL,
                 ActivityThread.currentApplication().getMainExecutor(),
-                (namespace, key, value) -> onDeviceConfigChange(key, value));
+                (namespace, key, value) -> onDeviceConfigChange(key));
 
         setLogLevelFromSettings();
         setMaxPartitionsFromSettings();
@@ -201,15 +208,20 @@
         mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
                 com.android.internal.R.string.config_defaultAugmentedAutofillService);
         mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback(
-                (u, s) -> getServiceForUserLocked(u).updateRemoteAugmentedAutofillService());
+                (u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
 
         if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
-            // Must eager load the services so they bind to the augmented autofill service
             final UserManager um = getContext().getSystemService(UserManager.class);
             final List<UserInfo> users = um.getUsers();
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
+                // Must eager load the services so they bind to the augmented autofill service
                 getServiceForUserLocked(userId);
+
+                // And also set the global state
+                mAugmentedAutofillState.setServiceInfo(userId,
+                        mAugmentedAutofillResolver.getServiceName(userId),
+                        mAugmentedAutofillResolver.isTemporary(userId));
             }
         }
     }
@@ -258,7 +270,7 @@
         }
     }
 
-    private void onDeviceConfigChange(@NonNull String key, @Nullable String value) {
+    private void onDeviceConfigChange(@NonNull String key) {
         switch (key) {
             case AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES:
             case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT:
@@ -270,6 +282,14 @@
         }
     }
 
+    private void onAugmentedServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
+            boolean isTemporary) {
+        mAugmentedAutofillState.setServiceInfo(userId, serviceName, isTemporary);
+        synchronized (mLock) {
+            getServiceForUserLocked(userId).updateRemoteAugmentedAutofillService();
+        }
+    }
+
     @Override // from AbstractMasterSystemService
     protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
             boolean disabled) {
@@ -783,15 +803,7 @@
             final boolean compatModeEnabled = mAutofillCompatState.isCompatibilityModeRequested(
                     packageName, versionCode, userId);
             final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled);
-
-            synchronized (mLock) {
-                final AutofillManagerServiceImpl service =
-                        getServiceForUserLocked(UserHandle.getCallingUserId());
-                if (service != null) {
-                    service.setAugmentedAutofillWhitelistLocked(options, packageName);
-                }
-            }
-
+            mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName);
             return options;
         }
     }
@@ -934,6 +946,89 @@
         }
     }
 
+    /**
+     * Augmented autofill metadata associated with all services.
+     *
+     * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because
+     * it cannot hold a lock on the main lock when
+     * {@link AugmentedAutofillState#injectAugmentedAutofillInfo(AutofillOptions, int, String)}
+     * is called by external services.
+     */
+    static final class AugmentedAutofillState extends GlobalWhitelistState {
+
+        @GuardedBy("mGlobalWhitelistStateLock")
+        private final SparseArray<String> mServicePackages = new SparseArray<>();
+        @GuardedBy("mGlobalWhitelistStateLock")
+        private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray();
+
+        private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName,
+                boolean isTemporary) {
+            synchronized (mGlobalWhitelistStateLock) {
+                if (isTemporary) {
+                    mTemporaryServices.put(userId, true);
+                } else {
+                    mTemporaryServices.delete(userId);
+                }
+                if (serviceName != null) {
+                    final ComponentName componentName =
+                            ComponentName.unflattenFromString(serviceName);
+                    if (componentName == null) {
+                        Slog.w(TAG, "setServiceInfo(): invalid name: " + serviceName);
+                        mServicePackages.remove(userId);
+                    } else {
+                        mServicePackages.put(userId, componentName.getPackageName());
+                    }
+                } else {
+                    mServicePackages.remove(userId);
+                }
+            }
+        }
+
+        public void injectAugmentedAutofillInfo(@NonNull AutofillOptions options,
+                @UserIdInt int userId, @NonNull String packageName) {
+            synchronized (mGlobalWhitelistStateLock) {
+                if (mWhitelisterHelpers == null) return;
+                final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+                if (helper != null) {
+                    options.augmentedAutofillEnabled = helper.isWhitelisted(packageName);
+                    options.whitelistedActivitiesForAugmentedAutofill = helper
+                            .getWhitelistedComponents(packageName);
+                }
+            }
+        }
+
+        @Override
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+            synchronized (mGlobalWhitelistStateLock) {
+                if (!super.isWhitelisted(userId, componentName)) return false;
+
+                if (Build.IS_USER && mTemporaryServices.get(userId)) {
+                    final String packageName = componentName.getPackageName();
+                    if (!packageName.equals(mServicePackages.get(userId))) {
+                        Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill "
+                                + "while using temporary service " + mServicePackages.get(userId));
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+            super.dump(prefix, pw);
+
+            synchronized (mGlobalWhitelistStateLock) {
+                if (mServicePackages.size() > 0) {
+                    pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages);
+                }
+                if (mTemporaryServices.size() > 0) {
+                    pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices);
+                }
+            }
+        }
+    }
+
     final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
         @Override
         public void addClient(IAutoFillManagerClient client, ComponentName componentName,
@@ -1370,6 +1465,8 @@
                         pw.println(); pw.println("WTF history:"); pw.println();
                         mWtfHistory.reverseDump(fd, pw, args);
                     }
+                    pw.println("Augmented Autofill State: ");
+                    mAugmentedAutofillState.dump(prefix, pw);
                 }
             } finally {
                 sDebug = realDebug;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fe540bf..4bd6fbd 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -31,7 +31,6 @@
 import android.app.ActivityManagerInternal;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
-import android.content.AutofillOptions;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -40,7 +39,6 @@
 import android.metrics.LogMaker;
 import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -76,7 +74,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.WhitelistHelper;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
@@ -170,12 +167,6 @@
     @Nullable
     private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
 
-    /**
-     * List of packages/activities that are whitelisted to be trigger augmented autofill.
-     */
-    @GuardedBy("mLock")
-    private final WhitelistHelper mAugmentedWhitelistHelper = new WhitelistHelper();
-
     AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
             LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
             AutofillCompatState autofillCompatState,
@@ -951,8 +942,6 @@
             pw.println(mRemoteAugmentedAutofillServiceInfo);
         }
 
-        mAugmentedWhitelistHelper.dump(prefix, "Augmented autofill whitelist", pw);
-
         pw.print(prefix); pw.print("Field classification enabled: ");
             pw.println(isFieldClassificationEnabledLocked());
         pw.print(prefix); pw.print("Compat pkgs: ");
@@ -1234,27 +1223,7 @@
 
     @GuardedBy("mLock")
     boolean isWhitelistedForAugmentedAutofillLocked(@NonNull ComponentName componentName) {
-        if (Build.IS_USER && mMaster.mAugmentedAutofillResolver.isTemporary(mUserId)) {
-            final String serviceName = mMaster.mAugmentedAutofillResolver.getServiceName(mUserId);
-            final ComponentName component = ComponentName.unflattenFromString(serviceName);
-            final String servicePackage = component == null ? null : component.getPackageName();
-            final String packageName = componentName.getPackageName();
-            if (!packageName.equals(servicePackage)) {
-                Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill while "
-                        + "using temporary service " + servicePackage);
-                return false;
-            }
-        }
-
-        return mAugmentedWhitelistHelper.isWhitelisted(componentName);
-    }
-
-    @GuardedBy("mLock")
-    void setAugmentedAutofillWhitelistLocked(@NonNull AutofillOptions options,
-            @NonNull String packageName) {
-        options.augmentedAutofillEnabled = mAugmentedWhitelistHelper.isWhitelisted(packageName);
-        options.whitelistedActivitiesForAugmentedAutofill = mAugmentedWhitelistHelper
-                .getWhitelistedComponents(packageName);
+        return mMaster.mAugmentedAutofillState.isWhitelisted(mUserId, componentName);
     }
 
     /**
@@ -1268,7 +1237,7 @@
             if (mMaster.verbose) {
                 Slog.v(TAG, "whitelisting packages: " + packages + "and activities: " + components);
             }
-            mAugmentedWhitelistHelper.setWhitelist(packages, components);
+            mMaster.mAugmentedAutofillState.setWhitelist(mUserId, packages, components);
         }
     }
 
@@ -1280,7 +1249,7 @@
         if (mMaster.verbose) {
             Slog.v(TAG, "resetting augmented autofill whitelist");
         }
-        whitelistForAugmentedAutofillPackages(null, null);
+        mMaster.mAugmentedAutofillState.resetWhitelist(mUserId);
     }
 
     private void sendStateToClients(boolean resetClient) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index b2760e0..4a230e7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -40,6 +40,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -50,8 +51,10 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.contentcapture.ContentCaptureHelper;
 import android.view.contentcapture.ContentCaptureManager;
@@ -60,6 +63,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.GlobalWhitelistState;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -117,6 +121,9 @@
     @GuardedBy("mLock") int mDevCfgLogHistorySize;
     @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
 
+    final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
+            new GlobalContentCaptureOptions();
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -136,12 +143,12 @@
             mRequestsHistory = null;
         }
 
-        // Sets which services are disabled by settings
         final UserManager um = getContext().getSystemService(UserManager.class);
         final List<UserInfo> users = um.getUsers();
         for (int i = 0; i < users.size(); i++) {
             final int userId = users.get(i).id;
             final boolean disabled = !isEnabledBySettings(userId);
+            // Sets which services are disabled by settings
             if (disabled) {
                 Slog.i(mTag, "user " + userId + " disabled by settings");
                 if (mDisabledBySettings == null) {
@@ -149,6 +156,10 @@
                 }
                 mDisabledBySettings.put(userId, true);
             }
+            // Sets the global options for the service.
+            mGlobalContentCaptureOptions.setServiceInfo(userId,
+                    mServiceNameResolver.getServiceName(userId),
+                    mServiceNameResolver.isTemporary(userId));
         }
     }
 
@@ -188,6 +199,14 @@
     }
 
     @Override // from AbstractMasterSystemService
+    protected void onServiceNameChanged(@UserIdInt int userId, @NonNull String serviceName,
+            boolean isTemporary) {
+        mGlobalContentCaptureOptions.setServiceInfo(userId, serviceName, isTemporary);
+
+        super.onServiceNameChanged(userId, serviceName, isTemporary);
+    }
+
+    @Override // from AbstractMasterSystemService
     protected void enforceCallingPermissionForManagement() {
         getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, mTag);
     }
@@ -496,6 +515,8 @@
         pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
         pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
         pw.println(mDevCfgIdleUnbindTimeoutMs);
+        pw.print(prefix); pw.println("Global Options:");
+        mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@@ -670,13 +691,7 @@
 
         @Override
         public ContentCaptureOptions getOptionsForPackage(int userId, @NonNull String packageName) {
-            synchronized (mLock) {
-                final ContentCapturePerUserService service = peekServiceForUserLocked(userId);
-                if (service != null) {
-                    return service.getOptionsForPackageLocked(packageName);
-                }
-            }
-            return null;
+            return mGlobalContentCaptureOptions.getOptions(userId, packageName);
         }
 
         @Override
@@ -690,4 +705,92 @@
             }
         }
     }
+
+    /**
+     * Content capture options associated with all services.
+     *
+     * <p>This object is defined here instead of on each {@link ContentCapturePerUserService}
+     * because it cannot hold a lock on the main lock when
+     * {@link GlobalContentCaptureOptions#getOptions(int, String)} is called by external services.
+     */
+    final class GlobalContentCaptureOptions extends GlobalWhitelistState {
+
+        @GuardedBy("mGlobalWhitelistStateLock")
+        private final SparseArray<String> mServicePackages = new SparseArray<>();
+        @GuardedBy("mGlobalWhitelistStateLock")
+        private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray();
+
+        private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName,
+                boolean isTemporary) {
+            synchronized (mGlobalWhitelistStateLock) {
+                if (isTemporary) {
+                    mTemporaryServices.put(userId, true);
+                } else {
+                    mTemporaryServices.delete(userId);
+                }
+                if (serviceName != null) {
+                    final ComponentName componentName =
+                            ComponentName.unflattenFromString(serviceName);
+                    if (componentName == null) {
+                        Slog.w(mTag, "setServiceInfo(): invalid name: " + serviceName);
+                        mServicePackages.remove(userId);
+                    } else {
+                        mServicePackages.put(userId, componentName.getPackageName());
+                    }
+                } else {
+                    mServicePackages.remove(userId);
+                }
+            }
+        }
+
+        @Nullable
+        @GuardedBy("mGlobalWhitelistStateLock")
+        public ContentCaptureOptions getOptions(@UserIdInt int userId,
+                @NonNull String packageName) {
+            synchronized (mGlobalWhitelistStateLock) {
+                if (!isWhitelisted(userId, packageName)) {
+                    if (packageName.equals(mServicePackages.get(userId))) {
+                        if (verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName);
+                        return new ContentCaptureOptions(mDevCfgLoggingLevel);
+                    }
+                    if (verbose) {
+                        Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted");
+                    }
+                    return null;
+                }
+
+                final ArraySet<ComponentName> whitelistedComponents =
+                        getWhitelistedComponents(userId, packageName);
+                if (Build.IS_USER && mServiceNameResolver.isTemporary(userId)) {
+                    if (!packageName.equals(mServicePackages.get(userId))) {
+                        Slog.w(mTag, "Ignoring package " + packageName
+                                + " while using temporary service " + mServicePackages.get(userId));
+                        return null;
+                    }
+                }
+                final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
+                        mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
+                        mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
+                        whitelistedComponents);
+                if (verbose) {
+                    Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options);
+                }
+                return options;
+            }
+        }
+
+        @Override
+        public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+            super.dump(prefix, pw);
+
+            synchronized (mGlobalWhitelistStateLock) {
+                if (mServicePackages.size() > 0) {
+                    pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages);
+                }
+                if (mTemporaryServices.size() > 0) {
+                    pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices);
+                }
+            }
+        }
+    }
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index f0c6f7e..5934344 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -34,13 +34,11 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.UserHandle;
@@ -52,7 +50,6 @@
 import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Slog;
 import android.view.contentcapture.UserDataRemovalRequest;
 
@@ -238,7 +235,8 @@
         final int taskId = activityPresentationInfo.taskId;
         final int displayId = activityPresentationInfo.displayId;
         final ComponentName componentName = activityPresentationInfo.componentName;
-        final boolean whiteListed = isWhitelistedLocked(componentName);
+        final boolean whiteListed = mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId,
+                componentName);
         final ComponentName serviceComponentName = getServiceComponentName();
         final boolean enabled = isEnabledLocked();
         if (mMaster.mRequestsHistory != null) {
@@ -315,11 +313,6 @@
         newSession.notifySessionStartedLocked(clientReceiver);
     }
 
-    @GuardedBy("mLock")
-    private boolean isWhitelistedLocked(@NonNull ComponentName componentName) {
-        return mWhitelistHelper.isWhitelisted(componentName);
-    }
-
     // TODO(b/119613670): log metrics
     @GuardedBy("mLock")
     public void finishSessionLocked(@NonNull String sessionId) {
@@ -457,40 +450,6 @@
     }
 
     @GuardedBy("mLock")
-    @Nullable
-    ContentCaptureOptions getOptionsForPackageLocked(@NonNull String packageName) {
-        if (!mWhitelistHelper.isWhitelisted(packageName)) {
-            if (packageName.equals(getServicePackageName())) {
-                if (mMaster.verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName);
-                return new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel);
-            }
-            if (mMaster.verbose) {
-                Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted");
-            }
-            return null;
-        }
-
-        final ArraySet<ComponentName> whitelistedComponents = mWhitelistHelper
-                .getWhitelistedComponents(packageName);
-        if (Build.IS_USER && isTemporaryServiceSetLocked()) {
-            final String servicePackageName = getServicePackageName();
-            if (!packageName.equals(servicePackageName)) {
-                Slog.w(mTag, "Ignoring package " + packageName
-                        + " while using temporary service " + servicePackageName);
-                return null;
-            }
-        }
-        ContentCaptureOptions options = new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel,
-                mMaster.mDevCfgMaxBufferSize, mMaster.mDevCfgIdleFlushingFrequencyMs,
-                mMaster.mDevCfgTextChangeFlushingFrequencyMs, mMaster.mDevCfgLogHistorySize,
-                whitelistedComponents);
-        if (mMaster.verbose) {
-            Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options);
-        }
-        return options;
-    }
-
-    @GuardedBy("mLock")
     void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
         if (mRemoteService == null) {
             if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
@@ -522,8 +481,6 @@
             mRemoteService.dump(prefix2, pw);
         }
 
-        mWhitelistHelper.dump(prefix, "Whitelist", pw);
-
         if (mSessions.isEmpty()) {
             pw.print(prefix); pw.println("no sessions");
         } else {
@@ -560,7 +517,7 @@
         if (mMaster.verbose) {
             Slog.v(TAG, "resetting content capture whitelist");
         }
-        mWhitelistHelper.setWhitelist((List) null, null);
+        mMaster.mGlobalContentCaptureOptions.resetWhitelist(mUserId);
     }
 
     private final class ContentCaptureServiceRemoteCallback extends
@@ -576,9 +533,7 @@
                         + ", " + (activities == null
                         ? "null_activities" : activities.size() + " activities") + ")");
             }
-            synchronized (mLock) {
-                mWhitelistHelper.setWhitelist(packages, activities);
-            }
+            mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
index fa7d3fc..ad04b7d 100644
--- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
+++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
@@ -49,6 +49,9 @@
     /**
      * Gets the content capture options for the given user and package, or {@code null} if the
      * package is not whitelisted by the service.
+     *
+     * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot
+     * hold the main service lock.
      */
     @Nullable
     public abstract ContentCaptureOptions getOptionsForPackage(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index ed894ee..39e93f5 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -181,9 +181,8 @@
 
         mServiceNameResolver = serviceNameResolver;
         if (mServiceNameResolver != null) {
-            mServiceNameResolver
-                    .setOnTemporaryServiceNameChangedCallback(
-                            (u, s) -> updateCachedServiceLocked(u));
+            mServiceNameResolver.setOnTemporaryServiceNameChangedCallback(
+                    (u, s, t) -> onServiceNameChanged(u, s, t));
 
         }
         if (disallowProperty == null) {
@@ -582,6 +581,23 @@
     }
 
     /**
+     * Called when the service name changed (typically when using temporary services).
+     *
+     * <p>By default, it calls {@link #updateCachedServiceLocked(int)}; subclasses must either call
+     * that same method, or {@code super.onServiceNameChanged()}.
+     *
+     * @param userId user handle.
+     * @param serviceName the new service name.
+     * @param isTemporary whether the new service is temporary.
+     */
+    protected void onServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
+            boolean isTemporary) {
+        synchronized (mLock) {
+            updateCachedServiceLocked(userId);
+        }
+    }
+
+    /**
      * Visits all services in the cache.
      */
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index d204813..35d5956 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -155,7 +155,8 @@
             }
             mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
             mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
-            notifyTemporaryServiceNameChangedLocked(userId, componentName);
+            notifyTemporaryServiceNameChangedLocked(userId, componentName,
+                    /* isTemporary= */ true);
         }
     }
 
@@ -169,7 +170,8 @@
                 mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
                 mTemporaryHandler = null;
             }
-            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null);
+            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+                    /* isTemporary= */ false);
         }
     }
 
@@ -235,9 +237,9 @@
     }
 
     private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
-            @Nullable String newTemporaryName) {
+            @Nullable String newTemporaryName, boolean isTemporary) {
         if (mOnSetCallback != null) {
-            mOnSetCallback.onNameResolved(userId, newTemporaryName);
+            mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
         }
     }
 }
diff --git a/services/core/java/com/android/server/infra/ServiceNameResolver.java b/services/core/java/com/android/server/infra/ServiceNameResolver.java
index 8c348ebb..e20c459 100644
--- a/services/core/java/com/android/server/infra/ServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/ServiceNameResolver.java
@@ -39,7 +39,8 @@
         /**
          * The name change callback.
          */
-        void onNameResolved(@UserIdInt int userId, @Nullable String serviceName);
+        void onNameResolved(@UserIdInt int userId, @Nullable String serviceName,
+                boolean isTemporary);
     }
 
     /**