Merge "Skeleton for content protection allowlist manager" into main
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 57011e8..5f612d6b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -395,6 +395,22 @@
     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
             "content_protection_optional_groups_threshold";
 
+    /**
+     * Sets the initial delay for fetching content protection allowlist in milliseconds.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS =
+            "content_protection_allowlist_delay_ms";
+
+    /**
+     * Sets the timeout for fetching content protection allowlist in milliseconds.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS =
+            "content_protection_allowlist_timeout_ms";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -445,6 +461,10 @@
     public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
     /** @hide */
     public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
+    /** @hide */
+    public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000;
+    /** @hide */
+    public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250;
 
     private final Object mLock = new Object();
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 76ebdf4..9f4528b 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -20,6 +20,8 @@
 import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
 import static android.service.contentcapture.ContentCaptureService.setClientState;
 import static android.view.contentcapture.ContentCaptureHelper.toList;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS;
+import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS;
 import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG;
 import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD;
 import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG;
@@ -219,6 +221,12 @@
     @GuardedBy("mLock")
     int mDevCfgContentProtectionOptionalGroupsThreshold;
 
+    @GuardedBy("mLock")
+    long mDevCfgContentProtectionAllowlistDelayMs;
+
+    @GuardedBy("mLock")
+    long mDevCfgContentProtectionAllowlistTimeoutMs;
+
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -231,11 +239,17 @@
     final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
             new GlobalContentCaptureOptions();
 
-    @Nullable private final ComponentName mContentProtectionServiceComponentName;
+    @GuardedBy("mLock")
+    @Nullable
+    private ComponentName mContentProtectionServiceComponentName;
 
-    @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
+    @GuardedBy("mLock")
+    @Nullable
+    private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
 
-    @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
+    @GuardedBy("mLock")
+    @Nullable
+    private ContentProtectionConsentManager mContentProtectionConsentManager;
 
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
@@ -279,21 +293,6 @@
                     mServiceNameResolver.getServiceName(userId),
                     mServiceNameResolver.isTemporary(userId));
         }
-
-        if (getEnableContentProtectionReceiverLocked()) {
-            mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
-            if (mContentProtectionServiceComponentName != null) {
-                mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
-                mContentProtectionConsentManager = createContentProtectionConsentManager();
-            } else {
-                mContentProtectionAllowlistManager = null;
-                mContentProtectionConsentManager = null;
-            }
-        } else {
-            mContentProtectionServiceComponentName = null;
-            mContentProtectionAllowlistManager = null;
-            mContentProtectionConsentManager = null;
-        }
     }
 
     @Override // from AbstractMasterSystemService
@@ -442,6 +441,8 @@
                 case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
                 case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
                 case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS:
+                case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -453,8 +454,15 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setFineTuneParamsFromDeviceConfig() {
+        boolean enableContentProtectionReceiverOld;
+        boolean enableContentProtectionReceiverNew;
         String contentProtectionRequiredGroupsConfig;
         String contentProtectionOptionalGroupsConfig;
+        int contentProtectionOptionalGroupsThreshold;
+        long contentProtectionAllowlistDelayMs;
+        long contentProtectionAllowlistTimeoutMs;
+        ContentProtectionAllowlistManager contentProtectionAllowlistManagerOld;
+
         synchronized (mLock) {
             mDevCfgMaxBufferSize =
                     DeviceConfig.getInt(
@@ -488,12 +496,9 @@
                             ContentCaptureManager
                                     .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
                             false);
-            mDevCfgEnableContentProtectionReceiver =
-                    DeviceConfig.getBoolean(
-                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                            ContentCaptureManager
-                                    .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
-                            ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+
+            enableContentProtectionReceiverOld = mDevCfgEnableContentProtectionReceiver;
+            enableContentProtectionReceiverNew = getDeviceConfigEnableContentProtectionReceiver();
             mDevCfgContentProtectionBufferSize =
                     DeviceConfig.getInt(
                             DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -512,12 +517,25 @@
                             DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG,
                             ContentCaptureManager
                                     .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG);
-            mDevCfgContentProtectionOptionalGroupsThreshold =
+            contentProtectionOptionalGroupsThreshold =
                     DeviceConfig.getInt(
                             DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                             DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD,
                             ContentCaptureManager
                                     .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
+            contentProtectionAllowlistDelayMs =
+                    DeviceConfig.getLong(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS);
+            contentProtectionAllowlistTimeoutMs =
+                    DeviceConfig.getLong(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS);
+
+            contentProtectionAllowlistManagerOld = mContentProtectionAllowlistManager;
+
             if (verbose) {
                 Slog.v(
                         TAG,
@@ -535,7 +553,7 @@
                                 + ", disableFlushForViewTreeAppearing="
                                 + mDevCfgDisableFlushForViewTreeAppearing
                                 + ", enableContentProtectionReceiver="
-                                + mDevCfgEnableContentProtectionReceiver
+                                + enableContentProtectionReceiverNew
                                 + ", contentProtectionBufferSize="
                                 + mDevCfgContentProtectionBufferSize
                                 + ", contentProtectionRequiredGroupsConfig="
@@ -543,7 +561,11 @@
                                 + ", contentProtectionOptionalGroupsConfig="
                                 + contentProtectionOptionalGroupsConfig
                                 + ", contentProtectionOptionalGroupsThreshold="
-                                + mDevCfgContentProtectionOptionalGroupsThreshold);
+                                + contentProtectionOptionalGroupsThreshold
+                                + ", contentProtectionAllowlistDelayMs="
+                                + contentProtectionAllowlistDelayMs
+                                + ", contentProtectionAllowlistTimeoutMs="
+                                + contentProtectionAllowlistTimeoutMs);
             }
         }
 
@@ -551,9 +573,37 @@
                 parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig);
         List<List<String>> contentProtectionOptionalGroups =
                 parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig);
+        ComponentName contentProtectionServiceComponentNameNew = null;
+        ContentProtectionAllowlistManager contentProtectionAllowlistManagerNew = null;
+        ContentProtectionConsentManager contentProtectionConsentManagerNew = null;
+
+        if (contentProtectionAllowlistManagerOld != null && !enableContentProtectionReceiverNew) {
+            contentProtectionAllowlistManagerOld.stop();
+        }
+        if (!enableContentProtectionReceiverOld && enableContentProtectionReceiverNew) {
+            contentProtectionServiceComponentNameNew = getContentProtectionServiceComponentName();
+            if (contentProtectionServiceComponentNameNew != null) {
+                contentProtectionAllowlistManagerNew =
+                        createContentProtectionAllowlistManager(
+                                contentProtectionAllowlistTimeoutMs);
+                contentProtectionAllowlistManagerNew.start(contentProtectionAllowlistDelayMs);
+                contentProtectionConsentManagerNew = createContentProtectionConsentManager();
+            }
+        }
+
         synchronized (mLock) {
+            mDevCfgEnableContentProtectionReceiver = enableContentProtectionReceiverNew;
             mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups;
             mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups;
+            mDevCfgContentProtectionOptionalGroupsThreshold =
+                    contentProtectionOptionalGroupsThreshold;
+            mDevCfgContentProtectionAllowlistDelayMs = contentProtectionAllowlistDelayMs;
+
+            if (enableContentProtectionReceiverOld ^ enableContentProtectionReceiverNew) {
+                mContentProtectionServiceComponentName = contentProtectionServiceComponentNameNew;
+                mContentProtectionAllowlistManager = contentProtectionAllowlistManagerNew;
+                mContentProtectionConsentManager = contentProtectionConsentManagerNew;
+            }
         }
     }
 
@@ -837,27 +887,34 @@
         pw.print(prefix2);
         pw.print("contentProtectionOptionalGroupsThreshold: ");
         pw.println(mDevCfgContentProtectionOptionalGroupsThreshold);
+        pw.print(prefix2);
+        pw.print("contentProtectionAllowlistDelayMs: ");
+        pw.println(mDevCfgContentProtectionAllowlistDelayMs);
+        pw.print(prefix2);
+        pw.print("contentProtectionAllowlistTimeoutMs: ");
+        pw.println(mDevCfgContentProtectionAllowlistTimeoutMs);
         pw.print(prefix);
         pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
-    /**
-     * Used by the constructor in order to be able to override the value in the tests.
-     *
-     * @hide
-     */
+    /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @GuardedBy("mLock")
-    protected boolean getEnableContentProtectionReceiverLocked() {
-        return mDevCfgEnableContentProtectionReceiver;
+    protected boolean getDeviceConfigEnableContentProtectionReceiver() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
     }
 
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull
-    protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
-        return new ContentProtectionAllowlistManager();
+    protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager(
+            long timeoutMs) {
+        // Same handler as used by AbstractMasterSystemService
+        return new ContentProtectionAllowlistManager(
+                this, BackgroundThread.getHandler(), timeoutMs);
     }
 
     /** @hide */
@@ -874,6 +931,9 @@
     @Nullable
     private ComponentName getContentProtectionServiceComponentName() {
         String flatComponentName = getContentProtectionServiceFlatComponentName();
+        if (flatComponentName == null) {
+            return null;
+        }
         return ComponentName.unflattenFromString(flatComponentName);
     }
 
@@ -898,27 +958,27 @@
                 getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId());
     }
 
+    /** @hide */
     @Nullable
-    private RemoteContentProtectionService createRemoteContentProtectionService() {
-        if (mContentProtectionServiceComponentName == null) {
-            // This case should not be possible but make sure
-            return null;
-        }
+    public RemoteContentProtectionService createRemoteContentProtectionService() {
+        ComponentName componentName;
         synchronized (mLock) {
-            if (!mDevCfgEnableContentProtectionReceiver) {
+            if (!mDevCfgEnableContentProtectionReceiver
+                    || mContentProtectionServiceComponentName == null) {
                 return null;
             }
+            componentName = mContentProtectionServiceComponentName;
         }
 
         // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
         try {
-            createContentProtectionServiceInfo(mContentProtectionServiceComponentName);
+            createContentProtectionServiceInfo(componentName);
         } catch (Exception ex) {
             // Swallow, exception was already logged
             return null;
         }
 
-        return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
+        return createRemoteContentProtectionService(componentName);
     }
 
     /** @hide */
@@ -976,6 +1036,16 @@
                 .toList();
     }
 
+    @GuardedBy("mLock")
+    private boolean isContentProtectionEnabledLocked() {
+        return mDevCfgEnableContentProtectionReceiver
+                && mContentProtectionServiceComponentName != null
+                && mContentProtectionAllowlistManager != null
+                && mContentProtectionConsentManager != null
+                && !(mDevCfgContentProtectionRequiredGroups.isEmpty()
+                        && mDevCfgContentProtectionOptionalGroups.isEmpty());
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -1406,22 +1476,17 @@
 
         private boolean isContentProtectionReceiverEnabled(
                 @UserIdInt int userId, @NonNull String packageName) {
-            if (mContentProtectionServiceComponentName == null
-                    || mContentProtectionAllowlistManager == null
-                    || mContentProtectionConsentManager == null) {
-                return false;
-            }
+            ContentProtectionConsentManager consentManager;
+            ContentProtectionAllowlistManager allowlistManager;
             synchronized (mLock) {
-                if (!mDevCfgEnableContentProtectionReceiver) {
+                if (!isContentProtectionEnabledLocked()) {
                     return false;
                 }
-                if (mDevCfgContentProtectionRequiredGroups.isEmpty()
-                        && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
-                    return false;
-                }
+                consentManager = mContentProtectionConsentManager;
+                allowlistManager = mContentProtectionAllowlistManager;
             }
-            return mContentProtectionConsentManager.isConsentGranted(userId)
-                    && mContentProtectionAllowlistManager.isAllowed(packageName);
+            return consentManager.isConsentGranted(userId)
+                    && allowlistManager.isAllowed(packageName);
         }
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
index 59af526..f77430d 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
@@ -16,8 +16,22 @@
 
 package com.android.server.contentprotection;
 
+import static android.view.contentprotection.flags.Flags.blocklistUpdateEnabled;
+
 import android.annotation.NonNull;
-import android.util.Slog;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Manages whether the content protection is enabled for an app using a allowlist.
@@ -28,11 +42,124 @@
 
     private static final String TAG = "ContentProtectionAllowlistManager";
 
-    public ContentProtectionAllowlistManager() {}
+    @NonNull private final ContentCaptureManagerService mContentCaptureManagerService;
+
+    @NonNull private final Handler mHandler;
+
+    private final long mTimeoutMs;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    final PackageMonitor mPackageMonitor;
+
+    private final Object mHandlerToken = new Object();
+
+    private final Object mLock = new Object();
+
+    // Used outside of the handler
+    private boolean mStarted;
+
+    // Used inside the handler
+    @Nullable private Instant mUpdatePendingUntil;
+
+    @NonNull
+    @GuardedBy("mLock")
+    private Set<String> mAllowedPackages = Set.of();
+
+    public ContentProtectionAllowlistManager(
+            @NonNull ContentCaptureManagerService contentCaptureManagerService,
+            @NonNull Handler handler,
+            long timeoutMs) {
+        mContentCaptureManagerService = contentCaptureManagerService;
+        mHandler = handler;
+        mTimeoutMs = timeoutMs;
+        mPackageMonitor = createPackageMonitor();
+    }
+
+    /** Starts the manager. */
+    public void start(long delayMs) {
+        if (mStarted) {
+            return;
+        }
+        mStarted = true;
+        mHandler.postDelayed(this::handleInitialUpdate, mHandlerToken, delayMs);
+        // PackageMonitor will be registered inside handleInitialUpdate to respect the initial delay
+    }
+
+    /** Stops the manager. */
+    public void stop() {
+        try {
+            mPackageMonitor.unregister();
+        } catch (IllegalStateException ex) {
+            // Swallow, throws if not registered
+        }
+        mHandler.removeCallbacksAndMessages(mHandlerToken);
+        mUpdatePendingUntil = null;
+        mStarted = false;
+    }
 
     /** Returns true if the package is allowed. */
     public boolean isAllowed(@NonNull String packageName) {
-        Slog.v(TAG, packageName);
-        return false;
+        Set<String> allowedPackages;
+        synchronized (mLock) {
+            allowedPackages = mAllowedPackages;
+        }
+        return allowedPackages.contains(packageName);
+    }
+
+    private void setAllowlist(@NonNull List<String> packages) {
+        synchronized (mLock) {
+            mAllowedPackages = packages.stream().collect(Collectors.toUnmodifiableSet());
+        }
+        mUpdatePendingUntil = null;
+    }
+
+    private void handleInitialUpdate() {
+        handleUpdate();
+
+        // Initial update done, start listening to package updates now
+        mPackageMonitor.register(
+                mContentCaptureManagerService.getContext(), UserHandle.ALL, mHandler);
+    }
+
+    private void handleUpdate() {
+        if (!blocklistUpdateEnabled()) {
+            return;
+        }
+
+        /**
+         * PackageMonitor callback can be invoked more than once in a matter of milliseconds on the
+         * same monitor instance for the same package (eg: b/295969873). This check acts both as a
+         * simple generic rate limit and as a mitigation for this quirk.
+         */
+        if (mUpdatePendingUntil != null && Instant.now().isBefore(mUpdatePendingUntil)) {
+            return;
+        }
+
+        RemoteContentProtectionService remoteContentProtectionService =
+                mContentCaptureManagerService.createRemoteContentProtectionService();
+        if (remoteContentProtectionService == null) {
+            return;
+        }
+
+        // If there are any pending updates queued already, they can be removed immediately
+        mHandler.removeCallbacksAndMessages(mHandlerToken);
+        mUpdatePendingUntil = Instant.now().plusMillis(mTimeoutMs);
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected PackageMonitor createPackageMonitor() {
+        return new ContentProtectionPackageMonitor();
+    }
+
+    private final class ContentProtectionPackageMonitor extends PackageMonitor {
+
+        // This callback might be invoked multiple times, for more info refer to the comment above
+        @Override
+        public void onSomePackagesChanged() {
+            handleUpdate();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 78bf9b0..9a5241e 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -31,7 +32,6 @@
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -130,36 +130,10 @@
     }
 
     @Test
-    public void constructor_contentProtection_flagDisabled_noManagers() {
+    public void constructor_contentProtection_disabled_noManagers() {
         assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
-        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
-        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
-        verifyZeroInteractions(mMockContentProtectionConsentManager);
-    }
-
-    @Test
-    public void constructor_contentProtection_componentNameNull_noManagers() {
-        mConfigDefaultContentProtectionService = null;
-
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
-        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
-        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
-        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
-        verifyZeroInteractions(mMockContentProtectionConsentManager);
-    }
-
-    @Test
-    public void constructor_contentProtection_componentNameBlank_noManagers() {
-        mConfigDefaultContentProtectionService = "   ";
-
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
-        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
-        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
-        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
         verifyZeroInteractions(mMockContentProtectionAllowlistManager);
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
@@ -173,20 +147,137 @@
         assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
         assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager, never()).stop();
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_disabled_to_disabled() {
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_disabled_to_enabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager, never()).stop();
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_enabled_to_enabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager, never()).stop();
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_enabled_to_disabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+        mDevCfgEnableContentProtectionReceiver = false;
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager).stop();
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_enabled_componentNameNull() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mConfigDefaultContentProtectionService = null;
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_enabled_componentNameBlank() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mConfigDefaultContentProtectionService = "   ";
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionAllowlistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_disabled_componentNameNull() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+        mDevCfgEnableContentProtectionReceiver = false;
+        mConfigDefaultContentProtectionService = null;
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager).stop();
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_contentProtection_disabled_componentNameBlank() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+        mDevCfgEnableContentProtectionReceiver = false;
+        mConfigDefaultContentProtectionService = "   ";
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionAllowlistManager).start(anyLong());
+        verify(mMockContentProtectionAllowlistManager).stop();
         verifyZeroInteractions(mMockContentProtectionConsentManager);
     }
 
     @Test
     public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
         ContentCaptureOptions actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isNull();
-        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
@@ -210,8 +301,6 @@
 
     @Test
     public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
                 USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
 
@@ -224,7 +313,7 @@
         assertThat(actual.contentProtectionOptions).isNotNull();
         assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
         assertThat(actual.whitelistedComponents).isNull();
-        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
         verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
@@ -249,31 +338,14 @@
     }
 
     @Test
-    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionNotGranted() {
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
-        boolean actual =
-                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
-                        USER_ID, PACKAGE_NAME);
-
-        assertThat(actual).isFalse();
-        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
-    }
-
-    @Test
     public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
-        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isFalse();
-        verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -291,7 +363,21 @@
     }
 
     @Test
-    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionDisabled() {
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionEnabled() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -307,31 +393,14 @@
     }
 
     @Test
-    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionNotGranted() {
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
-        boolean actual =
-                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
-                        USER_ID, COMPONENT_NAME);
-
-        assertThat(actual).isFalse();
-        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
-        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
-    }
-
-    @Test
     public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
-        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
-        mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService = new TestContentCaptureManagerService();
-
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, COMPONENT_NAME);
 
         assertThat(actual).isFalse();
-        verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
     }
 
     @Test
@@ -349,7 +418,21 @@
     }
 
     @Test
-    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionDisabled() {
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionEnabled() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -544,8 +627,6 @@
 
         TestContentCaptureManagerService() {
             super(sContext);
-            this.mDevCfgEnableContentProtectionReceiver =
-                    ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
             this.mDevCfgContentProtectionRequiredGroups =
                     ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups;
             this.mDevCfgContentProtectionOptionalGroups =
@@ -553,12 +634,13 @@
         }
 
         @Override
-        protected boolean getEnableContentProtectionReceiverLocked() {
+        protected boolean getDeviceConfigEnableContentProtectionReceiver() {
             return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
         }
 
         @Override
-        protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+        protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager(
+                long timeoutMs) {
             mContentProtectionAllowlistManagersCreated++;
             return mMockContentProtectionAllowlistManager;
         }
@@ -570,7 +652,7 @@
 
         @Override
         protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
-                @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+                @NonNull ComponentName componentName) {
             mContentProtectionServiceInfosCreated++;
             if (mContentProtectionServiceInfoConstructorShouldThrow) {
                 throw new RuntimeException("TEST RUNTIME EXCEPTION");
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
index 6767a85..dc38f2b 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -16,15 +16,35 @@
 
 package com.android.server.contentprotection;
 
+import static android.view.contentprotection.flags.Flags.FLAG_BLOCKLIST_UPDATE_ENABLED;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.content.PackageMonitor;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -40,13 +60,236 @@
 
     private static final String PACKAGE_NAME = "com.test.package.name";
 
+    private static final long TIMEOUT_MS = 111_111_111L;
+
+    private static final long DELAY_MS = 222_222_222L;
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock private ContentCaptureManagerService mMockContentCaptureManagerService;
+
+    @Mock private PackageMonitor mMockPackageMonitor;
+
+    @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
+
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private Handler mHandler;
+
     private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
 
     @Before
     public void setup() {
-        mContentProtectionAllowlistManager = new ContentProtectionAllowlistManager();
+        mHandler = new Handler(mTestLooper.getLooper());
+        mContentProtectionAllowlistManager = new TestContentProtectionAllowlistManager();
+    }
+
+    @Test
+    public void constructor() {
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verifyZeroInteractions(mMockPackageMonitor);
+    }
+
+    @Test
+    public void start_updateEnabled_firstTime_beforeDelay() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isTrue();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verifyZeroInteractions(mMockPackageMonitor);
+    }
+
+    @Test
+    public void start_updateEnabled_firstTime_afterDelay() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor, never()).unregister();
+    }
+
+    @Test
+    public void start_updateEnabled_secondTime() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor, never()).unregister();
+    }
+
+    @Test
+    public void start_updateDisabled_firstTime_beforeDelay() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isTrue();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verifyZeroInteractions(mMockPackageMonitor);
+    }
+
+    @Test
+    public void start_updateDisabled_firstTime_afterDelay() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor, never()).unregister();
+    }
+
+    @Test
+    public void start_updateDisabled_secondTime() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor, never()).unregister();
+    }
+
+    @Test
+    public void stop_updateEnabled_notStarted() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        doThrow(new IllegalStateException("NOT REGISTERED")).when(mMockPackageMonitor).unregister();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor, never()).register(any(), any(), any());
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void stop_updateEnabled_started_beforeDelay() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        doThrow(new IllegalStateException("NOT REGISTERED")).when(mMockPackageMonitor).unregister();
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor, never()).register(any(), any(), any());
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void stop_updateEnabled_started_afterDelay() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void stop_updateDisabled_notStarted() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        doThrow(new IllegalStateException("NOT REGISTERED")).when(mMockPackageMonitor).unregister();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor, never()).register(any(), any(), any());
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void stop_updateDisabled_started_beforeDelay() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        doThrow(new IllegalStateException("NOT REGISTERED")).when(mMockPackageMonitor).unregister();
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.dispatchAll();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor, never()).register(any(), any(), any());
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void stop_updateDisabled_started_afterDelay() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        mContentProtectionAllowlistManager.stop();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void start_afterStop_beforeDelay() {
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.dispatchAll();
+        mContentProtectionAllowlistManager.stop();
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verify(mMockPackageMonitor).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor).unregister();
+    }
+
+    @Test
+    public void start_afterStop_afterDelay() {
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+        mContentProtectionAllowlistManager.stop();
+
+        mContentProtectionAllowlistManager.start(DELAY_MS);
+        mTestLooper.moveTimeForward(DELAY_MS);
+        mTestLooper.dispatchNext();
+
+        assertThat(mHandler.hasMessagesOrCallbacks()).isFalse();
+        verify(mMockPackageMonitor, times(2)).register(any(), eq(UserHandle.ALL), eq(mHandler));
+        verify(mMockPackageMonitor).unregister();
     }
 
     @Test
@@ -54,5 +297,86 @@
         boolean actual = mContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME);
 
         assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+        verifyZeroInteractions(mMockPackageMonitor);
+    }
+
+    @Test
+    public void handleUpdate_updateDisabled() {
+        mSetFlagsRule.disableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        ContentProtectionAllowlistManager manager =
+                new ContentProtectionAllowlistManager(
+                        mMockContentCaptureManagerService, mHandler, TIMEOUT_MS);
+
+        manager.mPackageMonitor.onSomePackagesChanged();
+
+        verifyZeroInteractions(mMockContentCaptureManagerService);
+    }
+
+    @Test
+    public void handleUpdate_updateEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        ContentProtectionAllowlistManager manager =
+                new ContentProtectionAllowlistManager(
+                        mMockContentCaptureManagerService, mHandler, TIMEOUT_MS);
+
+        manager.mPackageMonitor.onSomePackagesChanged();
+
+        verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
+    }
+
+    @Test
+    public void handleUpdate_rateLimit_noService() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        ContentProtectionAllowlistManager manager =
+                new ContentProtectionAllowlistManager(
+                        mMockContentCaptureManagerService, mHandler, TIMEOUT_MS);
+
+        manager.mPackageMonitor.onSomePackagesChanged();
+        manager.mPackageMonitor.onSomePackagesChanged();
+
+        verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
+    }
+
+    @Test
+    public void handleUpdate_rateLimit_beforeTimeout() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        ContentProtectionAllowlistManager manager =
+                new ContentProtectionAllowlistManager(
+                        mMockContentCaptureManagerService, mHandler, TIMEOUT_MS);
+        when(mMockContentCaptureManagerService.createRemoteContentProtectionService())
+                .thenReturn(mMockRemoteContentProtectionService);
+
+        manager.mPackageMonitor.onSomePackagesChanged();
+        manager.mPackageMonitor.onSomePackagesChanged();
+
+        verify(mMockContentCaptureManagerService).createRemoteContentProtectionService();
+    }
+
+    @Test
+    public void handleUpdate_rateLimit_afterTimeout() {
+        mSetFlagsRule.enableFlags(FLAG_BLOCKLIST_UPDATE_ENABLED);
+        ContentProtectionAllowlistManager manager =
+                new ContentProtectionAllowlistManager(
+                        mMockContentCaptureManagerService, mHandler, /* timeoutMs= */ 0L);
+        when(mMockContentCaptureManagerService.createRemoteContentProtectionService())
+                .thenReturn(mMockRemoteContentProtectionService);
+
+        manager.mPackageMonitor.onSomePackagesChanged();
+        manager.mPackageMonitor.onSomePackagesChanged();
+
+        verify(mMockContentCaptureManagerService, times(2)).createRemoteContentProtectionService();
+    }
+
+    private class TestContentProtectionAllowlistManager extends ContentProtectionAllowlistManager {
+
+        TestContentProtectionAllowlistManager() {
+            super(mMockContentCaptureManagerService, mHandler, TIMEOUT_MS);
+        }
+
+        @Override
+        protected PackageMonitor createPackageMonitor() {
+            return mMockPackageMonitor;
+        }
     }
 }