Merge "Add util methods for WorkPolicy in SettingsLib" into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index b9673f2..18665e7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
@@ -1332,6 +1333,7 @@
                 builder.addCapability(NET_CAPABILITY_INTERNET);
                 builder.addCapability(NET_CAPABILITY_VALIDATED);
                 builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+                builder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
 
                 if (networkType == NETWORK_TYPE_ANY) {
                     // No other capabilities
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 167cd34..328708f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -168,11 +168,11 @@
   }
 
   public static interface ActivityOptions.OnAnimationFinishedListener {
-    method public void onAnimationFinished();
+    method public void onAnimationFinished(long);
   }
 
   public static interface ActivityOptions.OnAnimationStartedListener {
-    method public void onAnimationStarted();
+    method public void onAnimationStarted(long);
   }
 
   public class ActivityTaskManager {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 709272c..2eebc01 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -46,6 +46,7 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.transition.TransitionManager;
 import android.util.Pair;
@@ -634,9 +635,10 @@
             mAnimationStartedListener = new IRemoteCallback.Stub() {
                 @Override
                 public void sendResult(Bundle data) throws RemoteException {
+                    final long elapsedRealtime = SystemClock.elapsedRealtime();
                     handler.post(new Runnable() {
                         @Override public void run() {
-                            listener.onAnimationStarted();
+                            listener.onAnimationStarted(elapsedRealtime);
                         }
                     });
                 }
@@ -645,13 +647,15 @@
     }
 
     /**
-     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
-     * to find out when the given animation has started running.
+     * Callback for finding out when the given animation has started running.
      * @hide
      */
     @TestApi
     public interface OnAnimationStartedListener {
-        void onAnimationStarted();
+        /**
+         * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation started.
+         */
+        void onAnimationStarted(long elapsedRealTime);
     }
 
     private void setOnAnimationFinishedListener(final Handler handler,
@@ -660,10 +664,11 @@
             mAnimationFinishedListener = new IRemoteCallback.Stub() {
                 @Override
                 public void sendResult(Bundle data) throws RemoteException {
+                    final long elapsedRealtime = SystemClock.elapsedRealtime();
                     handler.post(new Runnable() {
                         @Override
                         public void run() {
-                            listener.onAnimationFinished();
+                            listener.onAnimationFinished(elapsedRealtime);
                         }
                     });
                 }
@@ -672,13 +677,15 @@
     }
 
     /**
-     * Callback for use with {@link ActivityOptions#makeThumbnailAspectScaleDownAnimation}
-     * to find out when the given animation has drawn its last frame.
+     * Callback for finding out when the given animation has drawn its last frame.
      * @hide
      */
     @TestApi
     public interface OnAnimationFinishedListener {
-        void onAnimationFinished();
+        /**
+         * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation finished.
+         */
+        void onAnimationFinished(long elapsedRealTime);
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4829dc0..6e395be 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2916,6 +2916,133 @@
     };
 
     /**
+     * This specifies whether each option is only allowed to be read
+     * by apps with manage appops permission.
+     */
+    private static boolean[] sOpRestrictRead = new boolean[] {
+            false, // COARSE_LOCATION
+            false, // FINE_LOCATION
+            false, // GPS
+            false, // VIBRATE
+            false, // READ_CONTACTS
+            false, // WRITE_CONTACTS
+            false, // READ_CALL_LOG
+            false, // WRITE_CALL_LOG
+            false, // READ_CALENDAR
+            false, // WRITE_CALENDAR
+            false, // WIFI_SCAN
+            false, // POST_NOTIFICATION
+            false, // NEIGHBORING_CELLS
+            false, // CALL_PHONE
+            false, // READ_SMS
+            false, // WRITE_SMS
+            false, // RECEIVE_SMS
+            false, // RECEIVE_EMERGENCY_BROADCAST
+            false, // RECEIVE_MMS
+            false, // RECEIVE_WAP_PUSH
+            false, // SEND_SMS
+            false, // READ_ICC_SMS
+            false, // WRITE_ICC_SMS
+            false, // WRITE_SETTINGS
+            false, // SYSTEM_ALERT_WINDOW
+            false, // ACCESS_NOTIFICATIONS
+            false, // CAMERA
+            false, // RECORD_AUDIO
+            false, // PLAY_AUDIO
+            false, // READ_CLIPBOARD
+            false, // WRITE_CLIPBOARD
+            false, // TAKE_MEDIA_BUTTONS
+            false, // TAKE_AUDIO_FOCUS
+            false, // AUDIO_MASTER_VOLUME
+            false, // AUDIO_VOICE_VOLUME
+            false, // AUDIO_RING_VOLUME
+            false, // AUDIO_MEDIA_VOLUME
+            false, // AUDIO_ALARM_VOLUME
+            false, // AUDIO_NOTIFICATION_VOLUME
+            false, // AUDIO_BLUETOOTH_VOLUME
+            false, // WAKE_LOCK
+            false, // MONITOR_LOCATION
+            false, // MONITOR_HIGH_POWER_LOCATION
+            false, // GET_USAGE_STATS
+            false, // MUTE_MICROPHONE
+            false, // TOAST_WINDOW
+            false, // PROJECT_MEDIA
+            false, // ACTIVATE_VPN
+            false, // WRITE_WALLPAPER
+            false, // ASSIST_STRUCTURE
+            false, // ASSIST_SCREENSHOT
+            false, // READ_PHONE_STATE
+            false, // ADD_VOICEMAIL
+            false, // USE_SIP
+            false, // PROCESS_OUTGOING_CALLS
+            false, // USE_FINGERPRINT
+            false, // BODY_SENSORS
+            false, // READ_CELL_BROADCASTS
+            false, // MOCK_LOCATION
+            false, // READ_EXTERNAL_STORAGE
+            false, // WRITE_EXTERNAL_STORAGE
+            false, // TURN_SCREEN_ON
+            false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
+            false, // AUDIO_ACCESSIBILITY_VOLUME
+            false, // READ_PHONE_NUMBERS
+            false, // REQUEST_INSTALL_PACKAGES
+            false, // PICTURE_IN_PICTURE
+            false, // INSTANT_APP_START_FOREGROUND
+            false, // ANSWER_PHONE_CALLS
+            false, // RUN_ANY_IN_BACKGROUND
+            false, // CHANGE_WIFI_STATE
+            false, // REQUEST_DELETE_PACKAGES
+            false, // BIND_ACCESSIBILITY_SERVICE
+            false, // ACCEPT_HANDOVER
+            false, // MANAGE_IPSEC_TUNNELS
+            false, // START_FOREGROUND
+            false, // BLUETOOTH_SCAN
+            false, // USE_BIOMETRIC
+            false, // ACTIVITY_RECOGNITION
+            false, // SMS_FINANCIAL_TRANSACTIONS
+            false, // READ_MEDIA_AUDIO
+            false, // WRITE_MEDIA_AUDIO
+            false, // READ_MEDIA_VIDEO
+            false,  // WRITE_MEDIA_VIDEO
+            false, // READ_MEDIA_IMAGES
+            false,  // WRITE_MEDIA_IMAGES
+            false,  // LEGACY_STORAGE
+            false, // ACCESS_ACCESSIBILITY
+            false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
+            false, // QUERY_ALL_PACKAGES
+            false, // MANAGE_EXTERNAL_STORAGE
+            false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
+            false, // LOADER_USAGE_STATS
+            false, // deprecated operation
+            false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+            false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
+            false, // NO_ISOLATED_STORAGE
+            false, // PHONE_CALL_MICROPHONE
+            false, // PHONE_CALL_CAMERA
+            false, // RECORD_AUDIO_HOTWORD
+            false, // MANAGE_ONGOING_CALLS
+            false, // MANAGE_CREDENTIALS
+            false, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
+            false, // RECORD_AUDIO_OUTPUT
+            false, // SCHEDULE_EXACT_ALARM
+            false, // ACCESS_FINE_LOCATION_SOURCE
+            false, // ACCESS_COARSE_LOCATION_SOURCE
+            false, // MANAGE_MEDIA
+            false, // BLUETOOTH_CONNECT
+            false, // UWB_RANGING
+            false, // ACTIVITY_RECOGNITION_SOURCE
+            false, // BLUETOOTH_ADVERTISE
+            false, // RECORD_INCOMING_PHONE_AUDIO
+            false, // NEARBY_WIFI_DEVICES
+            false, // OP_ESTABLISH_VPN_SERVICE
+            false, // OP_ESTABLISH_VPN_MANAGER
+            true, // ACCESS_RESTRICTED_SETTINGS
+    };
+
+    /**
      * Mapping from an app op name to the app op code.
      */
     private static HashMap<String, Integer> sOpStrToOp = new HashMap<>();
@@ -3143,6 +3270,14 @@
     }
 
     /**
+     * Retrieve whether the op can be read by apps with manage appops permission.
+     * @hide
+     */
+    public static boolean opRestrictsRead(int op) {
+        return sOpRestrictRead[op];
+    }
+
+    /**
      * Retrieve whether the op allows itself to be reset.
      * @hide
      */
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 7c337a4..2767b43 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -30,11 +30,9 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
-import android.util.Slog;
 import android.view.autofill.AutofillManager;
 
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Base class for maintaining global application state. You can provide your own
@@ -56,9 +54,6 @@
 public class Application extends ContextWrapper implements ComponentCallbacks2 {
     private static final String TAG = "Application";
 
-    /** Whether to enable the check to detect "duplicate application instances". */
-    private static final boolean DEBUG_DUP_APP_INSTANCES = true;
-
     @UnsupportedAppUsage
     private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
             new ArrayList<ActivityLifecycleCallbacks>();
@@ -72,9 +67,6 @@
     @UnsupportedAppUsage
     public LoadedApk mLoadedApk;
 
-    private static final AtomicReference<StackTrace> sConstructorStackTrace =
-            new AtomicReference<>();
-
     public interface ActivityLifecycleCallbacks {
 
         /**
@@ -240,26 +232,6 @@
 
     public Application() {
         super(null);
-        if (DEBUG_DUP_APP_INSTANCES) {
-            checkDuplicateInstances();
-        }
-    }
-
-    private void checkDuplicateInstances() {
-        // STOPSHIP: Delete this check b/221248960
-        // Only run this check for gms-core.
-        if (!"com.google.android.gms".equals(ActivityThread.currentOpPackageName())) {
-            return;
-        }
-
-        final StackTrace previousStackTrace = sConstructorStackTrace.getAndSet(
-                new StackTrace("Previous stack trace"));
-        if (previousStackTrace == null) {
-            // This is the first call.
-            return;
-        }
-        Slog.wtf(TAG, "Application ctor called twice for " + this.getClass(),
-                new StackTrace("Current stack trace", previousStackTrace));
     }
 
     private String getLoadedApkInfo() {
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 9c07f85..da62375 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -41,8 +41,7 @@
     /**
      * See {@link DevicePolicyManager#getScreenCaptureDisabled}
      */
-    public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle,
-            boolean ownerCanAddInternalSystemWindow);
+    public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle);
 
     /**
      * Caches {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} of the
@@ -70,8 +69,7 @@
         private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache();
 
         @Override
-        public boolean isScreenCaptureAllowed(int userHandle,
-                boolean ownerCanAddInternalSystemWindow) {
+        public boolean isScreenCaptureAllowed(int userHandle) {
             return true;
         }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index dc65bef..d235f12 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -422,6 +422,18 @@
         }
 
         /**
+         * Set if BiometricPrompt is being used by the legacy fingerprint manager API.
+         * @param sensorId sensor id
+         * @return This builder.
+         * @hide
+         */
+        @NonNull
+        public Builder setIsForLegacyFingerprintManager(int sensorId) {
+            mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          *
          * @return An instance of {@link BiometricPrompt}.
@@ -883,28 +895,36 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
             int userId) {
-        authenticateUserForOperation(cancel, executor, callback, userId, 0 /* operationId */);
+        if (cancel == null) {
+            throw new IllegalArgumentException("Must supply a cancellation signal");
+        }
+        if (executor == null) {
+            throw new IllegalArgumentException("Must supply an executor");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Must supply a callback");
+        }
+
+        authenticateInternal(0 /* operationId */, cancel, executor, callback, userId);
     }
 
     /**
-     * Authenticates for the given user and keystore operation.
+     * Authenticates for the given keystore operation.
      *
      * @param cancel An object that can be used to cancel authentication
      * @param executor An executor to handle callback events
      * @param callback An object to receive authentication events
-     * @param userId The user to authenticate
      * @param operationId The keystore operation associated with authentication
      *
      * @return A requestId that can be used to cancel this operation.
      *
      * @hide
      */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public long authenticateUserForOperation(
+    @RequiresPermission(USE_BIOMETRIC)
+    public long authenticateForOperation(
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
-            int userId,
             long operationId) {
         if (cancel == null) {
             throw new IllegalArgumentException("Must supply a cancellation signal");
@@ -916,7 +936,7 @@
             throw new IllegalArgumentException("Must supply a callback");
         }
 
-        return authenticateInternal(operationId, cancel, executor, callback, userId);
+        return authenticateInternal(operationId, cancel, executor, callback, mContext.getUserId());
     }
 
     /**
@@ -1050,7 +1070,7 @@
     private void cancelAuthentication(long requestId) {
         if (mService != null) {
             try {
-                mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
+                mService.cancelAuthentication(mToken, mContext.getPackageName(), requestId);
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to cancel authentication", e);
             }
@@ -1109,7 +1129,7 @@
             }
 
             final long authId = mService.authenticate(mToken, operationId, userId,
-                    mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+                    mBiometricServiceReceiver, mContext.getPackageName(), promptInfo);
             cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
             return authId;
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
index 3d9517f..b336a9f 100644
--- a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
+++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
@@ -19,7 +19,7 @@
  * ITestSession callback for FingerprintManager and BiometricManager.
  * @hide
  */
-interface ITestSessionCallback {
+oneway interface ITestSessionCallback {
     void onCleanupStarted(int userId);
     void onCleanupFinished(int userId);
 }
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 0c03948..a6b8096 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -46,6 +46,7 @@
     @NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>();
     private boolean mAllowBackgroundAuthentication;
     private boolean mIgnoreEnrollmentState;
+    private boolean mIsForLegacyFingerprintManager = false;
 
     public PromptInfo() {
 
@@ -68,6 +69,7 @@
         mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
         mAllowBackgroundAuthentication = in.readBoolean();
         mIgnoreEnrollmentState = in.readBoolean();
+        mIsForLegacyFingerprintManager = in.readBoolean();
     }
 
     public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -105,10 +107,15 @@
         dest.writeList(mAllowedSensorIds);
         dest.writeBoolean(mAllowBackgroundAuthentication);
         dest.writeBoolean(mIgnoreEnrollmentState);
+        dest.writeBoolean(mIsForLegacyFingerprintManager);
     }
 
     public boolean containsTestConfigurations() {
-        if (!mAllowedSensorIds.isEmpty()) {
+        if (mIsForLegacyFingerprintManager
+                && mAllowedSensorIds.size() == 1
+                && !mAllowBackgroundAuthentication) {
+            return false;
+        } else if (!mAllowedSensorIds.isEmpty()) {
             return true;
         } else if (mAllowBackgroundAuthentication) {
             return true;
@@ -188,7 +195,8 @@
     }
 
     public void setAllowedSensorIds(@NonNull List<Integer> sensorIds) {
-        mAllowedSensorIds = sensorIds;
+        mAllowedSensorIds.clear();
+        mAllowedSensorIds.addAll(sensorIds);
     }
 
     public void setAllowBackgroundAuthentication(boolean allow) {
@@ -199,6 +207,12 @@
         mIgnoreEnrollmentState = ignoreEnrollmentState;
     }
 
+    public void setIsForLegacyFingerprintManager(int sensorId) {
+        mIsForLegacyFingerprintManager = true;
+        mAllowedSensorIds.clear();
+        mAllowedSensorIds.add(sensorId);
+    }
+
     // Getters
 
     public CharSequence getTitle() {
@@ -272,4 +286,8 @@
     public boolean isIgnoreEnrollmentState() {
         return mIgnoreEnrollmentState;
     }
+
+    public boolean isForLegacyFingerprintManager() {
+        return mIsForLegacyFingerprintManager;
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9d166b99..34647b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7086,7 +7086,7 @@
          *
          * @hide
          */
-        @Readable
+        @Readable(maxTargetSdk = Build.VERSION_CODES.S)
         public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
                 "always_on_vpn_lockdown_whitelist";
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 5ce5477..c83869c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -249,7 +249,7 @@
      * Set whether screen capture is disabled for all windows of a specific user from
      * the device policy cache.
      */
-    void refreshScreenCaptureDisabled(int userId);
+    void refreshScreenCaptureDisabled();
 
     // These can only be called with the SET_ORIENTATION permission.
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 90e3498..4baad1e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1058,11 +1058,6 @@
 
     /**
      * Refreshes this info with the latest state of the view it represents.
-     * <p>
-     * <strong>Note:</strong> If this method returns false this info is obsolete
-     * since it represents a view that is no longer in the view tree and should
-     * be recycled.
-     * </p>
      *
      * @param bypassCache Whether to bypass the cache.
      * @return Whether the refresh succeeded.
@@ -1089,8 +1084,7 @@
      * Refreshes this info with the latest state of the view it represents.
      *
      * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
-     * by this node is no longer in the view tree (and thus this node is obsolete and should be
-     * recycled).
+     * by this node is no longer in the view tree (and thus this node is obsolete).
      */
     public boolean refresh() {
         return refresh(null, true);
@@ -1109,8 +1103,7 @@
      * @param args A bundle of arguments for the request. These depend on the particular request.
      *
      * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
-     * by this node is no longer in the view tree (and thus this node is obsolete and should be
-     * recycled).
+     * by this node is no longer in the view tree (and thus this node is obsolete).
      */
     public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
         // limits the text location length to make sure the rectangle array allocation avoids
@@ -1823,11 +1816,6 @@
      * this info is the root of the traversed tree.
      *
      * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
-     * <p>
      * <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
      * view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
      * this API won't be able to find the node for the view on the embedded view hierarchy. It's
@@ -1855,11 +1843,6 @@
      * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
      *
      * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
-     * <p>
      *   <strong>Note:</strong> The primary usage of this API is for UI test automation
      *   and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
      *   the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
@@ -3282,11 +3265,6 @@
     /**
      * Gets the node info for which the view represented by this info serves as
      * a label for accessibility purposes.
-     * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
      *
      * @return The labeled info.
      */
@@ -3334,11 +3312,6 @@
     /**
      * Gets the node info which serves as the label of the view represented by
      * this info for accessibility purposes.
-     * <p>
-     *   <strong>Note:</strong> It is a client responsibility to recycle the
-     *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-     *     to avoid creating of multiple instances.
-     * </p>
      *
      * @return The label.
      */
@@ -5312,9 +5285,7 @@
     }
 
     /**
-     * Class with information if a node is a range. Use
-     * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. Recycling is
-     * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+     * Class with information if a node is a range.
      */
     public static final class RangeInfo {
 
@@ -5423,9 +5394,7 @@
     }
 
     /**
-     * Class with information if a node is a collection. Use
-     * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. Recycling is
-     * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+     * Class with information if a node is a collection.
      * <p>
      * A collection of items has rows and columns and may be hierarchical.
      * For example, a horizontal list is a collection with one column, as
@@ -5591,10 +5560,7 @@
     }
 
     /**
-     * Class with information if a node is a collection item. Use
-     * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
-     * to get an instance. Recycling is handled by the {@link AccessibilityNodeInfo} to which this
-     * object is attached.
+     * Class with information if a node is a collection item.
      * <p>
      * A collection item is contained in a collection, it starts at
      * a given row and column in the collection, and spans one or
@@ -6085,11 +6051,6 @@
          * <p>
          *   <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
          * </p>
-         * <p>
-         *   <strong>Note:</strong> It is a client responsibility to recycle the
-         *     received info by calling {@link AccessibilityNodeInfo#recycle()}
-         *     to avoid creating of multiple instances.
-         * </p>
          *
          * @param region The region retrieved from {@link #getRegionAt(int)}.
          * @return The target node associates with the given region.
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 2c62647..41ff69d 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -32,6 +32,7 @@
 import android.database.ContentObserver;
 import android.os.Build;
 import android.os.Handler;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.PluralsMessageFormatter;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -230,7 +231,7 @@
 
         // Set the text
         String text = format.format(new Date(time));
-        setText(text);
+        maybeSetText(text);
 
         // Schedule the next update
         if (display == SHOW_TIME) {
@@ -258,7 +259,7 @@
         boolean past = (now >= mTimeMillis);
         String result;
         if (duration < MINUTE_IN_MILLIS) {
-            setText(mNowText);
+            maybeSetText(mNowText);
             mUpdateTimeMillis = mTimeMillis + MINUTE_IN_MILLIS + 1;
             return;
         } else if (duration < HOUR_IN_MILLIS) {
@@ -308,7 +309,19 @@
                 mUpdateTimeMillis = mTimeMillis - millisIncrease * count + 1;
             }
         }
-        setText(result);
+        maybeSetText(result);
+    }
+
+    /**
+     * Sets text only if the text has actually changed. This prevents needles relayouts of this
+     * view when set to wrap_content.
+     */
+    private void maybeSetText(String text) {
+        if (TextUtils.equals(getText(), text)) {
+            return;
+        }
+
+        setText(text);
     }
 
     /**
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 42fc7bd..9759540 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -228,12 +228,6 @@
      */
     abstract float getScore(ComponentName name);
 
-    /**
-     * Returns the list of top K component names which have highest
-     * {@link #getScore(ComponentName)}
-     */
-    abstract List<ComponentName> getTopComponentNames(int topK);
-
     /** Handles result message sent to mHandler. */
     abstract void handleResultMessage(Message message);
 
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index bc9eff0..b19ac2f 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -18,6 +18,7 @@
 
 import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
 
+import android.annotation.Nullable;
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
@@ -33,12 +34,11 @@
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
 
 /**
  * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be
@@ -58,7 +58,9 @@
     private final String mReferrerPackage;
     // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall
     // back to using the ResolverRankerService.
+    // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client.
     private ResolverRankerServiceResolverComparator mResolverRankerService;
+    private AppPredictionServiceComparatorModel mComparatorModel;
 
     AppPredictionServiceResolverComparator(
             Context context,
@@ -74,25 +76,12 @@
         mUser = user;
         mReferrerPackage = referrerPackage;
         setChooserActivityLogger(chooserActivityLogger);
+        mComparatorModel = buildUpdatedModel();
     }
 
     @Override
     int compare(ResolveInfo lhs, ResolveInfo rhs) {
-        if (mResolverRankerService != null) {
-            return mResolverRankerService.compare(lhs, rhs);
-        }
-        Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
-                lhs.activityInfo.name));
-        Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
-                rhs.activityInfo.name));
-        if (lhsRank == null && rhsRank == null) {
-            return 0;
-        } else if (lhsRank == null) {
-            return -1;
-        } else if (rhsRank == null) {
-            return 1;
-        }
-        return lhsRank - rhsRank;
+        return mComparatorModel.getComparator().compare(lhs, rhs);
     }
 
     @Override
@@ -121,6 +110,7 @@
                                         mContext, mIntent, mReferrerPackage,
                                         () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
                                         getChooserActivityLogger());
+                        mComparatorModel = buildUpdatedModel();
                         mResolverRankerService.compute(targets);
                     } else {
                         Log.i(TAG, "AppPredictionService response received");
@@ -163,6 +153,7 @@
             mTargetRanks.put(componentName, i);
             Log.i(TAG, "handleSortedAppTargets, sortedAppTargets #" + i + ": " + componentName);
         }
+        mComparatorModel = buildUpdatedModel();
     }
 
     private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) {
@@ -176,43 +167,12 @@
 
     @Override
     float getScore(ComponentName name) {
-        if (mResolverRankerService != null) {
-            return mResolverRankerService.getScore(name);
-        }
-        Integer rank = mTargetRanks.get(name);
-        if (rank == null) {
-            Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
-            return 0f;
-        }
-        int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
-        return 1.0f - (((float) rank) / consecutiveSumOfRanks);
-    }
-
-    @Override
-    List<ComponentName> getTopComponentNames(int topK) {
-        if (mResolverRankerService != null) {
-            return mResolverRankerService.getTopComponentNames(topK);
-        }
-        return mTargetRanks.entrySet().stream()
-                .sorted(Entry.comparingByValue())
-                .limit(topK)
-                .map(Entry::getKey)
-                .collect(Collectors.toList());
+        return mComparatorModel.getScore(name);
     }
 
     @Override
     void updateModel(ComponentName componentName) {
-        if (mResolverRankerService != null) {
-            mResolverRankerService.updateModel(componentName);
-            return;
-        }
-        mAppPredictor.notifyAppTargetEvent(
-                new AppTargetEvent.Builder(
-                    new AppTarget.Builder(
-                        new AppTargetId(componentName.toString()),
-                        componentName.getPackageName(), mUser)
-                        .setClassName(componentName.getClassName()).build(),
-                    ACTION_LAUNCH).build());
+        mComparatorModel.notifyOnTargetSelected(componentName);
     }
 
     @Override
@@ -220,6 +180,97 @@
         if (mResolverRankerService != null) {
             mResolverRankerService.destroy();
             mResolverRankerService = null;
+            mComparatorModel = buildUpdatedModel();
+        }
+    }
+
+    /**
+     * Re-construct an {@code AppPredictionServiceComparatorModel} to replace the current model
+     * instance (if any) using the up-to-date {@code AppPredictionServiceResolverComparator} ivar
+     * values.
+     *
+     * TODO: each time we replace the model instance, we're either updating the model to use
+     * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
+     * that wasn't available the last time the model was updated. For those latter cases, we should
+     * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
+     * we can probably simplify the logic in {@code AppPredictionServiceComparatorModel} since we
+     * won't need to handle edge cases when the model data isn't fully prepared.
+     * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
+     * initializing the first time and now want to adjust some data, but still need to wait for
+     * changes to propagate to the other ivars before rebuilding the model.)
+     */
+    private AppPredictionServiceComparatorModel buildUpdatedModel() {
+        return new AppPredictionServiceComparatorModel(
+                mAppPredictor, mResolverRankerService, mUser, mTargetRanks);
+    }
+
+    // TODO: Finish separating behaviors of AbstractResolverComparator, then (probably) make this a
+    // standalone class once clients are written in terms of ResolverComparatorModel.
+    static class AppPredictionServiceComparatorModel implements ResolverComparatorModel {
+        private final AppPredictor mAppPredictor;
+        private final ResolverRankerServiceResolverComparator mResolverRankerService;
+        private final UserHandle mUser;
+        private final Map<ComponentName, Integer> mTargetRanks;  // Treat as immutable.
+
+        AppPredictionServiceComparatorModel(
+                AppPredictor appPredictor,
+                @Nullable ResolverRankerServiceResolverComparator resolverRankerService,
+                UserHandle user,
+                Map<ComponentName, Integer> targetRanks) {
+            mAppPredictor = appPredictor;
+            mResolverRankerService = resolverRankerService;
+            mUser = user;
+            mTargetRanks = targetRanks;
+        }
+
+        @Override
+        public Comparator<ResolveInfo> getComparator() {
+            return (lhs, rhs) -> {
+                if (mResolverRankerService != null) {
+                    return mResolverRankerService.compare(lhs, rhs);
+                }
+                Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
+                        lhs.activityInfo.name));
+                Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
+                        rhs.activityInfo.name));
+                if (lhsRank == null && rhsRank == null) {
+                    return 0;
+                } else if (lhsRank == null) {
+                    return -1;
+                } else if (rhsRank == null) {
+                    return 1;
+                }
+                return lhsRank - rhsRank;
+            };
+        }
+
+        @Override
+        public float getScore(ComponentName name) {
+            if (mResolverRankerService != null) {
+                return mResolverRankerService.getScore(name);
+            }
+            Integer rank = mTargetRanks.get(name);
+            if (rank == null) {
+                Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
+                return 0f;
+            }
+            int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
+            return 1.0f - (((float) rank) / consecutiveSumOfRanks);
+        }
+
+        @Override
+        public void notifyOnTargetSelected(ComponentName componentName) {
+            if (mResolverRankerService != null) {
+                mResolverRankerService.updateModel(componentName);
+                return;
+            }
+            mAppPredictor.notifyAppTargetEvent(
+                    new AppTargetEvent.Builder(
+                        new AppTarget.Builder(
+                            new AppTargetId(componentName.toString()),
+                            componentName.getPackageName(), mUser)
+                            .setClassName(componentName.getClassName()).build(),
+                        ACTION_LAUNCH).build());
         }
     }
 }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 49ee91c..3cb39e7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -194,9 +194,6 @@
     private static final String PLURALS_COUNT = "count";
     private static final String PLURALS_FILE_NAME = "file_name";
 
-    @VisibleForTesting
-    public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
-
     private boolean mIsAppPredictorComponentAvailable;
     private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
     private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
@@ -248,6 +245,13 @@
                     SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
                     DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
 
+    private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250;
+
+    @VisibleForTesting
+    int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+            SystemUiDeviceConfigFlags.SHARESHEET_LIST_VIEW_UPDATE_DELAY,
+            DEFAULT_LIST_VIEW_UPDATE_DELAY_MS);
+
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
     private IntentSender mRefinementIntentSender;
@@ -2624,7 +2628,7 @@
         Message msg = Message.obtain();
         msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
         msg.obj = userHandle;
-        mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        mChooserHandler.sendMessageDelayed(msg, mListViewUpdateDelayMs);
     }
 
     @Override
diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java
new file mode 100644
index 0000000..3e8f64b
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverComparatorModel.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 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.app;
+
+import android.content.ComponentName;
+import android.content.pm.ResolveInfo;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
+ *
+ * As required by the {@link Comparator} contract, objects returned by {@code getComparator()} must
+ * apply a total ordering on its inputs consistent across all calls to {@code Comparator#compare()}.
+ * Other query methods and ranking feedback should refer to that same ordering, so implementors are
+ * generally advised to "lock in" an immutable snapshot of their model data when this object is
+ * initialized (preferring to replace the entire {@code ResolverComparatorModel} instance if the
+ * backing data needs to be updated in the future).
+ */
+interface ResolverComparatorModel {
+    /**
+     * Get a {@code Comparator} that can be used to sort {@code ResolveInfo} targets according to
+     * the model ranking.
+     */
+    Comparator<ResolveInfo> getComparator();
+
+    /**
+     * Get the numerical score, if any, that the model assigns to the component with the specified
+     * {@code name}. Scores range from zero to one, with one representing the highest possible
+     * likelihood that the user will select that component as the target. Implementations that don't
+     * assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
+     */
+    float getScore(ComponentName name);
+
+    /**
+     * Notify the model that the user selected a target. (Models may log this information, use it as
+     * a feedback signal for their ranking, etc.) Because the data in this
+     * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
+     * instance in order to see any changes in the ranking that might result from this feedback.
+     */
+    void notifyOnTargetSelected(ComponentName componentName);
+}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ac9b2d8..351ac45 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -155,14 +155,6 @@
         return mResolverListController.getScore(componentName);
     }
 
-    /**
-     * Returns the list of top K component names which have highest
-     * {@link #getScore(DisplayResolveInfo)}
-     */
-    public List<ComponentName> getTopComponentNames(int topK) {
-        return mResolverListController.getTopComponentNames(topK);
-    }
-
     public void updateModel(ComponentName componentName) {
         mResolverListController.updateModel(componentName);
     }
@@ -177,107 +169,206 @@
     }
 
     /**
-     * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
-     * to complete.
+     * Rebuild the list of resolvers. When rebuilding is complete, queue the {@code onPostListReady}
+     * callback on the main handler with {@code rebuildCompleted} true.
      *
-     * The {@code doPostProcessing } parameter is used to specify whether to update the UI and
-     * load additional targets (e.g. direct share) after the list has been rebuilt. This is used
-     * in the case where we want to load the inactive profile's resolved apps to know the
+     * In some cases some parts will need some asynchronous work to complete. Then this will first
+     * immediately queue {@code onPostListReady} (on the main handler) with {@code rebuildCompleted}
+     * false; only when the asynchronous work completes will this then go on to queue another
+     * {@code onPostListReady} callback with {@code rebuildCompleted} true.
+     *
+     * The {@code doPostProcessing} parameter is used to specify whether to update the UI and
+     * load additional targets (e.g. direct share) after the list has been rebuilt. We may choose
+     * to skip that step if we're only loading the inactive profile's resolved apps to know the
      * number of targets.
      *
-     * @return Whether or not the list building is completed.
+     * @return Whether the list building was completed synchronously. If not, we'll queue the
+     * {@code onPostListReady} callback first with {@code rebuildCompleted} false, and then again
+     * with {@code rebuildCompleted} true at the end of some newly-launched asynchronous work.
+     * Otherwise the callback is only queued once, with {@code rebuildCompleted} true.
      */
     protected boolean rebuildList(boolean doPostProcessing) {
-        List<ResolvedComponentInfo> currentResolveList = null;
-        // Clear the value of mOtherProfile from previous call.
-        mOtherProfile = null;
-        mLastChosen = null;
-        mLastChosenPosition = -1;
         mDisplayList.clear();
         mIsTabLoaded = false;
+        mLastChosenPosition = -1;
 
-        if (mBaseResolveList != null) {
-            currentResolveList = mUnfilteredResolveList = new ArrayList<>();
-            mResolverListController.addResolveListDedupe(currentResolveList,
-                    mResolverListCommunicator.getTargetIntent(),
-                    mBaseResolveList);
-        } else {
-            currentResolveList = mUnfilteredResolveList =
-                    mResolverListController.getResolversForIntent(
-                            /* shouldGetResolvedFilter= */ true,
-                            mResolverListCommunicator.shouldGetActivityMetadata(),
-                            mIntents);
-            if (currentResolveList == null) {
-                processSortedList(currentResolveList, doPostProcessing);
-                return true;
-            }
-            List<ResolvedComponentInfo> originalList =
-                    mResolverListController.filterIneligibleActivities(currentResolveList,
-                            true);
-            if (originalList != null) {
-                mUnfilteredResolveList = originalList;
-            }
-        }
+        List<ResolvedComponentInfo> currentResolveList = getInitialRebuiltResolveList();
+
+        /* TODO: this seems like unnecessary extra complexity; why do we need to do this "primary"
+         * (i.e. "eligibility") filtering before evaluating the "other profile" special-treatment,
+         * but the "secondary" (i.e. "priority") filtering after? Are there in fact cases where the
+         * eligibility conditions will filter out a result that would've otherwise gotten the "other
+         * profile" treatment? Or, are there cases where the priority conditions *would* filter out
+         * a result, but we *want* that result to get the "other profile" treatment, so we only
+         * filter *after* evaluating the special-treatment conditions? If the answer to either is
+         * "no," then the filtering steps can be consolidated. (And that also makes the "unfiltered
+         * list" bookkeeping a little cleaner.)
+         */
+        mUnfilteredResolveList = performPrimaryResolveListFiltering(currentResolveList);
 
         // So far we only support a single other profile at a time.
         // The first one we see gets special treatment.
-        for (ResolvedComponentInfo info : currentResolveList) {
-            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
-            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
-                Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
-                        resolveInfo.activityInfo,
-                        info.getIntentAt(0));
-                Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
-                        resolveInfo.activityInfo,
-                        mResolverListCommunicator.getTargetIntent());
-                mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
-                        resolveInfo,
-                        resolveInfo.loadLabel(mPm),
-                        resolveInfo.loadLabel(mPm),
-                        pOrigIntent != null ? pOrigIntent : replacementIntent,
-                        makePresentationGetter(resolveInfo));
-                currentResolveList.remove(info);
-                break;
-            }
+        ResolvedComponentInfo otherProfileInfo =
+                getFirstNonCurrentUserResolvedComponentInfo(currentResolveList);
+        updateOtherProfileTreatment(otherProfileInfo);
+        if (otherProfileInfo != null) {
+            currentResolveList.remove(otherProfileInfo);
+            /* TODO: the previous line removed the "other profile info" item from
+             * mUnfilteredResolveList *ONLY IF* that variable is an alias for the same List instance
+             * as currentResolveList (i.e., if no items were filtered out as the result of the
+             * earlier "primary" filtering). It seems wrong for our behavior to depend on that.
+             * Should we:
+             *  A. replicate the above removal to mUnfilteredResolveList (which is idempotent, so we
+             *     don't even have to check whether they're aliases); or
+             *  B. break the alias relationship by copying currentResolveList to a new
+             *  mUnfilteredResolveList instance if necessary before removing otherProfileInfo?
+             * In other words: do we *want* otherProfileInfo in the "unfiltered" results? Either
+             * way, we'll need one of the changes suggested above.
+             */
         }
 
-        if (mOtherProfile == null) {
+        // If no results have yet been filtered, mUnfilteredResolveList is an alias for the same
+        // List instance as currentResolveList. Then we need to make a copy to store as the
+        // mUnfilteredResolveList if we go on to filter any more items. Otherwise we've already
+        // copied the original unfiltered items to a separate List instance and can now filter
+        // the remainder in-place without any further bookkeeping.
+        boolean needsCopyOfUnfiltered = (mUnfilteredResolveList == currentResolveList);
+        mUnfilteredResolveList = performSecondaryResolveListFiltering(
+                currentResolveList, needsCopyOfUnfiltered);
+
+        return finishRebuildingListWithFilteredResults(currentResolveList, doPostProcessing);
+    }
+
+    /**
+     * Get the full (unfiltered) set of {@code ResolvedComponentInfo} records for all resolvers
+     * to be considered in a newly-rebuilt list. This list will be filtered and ranked before the
+     * rebuild is complete.
+     */
+    List<ResolvedComponentInfo> getInitialRebuiltResolveList() {
+        if (mBaseResolveList != null) {
+            List<ResolvedComponentInfo> currentResolveList = new ArrayList<>();
+            mResolverListController.addResolveListDedupe(currentResolveList,
+                    mResolverListCommunicator.getTargetIntent(),
+                    mBaseResolveList);
+            return currentResolveList;
+        } else {
+            return mResolverListController.getResolversForIntent(
+                            /* shouldGetResolvedFilter= */ true,
+                            mResolverListCommunicator.shouldGetActivityMetadata(),
+                            mIntents);
+        }
+    }
+
+    /**
+     * Remove ineligible activities from {@code currentResolveList} (if non-null), in-place. More
+     * broadly, filtering logic should apply in the "primary" stage if it should preclude items from
+     * receiving the "other profile" special-treatment described in {@code rebuildList()}.
+     *
+     * @return A copy of the original {@code currentResolveList}, if any items were removed, or a
+     * (possibly null) reference to the original list otherwise. (That is, this always returns a
+     * list of all the unfiltered items, but if no items were filtered, it's just an alias for the
+     * same list that was passed in).
+     */
+    @Nullable
+    List<ResolvedComponentInfo> performPrimaryResolveListFiltering(
+            @Nullable List<ResolvedComponentInfo> currentResolveList) {
+        /* TODO: mBaseResolveList appears to be(?) some kind of configured mode. Why is it not
+         * subject to filterIneligibleActivities, even though all the other logic still applies
+         * (including "secondary" filtering)? (This also relates to the earlier question; do we
+         * believe there's an item that would be eligible for "other profile" special treatment,
+         * except we want to filter it out as ineligible... but only if we're not in
+         * "mBaseResolveList mode"? */
+        if ((mBaseResolveList != null) || (currentResolveList == null)) {
+            return currentResolveList;
+        }
+
+        List<ResolvedComponentInfo> originalList =
+                mResolverListController.filterIneligibleActivities(currentResolveList, true);
+        return (originalList == null) ? currentResolveList : originalList;
+    }
+
+    /**
+     * Remove low-priority activities from {@code currentResolveList} (if non-null), in place. More
+     * broadly, filtering logic should apply in the "secondary" stage to prevent items from
+     * appearing in the rebuilt-list results, while still considering those items for the "other
+     * profile" special-treatment described in {@code rebuildList()}.
+     *
+     * @return the same (possibly null) List reference as {@code currentResolveList}, if the list is
+     * unmodified as a result of filtering; or, if some item(s) were removed, then either a copy of
+     * the original {@code currentResolveList} (if {@code returnCopyOfOriginalListIfModified} is
+     * true), or null (otherwise).
+     */
+    @Nullable
+    List<ResolvedComponentInfo> performSecondaryResolveListFiltering(
+            @Nullable List<ResolvedComponentInfo> currentResolveList,
+            boolean returnCopyOfOriginalListIfModified) {
+        if ((currentResolveList == null) || currentResolveList.isEmpty()) {
+            return currentResolveList;
+        }
+        return mResolverListController.filterLowPriority(
+                currentResolveList, returnCopyOfOriginalListIfModified);
+    }
+
+    /**
+     * Update the special "other profile" UI treatment based on the components resolved for a
+     * newly-built list.
+     *
+     * @param otherProfileInfo the first {@code ResolvedComponentInfo} specifying a
+     * {@code targetUserId} other than {@code USER_CURRENT}, or null if no such component info was
+     * found in the process of rebuilding the list (or if any such candidates were already removed
+     * due to "primary filtering").
+     */
+    void updateOtherProfileTreatment(@Nullable ResolvedComponentInfo otherProfileInfo) {
+        mLastChosen = null;
+
+        if (otherProfileInfo != null) {
+            mOtherProfile = makeOtherProfileDisplayResolveInfo(
+                    mContext, otherProfileInfo, mPm, mResolverListCommunicator, mIconDpi);
+        } else {
+            mOtherProfile = null;
             try {
                 mLastChosen = mResolverListController.getLastChosen();
+                // TODO: does this also somehow need to update mLastChosenPosition? If so, maybe
+                // the current method should also take responsibility for re-initializing
+                // mLastChosenPosition, where it's currently done at the start of rebuildList()?
+                // (Why is this related to the presence of mOtherProfile in fhe first place?)
             } catch (RemoteException re) {
                 Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
             }
         }
+    }
 
-        setPlaceholderCount(0);
-        int n;
-        if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
-            // We only care about fixing the unfilteredList if the current resolve list and
-            // current resolve list are currently the same.
-            List<ResolvedComponentInfo> originalList =
-                    mResolverListController.filterLowPriority(currentResolveList,
-                            mUnfilteredResolveList == currentResolveList);
-            if (originalList != null) {
-                mUnfilteredResolveList = originalList;
-            }
-
-            if (currentResolveList.size() > 1) {
-                int placeholderCount = currentResolveList.size();
-                if (mResolverListCommunicator.useLayoutWithDefault()) {
-                    --placeholderCount;
-                }
-                setPlaceholderCount(placeholderCount);
-                createSortingTask(doPostProcessing).execute(currentResolveList);
-                postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
-                return false;
-            } else {
-                processSortedList(currentResolveList, doPostProcessing);
-                return true;
-            }
-        } else {
-            processSortedList(currentResolveList, doPostProcessing);
+    /**
+     * Prepare the appropriate placeholders to eventually display the final set of resolved
+     * components in a newly-rebuilt list, and spawn an asynchronous sorting task if necessary.
+     * This eventually results in a {@code onPostListReady} callback with {@code rebuildCompleted}
+     * true; if any asynchronous work is required, that will first be preceded by a separate
+     * occurrence of the callback with {@code rebuildCompleted} false (once there are placeholders
+     * set up to represent the pending asynchronous results).
+     * @return Whether we were able to do all the work to prepare the list for display
+     * synchronously; if false, there will eventually be two separate {@code onPostListReady}
+     * callbacks, first with placeholders to represent pending asynchronous results, then later when
+     * the results are ready for presentation.
+     */
+    boolean finishRebuildingListWithFilteredResults(
+            @Nullable List<ResolvedComponentInfo> filteredResolveList, boolean doPostProcessing) {
+        if (filteredResolveList == null || filteredResolveList.size() < 2) {
+            // No asynchronous work to do.
+            setPlaceholderCount(0);
+            processSortedList(filteredResolveList, doPostProcessing);
             return true;
         }
+
+        int placeholderCount = filteredResolveList.size();
+        if (mResolverListCommunicator.useLayoutWithDefault()) {
+            --placeholderCount;
+        }
+        setPlaceholderCount(placeholderCount);
+
+        // Send an "incomplete" list-ready while the async task is running.
+        postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
+        createSortingTask(doPostProcessing).execute(filteredResolveList);
+        return false;
     }
 
     AsyncTask<List<ResolvedComponentInfo>,
@@ -644,6 +735,59 @@
     }
 
     /**
+     * Find the first element in a list of {@code ResolvedComponentInfo} objects whose
+     * {@code ResolveInfo} specifies a {@code targetUserId} other than the current user.
+     * @return the first ResolvedComponentInfo targeting a non-current user, or null if there are
+     * none (or if the list itself is null).
+     */
+    private static ResolvedComponentInfo getFirstNonCurrentUserResolvedComponentInfo(
+            @Nullable List<ResolvedComponentInfo> resolveList) {
+        if (resolveList == null) {
+            return null;
+        }
+
+        for (ResolvedComponentInfo info : resolveList) {
+            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set up a {@code DisplayResolveInfo} to provide "special treatment" for the first "other"
+     * profile in the resolve list (i.e., the first non-current profile to appear as the target user
+     * of an element in the resolve list).
+     */
+    private static DisplayResolveInfo makeOtherProfileDisplayResolveInfo(
+            Context context,
+            ResolvedComponentInfo resolvedComponentInfo,
+            PackageManager pm,
+            ResolverListCommunicator resolverListCommunicator,
+            int iconDpi) {
+        ResolveInfo resolveInfo = resolvedComponentInfo.getResolveInfoAt(0);
+
+        Intent pOrigIntent = resolverListCommunicator.getReplacementIntent(
+                resolveInfo.activityInfo,
+                resolvedComponentInfo.getIntentAt(0));
+        Intent replacementIntent = resolverListCommunicator.getReplacementIntent(
+                resolveInfo.activityInfo,
+                resolverListCommunicator.getTargetIntent());
+
+        ResolveInfoPresentationGetter presentationGetter =
+                new ResolveInfoPresentationGetter(context, iconDpi, resolveInfo);
+
+        return new DisplayResolveInfo(
+                resolvedComponentInfo.getIntentAt(0),
+                resolveInfo,
+                resolveInfo.loadLabel(pm),
+                resolveInfo.loadLabel(pm),
+                pOrigIntent != null ? pOrigIntent : replacementIntent,
+                presentationGetter);
+    }
+
+    /**
      * Necessary methods to communicate between {@link ResolverListAdapter}
      * and {@link ResolverActivity}.
      */
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 9a95e64..2757363 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -393,14 +393,6 @@
         return mResolverComparator.getScore(componentName);
     }
 
-    /**
-     * Returns the list of top K component names which have highest
-     * {@link #getScore(DisplayResolveInfo)}
-     */
-    public List<ComponentName> getTopComponentNames(int topK) {
-        return mResolverComparator.getTopComponentNames(topK);
-    }
-
     public void updateModel(ComponentName componentName) {
         mResolverComparator.updateModel(componentName);
     }
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index cb946c0..c5b21ac 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -43,12 +43,12 @@
 
 import java.text.Collator;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 /**
  * Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}.
@@ -83,6 +83,7 @@
     private ResolverRankerServiceConnection mConnection;
     private Context mContext;
     private CountDownLatch mConnectSignal;
+    private ResolverRankerServiceComparatorModel mComparatorModel;
 
     public ResolverRankerServiceResolverComparator(Context context, Intent intent,
                 String referrerPackage, AfterCompute afterCompute,
@@ -99,6 +100,8 @@
         mRankerServiceName = new ComponentName(mContext, this.getClass());
         setCallBack(afterCompute);
         setChooserActivityLogger(chooserActivityLogger);
+
+        mComparatorModel = buildUpdatedModel();
     }
 
     @Override
@@ -125,6 +128,7 @@
             }
             if (isUpdated) {
                 mRankerServiceName = mResolvedRankerName;
+                mComparatorModel = buildUpdatedModel();
             }
         } else {
             Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
@@ -218,83 +222,25 @@
             }
         }
         predictSelectProbabilities(mTargets);
+
+        mComparatorModel = buildUpdatedModel();
     }
 
     @Override
     public int compare(ResolveInfo lhs, ResolveInfo rhs) {
-        if (mStats != null) {
-            final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
-                    lhs.activityInfo.packageName, lhs.activityInfo.name));
-            final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
-                    rhs.activityInfo.packageName, rhs.activityInfo.name));
-
-            if (lhsTarget != null && rhsTarget != null) {
-                final int selectProbabilityDiff = Float.compare(
-                        rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
-
-                if (selectProbabilityDiff != 0) {
-                    return selectProbabilityDiff > 0 ? 1 : -1;
-                }
-            }
-        }
-
-        CharSequence  sa = lhs.loadLabel(mPm);
-        if (sa == null) sa = lhs.activityInfo.name;
-        CharSequence  sb = rhs.loadLabel(mPm);
-        if (sb == null) sb = rhs.activityInfo.name;
-
-        return mCollator.compare(sa.toString().trim(), sb.toString().trim());
+        return mComparatorModel.getComparator().compare(lhs, rhs);
     }
 
     @Override
     public float getScore(ComponentName name) {
-        final ResolverTarget target = mTargetsDict.get(name);
-        if (target != null) {
-            return target.getSelectProbability();
-        }
-        return 0;
-    }
-
-    @Override
-    List<ComponentName> getTopComponentNames(int topK) {
-        return mTargetsDict.entrySet().stream()
-                .sorted((o1, o2) -> -Float.compare(getScore(o1.getKey()), getScore(o2.getKey())))
-                .limit(topK)
-                .map(Map.Entry::getKey)
-                .collect(Collectors.toList());
+        return mComparatorModel.getScore(name);
     }
 
     // update ranking model when the connection to it is valid.
     @Override
     public void updateModel(ComponentName componentName) {
         synchronized (mLock) {
-            if (mRanker != null) {
-                try {
-                    int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
-                            .indexOf(componentName);
-                    if (selectedPos >= 0 && mTargets != null) {
-                        final float selectedProbability = getScore(componentName);
-                        int order = 0;
-                        for (ResolverTarget target : mTargets) {
-                            if (target.getSelectProbability() > selectedProbability) {
-                                order++;
-                            }
-                        }
-                        logMetrics(order);
-                        mRanker.train(mTargets, selectedPos);
-                    } else {
-                        if (DEBUG) {
-                            Log.d(TAG, "Selected a unknown component: " + componentName);
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in Train: " + e);
-                }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "Ranker is null; skip updateModel.");
-                }
-            }
+            mComparatorModel.notifyOnTargetSelected(componentName);
         }
     }
 
@@ -313,19 +259,6 @@
         }
     }
 
-    // records metrics for evaluation.
-    private void logMetrics(int selectedPos) {
-        if (mRankerServiceName != null) {
-            MetricsLogger metricsLogger = new MetricsLogger();
-            LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
-            log.setComponentName(mRankerServiceName);
-            int isCategoryUsed = (mAnnotations == null) ? 0 : 1;
-            log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, isCategoryUsed);
-            log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
-            metricsLogger.write(log);
-        }
-    }
-
     // connect to a ranking service.
     private void initRanker(Context context) {
         synchronized (mLock) {
@@ -426,6 +359,7 @@
             }
             synchronized (mLock) {
                 mRanker = IResolverRankerService.Stub.asInterface(service);
+                mComparatorModel = buildUpdatedModel();
                 mConnectSignal.countDown();
             }
         }
@@ -443,6 +377,7 @@
         public void destroy() {
             synchronized (mLock) {
                 mRanker = null;
+                mComparatorModel = buildUpdatedModel();
             }
         }
     }
@@ -453,6 +388,7 @@
         mTargetsDict.clear();
         mTargets = null;
         mRankerServiceName = new ComponentName(mContext, this.getClass());
+        mComparatorModel = buildUpdatedModel();
         mResolvedRankerName = null;
         initRanker(mContext);
     }
@@ -508,4 +444,155 @@
         }
         return false;
     }
+
+    /**
+     * Re-construct a {@code ResolverRankerServiceComparatorModel} to replace the current model
+     * instance (if any) using the up-to-date {@code ResolverRankerServiceResolverComparator} ivar
+     * values.
+     *
+     * TODO: each time we replace the model instance, we're either updating the model to use
+     * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
+     * that wasn't available the last time the model was updated. For those latter cases, we should
+     * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
+     * we can probably simplify the logic in {@code ResolverRankerServiceComparatorModel} since we
+     * won't need to handle edge cases when the model data isn't fully prepared.
+     * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
+     * initializing the first time and now want to adjust some data, but still need to wait for
+     * changes to propagate to the other ivars before rebuilding the model.)
+     */
+    private ResolverRankerServiceComparatorModel buildUpdatedModel() {
+        // TODO: we don't currently guarantee that the underlying target list/map won't be mutated,
+        // so the ResolverComparatorModel may provide inconsistent results. We should make immutable
+        // copies of the data (waiting for any necessary remaining data before creating the model).
+        return new ResolverRankerServiceComparatorModel(
+                mStats,
+                mTargetsDict,
+                mTargets,
+                mCollator,
+                mRanker,
+                mRankerServiceName,
+                (mAnnotations != null),
+                mPm);
+    }
+
+    /**
+     * Implementation of a {@code ResolverComparatorModel} that provides the same ranking logic as
+     * the legacy {@code ResolverRankerServiceResolverComparator}, as a refactoring step toward
+     * removing the complex legacy API.
+     */
+    static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel {
+        private final Map<String, UsageStats> mStats;  // Treat as immutable.
+        private final Map<ComponentName, ResolverTarget> mTargetsDict;  // Treat as immutable.
+        private final List<ResolverTarget> mTargets;  // Treat as immutable.
+        private final Collator mCollator;
+        private final IResolverRankerService mRanker;
+        private final ComponentName mRankerServiceName;
+        private final boolean mAnnotationsUsed;
+        private final PackageManager mPm;
+
+        // TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's
+        // not written in a way that makes it clear whether we can derive one from the other (at
+        // least in this constructor).
+        ResolverRankerServiceComparatorModel(
+                Map<String, UsageStats> stats,
+                Map<ComponentName, ResolverTarget> targetsDict,
+                List<ResolverTarget> targets,
+                Collator collator,
+                IResolverRankerService ranker,
+                ComponentName rankerServiceName,
+                boolean annotationsUsed,
+                PackageManager pm) {
+            mStats = stats;
+            mTargetsDict = targetsDict;
+            mTargets = targets;
+            mCollator = collator;
+            mRanker = ranker;
+            mRankerServiceName = rankerServiceName;
+            mAnnotationsUsed = annotationsUsed;
+            mPm = pm;
+        }
+
+        @Override
+        public Comparator<ResolveInfo> getComparator() {
+            // TODO: doCompute() doesn't seem to be concerned about null-checking mStats. Is that
+            // a bug there, or do we have a way of knowing it will be non-null under certain
+            // conditions?
+            return (lhs, rhs) -> {
+                if (mStats != null) {
+                    final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
+                            lhs.activityInfo.packageName, lhs.activityInfo.name));
+                    final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
+                            rhs.activityInfo.packageName, rhs.activityInfo.name));
+
+                    if (lhsTarget != null && rhsTarget != null) {
+                        final int selectProbabilityDiff = Float.compare(
+                                rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+
+                        if (selectProbabilityDiff != 0) {
+                            return selectProbabilityDiff > 0 ? 1 : -1;
+                        }
+                    }
+                }
+
+                CharSequence  sa = lhs.loadLabel(mPm);
+                if (sa == null) sa = lhs.activityInfo.name;
+                CharSequence  sb = rhs.loadLabel(mPm);
+                if (sb == null) sb = rhs.activityInfo.name;
+
+                return mCollator.compare(sa.toString().trim(), sb.toString().trim());
+            };
+        }
+
+        @Override
+        public float getScore(ComponentName name) {
+            final ResolverTarget target = mTargetsDict.get(name);
+            if (target != null) {
+                return target.getSelectProbability();
+            }
+            return 0;
+        }
+
+        @Override
+        public void notifyOnTargetSelected(ComponentName componentName) {
+            if (mRanker != null) {
+                try {
+                    int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
+                            .indexOf(componentName);
+                    if (selectedPos >= 0 && mTargets != null) {
+                        final float selectedProbability = getScore(componentName);
+                        int order = 0;
+                        for (ResolverTarget target : mTargets) {
+                            if (target.getSelectProbability() > selectedProbability) {
+                                order++;
+                            }
+                        }
+                        logMetrics(order);
+                        mRanker.train(mTargets, selectedPos);
+                    } else {
+                        if (DEBUG) {
+                            Log.d(TAG, "Selected a unknown component: " + componentName);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error in Train: " + e);
+                }
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "Ranker is null; skip updateModel.");
+                }
+            }
+        }
+
+        /** Records metrics for evaluation. */
+        private void logMetrics(int selectedPos) {
+            if (mRankerServiceName != null) {
+                MetricsLogger metricsLogger = new MetricsLogger();
+                LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
+                log.setComponentName(mRankerServiceName);
+                log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed);
+                log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
+                metricsLogger.write(log);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index c94438e..ffb3752 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -520,6 +520,13 @@
     public static final String USE_UNBUNDLED_SHARESHEET = "use_unbundled_sharesheet";
 
     /**
+     * (int) The delay (in ms) before refreshing the Sharesheet UI after a change to the share
+     * target data model. For more info see go/sharesheet-list-view-update-delay.
+     */
+    public static final String SHARESHEET_LIST_VIEW_UPDATE_DELAY =
+            "sharesheet_list_view_update_delay";
+
+    /**
      * (string) Name of the default QR code scanner activity. On the eligible devices this activity
      * is provided by GMS core.
      */
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index fc12e17..a8d7231 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -459,8 +459,12 @@
     return nullptr;
   }
 
-  auto overlayable_name_native = std::string(env->GetStringUTFChars(overlayable_name, NULL));
+  const char* overlayable_name_native = env->GetStringUTFChars(overlayable_name, nullptr);
+  if (overlayable_name_native == nullptr) {
+      return nullptr;
+  }
   auto actor = overlayable_map.find(overlayable_name_native);
+  env->ReleaseStringUTFChars(overlayable_name, overlayable_name_native);
   if (actor == overlayable_map.end()) {
     return nullptr;
   }
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index db33863d..c947fba 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -48,6 +48,7 @@
 
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 using namespace android;
 using namespace img_utils;
@@ -1264,16 +1265,14 @@
 
     sp<NativeContext> nativeContext = new NativeContext(characteristics, results);
 
-    const char* captureTime = env->GetStringUTFChars(formattedCaptureTime, nullptr);
-
-    size_t len = strlen(captureTime) + 1;
-    if (len != NativeContext::DATETIME_COUNT) {
+    ScopedUtfChars captureTime(env, formattedCaptureTime);
+    if (captureTime.size() + 1 != NativeContext::DATETIME_COUNT) {
         jniThrowException(env, "java/lang/IllegalArgumentException",
                 "Formatted capture time string length is not required 20 characters");
         return;
     }
 
-    nativeContext->setCaptureTime(String8(captureTime));
+    nativeContext->setCaptureTime(String8(captureTime.c_str()));
 
     DngCreator_setNativeContext(env, thiz, nativeContext);
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b23d8ca..19b72bf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2966,6 +2966,9 @@
          config_preventImeStartupUnlessTextEditor. -->
     <string-array name="config_nonPreemptibleInputMethods" translatable="false" />
 
+    <!-- Flag indicating that enhanced confirmation mode is enabled. -->
+    <bool name="config_enhancedConfirmationModeEnabled">true</bool>
+
     <!-- The list of classes that should be added to the notification ranking pipeline.
      See {@link com.android.server.notification.NotificationSignalExtractor}
       If you add a new extractor to this list make sure to update
@@ -5775,4 +5778,7 @@
     <string name="safety_protection_display_text"></string>
 
     <!-- End safety protection resources to be overlaid -->
+
+    <!-- List of the labels of requestable device state config values -->
+    <string-array name="config_deviceStatesAvailableForAppRequests"/>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 21b2351..012030e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2256,6 +2256,7 @@
   <java-symbol type="bool" name="config_killableInputMethods" />
   <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
   <java-symbol type="array" name="config_nonPreemptibleInputMethods" />
+  <java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
@@ -4773,6 +4774,7 @@
   <java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" />
   <java-symbol type="drawable" name="ic_swap_horiz" />
   <java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" />
+  <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
 
   <!-- For app language picker -->
   <java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index d0bd4b8..14b48ed 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -16,14 +16,20 @@
 
 package android.widget;
 
+import android.view.View;
+import android.view.ViewGroup;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DateTimeViewTest {
@@ -39,7 +45,33 @@
         dateTimeView.detachedFromWindow();
     }
 
+    @UiThreadTest
+    @Test
+    public void noChangeInRelativeText_doesNotTriggerRelayout() {
+        // Week in the future is chosen because it'll result in a stable string during this test
+        // run. This should be improved once the class is refactored to be more testable in
+        // respect of clock retrieval.
+        final long weekInTheFuture = System.currentTimeMillis() + Duration.ofDays(7).toMillis();
+        final TestDateTimeView dateTimeView = new TestDateTimeView();
+        dateTimeView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+        dateTimeView.setShowRelativeTime(true);
+        dateTimeView.setTime(weekInTheFuture);
+        // View needs to be measured to request layout, skipping this would make this test pass
+        // always.
+        dateTimeView.measure(View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.UNSPECIFIED),
+                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.UNSPECIFIED));
+        dateTimeView.reset();
+
+        // This should not change the text content and thus no relayout is expected.
+        dateTimeView.setTime(weekInTheFuture + 1000);
+
+        Assert.assertFalse(dateTimeView.wasLayoutRequested());
+    }
+
     private static class TestDateTimeView extends DateTimeView {
+        private boolean mRequestedLayout = false;
+
         TestDateTimeView() {
             super(InstrumentationRegistry.getContext());
         }
@@ -51,5 +83,18 @@
         void detachedFromWindow() {
             super.onDetachedFromWindow();
         }
+
+        public void requestLayout() {
+            super.requestLayout();
+            mRequestedLayout = true;
+        }
+
+        public boolean wasLayoutRequested() {
+            return mRequestedLayout;
+        }
+
+        public void reset() {
+            mRequestedLayout = false;
+        }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
index 04b8886..3e640c1 100644
--- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -115,11 +115,6 @@
 
             @Override
             void handleResultMessage(Message message) {}
-
-            @Override
-            List<ComponentName> getTopComponentNames(int topK) {
-                return null;
-            }
         };
         return testComparator;
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cf78646..b38e1c2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -81,7 +81,6 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.chooser.ChooserTarget;
-import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.CallSuper;
@@ -623,7 +622,7 @@
         List<ResolvedComponentInfo> stableCopy =
                 createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
         waitForIdle();
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
                 .perform(click());
@@ -1437,7 +1436,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                 activity.getAdapter().getCount(), is(3));
@@ -1513,7 +1512,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                 activity.getAdapter().getCount(), is(3));
@@ -1595,7 +1594,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                 wrapper.getAdapter().getCount(), is(3));
@@ -1667,7 +1666,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
                 wrapper.getAdapter().getCount(), is(4));
@@ -1754,7 +1753,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat(
                 String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)",
@@ -1879,12 +1878,13 @@
             return true;
         };
 
-        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
         onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
         waitForIdle();
         // wait for the share sheet to expand
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         onView(first(allOf(
                 withText(workResolvedComponentInfos.get(0)
@@ -2023,7 +2023,7 @@
                 .check(matches(isDisplayed()));
     }
 
-    @Test
+    @Test @Ignore("b/222124533")
     public void testAppTargetLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -2042,6 +2042,10 @@
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
         waitForIdle();
 
+        // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully
+        // populated; without one, this test flakes. Ideally we should address the need for a
+        // timeout everywhere instead of introducing one to fix this particular test.
+
         assertThat(activity.getAdapter().getCount(), is(2));
         onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
 
@@ -2143,7 +2147,7 @@
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
         // TODO: restructure the tests b/129870719
-        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+        Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
 
         assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
                 activity.getAdapter().getCount(), is(3));
@@ -2324,7 +2328,7 @@
         assertThat(logger.numCalls(), is(6));
     }
 
-    @Test
+    @Test @Ignore("b/222124533")
     public void testSwitchProfileLogging() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -3069,8 +3073,15 @@
     // framework code on the device is up-to-date.
     // TODO: is there a better way to do this? (Other than abandoning inheritance-based DI wrapper?)
     private int getRuntimeResourceId(String name, String defType) {
-        int id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
+        int id = -1;
+        if (ChooserActivityOverrideData.getInstance().resources != null) {
+            id = ChooserActivityOverrideData.getInstance().resources.getIdentifier(
+                  name, defType, "android");
+        } else {
+            id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
+        }
         assertThat(id, greaterThan(0));
+
         return id;
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
new file mode 100644
index 0000000..fbbe57c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 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.app;
+
+import android.content.ComponentName;
+import android.content.pm.ResolveInfo;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Basic {@link ResolverComparatorModel} implementation that sorts according to a pre-defined (or
+ * default) {@link java.util.Comparator}.
+ */
+public class FakeResolverComparatorModel implements ResolverComparatorModel {
+    private final Comparator<ResolveInfo> mComparator;
+
+    public static FakeResolverComparatorModel makeModelFromComparator(
+            Comparator<ResolveInfo> comparator) {
+        return new FakeResolverComparatorModel(comparator);
+    }
+
+    public static FakeResolverComparatorModel makeDefaultModel() {
+       return makeModelFromComparator(Comparator.comparing(ri -> ri.activityInfo.name));
+    }
+
+    @Override
+    public Comparator<ResolveInfo> getComparator() {
+        return mComparator;
+    }
+
+    @Override
+    public float getScore(ComponentName name) {
+        return 0.0f;  // Models are not required to provide numerical scores.
+    }
+
+    @Override
+    public void notifyOnTargetSelected(ComponentName componentName) {
+        System.out.println(
+                "User selected " + componentName + " under model " + System.identityHashCode(this));
+    }
+
+    private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {
+        mComparator = comparator;
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 091e322..bdeb474 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -230,7 +230,9 @@
         final int mode = mAppOpsManager.noteOpNoThrow(
                 AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
                 uid, packageName);
-        final boolean appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED;
+        final boolean ecmEnabled = getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+        final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
         if (appOpsAllowed || isEnabled) {
             setEnabled(true);
         } else {
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index d4b4a74..f492c06 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -94,6 +94,12 @@
         void setPrimaryTextColor(int color);
 
         /**
+         * When the view is displayed on Dream, set the flag to true, immediately after the view is
+         * created.
+         */
+        void setIsDreaming(boolean isDreaming);
+
+        /**
          * Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD
          */
         void setDozeAmount(float amount);
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 7962e22..534c80d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -80,7 +80,7 @@
         android:background="@drawable/qs_media_light_source"
         android:forceHasOverlappingRendering="false"
         android:layout_width="wrap_content"
-        android:layout_height="48dp"
+        android:layout_height="@dimen/min_clickable_item_size"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
@@ -92,8 +92,9 @@
         <LinearLayout
             android:id="@+id/media_seamless_button"
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/qs_seamless_height"
+            android:layout_height="wrap_content"
             android:minHeight="@dimen/qs_seamless_height"
+            android:maxHeight="@dimen/min_clickable_item_size"
             android:theme="@style/MediaPlayer.SolidButton"
             android:background="@drawable/qs_media_seamless_background"
             android:orientation="horizontal"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 659a578..79ba7ead 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -16,6 +16,8 @@
   -->
 
 <!-- Layout for media recommendations inside QSPanel carousel -->
+<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
+     constraints. -->
 <com.android.systemui.util.animation.TransitionLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -46,14 +48,6 @@
     <FrameLayout
         android:id="@+id/media_cover1_container"
         style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_title1"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintHorizontal_bias="1.0"
-        app:layout_constraintVertical_bias="0.5"
         >
         <ImageView
             android:id="@+id/media_cover1"
@@ -71,31 +65,16 @@
     <TextView
         android:id="@+id/media_title1"
         style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
         />
 
     <TextView
         android:id="@+id/media_subtitle1"
         style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title1"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
         />
 
     <FrameLayout
         android:id="@+id/media_cover2_container"
         style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title2"
-        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
-        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintVertical_bias="0.5"
         >
         <ImageView
             android:id="@+id/media_cover2"
@@ -111,31 +90,16 @@
     <TextView
         android:id="@+id/media_title2"
         style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
         />
 
     <TextView
         android:id="@+id/media_subtitle2"
         style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title2"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
         />
 
     <FrameLayout
         android:id="@+id/media_cover3_container"
         style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title3"
-        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constraintVertical_bias="0.5"
         >
         <ImageView
             android:id="@+id/media_cover3"
@@ -151,20 +115,11 @@
     <TextView
         android:id="@+id/media_title3"
         style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
         />
 
     <TextView
         android:id="@+id/media_subtitle3"
         style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title3"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
         />
 
     <include
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index a6113473..b7d4b3a 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -25,8 +25,10 @@
         />
 
     <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
-         the same as the constraints in media_smartspace_recommendations. But due to how
-         ConstraintSets work, all the constraints need to be in the same place.
+         the same as the constraints in media_recommendations_expanded.xml. But, due to how
+         ConstraintSets work, all the constraints need to be in the same place. So, the shared
+         constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
+         the constraints are instead duplicated between here and media_recommendations_expanded.xml.
          Ditto for the other cover containers. -->
     <Constraint
         android:id="@+id/media_cover1_container"
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 09ffebb..ce25a7d 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License
   -->
 <ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android" >
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
 
     <Constraint
         android:id="@+id/sizing_view"
@@ -23,4 +25,99 @@
         android:layout_height="@dimen/qs_media_session_height_expanded"
         />
 
+    <Constraint
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/media_title1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
+        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintVertical_bias="0.4"
+        />
+
+    <Constraint
+        android:id="@+id/media_title1"
+        style="@style/MediaPlayer.Recommendation.Text.Title"
+        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
+        app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
+        />
+
+    <Constraint
+        android:id="@+id/media_subtitle1"
+        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_title1"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="@dimen/qs_media_padding"
+        />
+
+    <Constraint
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/media_title2"
+        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
+        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:layout_constraintVertical_bias="0.4"
+        />
+
+    <Constraint
+        android:id="@+id/media_title2"
+        style="@style/MediaPlayer.Recommendation.Text.Title"
+        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
+        app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
+        />
+
+    <Constraint
+        android:id="@+id/media_subtitle2"
+        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_title2"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="@dimen/qs_media_padding"
+        />
+
+    <Constraint
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/media_title3"
+        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginEnd="@dimen/qs_media_padding"
+        app:layout_constraintVertical_chainStyle="packed"
+        app:layout_constraintVertical_bias="0.4"
+        />
+
+    <Constraint
+        android:id="@+id/media_title3"
+        style="@style/MediaPlayer.Recommendation.Text.Title"
+        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
+        app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
+        />
+
+    <Constraint
+        android:id="@+id/media_subtitle3"
+        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
+        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
+        app:layout_constraintTop_toBottomOf="@+id/media_title3"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginBottom="@dimen/qs_media_padding"
+        />
+
 </ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
index e253360..b8319e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -45,7 +45,7 @@
             private boolean mHandled;
 
             @Override
-            public void onAnimationStarted() {
+            public void onAnimationStarted(long elapsedRealTime) {
                 // OnAnimationStartedListener can be called numerous times, so debounce here to
                 // prevent multiple callbacks
                 if (mHandled) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index e38d2ba..add2d02 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -67,7 +67,7 @@
                 callbackHandler,
                 new ActivityOptions.OnAnimationStartedListener() {
                     @Override
-                    public void onAnimationStarted() {
+                    public void onAnimationStarted(long elapsedRealTime) {
                         if (callback != null) {
                             callbackHandler.post(callback);
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8e11326..d4dad73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -149,7 +149,7 @@
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskStackChanged() {
-            mHandler.post(AuthController.this::handleTaskStackChanged);
+            mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
         }
     };
 
@@ -200,7 +200,7 @@
         }
     };
 
-    private void handleTaskStackChanged() {
+    private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
             try {
@@ -212,7 +212,7 @@
                     final String topPackage = runningTasks.get(0).topActivity.getPackageName();
                     if (!topPackage.contentEquals(clientPackage)
                             && !Utils.isSystem(mContext, clientPackage)) {
-                        Log.w(TAG, "Evicting client due to: " + topPackage);
+                        Log.e(TAG, "Evicting client due to: " + topPackage);
                         mCurrentDialog.dismissWithoutCallback(true /* animate */);
                         mCurrentDialog = null;
                         mOrientationListener.disable();
@@ -919,6 +919,10 @@
         mCurrentDialog = newDialog;
         mCurrentDialog.show(mWindowManager, savedState);
         mOrientationListener.enable();
+
+        if (!promptInfo.isAllowBackgroundAuthentication()) {
+            mHandler.post(this::cancelIfOwnerIsNotInForeground);
+        }
     }
 
     private void onDialogDismissed(@DismissedReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 67d0db2..842791f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -128,10 +128,6 @@
 
         if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) {
             mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
-
-            mLockScreenFp.setTranslationX(mBurnInOffsetX);
-            mLockScreenFp.setTranslationY(mBurnInOffsetY);
-            mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
             mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
         } else if (mInterpolatedDarkAmount == 0f) {
             mBgProtection.setAlpha(mAlpha / 255f);
@@ -140,6 +136,9 @@
             mBgProtection.setAlpha(0f);
             mLockScreenFp.setAlpha(0f);
         }
+        mLockScreenFp.setTranslationX(mBurnInOffsetX);
+        mLockScreenFp.setTranslationY(mBurnInOffsetY);
+        mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
 
         mAodFp.setTranslationX(mBurnInOffsetX);
         mAodFp.setTranslationY(mBurnInOffsetY);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
index 9b99c52..a309547 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
@@ -98,6 +98,7 @@
             view.setPrimaryTextColor(Color.WHITE)
             smartspaceViews.add(view)
             connectSession()
+            view.setDozeAmount(0f)
         }
 
         override fun onViewDetachedFromWindow(v: View) {
@@ -127,6 +128,7 @@
         }
 
         val view = buildView(parent)
+
         connectSession()
 
         return view
@@ -136,12 +138,13 @@
         return if (plugin != null) {
             var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
                     .getView()
-
-            if (view is View) {
-                return view
+            if (view !is View) {
+                return null
             }
 
-            return null
+            view.setIsDreaming(true)
+
+            return view
         } else {
             null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6843bf..2e13732 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2604,6 +2604,9 @@
 
             finishSurfaceBehindRemoteAnimation(cancelled);
             mSurfaceBehindRemoteAnimationRequested = false;
+
+            // The remote animation is over, so we're not going away anymore.
+            mKeyguardStateController.notifyKeyguardGoingAway(false);
         });
 
         mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
@@ -2622,6 +2625,7 @@
             ActivityTaskManager.getService().keyguardGoingAway(
                     WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
                             | WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER);
+            mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
             e.printStackTrace();
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 39f00f4..2467169 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -208,6 +208,16 @@
         return factory.create("MediaView", 100);
     }
 
+    /**
+     * Provides a buffer for media playback state changes
+     */
+    @Provides
+    @SysUISingleton
+    @MediaTimeoutListenerLog
+    public static LogBuffer providesMediaTimeoutListenerLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaTimeout", 100);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
new file mode 100644
index 0000000..53963fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for {@link com.android.systemui.media.MediaTimeoutLogger}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTimeoutListenerLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index af54e96..d2c35bd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -991,6 +991,9 @@
         List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
         int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
                 MEDIA_RECOMMENDATION_MAX_NUM);
+
+        boolean hasTitle = false;
+        boolean hasSubtitle = false;
         int uiComponentIndex = 0;
         for (int itemIndex = 0;
                 itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
@@ -1036,26 +1039,33 @@
 
             // Set up title
             CharSequence title = recommendation.getTitle();
+            hasTitle |= !TextUtils.isEmpty(title);
             TextView titleView =
                     mRecommendationViewHolder.getMediaTitles().get(uiComponentIndex);
             titleView.setText(title);
-            // TODO(b/223603970): If none of them have titles, should we then hide the views?
 
             // Set up subtitle
-            CharSequence subtitle = recommendation.getSubtitle();
-            TextView subtitleView =
-                    mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex);
             // It would look awkward to show a subtitle if we don't have a title.
             boolean shouldShowSubtitleText = !TextUtils.isEmpty(title);
-            CharSequence subtitleText = shouldShowSubtitleText ? subtitle : "";
-            subtitleView.setText(subtitleText);
-            // TODO(b/223603970): If none of them have subtitles, should we then hide the views?
+            CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : "";
+            hasSubtitle |= !TextUtils.isEmpty(subtitle);
+            TextView subtitleView =
+                    mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex);
+            subtitleView.setText(subtitle);
 
             uiComponentIndex++;
         }
-
         mSmartspaceMediaItemsCount = uiComponentIndex;
 
+        // If there's no subtitles and/or titles for any of the albums, hide those views.
+        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+        final boolean titlesVisible = hasTitle;
+        final boolean subtitlesVisible = hasSubtitle;
+        mRecommendationViewHolder.getMediaTitles().forEach((titleView) ->
+                setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible));
+        mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) ->
+                setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible));
+
         // Guts
         Runnable onDismissClickedRunnable = () -> {
             closeGuts();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 51755065..8c6710a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -19,7 +19,6 @@
 import android.media.session.MediaController
 import android.media.session.PlaybackState
 import android.os.SystemProperties
-import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -28,9 +27,6 @@
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
-private const val DEBUG = true
-private const val TAG = "MediaTimeout"
-
 @VisibleForTesting
 val PAUSED_MEDIA_TIMEOUT = SystemProperties
         .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
@@ -45,7 +41,8 @@
 @SysUISingleton
 class MediaTimeoutListener @Inject constructor(
     private val mediaControllerFactory: MediaControllerFactory,
-    @Main private val mainExecutor: DelayableExecutor
+    @Main private val mainExecutor: DelayableExecutor,
+    private val logger: MediaTimeoutLogger
 ) : MediaDataManager.Listener {
 
     private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
@@ -75,9 +72,7 @@
             }
 
             // If listener was destroyed previously, we'll need to re-register it
-            if (DEBUG) {
-                Log.d(TAG, "Reusing destroyed listener $key")
-            }
+            logger.logReuseListener(key)
             reusedListener = it
         }
 
@@ -86,16 +81,12 @@
         val migrating = oldKey != null && key != oldKey
         if (migrating) {
             reusedListener = mediaListeners.remove(oldKey)
-            if (reusedListener != null) {
-                if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
-            } else {
-                Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
-            }
+            logger.logMigrateListener(oldKey, key, reusedListener != null)
         }
 
         reusedListener?.let {
             val wasPlaying = it.playing ?: false
-            if (DEBUG) Log.d(TAG, "updating listener for $key, was playing? $wasPlaying")
+            logger.logUpdateListener(key, wasPlaying)
             it.mediaData = data
             it.key = key
             mediaListeners[key] = it
@@ -105,7 +96,7 @@
                 // until we're done.
                 mainExecutor.execute {
                     if (mediaListeners[key]?.playing == true) {
-                        if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+                        logger.logDelayedUpdate(key)
                         timeoutCallback.invoke(key, false /* timedOut */)
                     }
                 }
@@ -169,10 +160,7 @@
         }
 
         override fun onSessionDestroyed() {
-            if (DEBUG) {
-                Log.d(TAG, "Session destroyed for $key")
-            }
-
+            logger.logSessionDestroyed(key)
             if (resumption == true) {
                 // Some apps create a session when MBS is queried. We should unregister the
                 // controller since it will no longer be valid, but don't cancel the timeout
@@ -185,9 +173,7 @@
         }
 
         private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
-            if (DEBUG) {
-                Log.v(TAG, "processState $key: $state")
-            }
+            logger.logPlaybackState(key, state)
 
             val isPlaying = state != null && isPlayingState(state.state)
             val resumptionChanged = resumption != mediaData.resumption
@@ -198,12 +184,10 @@
             resumption = mediaData.resumption
 
             if (!isPlaying) {
-                if (DEBUG) {
-                    Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
-                }
+                logger.logScheduleTimeout(key, isPlaying, resumption!!)
                 if (cancellation != null && !resumptionChanged) {
                     // if the media changed resume state, we'll need to adjust the timeout length
-                    if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
+                    logger.logCancelIgnored(key)
                     return
                 }
                 expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
@@ -214,9 +198,7 @@
                 }
                 cancellation = mainExecutor.executeDelayed({
                     cancellation = null
-                    if (DEBUG) {
-                        Log.v(TAG, "Execute timeout for $key")
-                    }
+                    logger.logTimeout(key)
                     timedOut = true
                     // this event is async, so it's safe even when `dispatchEvents` is false
                     timeoutCallback(key, timedOut)
@@ -232,9 +214,7 @@
 
         private fun expireMediaTimeout(mediaKey: String, reason: String) {
             cancellation?.apply {
-                if (DEBUG) {
-                    Log.v(TAG, "media timeout cancelled for  $mediaKey, reason: $reason")
-                }
+                logger.logTimeoutCancelled(mediaKey, reason)
                 run()
             }
             cancellation = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
new file mode 100644
index 0000000..a865159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 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.systemui.media
+
+import android.media.session.PlaybackState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.MediaTimeoutListenerLog
+import javax.inject.Inject
+
+private const val TAG = "MediaTimeout"
+
+/**
+ * A buffered log for [MediaTimeoutListener] events
+ */
+@SysUISingleton
+class MediaTimeoutLogger @Inject constructor(
+    @MediaTimeoutListenerLog private val buffer: LogBuffer
+) {
+    fun logReuseListener(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+        },
+        {
+            "reuse listener: $str1"
+        }
+    )
+
+    fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = oldKey
+            str2 = newKey
+            bool1 = hadListener
+        },
+        {
+            "migrate from $str1 to $str2, had listener? $bool1"
+        }
+    )
+
+    fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+            bool1 = wasPlaying
+        },
+        {
+            "updating $str1, was playing? $bool1"
+        }
+    )
+
+    fun logDelayedUpdate(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+        },
+        {
+            "deliver delayed playback state for $str1"
+        }
+    )
+
+    fun logSessionDestroyed(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+        },
+        {
+            "session destroyed $str1"
+        }
+    )
+
+    fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log(
+        TAG,
+        LogLevel.VERBOSE,
+        {
+            str1 = key
+            str2 = state?.toString()
+        },
+        {
+            "state update: key=$str1 state=$str2"
+        }
+    )
+
+    fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+            bool1 = playing
+            bool2 = resumption
+        },
+        {
+            "schedule timeout $str1, playing=$bool1 resumption=$bool2"
+        }
+    )
+
+    fun logCancelIgnored(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+        },
+        {
+            "cancellation already exists for $str1"
+        }
+    )
+
+    fun logTimeout(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+        },
+        {
+            "execute timeout for $str1"
+        }
+    )
+
+    fun logTimeoutCancelled(key: String, reason: String) = buffer.log(
+        TAG,
+        LogLevel.VERBOSE,
+        {
+            str1 = key
+            str2 = reason
+        },
+        {
+            "media timeout cancelled for $str1, reason: $str2"
+        }
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 193166b..0359c63 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -151,13 +151,21 @@
     }
 
     /**
-     * Event indicating that the user has moved the seek bar but hasn't yet finished the gesture.
+     * Event indicating that the user has moved the seek bar.
+     *
      * @param position Current location in the track.
      */
     @AnyThread
     fun onSeekProgress(position: Long) = bgExecutor.execute {
         if (scrubbing) {
+            // The user hasn't yet finished their touch gesture, so only update the data for visual
+            // feedback and don't update [controller] yet.
             _data = _data.copy(elapsedTime = position.toInt())
+        } else {
+            // The seek progress came from an a11y action and we should immediately update to the
+            // new position. (a11y actions to change the seekbar position don't trigger
+            // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
+            onSeek(position)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 050b670..233778d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.app.IActivityTaskManager;
+
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
 
@@ -234,6 +236,12 @@
         default void onKeyguardFadingAwayChanged() {}
 
         /**
+         * We've called {@link IActivityTaskManager#keyguardGoingAway}, which initiates the unlock
+         * sequence.
+         */
+        default void onKeyguardGoingAwayChanged() {}
+
+        /**
          * Triggered when the keyguard dismiss amount has changed, via either a swipe gesture or an
          * animation.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 2f56576..be5da37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -323,6 +323,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
                     keyguardGoingAway ? 1 : 0);
             mKeyguardGoingAway = keyguardGoingAway;
+            new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 03b18ae..eefc412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -603,15 +603,25 @@
     }
 
     @Test
+    public void testClientNotified_whenTaskStackChangesDuringShow() throws Exception {
+        switchTask("other_package");
+        showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+        mTestableLooper.processAllMessages();
+
+        assertNull(mAuthController.mCurrentDialog);
+        assertNull(mAuthController.mReceiver);
+        verify(mDialog1).dismissWithoutCallback(true /* animate */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
+    }
+
+    @Test
     public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
 
-        List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
-        ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
-        taskInfo.topActivity = mock(ComponentName.class);
-        when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
-        tasks.add(taskInfo);
-        when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+        switchTask("other_package");
 
         mAuthController.mTaskStackListener.onTaskStackChanged();
         mTestableLooper.processAllMessages();
@@ -729,6 +739,16 @@
                 BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
     }
 
+    private void switchTask(String packageName) {
+        final List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
+        final ActivityManager.RunningTaskInfo taskInfo =
+                mock(ActivityManager.RunningTaskInfo.class);
+        taskInfo.topActivity = mock(ComponentName.class);
+        when(taskInfo.topActivity.getPackageName()).thenReturn(packageName);
+        tasks.add(taskInfo);
+        when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+    }
+
     private PromptInfo createTestPromptInfo() {
         PromptInfo promptInfo = new PromptInfo();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 83fb82c..6a9c3e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -1401,22 +1401,23 @@
         val subtitle1 = "Subtitle1"
         val subtitle2 = "Subtitle2"
         val subtitle3 = "Subtitle3"
+        val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
 
         val data = smartspaceData.copy(
             recommendations = listOf(
                 SmartspaceAction.Builder("id1", title1)
                     .setSubtitle(subtitle1)
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                    .setIcon(icon)
                     .setExtras(Bundle.EMPTY)
                     .build(),
                 SmartspaceAction.Builder("id2", title2)
                     .setSubtitle(subtitle2)
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+                    .setIcon(icon)
                     .setExtras(Bundle.EMPTY)
                     .build(),
                 SmartspaceAction.Builder("id3", title3)
                     .setSubtitle(subtitle3)
-                    .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+                    .setIcon(icon)
                     .setExtras(Bundle.EMPTY)
                     .build()
             )
@@ -1449,6 +1450,135 @@
         assertThat(recSubtitle1.text).isEqualTo("")
     }
 
+    @Test
+    fun bindRecommendation_someHaveTitles_allTitleViewsShown() {
+        useRealConstraintSets()
+        player.attachRecommendation(recommendationViewHolder)
+
+        val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
+        val data = smartspaceData.copy(
+            recommendations = listOf(
+                SmartspaceAction.Builder("id1", "")
+                    .setSubtitle("fake subtitle")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id2", "title2")
+                    .setSubtitle("fake subtitle")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id3", "")
+                    .setSubtitle("fake subtitle")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build()
+            )
+        )
+        player.bindRecommendation(data)
+
+        assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
+    }
+
+    @Test
+    fun bindRecommendation_someHaveSubtitles_allSubtitleViewsShown() {
+        useRealConstraintSets()
+        player.attachRecommendation(recommendationViewHolder)
+
+        val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
+        val data = smartspaceData.copy(
+            recommendations = listOf(
+                SmartspaceAction.Builder("id1", "")
+                    .setSubtitle("")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id2", "title2")
+                    .setSubtitle("")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id3", "title3")
+                    .setSubtitle("subtitle3")
+                    .setIcon(icon)
+                    .setExtras(Bundle.EMPTY)
+                    .build()
+            )
+        )
+        player.bindRecommendation(data)
+
+        assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
+        assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
+    }
+
+    @Test
+    fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
+        useRealConstraintSets()
+        player.attachRecommendation(recommendationViewHolder)
+        val data = smartspaceData.copy(
+            recommendations = listOf(
+                SmartspaceAction.Builder("id1", "title1")
+                    .setSubtitle("")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id2", "title2")
+                    .setSubtitle("")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id3", "title3")
+                    .setSubtitle("")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+                    .setExtras(Bundle.EMPTY)
+                    .build()
+            )
+        )
+
+        player.bindRecommendation(data)
+
+        assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+    }
+
+    @Test
+    fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
+        useRealConstraintSets()
+        player.attachRecommendation(recommendationViewHolder)
+        val data = smartspaceData.copy(
+            recommendations = listOf(
+                SmartspaceAction.Builder("id1", "")
+                    .setSubtitle("subtitle1")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id2", "")
+                    .setSubtitle("subtitle2")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+                    .setExtras(Bundle.EMPTY)
+                    .build(),
+                SmartspaceAction.Builder("id3", "")
+                    .setSubtitle("subtitle3")
+                    .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+                    .setExtras(Bundle.EMPTY)
+                    .build()
+            )
+        )
+
+        player.bindRecommendation(data)
+
+        assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+    }
+
     private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
         withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 9116983..60cbb17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
@@ -33,7 +34,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
@@ -62,6 +62,7 @@
 
     @Mock private lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock private lateinit var mediaController: MediaController
+    @Mock private lateinit var logger: MediaTimeoutLogger
     private lateinit var executor: FakeExecutor
     @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
     @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
@@ -77,7 +78,7 @@
     fun setup() {
         `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
         executor = FakeExecutor(FakeSystemClock())
-        mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
+        mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor, logger)
         mediaTimeoutListener.timeoutCallback = timeoutCallback
 
         // Create a media session and notification for testing.
@@ -112,8 +113,9 @@
         `when`(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+        verify(logger).logPlaybackState(eq(KEY), eq(playingState))
 
-        // Ignores is same key
+        // Ignores if same key
         clearInvocations(mediaController)
         mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData)
         verify(mediaController, never()).registerCallback(anyObject())
@@ -125,6 +127,7 @@
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
         assertThat(executor.numPending()).isEqualTo(1)
         verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+        verify(logger).logScheduleTimeout(eq(KEY), eq(false), eq(false))
         assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
     }
 
@@ -153,6 +156,7 @@
 
     @Test
     fun testOnMediaDataLoaded_migratesKeys() {
+        val newKey = "NEWKEY"
         // From not playing
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         clearInvocations(mediaController)
@@ -161,9 +165,10 @@
         val playingState = mock(android.media.session.PlaybackState::class.java)
         `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
         `when`(mediaController.playbackState).thenReturn(playingState)
-        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
         verify(mediaController).unregisterCallback(anyObject())
         verify(mediaController).registerCallback(anyObject())
+        verify(logger).logMigrateListener(eq(KEY), eq(newKey), eq(true))
 
         // Enqueues callback
         assertThat(executor.numPending()).isEqualTo(1)
@@ -171,6 +176,7 @@
 
     @Test
     fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() {
+        val newKey = "NEWKEY"
         // From not playing
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         clearInvocations(mediaController)
@@ -179,11 +185,12 @@
         val playingState = mock(android.media.session.PlaybackState::class.java)
         `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
         `when`(mediaController.playbackState).thenReturn(playingState)
-        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+        mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
 
         // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
         // is another scheduled
         assertThat(executor.numPending()).isEqualTo(1)
+        verify(logger).logUpdateListener(eq(newKey), eq(false))
     }
 
     @Test
@@ -205,6 +212,7 @@
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
         assertThat(executor.numPending()).isEqualTo(0)
+        verify(logger).logTimeoutCancelled(eq(KEY), any())
     }
 
     @Test
@@ -249,6 +257,7 @@
         // THEN the controller is unregistered and timeout run
         verify(mediaController).unregisterCallback(anyObject())
         assertThat(executor.numPending()).isEqualTo(0)
+        verify(logger).logSessionDestroyed(eq(KEY))
     }
 
     @Test
@@ -270,6 +279,7 @@
             runAllReady()
         }
         verify(timeoutCallback).invoke(eq(KEY), eq(false))
+        verify(logger).logReuseListener(eq(KEY))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index afc9c81..82aa612 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -375,16 +375,20 @@
     }
 
     @Test
-    fun onProgressChangedFromUserWithoutStartTrackingTouch() {
-        // WHEN user starts dragging the seek bar
+    fun onProgressChangedFromUserWithoutStartTrackingTouch_transportUpdated() {
+        whenever(mockController.transportControls).thenReturn(mockTransport)
+        viewModel.updateController(mockController)
         val pos = 42
         val bar = SeekBar(context)
+
+        // WHEN we get an onProgressChanged event without an onStartTrackingTouch event
         with(viewModel.seekBarListener) {
             onProgressChanged(bar, pos, true)
         }
         fakeExecutor.runAllReady()
-        // THEN then elapsed time should not be updated
-        assertThat(viewModel.progress.value!!.elapsedTime).isNull()
+
+        // THEN we immediately update the transport
+        verify(mockTransport).seekTo(pos.toLong())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 57803e8..8340900 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -136,6 +136,8 @@
 
         override fun setPrimaryTextColor(color: Int) {}
 
+        override fun setIsDreaming(isDreaming: Boolean) {}
+
         override fun setDozeAmount(amount: Float) {}
 
         override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
@@ -173,6 +175,7 @@
         stateChangeListener.onViewAttachedToWindow(mockView)
 
         verify(smartspaceManager).createSmartspaceSession(any())
+        verify(mockView).setDozeAmount(0f)
 
         stateChangeListener.onViewDetachedFromWindow(mockView)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 188baaf..ce58a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -544,6 +544,9 @@
             override fun setPrimaryTextColor(color: Int) {
             }
 
+            override fun setIsDreaming(isDreaming: Boolean) {
+            }
+
             override fun setDozeAmount(amount: Float) {
             }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b..3e97b91 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -69,6 +69,7 @@
 import static android.app.AppOpsManager.makeKey;
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
 import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
@@ -2875,6 +2876,10 @@
             // features may require permissions our remote caller does not have.
             final long identity = Binder.clearCallingIdentity();
             try {
+                if (shouldIgnoreCallback(switchedCode, callback.mCallingPid,
+                        callback.mCallingUid)) {
+                    continue;
+                }
                 callback.mCallback.opChanged(switchedCode, uid, packageName);
             } catch (RemoteException e) {
                 /* ignore */
@@ -4205,6 +4210,9 @@
             for (int i = 0; i < callbackCount; i++) {
                 final ActiveCallback callback = callbacks.valueAt(i);
                 try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
                     callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
                             active, attributionFlags, attributionChainId);
                 } catch (RemoteException e) {
@@ -4258,6 +4266,9 @@
             for (int i = 0; i < callbackCount; i++) {
                 final StartedCallback callback = callbacks.valueAt(i);
                 try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
                     callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
                             result, startedType, attributionFlags, attributionChainId);
                 } catch (RemoteException e) {
@@ -4306,6 +4317,9 @@
             for (int i = 0; i < callbackCount; i++) {
                 final NotedCallback callback = callbacks.valueAt(i);
                 try {
+                    if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+                        continue;
+                    }
                     callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
                             result);
                 } catch (RemoteException e) {
@@ -4370,8 +4384,20 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
     private void verifyIncomingOp(int op) {
         if (op >= 0 && op < AppOpsManager._NUM_OP) {
+            // Enforce manage appops permission if it's a restricted read op.
+            if (opRestrictsRead(op)) {
+                mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+                        Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+            }
             return;
         }
         throw new IllegalArgumentException("Bad operation #" + op);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 6d68772..1002229 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -105,7 +105,7 @@
         mIsStrongBiometric = isStrongBiometric;
         mOperationId = operationId;
         mRequireConfirmation = requireConfirmation;
-        mActivityTaskManager = ActivityTaskManager.getInstance();
+        mActivityTaskManager = getActivityTaskManager();
         mBiometricManager = context.getSystemService(BiometricManager.class);
         mTaskStackListener = taskStackListener;
         mLockoutTracker = lockoutTracker;
@@ -133,6 +133,10 @@
         return mStartTimeMs;
     }
 
+    protected ActivityTaskManager getActivityTaskManager() {
+        return ActivityTaskManager.getInstance();
+    }
+
     @Override
     public void binderDied() {
         final boolean clearListener = !isBiometricPrompt();
@@ -310,45 +314,50 @@
                     sendCancelOnly(listener);
                 }
             });
-        } else {
-            // Allow system-defined limit of number of attempts before giving up
-            final @LockoutTracker.LockoutMode int lockoutMode =
-                    handleFailedAttempt(getTargetUserId());
-            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
-                markAlreadyDone();
+        } else { // not authenticated
+            if (isBackgroundAuth) {
+                Slog.e(TAG, "cancelling due to background auth");
+                cancel();
+            } else {
+                // Allow system-defined limit of number of attempts before giving up
+                final @LockoutTracker.LockoutMode int lockoutMode =
+                        handleFailedAttempt(getTargetUserId());
+                if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+                    markAlreadyDone();
+                }
+
+                final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+                coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
+                        new CoexCoordinator.Callback() {
+                            @Override
+                            public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
+                                if (listener != null) {
+                                    try {
+                                        listener.onAuthenticationFailed(getSensorId());
+                                    } catch (RemoteException e) {
+                                        Slog.e(TAG, "Unable to notify listener", e);
+                                    }
+                                }
+                            }
+
+                            @Override
+                            public void sendHapticFeedback() {
+                                if (listener != null && mShouldVibrate) {
+                                    vibrateError();
+                                }
+                            }
+
+                            @Override
+                            public void handleLifecycleAfterAuth() {
+                                AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
+                            }
+
+                            @Override
+                            public void sendAuthenticationCanceled() {
+                                sendCancelOnly(listener);
+                            }
+                        });
             }
-
-            final CoexCoordinator coordinator = CoexCoordinator.getInstance();
-            coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
-                    new CoexCoordinator.Callback() {
-                @Override
-                public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
-                    if (listener != null) {
-                        try {
-                            listener.onAuthenticationFailed(getSensorId());
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Unable to notify listener", e);
-                        }
-                    }
-                }
-
-                @Override
-                public void sendHapticFeedback() {
-                    if (listener != null && mShouldVibrate) {
-                        vibrateError();
-                    }
-                }
-
-                @Override
-                public void handleLifecycleAfterAuth() {
-                    AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
-                }
-
-                @Override
-                public void sendAuthenticationCanceled() {
-                    sendCancelOnly(listener);
-                }
-            });
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 97efc78..a26535f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -344,12 +344,12 @@
                     provider.second.getSensorProperties(sensorId);
             if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
                     && sensorProps != null && sensorProps.isAnyUdfpsType()) {
-                final long identity2 = Binder.clearCallingIdentity();
                 try {
                     return authenticateWithPrompt(operationId, sensorProps, userId, receiver,
-                            ignoreEnrollmentState);
-                } finally {
-                    Binder.restoreCallingIdentity(identity2);
+                            opPackageName, ignoreEnrollmentState);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.e(TAG, "Invalid package", e);
+                    return -1;
                 }
             }
             return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
@@ -362,12 +362,15 @@
                 @NonNull final FingerprintSensorPropertiesInternal props,
                 final int userId,
                 final IFingerprintServiceReceiver receiver,
-                boolean ignoreEnrollmentState) {
+                final String opPackageName,
+                boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
 
             final Context context = getUiContext();
+            final Context promptContext = context.createPackageContextAsUser(
+                    opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(userId));
             final Executor executor = context.getMainExecutor();
 
-            final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(context)
+            final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(promptContext)
                     .setTitle(context.getString(R.string.biometric_dialog_default_title))
                     .setSubtitle(context.getString(R.string.fingerprint_dialog_default_subtitle))
                     .setNegativeButton(
@@ -381,8 +384,7 @@
                                     Slog.e(TAG, "Remote exception in negative button onClick()", e);
                                 }
                             })
-                    .setAllowedSensorIds(new ArrayList<>(
-                            Collections.singletonList(props.sensorId)))
+                    .setIsForLegacyFingerprintManager(props.sensorId)
                     .setIgnoreEnrollmentState(ignoreEnrollmentState)
                     .build();
 
@@ -436,8 +438,8 @@
                         }
                     };
 
-            return biometricPrompt.authenticateUserForOperation(
-                    new CancellationSignal(), executor, promptCallback, userId, operationId);
+            return biometricPrompt.authenticateForOperation(
+                    new CancellationSignal(), executor, promptCallback, operationId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index d0e39cc..03e18a0 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -43,6 +43,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
@@ -59,7 +60,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 import java.util.WeakHashMap;
 
 /**
@@ -139,6 +142,8 @@
     @GuardedBy("mLock")
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
+    private Set<Integer> mDeviceStatesAvailableForAppRequests;
+
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, DeviceStatePolicy.Provider
                 .fromResources(context.getResources())
@@ -164,6 +169,10 @@
     public void onStart() {
         publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
         publishLocalService(DeviceStateManagerInternal.class, new LocalService());
+
+        synchronized (mLock) {
+            readStatesAvailableForRequestFromApps();
+        }
     }
 
     @VisibleForTesting
@@ -626,21 +635,78 @@
 
     /**
      * Allow top processes to request or cancel a device state change. If the calling process ID is
-     * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
-     * @param callingPid
+     * not the top app, then check if this process holds the
+     * {@link android.Manifest.permission.CONTROL_DEVICE_STATE} permission. If the calling process
+     * is the top app, check to verify they are requesting a state we've deemed to be able to be
+     * available for an app process to request. States that can be requested are based around
+     * features that we've created that require specific device state overrides.
+     * @param callingPid Process ID that is requesting this state change
+     * @param state state that is being requested.
      */
-    private void checkCanControlDeviceState(int callingPid) {
-        // Allow top processes to request a device state change
-        // If the calling process ID is not the top app, then we check if this process
-        // holds a permission to CONTROL_DEVICE_STATE
+    private void assertCanRequestDeviceState(int callingPid, int state) {
+        final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+        if (topApp == null || topApp.getPid() != callingPid
+                || !isStateAvailableForAppRequests(state)) {
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to request device state, "
+                            + "or the call must come from the top app "
+                            + "and be a device state that is available for apps to request.");
+        }
+    }
+
+    /**
+     * Checks if the process can control the device state. If the calling process ID is
+     * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+     *
+     * @param callingPid Process ID that is requesting this state change
+     */
+    private void assertCanControlDeviceState(int callingPid) {
         final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
         if (topApp == null || topApp.getPid() != callingPid) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
-                            + "or the call must come from the top focused app.");
+                            + "or the call must come from the top app.");
         }
     }
 
+    private boolean isStateAvailableForAppRequests(int state) {
+        synchronized (mLock) {
+            return mDeviceStatesAvailableForAppRequests.contains(state);
+        }
+    }
+
+    /**
+     * Adds device state values that are available to be requested by the top level app.
+     */
+    @GuardedBy("mLock")
+    private void readStatesAvailableForRequestFromApps() {
+        mDeviceStatesAvailableForAppRequests = new HashSet<>();
+        String[] availableAppStatesConfigIdentifiers = getContext().getResources()
+                .getStringArray(R.array.config_deviceStatesAvailableForAppRequests);
+        for (int i = 0; i < availableAppStatesConfigIdentifiers.length; i++) {
+            String identifierToFetch = availableAppStatesConfigIdentifiers[i];
+            int configValueIdentifier = getContext().getResources()
+                    .getIdentifier(identifierToFetch, "integer", "android");
+            int state = getContext().getResources().getInteger(configValueIdentifier);
+            if (isValidState(state)) {
+                mDeviceStatesAvailableForAppRequests.add(state);
+            } else {
+                Slog.e(TAG, "Invalid device state was found in the configuration file. State id: "
+                        + state);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean isValidState(int state) {
+        for (int i = 0; i < mDeviceStates.size(); i++) {
+            if (state == mDeviceStates.valueAt(i).getIdentifier()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
         @Override
         public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
@@ -777,7 +843,7 @@
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            checkCanControlDeviceState(callingPid);
+            assertCanRequestDeviceState(callingPid, state);
 
             if (token == null) {
                 throw new IllegalArgumentException("Request token must not be null.");
@@ -797,7 +863,7 @@
             // Allow top processes to cancel a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            checkCanControlDeviceState(callingPid);
+            assertCanControlDeviceState(callingPid);
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index dff7100..c447880 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -61,6 +61,7 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
@@ -179,7 +180,6 @@
     private final boolean mSystemAppsQueryable;
     private final FeatureConfig mFeatureConfig;
     private final OverlayReferenceMapper mOverlayReferenceMapper;
-    private final StateProvider mStateProvider;
     private SigningDetails mSystemSigningDetails;
 
     @GuardedBy("mLock")
@@ -192,7 +192,8 @@
     /**
      * This structure maps uid -> uid and indicates whether access from the first should be
      * filtered to the second. It's essentially a cache of the
-     * {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
+     * {@link #shouldFilterApplicationInternal(PackageDataSnapshot, int, Object,
+     * PackageStateInternal, int)} call.
      * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
      * initial scam and is empty until {@link #mSystemReady} is true.
      */
@@ -282,8 +283,7 @@
     }
 
     @VisibleForTesting(visibility = PRIVATE)
-    AppsFilterImpl(StateProvider stateProvider,
-            FeatureConfig featureConfig,
+    AppsFilterImpl(FeatureConfig featureConfig,
             String[] forceQueryableList,
             boolean systemAppsQueryable,
             @Nullable OverlayReferenceMapper.Provider overlayProvider,
@@ -293,7 +293,6 @@
         mSystemAppsQueryable = systemAppsQueryable;
         mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
                 overlayProvider);
-        mStateProvider = stateProvider;
         mBackgroundExecutor = backgroundExecutor;
         mShouldFilterCache = new WatchedSparseBooleanMatrix();
         mShouldFilterCacheSnapshot = new SnapshotCache.Auto<>(
@@ -352,7 +351,6 @@
         mSystemAppsQueryable = orig.mSystemAppsQueryable;
         mFeatureConfig = orig.mFeatureConfig;
         mOverlayReferenceMapper = orig.mOverlayReferenceMapper;
-        mStateProvider = orig.mStateProvider;
         mSystemSigningDetails = orig.mSystemSigningDetails;
         synchronized (orig.mCacheLock) {
             mShouldFilterCache = orig.mShouldFilterCacheSnapshot.snapshot();
@@ -361,7 +359,7 @@
 
         mBackgroundExecutor = null;
         mSnapshot = new SnapshotCache.Sealed<>();
-        mSystemReady = true;
+        mSystemReady = orig.mSystemReady;
     }
 
     /**
@@ -373,23 +371,6 @@
         return mSnapshot.snapshot();
     }
 
-    /**
-     * Provides system state to AppsFilter via {@link CurrentStateCallback} after properly guarding
-     * the data with the package lock.
-     *
-     * Don't call {@link #runWithState} with {@link #mCacheLock} held.
-     */
-    @VisibleForTesting(visibility = PRIVATE)
-    public interface StateProvider {
-        void runWithState(CurrentStateCallback callback);
-
-        interface CurrentStateCallback {
-            void currentState(ArrayMap<String, ? extends PackageStateInternal> settings,
-                    Collection<SharedUserSetting> sharedUserSettings,
-                    UserInfo[] users);
-        }
-    }
-
     @VisibleForTesting(visibility = PRIVATE)
     public interface FeatureConfig {
 
@@ -517,12 +498,13 @@
 
         @Override
         public void onCompatChange(String packageName) {
-            AndroidPackage pkg = mPmInternal.getPackage(packageName);
+            PackageDataSnapshot snapshot = mPmInternal.snapshot();
+            AndroidPackage pkg = snapshot.getPackage(packageName);
             if (pkg == null) {
                 return;
             }
             updateEnabledState(pkg);
-            mAppsFilter.updateShouldFilterCacheForPackage(packageName);
+            mAppsFilter.updateShouldFilterCacheForPackage(snapshot, packageName);
         }
 
         private void updateEnabledState(@NonNull AndroidPackage pkg) {
@@ -574,14 +556,7 @@
                 forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
             }
         }
-        final StateProvider stateProvider = command -> {
-            synchronized (injector.getLock()) {
-                command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(),
-                        injector.getSettings().getAllSharedUsersLPw(),
-                        injector.getUserManagerInternal().getUserInfos());
-            }
-        };
-        AppsFilterImpl appsFilter = new AppsFilterImpl(stateProvider, featureConfig,
+        AppsFilterImpl appsFilter = new AppsFilterImpl(featureConfig,
                 forcedQueryablePackageNames, forceSystemAppsQueryable, null,
                 injector.getBackgroundExecutor());
         featureConfig.setAppsFilter(appsFilter);
@@ -754,12 +729,11 @@
         return changed;
     }
 
-    public void onSystemReady() {
+    public void onSystemReady(PackageManagerInternal pmInternal) {
         mOverlayReferenceMapper.rebuildIfDeferred();
         mFeatureConfig.onSystemReady();
 
-        updateEntireShouldFilterCacheAsync();
-        onChanged();
+        updateEntireShouldFilterCacheAsync(pmInternal);
         mSystemReady = true;
     }
 
@@ -769,39 +743,41 @@
      * @param newPkgSetting the new setting being added
      * @param isReplace     if the package is being replaced and may need extra cleanup.
      */
-    public void addPackage(PackageStateInternal newPkgSetting, boolean isReplace) {
+    public void addPackage(PackageDataSnapshot snapshot, PackageStateInternal newPkgSetting,
+            boolean isReplace) {
         if (DEBUG_TRACING) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
         }
         try {
             if (isReplace) {
                 // let's first remove any prior rules for this package
-                removePackage(newPkgSetting, true /*isReplace*/);
+                removePackage(snapshot, newPkgSetting, true /*isReplace*/);
             }
-            mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-                ArraySet<String> additionalChangedPackages =
-                        addPackageInternal(newPkgSetting, settings);
-                if (mSystemReady) {
-                    updateShouldFilterCacheForPackage(null, newPkgSetting,
+            final ArrayMap<String, ? extends PackageStateInternal> settings =
+                    snapshot.getPackageStates();
+            final UserInfo[] users = snapshot.getUserInfos();
+            final ArraySet<String> additionalChangedPackages =
+                    addPackageInternal(newPkgSetting, settings);
+            if (mSystemReady) {
+                synchronized (mCacheLock) {
+                    updateShouldFilterCacheForPackage(snapshot, null, newPkgSetting,
                             settings, users, USER_ALL, settings.size());
                     if (additionalChangedPackages != null) {
                         for (int index = 0; index < additionalChangedPackages.size(); index++) {
                             String changedPackage = additionalChangedPackages.valueAt(index);
-                            PackageStateInternal changedPkgSetting =
-                                    settings.get(changedPackage);
+                            PackageStateInternal changedPkgSetting = settings.get(changedPackage);
                             if (changedPkgSetting == null) {
                                 // It's possible for the overlay mapper to know that an actor
                                 // package changed via an explicit reference, even if the actor
                                 // isn't installed, so skip if that's the case.
                                 continue;
                             }
-
-                            updateShouldFilterCacheForPackage(null, changedPkgSetting,
+                            updateShouldFilterCacheForPackage(snapshot, null, changedPkgSetting,
                                     settings, users, USER_ALL, settings.size());
                         }
                     }
-                } // else, rebuild entire cache when system is ready
-            });
+                }
+            } // else, rebuild entire cache when system is ready
         } finally {
             onChanged();
             if (DEBUG_TRACING) {
@@ -941,30 +917,32 @@
         }
     }
 
-    private void updateEntireShouldFilterCache() {
-        updateEntireShouldFilterCache(USER_ALL);
+    private void updateEntireShouldFilterCache(PackageDataSnapshot snapshot) {
+        updateEntireShouldFilterCache(snapshot, USER_ALL);
     }
 
-    private void updateEntireShouldFilterCache(int subjectUserId) {
-        mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-            int userId = USER_NULL;
-            for (int u = 0; u < users.length; u++) {
-                if (subjectUserId == users[u].id) {
-                    userId = subjectUserId;
-                    break;
-                }
+    private void updateEntireShouldFilterCache(PackageDataSnapshot snapshot, int subjectUserId) {
+        final ArrayMap<String, ? extends PackageStateInternal> settings =
+                snapshot.getPackageStates();
+        final UserInfo[] users = snapshot.getUserInfos();
+        int userId = USER_NULL;
+        for (int u = 0; u < users.length; u++) {
+            if (subjectUserId == users[u].id) {
+                userId = subjectUserId;
+                break;
             }
-            if (userId == USER_NULL) {
-                Slog.e(TAG, "We encountered a new user that isn't a member of known users, "
-                        + "updating the whole cache");
-                userId = USER_ALL;
-            }
-            updateEntireShouldFilterCacheInner(settings, users, userId);
-        });
+        }
+        if (userId == USER_NULL) {
+            Slog.e(TAG, "We encountered a new user that isn't a member of known users, "
+                    + "updating the whole cache");
+            userId = USER_ALL;
+        }
+        updateEntireShouldFilterCacheInner(snapshot, settings, users, userId);
+
         onChanged();
     }
 
-    private void updateEntireShouldFilterCacheInner(
+    private void updateEntireShouldFilterCacheInner(PackageDataSnapshot snapshot,
             ArrayMap<String, ? extends PackageStateInternal> settings,
             UserInfo[] users,
             int subjectUserId) {
@@ -973,67 +951,42 @@
                 mShouldFilterCache.clear();
             }
             mShouldFilterCache.setCapacity(users.length * settings.size());
-        }
-        for (int i = settings.size() - 1; i >= 0; i--) {
-            updateShouldFilterCacheForPackage(
-                    null /*skipPackage*/, settings.valueAt(i), settings, users,
-                    subjectUserId, i);
+            for (int i = settings.size() - 1; i >= 0; i--) {
+                updateShouldFilterCacheForPackage(snapshot,
+                        null /*skipPackage*/, settings.valueAt(i), settings, users,
+                        subjectUserId, i);
+            }
         }
     }
 
-    private void updateEntireShouldFilterCacheAsync() {
+    private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal) {
         mBackgroundExecutor.execute(() -> {
-            final ArrayMap<String, PackageStateInternal> settingsCopy = new ArrayMap<>();
-            final Collection<SharedUserSetting> sharedUserSettingsCopy = new ArraySet<>();
             final ArrayMap<String, AndroidPackage> packagesCache = new ArrayMap<>();
             final UserInfo[][] usersRef = new UserInfo[1][];
-            mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-                packagesCache.ensureCapacity(settings.size());
-                settingsCopy.putAll(settings);
-                usersRef[0] = users;
-                // store away the references to the immutable packages, since settings are retained
-                // during updates.
-                for (int i = 0, max = settings.size(); i < max; i++) {
-                    final AndroidPackage pkg = settings.valueAt(i).getPkg();
-                    packagesCache.put(settings.keyAt(i), pkg);
-                }
-                sharedUserSettingsCopy.addAll(sharedUserSettings);
-            });
+            final PackageDataSnapshot snapshot = pmInternal.snapshot();
+            final ArrayMap<String, ? extends PackageStateInternal> settings =
+                    snapshot.getPackageStates();
+            final UserInfo[] users = snapshot.getUserInfos();
 
-            boolean[] changed = new boolean[1];
-            // We have a cache, let's make sure the world hasn't changed out from under us.
-            mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-                if (settings.size() != settingsCopy.size()) {
-                    changed[0] = true;
-                    return;
-                }
-                for (int i = 0, max = settings.size(); i < max; i++) {
-                    final AndroidPackage pkg = settings.valueAt(i).getPkg();
-                    if (!Objects.equals(pkg, packagesCache.get(settings.keyAt(i)))) {
-                        changed[0] = true;
-                        return;
-                    }
-                }
-            });
-            if (changed[0]) {
-                // Something has changed, just update the cache inline with the lock held
-                updateEntireShouldFilterCache();
-                if (DEBUG_LOGGING) {
-                    Slog.i(TAG, "Rebuilding cache with lock due to package change.");
-                }
-            } else {
-                updateEntireShouldFilterCacheInner(settingsCopy,
-                        usersRef[0], USER_ALL);
-                onChanged();
+            packagesCache.ensureCapacity(settings.size());
+            usersRef[0] = users;
+            // store away the references to the immutable packages, since settings are retained
+            // during updates.
+            for (int i = 0, max = settings.size(); i < max; i++) {
+                final AndroidPackage pkg = settings.valueAt(i).getPkg();
+                packagesCache.put(settings.keyAt(i), pkg);
             }
+
+            updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL);
+            onChanged();
         });
     }
 
-    public void onUserCreated(int newUserId) {
+    public void onUserCreated(PackageDataSnapshot snapshot, int newUserId) {
         if (!mSystemReady) {
             return;
         }
-        updateEntireShouldFilterCache(newUserId);
+        updateEntireShouldFilterCache(snapshot, newUserId);
     }
 
     public void onUserDeleted(@UserIdInt int userId) {
@@ -1044,19 +997,24 @@
         onChanged();
     }
 
-    private void updateShouldFilterCacheForPackage(String packageName) {
-        mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-            if (!mSystemReady) {
-                return;
-            }
-            updateShouldFilterCacheForPackage(null /* skipPackage */,
+    private void updateShouldFilterCacheForPackage(PackageDataSnapshot snapshot,
+            String packageName) {
+        if (!mSystemReady) {
+            return;
+        }
+        final ArrayMap<String, ? extends PackageStateInternal> settings =
+                snapshot.getPackageStates();
+        final UserInfo[] users = snapshot.getUserInfos();
+        synchronized (mCacheLock) {
+            updateShouldFilterCacheForPackage(snapshot, null /* skipPackage */,
                     settings.get(packageName), settings, users, USER_ALL,
                     settings.size() /*maxIndex*/);
-        });
+        }
         onChanged();
     }
 
-    private void updateShouldFilterCacheForPackage(
+    @GuardedBy("mCacheLock")
+    private void updateShouldFilterCacheForPackage(PackageDataSnapshot snapshot,
             @Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
             ? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
             int maxIndex) {
@@ -1072,31 +1030,30 @@
             }
             if (subjectUserId == USER_ALL) {
                 for (int su = 0; su < allUsers.length; su++) {
-                    updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
+                    updateShouldFilterCacheForUser(snapshot, subjectSetting, allUsers, otherSetting,
                             allUsers[su].id);
                 }
             } else {
-                updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
+                updateShouldFilterCacheForUser(snapshot, subjectSetting, allUsers, otherSetting,
                         subjectUserId);
             }
         }
     }
 
-    private void updateShouldFilterCacheForUser(
+    @GuardedBy("mCacheLock")
+    private void updateShouldFilterCacheForUser(PackageDataSnapshot snapshot,
             PackageStateInternal subjectSetting, UserInfo[] allUsers,
             PackageStateInternal otherSetting, int subjectUserId) {
         for (int ou = 0; ou < allUsers.length; ou++) {
             int otherUser = allUsers[ou].id;
             int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
             int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
-            final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(
+            final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(snapshot,
                     subjectUid, subjectSetting, otherSetting, otherUser);
-            final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(
+            final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(snapshot,
                     otherUid, otherSetting, subjectSetting, subjectUserId);
-            synchronized (mCacheLock) {
-                mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
-                mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
-            }
+            mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
+            mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
         }
     }
 
@@ -1182,11 +1139,13 @@
     }
 
     /**
-     * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageStateInternal, int[], ArrayMap)}
+     * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageDataSnapshot,
+     * PackageStateInternal, int[], ArrayMap)}
      */
     @Override
     @Nullable
-    public SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+    public SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+            PackageStateInternal setting, int[] users,
             ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
         synchronized (mLock) {
             if (mForceQueryable.contains(setting.getAppId())) {
@@ -1211,7 +1170,8 @@
                     continue;
                 }
                 final int existingUid = UserHandle.getUid(userId, existingAppId);
-                if (!shouldFilterApplication(existingUid, existingSetting, setting, userId)) {
+                if (!shouldFilterApplication(snapshot, existingUid, existingSetting, setting,
+                        userId)) {
                     if (buffer == null) {
                         buffer = new int[appIds.length];
                     }
@@ -1232,19 +1192,21 @@
      */
     @VisibleForTesting(visibility = PRIVATE)
     @Nullable
-    SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+    SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+            PackageStateInternal setting, int[] users,
             WatchedArrayMap<String, ? extends PackageStateInternal> existingSettings) {
-        return getVisibilityAllowList(setting, users, existingSettings.untrackedStorage());
+        return getVisibilityAllowList(snapshot, setting, users,
+                existingSettings.untrackedStorage());
     }
 
     /**
-     * Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
-     * {@code isReplace} equal to {@code false}.
+     * Equivalent to calling {@link #addPackage(PackageDataSnapshot, PackageStateInternal, boolean)}
+     * with {@code isReplace} equal to {@code false}.
      *
-     * @see AppsFilterImpl#addPackage(PackageStateInternal, boolean)
+     * @see AppsFilterImpl#addPackage(PackageDataSnapshot, PackageStateInternal, boolean)
      */
-    public void addPackage(PackageStateInternal newPkgSetting) {
-        addPackage(newPkgSetting, false /* isReplace */);
+    public void addPackage(PackageDataSnapshot snapshot, PackageStateInternal newPkgSetting) {
+        addPackage(snapshot, newPkgSetting, false /* isReplace */);
     }
 
     /**
@@ -1253,119 +1215,122 @@
      * @param setting   the setting of the package being removed.
      * @param isReplace if the package is being replaced.
      */
-    public void removePackage(PackageStateInternal setting, boolean isReplace) {
-        mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-            final ArraySet<String> additionalChangedPackages;
-            final int userCount = users.length;
-            synchronized (mLock) {
-                for (int u = 0; u < userCount; u++) {
-                    final int userId = users[u].id;
-                    final int removingUid = UserHandle.getUid(userId, setting.getAppId());
-                    mImplicitlyQueryable.remove(removingUid);
-                    for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                        mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
-                                removingUid);
-                    }
-
-                    if (isReplace) {
-                        continue;
-                    }
-
-                    mRetainedImplicitlyQueryable.remove(removingUid);
-                    for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                        mRetainedImplicitlyQueryable.remove(
-                                mRetainedImplicitlyQueryable.keyAt(i), removingUid);
-                    }
+    public void removePackage(PackageDataSnapshot snapshot, PackageStateInternal setting,
+            boolean isReplace) {
+        final ArraySet<String> additionalChangedPackages;
+        final ArrayMap<String, ? extends PackageStateInternal> settings =
+                snapshot.getPackageStates();
+        final UserInfo[] users = snapshot.getUserInfos();
+        final Collection<SharedUserSetting> sharedUserSettings = snapshot.getAllSharedUsers();
+        final int userCount = users.length;
+        synchronized (mLock) {
+            for (int u = 0; u < userCount; u++) {
+                final int userId = users[u].id;
+                final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+                mImplicitlyQueryable.remove(removingUid);
+                for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                    mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
+                            removingUid);
                 }
 
-                if (!mQueriesViaComponentRequireRecompute) {
-                    mQueriesViaComponent.remove(setting.getAppId());
-                    for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
-                        mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
-                                setting.getAppId());
-                    }
-                }
-                mQueriesViaPackage.remove(setting.getAppId());
-                for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
-                    mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i),
-                            setting.getAppId());
-                }
-                mQueryableViaUsesLibrary.remove(setting.getAppId());
-                for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
-                    mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
-                            setting.getAppId());
+                if (isReplace) {
+                    continue;
                 }
 
-                mForceQueryable.remove(setting.getAppId());
-
-                if (setting.getPkg() != null
-                        && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
-                    final String removingPackageName = setting.getPkg().getPackageName();
-                    final ArrayList<String> protectedBroadcasts = new ArrayList<>();
-                    protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
-                    collectProtectedBroadcasts(settings, removingPackageName);
-                    if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
-                        mQueriesViaComponentRequireRecompute = true;
-                    }
+                mRetainedImplicitlyQueryable.remove(removingUid);
+                for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                    mRetainedImplicitlyQueryable.remove(
+                            mRetainedImplicitlyQueryable.keyAt(i), removingUid);
                 }
             }
 
-            additionalChangedPackages = mOverlayReferenceMapper.removePkg(setting.getPackageName());
-            mFeatureConfig.updatePackageState(setting, true /*removed*/);
-
-            // After removing all traces of the package, if it's part of a shared user,
-            // re-add other
-            // shared user members to re-establish visibility between them and other
-            // packages.
-            // NOTE: this must come after all removals from data structures but before we
-            // update the
-            //       cache
-            if (setting.hasSharedUser()) {
-                final ArraySet<? extends PackageStateInternal> sharedUserPackages =
-                        getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
-                for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
-                    if (sharedUserPackages.valueAt(i) == setting) {
-                        continue;
-                    }
-                    addPackageInternal(
-                            sharedUserPackages.valueAt(i), settings);
+            if (!mQueriesViaComponentRequireRecompute) {
+                mQueriesViaComponent.remove(setting.getAppId());
+                for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+                    mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
+                            setting.getAppId());
                 }
             }
+            mQueriesViaPackage.remove(setting.getAppId());
+            for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
+                mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i),
+                        setting.getAppId());
+            }
+            mQueryableViaUsesLibrary.remove(setting.getAppId());
+            for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
+                mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
+                        setting.getAppId());
+            }
 
-            removeAppIdFromVisibilityCache(setting.getAppId());
-            if (mSystemReady && setting.hasSharedUser()) {
-                final ArraySet<? extends PackageStateInternal> sharedUserPackages =
-                        getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
-                for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
-                    PackageStateInternal siblingSetting =
-                            sharedUserPackages.valueAt(i);
-                    if (siblingSetting == setting) {
-                        continue;
-                    }
-                    updateShouldFilterCacheForPackage(
+            mForceQueryable.remove(setting.getAppId());
+
+            if (setting.getPkg() != null
+                    && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
+                final String removingPackageName = setting.getPkg().getPackageName();
+                final ArrayList<String> protectedBroadcasts = new ArrayList<>();
+                protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
+                collectProtectedBroadcasts(settings, removingPackageName);
+                if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
+                    mQueriesViaComponentRequireRecompute = true;
+                }
+            }
+        }
+
+        additionalChangedPackages = mOverlayReferenceMapper.removePkg(setting.getPackageName());
+        mFeatureConfig.updatePackageState(setting, true /*removed*/);
+
+        // After removing all traces of the package, if it's part of a shared user, re-add other
+        // shared user members to re-establish visibility between them and other packages.
+        // NOTE: this must come after all removals from data structures but before we update the
+        // cache
+        if (setting.hasSharedUser()) {
+            final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+                    getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
+            for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+                if (sharedUserPackages.valueAt(i) == setting) {
+                    continue;
+                }
+                addPackageInternal(
+                        sharedUserPackages.valueAt(i), settings);
+            }
+        }
+
+        removeAppIdFromVisibilityCache(setting.getAppId());
+        if (mSystemReady && setting.hasSharedUser()) {
+            final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+                    getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
+            for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+                PackageStateInternal siblingSetting =
+                        sharedUserPackages.valueAt(i);
+                if (siblingSetting == setting) {
+                    continue;
+                }
+                synchronized (mCacheLock) {
+                    updateShouldFilterCacheForPackage(snapshot,
                             setting.getPackageName(), siblingSetting, settings,
                             users, USER_ALL, settings.size());
                 }
             }
+        }
 
-            if (mSystemReady) {
-                if (additionalChangedPackages != null) {
-                    for (int index = 0; index < additionalChangedPackages.size(); index++) {
-                        String changedPackage = additionalChangedPackages.valueAt(index);
-                        PackageStateInternal changedPkgSetting = settings.get(changedPackage);
-                        if (changedPkgSetting == null) {
-                            // It's possible for the overlay mapper to know that an actor
-                            // package changed via an explicit reference, even if the actor
-                            // isn't installed, so skip if that's the case.
-                            continue;
-                        }
-
-                        updateShouldFilterCacheForPackage(null, changedPkgSetting,
+        if (mSystemReady) {
+            if (additionalChangedPackages != null) {
+                for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                    String changedPackage = additionalChangedPackages.valueAt(index);
+                    PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+                    if (changedPkgSetting == null) {
+                        // It's possible for the overlay mapper to know that an actor
+                        // package changed via an explicit reference, even if the actor
+                        // isn't installed, so skip if that's the case.
+                        continue;
+                    }
+                    synchronized (mCacheLock) {
+                        updateShouldFilterCacheForPackage(snapshot, null, changedPkgSetting,
                                 settings, users, USER_ALL, settings.size());
                     }
                 }
             }
-        });
+        }
         onChanged();
     }
 
@@ -1382,12 +1347,12 @@
 
     /**
      * See
-     * {@link AppsFilterSnapshot#shouldFilterApplication(int, Object, PackageStateInternal,
-     * int)}
+     * {@link AppsFilterSnapshot#shouldFilterApplication(PackageDataSnapshot, int, Object,
+     * PackageStateInternal, int)}
      */
     @Override
-    public boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
-            PackageStateInternal targetPkgSetting, int userId) {
+    public boolean shouldFilterApplication(PackageDataSnapshot snapshot, int callingUid,
+            @Nullable Object callingSetting, PackageStateInternal targetPkgSetting, int userId) {
         if (DEBUG_TRACING) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
         }
@@ -1405,7 +1370,7 @@
                     return false;
                 }
             } else {
-                if (!shouldFilterApplicationInternal(
+                if (!shouldFilterApplicationInternal(snapshot,
                         callingUid, callingSetting, targetPkgSetting, userId)) {
                     return false;
                 }
@@ -1440,8 +1405,8 @@
         }
     }
 
-    private boolean shouldFilterApplicationInternal(int callingUid, Object callingSetting,
-            PackageStateInternal targetPkgSetting, int targetUserId) {
+    private boolean shouldFilterApplicationInternal(PackageDataSnapshot snapshot, int callingUid,
+            Object callingSetting, PackageStateInternal targetPkgSetting, int targetUserId) {
         if (DEBUG_TRACING) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
         }
@@ -1467,9 +1432,8 @@
                 final PackageStateInternal packageState = (PackageStateInternal) callingSetting;
                 if (packageState.hasSharedUser()) {
                     callingPkgSetting = null;
-                    mStateProvider.runWithState((settings, sharedUserSettings, users) ->
-                            callingSharedPkgSettings.addAll(getSharedUserPackages(
-                                    packageState.getSharedUserAppId(), sharedUserSettings)));
+                    callingSharedPkgSettings.addAll(getSharedUserPackages(
+                            packageState.getSharedUserAppId(), snapshot.getAllSharedUsers()));
 
                 } else {
                     callingPkgSetting = packageState;
@@ -1600,11 +1564,7 @@
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
                 }
                 if (mQueriesViaComponentRequireRecompute) {
-                    final ArrayMap<String, PackageStateInternal> settingsCopy = new ArrayMap<>();
-                    mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
-                        settingsCopy.putAll(settings);
-                    });
-                    recomputeComponentVisibility(settingsCopy);
+                    recomputeComponentVisibility(snapshot.getPackageStates());
                     onChanged();
                 }
                 synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
index cb8c649..de037f3 100644
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
@@ -25,6 +25,7 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
 
 import java.io.PrintWriter;
 
@@ -40,27 +41,30 @@
      * If the setting is visible to all UIDs, null is returned. If an app is not visible to any
      * applications, the int array will be empty.
      *
+     * @param snapshot         the snapshot of the computer that contains all package information
      * @param users            the set of users that should be evaluated for this calculation
      * @param existingSettings the set of all package settings that currently exist on device
      * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
      * provided setting or null if the app is visible to all and no allow list should be
      * applied.
      */
-    SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+    SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+            PackageStateInternal setting, int[] users,
             ArrayMap<String, ? extends PackageStateInternal> existingSettings);
 
     /**
      * Returns true if the calling package should not be able to see the target package, false if no
      * filtering should be done.
      *
+     * @param snapshot         the snapshot of the computer that contains all package information
      * @param callingUid       the uid of the caller attempting to access a package
      * @param callingSetting   the setting attempting to access a package or null if it could not be
      *                         found
      * @param targetPkgSetting the package being accessed
      * @param userId           the user in which this access is being attempted
      */
-    boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
-            PackageStateInternal targetPkgSetting, int userId);
+    boolean shouldFilterApplication(PackageDataSnapshot snapshot, int callingUid,
+            @Nullable Object callingSetting, PackageStateInternal targetPkgSetting, int userId);
 
     /**
      * Returns whether the querying package is allowed to see the target package.
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c259797..db48a1f 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -59,6 +59,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -128,6 +129,7 @@
      */
     ActivityInfo getActivityInfoInternal(ComponentName component, long flags,
             int filterCallingUid, int userId);
+    @Override
     AndroidPackage getPackage(String packageName);
     AndroidPackage getPackage(int uid);
     ApplicationInfo generateApplicationInfoFromSettings(String packageName, long flags,
@@ -289,6 +291,7 @@
     PreferredIntentResolver getPreferredActivities(@UserIdInt int userId);
 
     @NonNull
+    @Override
     ArrayMap<String, ? extends PackageStateInternal> getPackageStates();
 
     @Nullable
@@ -602,4 +605,12 @@
 
     @NonNull
     List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid);
+
+    @Override
+    @NonNull
+    UserInfo[] getUserInfos();
+
+    @Override
+    @NonNull
+    Collection<SharedUserSetting> getAllSharedUsers();
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 80d61b5..bf9f4fa 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1348,7 +1348,7 @@
                 PackageStateInternal resolvedSetting =
                         getPackageStateInternal(info.activityInfo.packageName, 0);
                 if (resolveForStart
-                        || !mAppsFilter.shouldFilterApplication(
+                        || !mAppsFilter.shouldFilterApplication(this,
                         filterCallingUid, callingSetting, resolvedSetting, userId)) {
                     continue;
                 }
@@ -1382,7 +1382,7 @@
                         mSettings.getSettingBase(UserHandle.getAppId(filterCallingUid));
                 PackageStateInternal resolvedSetting =
                         getPackageStateInternal(info.serviceInfo.packageName, 0);
-                if (!mAppsFilter.shouldFilterApplication(
+                if (!mAppsFilter.shouldFilterApplication(this,
                         filterCallingUid, callingSetting, resolvedSetting, userId)) {
                     continue;
                 }
@@ -2730,7 +2730,7 @@
         }
         int appId = UserHandle.getAppId(callingUid);
         final SettingBase callingPs = mSettings.getSettingBase(appId);
-        return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId);
+        return mAppsFilter.shouldFilterApplication(this, callingUid, callingPs, ps, userId);
     }
 
     /**
@@ -5036,7 +5036,7 @@
         if (setting == null) {
             return null;
         }
-        return mAppsFilter.getVisibilityAllowList(setting, userIds, getPackageStates());
+        return mAppsFilter.getVisibilityAllowList(this, setting, userIds, getPackageStates());
     }
 
     @Nullable
@@ -5323,7 +5323,7 @@
         if (ps == null) {
             return null;
         }
-        final SparseArray<int[]> visibilityAllowList = mAppsFilter.getVisibilityAllowList(ps,
+        final SparseArray<int[]> visibilityAllowList = mAppsFilter.getVisibilityAllowList(this, ps,
                 new int[]{userId}, getPackageStates());
         return visibilityAllowList != null ? visibilityAllowList.get(userId) : null;
     }
@@ -5823,4 +5823,16 @@
     public List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid) {
         return mSettings.getVolumePackages(volumeUuid);
     }
+
+    @Override
+    @NonNull
+    public Collection<SharedUserSetting> getAllSharedUsers() {
+        return mSettings.getAllSharedUsers();
+    }
+
+    @Override
+    @NonNull
+    public UserInfo[] getUserInfos() {
+        return mInjector.getUserManagerInternal().getUserInfos();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index d3d291e..cb38d52 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -543,8 +543,9 @@
         synchronized (mPm.mLock) {
             if (outInfo != null) {
                 outInfo.mUid = ps.getAppId();
-                outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(ps,
-                        allUserHandles, mPm.mSettings.getPackagesLocked());
+                outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+                        mPm.snapshotComputer(), ps, allUserHandles,
+                        mPm.mSettings.getPackagesLocked());
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index bbdb7eb..8bd1da9 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -464,9 +464,9 @@
             KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
             ksms.addScannedPackageLPw(pkg);
 
-            mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage,
-                    mPm.snapshotComputer());
-            mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
+            final Computer snapshot = mPm.snapshotComputer();
+            mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot);
+            mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace);
             mPm.addAllPackageProperties(pkg);
 
             if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
@@ -1916,7 +1916,7 @@
                         .setLastUpdateTime(System.currentTimeMillis());
 
                 res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                        reconciledPkg.mPkgSetting, request.mAllUsers,
+                        mPm.snapshotComputer(), reconciledPkg.mPkgSetting, request.mAllUsers,
                         mPm.mSettings.getPackagesLocked());
                 if (reconciledPkg.mPrepareResult.mSystem) {
                     // Remove existing system package
@@ -2712,9 +2712,9 @@
                 // Send to all running apps.
                 final SparseArray<int[]> newBroadcastAllowList;
                 synchronized (mPm.mLock) {
-                    newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                            mPm.snapshotComputer()
-                                    .getPackageStateInternal(packageName, Process.SYSTEM_UID),
+                    final Computer snapshot = mPm.snapshotComputer();
+                    newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot,
+                            snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
                             updateUserIds, mPm.mSettings.getPackagesLocked());
                 }
                 mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7fd28f6..88564aa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1430,7 +1430,7 @@
                 (i, pm) -> new Settings(Environment.getDataDirectory(),
                         RuntimePermissionsPersistence.createInstance(),
                         i.getPermissionManagerServiceInternal(),
-                        domainVerificationService, lock),
+                        domainVerificationService, backgroundHandler, lock),
                 (i, pm) -> AppsFilterImpl.create(i,
                         i.getLocalService(PackageManagerInternal.class)),
                 (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
@@ -2992,7 +2992,7 @@
         if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
             return;
         }
-        SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(
+        SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
                 snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
                 userIds, snapshot.getPackageStates());
         mHandler.post(() -> mBroadcastHelper.sendPackageAddedForNewUsers(
@@ -4013,7 +4013,7 @@
                 .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_ALL);
         co.onChange(true);
 
-        mAppsFilter.onSystemReady();
+        mAppsFilter.onSystemReady(LocalServices.getService(PackageManagerInternal.class));
 
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
@@ -4226,7 +4226,7 @@
         synchronized (mLock) {
             scheduleWritePackageRestrictions(userId);
             scheduleWritePackageListLocked(userId);
-            mAppsFilter.onUserCreated(userId);
+            mAppsFilter.onUserCreated(snapshotComputer(), userId);
         }
     }
 
@@ -5751,7 +5751,7 @@
                     targetPackageState = snapshotComputer().getPackageStateInternal(targetPackage);
                     mSettings.addInstallerPackageNames(targetPackageState.getInstallSource());
                 }
-                mAppsFilter.addPackage(targetPackageState);
+                mAppsFilter.addPackage(snapshotComputer(), targetPackageState);
                 scheduleWriteSettings();
             }
         }
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index baa3a9d..65d3430 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -274,8 +274,9 @@
             synchronized (mPm.mLock) {
                 mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
                 mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
-                mPm.mAppsFilter.removePackage(mPm.snapshotComputer()
-                                .getPackageStateInternal(packageName), false /* isReplace */);
+                final Computer snapshot = mPm.snapshotComputer();
+                mPm.mAppsFilter.removePackage(snapshot,
+                        snapshot.getPackageStateInternal(packageName), false /* isReplace */);
                 removedAppId = mPm.mSettings.removePackageLPw(packageName);
                 if (outInfo != null) {
                     outInfo.mRemovedAppId = removedAppId;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e6d59d4..a1b4b30 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -360,6 +360,8 @@
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
 
+    private final Handler mHandler;
+
     private final PackageManagerTracedLock mLock;
 
     @Watched(manual = true)
@@ -583,6 +585,8 @@
                                          "Settings.mInstallerPackages");
         mKeySetManagerService = new KeySetManagerService(mPackages);
 
+        // Test-only handler working on background thread.
+        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
         mLock = new PackageManagerTracedLock();
         mPackages.putAll(pkgSettings);
         mAppIds = new AppIdSettingMap();
@@ -607,6 +611,7 @@
     Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
             LegacyPermissionDataProvider permissionDataProvider,
             @NonNull DomainVerificationManagerInternal domainVerificationManager,
+            @NonNull Handler handler,
             @NonNull PackageManagerTracedLock lock)  {
         mPackages = new WatchedArrayMap<>();
         mPackagesSnapshot  =
@@ -620,6 +625,7 @@
                                          "Settings.mInstallerPackages");
         mKeySetManagerService = new KeySetManagerService(mPackages);
 
+        mHandler = handler;
         mLock = lock;
         mAppIds = new AppIdSettingMap();
         mPermissions = new LegacyPermissionSettings(lock);
@@ -627,10 +633,8 @@
                 runtimePermissionsPersistence, new Consumer<Integer>() {
             @Override
             public void accept(Integer userId) {
-                synchronized (mLock) {
-                    mRuntimePermissionsPersistence.writeStateForUserSync(userId,
-                            mPermissionDataProvider, mPackages, mSharedUsers);
-                }
+                mRuntimePermissionsPersistence.writeStateForUser(userId,
+                        mPermissionDataProvider, mPackages, mSharedUsers, mHandler, mLock);
             }
         });
         mPermissionDataProvider = permissionDataProvider;
@@ -679,6 +683,7 @@
         // needed by the read-only methods.  Note especially that the lock
         // is not required because this clone is meant to support lock-free
         // read-only methods.
+        mHandler = null;
         mLock = null;
         mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
         mSettingsFilename = null;
@@ -5286,8 +5291,8 @@
 
     public void writePermissionStateForUserLPr(int userId, boolean sync) {
         if (sync) {
-            mRuntimePermissionsPersistence.writeStateForUserSync(userId, mPermissionDataProvider,
-                    mPackages, mSharedUsers);
+            mRuntimePermissionsPersistence.writeStateForUser(userId, mPermissionDataProvider,
+                    mPackages, mSharedUsers, /*handler=*/null, mLock);
         } else {
             mRuntimePermissionsPersistence.writeStateForUserAsync(userId);
         }
@@ -5373,9 +5378,14 @@
 
         private String mExtendedFingerprint;
 
+        @GuardedBy("mPersistenceLock")
         private final RuntimePermissionsPersistence mPersistence;
+        private final Object mPersistenceLock = new Object();
 
-        private final Handler mHandler = new MyHandler();
+        // Low-priority handlers running on SystemBg thread.
+        private final Handler mAsyncHandler = new MyHandler();
+        private final Handler mPersistenceHandler = new Handler(
+                BackgroundThread.getHandler().getLooper());
 
         private final Object mLock = new Object();
 
@@ -5398,6 +5408,11 @@
         // The mapping keys are user ids.
         private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray();
 
+        @GuardedBy("mLock")
+        // Staging area for states prepared to be written.
+        private final SparseArray<RuntimePermissionsState> mPendingStatesToWrite =
+                new SparseArray<>();
+
         // This is a hack to allow this class to invoke a write using Settings's data structures,
         // to facilitate moving to a finer scoped lock without a significant refactor.
         private final Consumer<Integer> mInvokeWriteUserStateAsyncCallback;
@@ -5462,7 +5477,7 @@
                 final long currentTimeMillis = SystemClock.uptimeMillis();
 
                 if (mWriteScheduled.get(userId)) {
-                    mHandler.removeMessages(userId);
+                    mAsyncHandler.removeMessages(userId);
 
                     // If enough time passed, write without holding off anymore.
                     final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
@@ -5471,7 +5486,7 @@
                             - lastNotWrittenMutationTimeMillis;
                     if (timeSinceLastNotWrittenMutationMillis
                             >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
-                        mHandler.obtainMessage(userId).sendToTarget();
+                        mAsyncHandler.obtainMessage(userId).sendToTarget();
                         return;
                     }
 
@@ -5481,67 +5496,110 @@
                     final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
                             maxDelayMillis);
 
-                    Message message = mHandler.obtainMessage(userId);
-                    mHandler.sendMessageDelayed(message, writeDelayMillis);
+                    Message message = mAsyncHandler.obtainMessage(userId);
+                    mAsyncHandler.sendMessageDelayed(message, writeDelayMillis);
                 } else {
                     mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
-                    Message message = mHandler.obtainMessage(userId);
-                    mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
+                    Message message = mAsyncHandler.obtainMessage(userId);
+                    mAsyncHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
                     mWriteScheduled.put(userId, true);
                 }
             }
         }
 
-        public void writeStateForUserSync(int userId, @NonNull LegacyPermissionDataProvider
+        public void writeStateForUser(int userId, @NonNull LegacyPermissionDataProvider
                 legacyPermissionDataProvider,
                 @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
-                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
+                @Nullable Handler pmHandler, @NonNull Object pmLock) {
+            final int version;
+            final String fingerprint;
             synchronized (mLock) {
-                mHandler.removeMessages(userId);
+                mAsyncHandler.removeMessages(userId);
                 mWriteScheduled.delete(userId);
 
-                legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
+                version = mVersions.get(userId, INITIAL_VERSION);
+                fingerprint = mFingerprints.get(userId);
+            }
 
-                int version = mVersions.get(userId, INITIAL_VERSION);
+            Runnable writer = () -> {
+                final RuntimePermissionsState runtimePermissions;
+                synchronized (pmLock) {
+                    legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
 
-                String fingerprint = mFingerprints.get(userId);
+                    Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+                            new ArrayMap<>();
+                    int packagesSize = packageStates.size();
+                    for (int i = 0; i < packagesSize; i++) {
+                        String packageName = packageStates.keyAt(i);
+                        PackageStateInternal packageState = packageStates.valueAt(i);
+                        if (!packageState.hasSharedUser()) {
+                            List<RuntimePermissionsState.PermissionState> permissions =
+                                    getPermissionsFromPermissionsState(
+                                            packageState.getLegacyPermissionState(), userId);
+                            if (permissions.isEmpty()
+                                    && !packageState.isInstallPermissionsFixed()) {
+                                // Storing an empty state means the package is known to the
+                                // system and its install permissions have been granted and fixed.
+                                // If this is not the case, we should not store anything.
+                                continue;
+                            }
+                            packagePermissions.put(packageName, permissions);
+                        }
+                    }
 
-                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
-                        new ArrayMap<>();
-                int packagesSize = packageStates.size();
-                for (int i = 0; i < packagesSize; i++) {
-                    String packageName = packageStates.keyAt(i);
-                    PackageStateInternal packageState = packageStates.valueAt(i);
-                    if (!packageState.hasSharedUser()) {
+                    Map<String, List<RuntimePermissionsState.PermissionState>>
+                            sharedUserPermissions =
+                            new ArrayMap<>();
+                    final int sharedUsersSize = sharedUsers.size();
+                    for (int i = 0; i < sharedUsersSize; i++) {
+                        String sharedUserName = sharedUsers.keyAt(i);
+                        SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
                         List<RuntimePermissionsState.PermissionState> permissions =
                                 getPermissionsFromPermissionsState(
-                                        packageState.getLegacyPermissionState(), userId);
-                        if (permissions.isEmpty() && !packageState.isInstallPermissionsFixed()) {
-                            // Storing an empty state means the package is known to the system and
-                            // its install permissions have been granted and fixed. If this is not
-                            // the case, we should not store anything.
-                            continue;
-                        }
-                        packagePermissions.put(packageName, permissions);
+                                        sharedUserSetting.getLegacyPermissionState(), userId);
+                        sharedUserPermissions.put(sharedUserName, permissions);
                     }
+
+                    runtimePermissions = new RuntimePermissionsState(version,
+                            fingerprint, packagePermissions, sharedUserPermissions);
                 }
-
-                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
-                        new ArrayMap<>();
-                final int sharedUsersSize = sharedUsers.size();
-                for (int i = 0; i < sharedUsersSize; i++) {
-                    String sharedUserName = sharedUsers.keyAt(i);
-                    SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
-                    List<RuntimePermissionsState.PermissionState> permissions =
-                            getPermissionsFromPermissionsState(
-                                    sharedUserSetting.getLegacyPermissionState(), userId);
-                    sharedUserPermissions.put(sharedUserName, permissions);
+                synchronized (mLock) {
+                    mPendingStatesToWrite.put(userId, runtimePermissions);
                 }
+                if (pmHandler != null) {
+                    // Async version.
+                    mPersistenceHandler.post(() -> writePendingStates());
+                } else {
+                    // Sync version.
+                    writePendingStates();
+                }
+            };
 
-                RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version,
-                        fingerprint, packagePermissions, sharedUserPermissions);
+            if (pmHandler != null) {
+                // Async version, use pmHandler.
+                pmHandler.post(writer);
+            } else {
+                // Sync version, use caller's thread.
+                writer.run();
+            }
+        }
 
-                mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
+        private void writePendingStates() {
+            while (true) {
+                final RuntimePermissionsState runtimePermissions;
+                final int userId;
+                synchronized (mLock) {
+                    if (mPendingStatesToWrite.size() == 0) {
+                        break;
+                    }
+                    userId = mPendingStatesToWrite.keyAt(0);
+                    runtimePermissions = mPendingStatesToWrite.valueAt(0);
+                    mPendingStatesToWrite.removeAt(0);
+                }
+                synchronized (mPersistenceLock) {
+                    mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
+                }
             }
         }
 
@@ -5563,7 +5621,7 @@
         private void onUserRemoved(int userId) {
             synchronized (mLock) {
                 // Make sure we do not
-                mHandler.removeMessages(userId);
+                mAsyncHandler.removeMessages(userId);
 
                 mPermissionUpgradeNeeded.delete(userId);
                 mVersions.delete(userId);
@@ -5572,7 +5630,7 @@
         }
 
         public void deleteUserRuntimePermissionsFile(int userId) {
-            synchronized (mLock) {
+            synchronized (mPersistenceLock) {
                 mPersistence.deleteForUser(UserHandle.of(userId));
             }
         }
@@ -5581,16 +5639,17 @@
                 @NonNull WatchedArrayMap<String, PackageSetting> packageSettings,
                 @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
                 @NonNull File userRuntimePermissionsFile) {
+            final RuntimePermissionsState runtimePermissions;
+            synchronized (mPersistenceLock) {
+                runtimePermissions = mPersistence.readForUser(UserHandle.of(userId));
+            }
+            if (runtimePermissions == null) {
+                readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings,
+                        sharedUsers);
+                writeStateForUserAsync(userId);
+                return;
+            }
             synchronized (mLock) {
-                RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of(
-                        userId));
-                if (runtimePermissions == null) {
-                    readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings,
-                            sharedUsers);
-                    writeStateForUserAsync(userId);
-                    return;
-                }
-
                 // If the runtime permissions file exists but the version is not set this is
                 // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
                 int version = runtimePermissions.getVersion();
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 860c54c..29c926c 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -598,7 +598,7 @@
             final String pkgName = pkgList[i];
             final int uid = uidList[i];
             SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList(
-                    snapshot.getPackageStateInternal(pkgName, SYSTEM_UID),
+                    snapshot, snapshot.getPackageStateInternal(pkgName, SYSTEM_UID),
                     userIds, snapshot.getPackageStates());
             if (allowList == null) {
                 allowList = new SparseArray<>(0);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 74f6296..092f3be 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -195,11 +195,6 @@
 
     /** All storage permissions */
     private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>();
-
-    private static final Set<String> READ_MEDIA_AURAL_PERMISSIONS = new ArraySet<>();
-
-    private static final Set<String> READ_MEDIA_VISUAL_PERMISSIONS = new ArraySet<>();
-
     /** All nearby devices permissions */
     private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
 
@@ -227,10 +222,10 @@
                 Manifest.permission.INTERACT_ACROSS_USERS_FULL);
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
         STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-        READ_MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
-        READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
-        READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
-        READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
@@ -2075,10 +2070,7 @@
                 PermissionInfo permInfo = getPermissionInfo(
                         newPackage.getRequestedPermissions().get(i),
                         newPackage.getPackageName(), 0);
-                boolean isStorageOrMedia = STORAGE_PERMISSIONS.contains(permInfo.name)
-                        || READ_MEDIA_AURAL_PERMISSIONS.contains(permInfo.name)
-                        || READ_MEDIA_VISUAL_PERMISSIONS.contains(permInfo.name);
-                if (permInfo == null || !isStorageOrMedia) {
+                if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
                     continue;
                 }
 
@@ -3152,9 +3144,7 @@
                 }
                 if (bp.isRuntime()) {
 
-                    if (!(newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)
-                            || READ_MEDIA_AURAL_PERMISSIONS.contains(newPerm)
-                            || READ_MEDIA_VISUAL_PERMISSIONS.contains(newPerm))) {
+                    if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
                         ps.updatePermissionFlags(bp,
                                 FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
                                 FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b091445..e1e2222 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,5 +16,22 @@
 
 package com.android.server.pm.snapshot;
 
+import android.annotation.NonNull;
+import android.content.pm.UserInfo;
+import android.util.ArrayMap;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.util.Collection;
+
 public interface PackageDataSnapshot {
+    @NonNull
+    ArrayMap<String, ? extends PackageStateInternal> getPackageStates();
+    @NonNull
+    UserInfo[] getUserInfos();
+    @NonNull
+    Collection<SharedUserSetting> getAllSharedUsers();
+    AndroidPackage getPackage(String packageName);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 75d4621..34c083a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2809,11 +2809,6 @@
             }
         }
 
-        if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED
-                && intentTask.getWindowingMode() != mPreferredWindowingMode) {
-            intentTask.setWindowingMode(mPreferredWindowingMode);
-        }
-
         // Update the target's launch cookie to those specified in the options if set
         if (mStartActivity.mLaunchCookie != null) {
             intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d997707..d254aaf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3331,7 +3331,7 @@
             }
             userId = activity.mUserId;
         }
-        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId, false);
+        return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
     }
 
     private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index cefc871..3bda2e6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,9 +562,6 @@
                 leafTask = null;
                 break;
             }
-            // The activity may be a child of embedded Task, but we want to find the owner Task.
-            // As a result, find the organized TaskFragment first.
-            final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
             // There are also cases where the Task contains non-embedded activity, such as launching
             // split TaskFragments from a non-embedded activity.
             // The hierarchy may looks like this:
@@ -575,10 +572,9 @@
             //    - TaskFragment
             //       - Activity
             // We also want to have the organizer handle the transition for such case.
-            final Task task = organizedTaskFragment != null
-                    ? organizedTaskFragment.getTask()
-                    : r.getTask();
-            if (task == null) {
+            final Task task = r.getTask();
+            // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
+            if (task == null || task.inPinnedWindowingMode()) {
                 leafTask = null;
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c162e8e..bb15d76 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -332,25 +332,29 @@
         if (windowSurface != null && windowSurface.isValid()) {
             Transaction transaction = mActivityRecord.getSyncTransaction();
 
+            final InsetsState insetsState = mainWindow.getInsetsState();
+            final InsetsSource taskbarInsetsSource =
+                    insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+
             if (!isLetterboxedNotForDisplayCutout(mainWindow)
-                    || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()) {
+                    || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+                    || taskbarInsetsSource == null) {
                 transaction
                         .setWindowCrop(windowSurface, null)
                         .setCornerRadius(windowSurface, 0);
                 return;
             }
 
-            final InsetsState insetsState = mainWindow.getInsetsState();
-            final InsetsSource taskbarInsetsSource =
-                    insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-
             Rect cropBounds = null;
 
             // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
             // an insets frame is equal to a navigation bar which shouldn't affect position of
             // rounded corners since apps are expected to handle navigation bar inset.
             // This condition checks whether the taskbar is visible.
-            if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+            // Do not crop the taskbar inset if the window is in immersive mode - the user can
+            // swipe to show/hide the taskbar as an overlay.
+            if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
+                    && taskbarInsetsSource.isVisible()) {
                 cropBounds = new Rect(mActivityRecord.getBounds());
                 // Activity bounds are in screen coordinates while (0,0) for activity's surface
                 // control is at the top left corner of an app window so offsetting bounds
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 22714c6..ca4c450 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -643,9 +643,9 @@
         }
     }
 
-    void setSecureSurfaceState(int userId) {
+    void refreshSecureSurfaceState() {
         forAllWindows((w) -> {
-            if (w.mHasSurface && userId == w.mShowUserId) {
+            if (w.mHasSurface) {
                 w.mWinAnimator.setSecureLocked(w.isSecureLocked());
             }
         }, true /* traverseTopToBottom */);
@@ -2097,11 +2097,14 @@
             r.setWindowingMode(intermediateWindowingMode);
             r.mWaitForEnteringPinnedMode = true;
             rootTask.forAllTaskFragments(tf -> {
-                // When the Task is entering picture-in-picture, we should clear all override from
-                // the client organizer, so the PIP activity can get the correct config from the
-                // Task, and prevent conflict with the PipTaskOrganizer.
-                if (tf.isOrganizedTaskFragment()) {
-                    tf.resetAdjacentTaskFragment();
+                if (!tf.isOrganizedTaskFragment()) {
+                    return;
+                }
+                tf.resetAdjacentTaskFragment();
+                if (tf.getTopNonFinishingActivity() != null) {
+                    // When the Task is entering picture-in-picture, we should clear all override
+                    // from the client organizer, so the PIP activity can get the correct config
+                    // from the Task, and prevent conflict with the PipTaskOrganizer.
                     tf.updateRequestedOverrideConfiguration(EMPTY);
                 }
             });
@@ -2116,7 +2119,8 @@
             // to the root pinned task
             r.supportsEnterPipOnTaskSwitch = false;
 
-            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) {
+            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip
+                    && organizedTf.isTaskVisibleRequested()) {
                 // Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
                 // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
                 mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7a7bf1f..ad1f121 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -977,6 +977,11 @@
                     candidateTask.reparent(this, onTop);
                 }
             }
+            // Update windowing mode if necessary, e.g. launch into a different windowing mode.
+            if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
+                    && candidateTask.getWindowingMode() != windowingMode) {
+                candidateTask.setWindowingMode(windowingMode);
+            }
             return candidateTask.getRootTask();
         }
         return new Task.Builder(mAtmService)
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e0346544..c78d4cb 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2302,11 +2302,32 @@
         return mTaskFragmentOrganizer != null;
     }
 
+    /** Whether the Task should be visible. */
+    boolean isTaskVisibleRequested() {
+        final Task task = getTask();
+        return task != null && task.isVisibleRequested();
+    }
+
     boolean isReadyToTransit() {
+        // We only wait when this is organized to give the organizer a chance to update.
+        if (!isOrganizedTaskFragment()) {
+            return true;
+        }
         // We don't want to start the transition if the organized TaskFragment is empty, unless
         // it is requested to be removed.
-        return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null
-                || mIsRemovalRequested;
+        if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+            return true;
+        }
+        // Organizer shouldn't change embedded TaskFragment in PiP.
+        if (isEmbeddedTaskFragmentInPip()) {
+            return true;
+        }
+        // The TaskFragment becomes empty because the last running activity enters PiP when the Task
+        // is minimized.
+        if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
+            return true;
+        }
+        return false;
     }
 
     /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
@@ -2424,8 +2445,19 @@
         mIsRemovalRequested = false;
         resetAdjacentTaskFragment();
         cleanUp();
+        final boolean shouldExecuteAppTransition =
+                mClearedTaskFragmentForPip && isTaskVisibleRequested();
         super.removeImmediately();
         sendTaskFragmentVanished();
+        if (shouldExecuteAppTransition && mDisplayContent != null) {
+            // When the Task is still visible, and the TaskFragment is removed because the last
+            // running activity is reparenting to PiP, it is possible that no activity is getting
+            // paused or resumed (having an embedded activity in split), thus we need to relayout
+            // and execute it explicitly.
+            mAtmService.addWindowLayoutReasons(
+                    ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
+            mDisplayContent.executeAppTransition();
+        }
     }
 
     /** Called on remove to cleanup. */
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index bd351ac..764960a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -518,8 +518,13 @@
         // longer has activities. As a result, the organizer will never get this info changed event
         // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
         // has running activities.
+        // Another case is when an organized TaskFragment became empty because the last running
+        // activity is reparented to a new Task due to enter PiP. We also want to notify the
+        // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment
+        // without causing the extra delay.
         return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
-                && task.topRunningActivity() == null && lastInfo != null
+                && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip())
+                && lastInfo != null
                 && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2f4dc51..c49cec2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2009,15 +2009,15 @@
      * the device policy cache.
      */
     @Override
-    public void refreshScreenCaptureDisabled(int userId) {
+    public void refreshScreenCaptureDisabled() {
         int callingUid = Binder.getCallingUid();
         if (callingUid != SYSTEM_UID) {
             throw new SecurityException("Only system can call refreshScreenCaptureDisabled.");
         }
 
         synchronized (mGlobalLock) {
-            // Update secure surface for all windows belonging to this user.
-            mRoot.setSecureSurfaceState(userId);
+            // Refresh secure surface for all windows.
+            mRoot.refreshSecureSurfaceState();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 238f96f..c6288a7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2040,8 +2040,7 @@
         if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
             return true;
         }
-        return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId,
-                mOwnerCanAddInternalSystemWindow);
+        return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index a301799..304d148 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -18,6 +18,7 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyManager;
+import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -37,8 +38,12 @@
      */
     private final Object mLock = new Object();
 
+    /**
+     * Indicates which user is screen capture disallowed on. Can be {@link UserHandle#USER_NULL},
+     * {@link UserHandle#USER_ALL} or a concrete user ID.
+     */
     @GuardedBy("mLock")
-    private final SparseBooleanArray mScreenCaptureDisabled = new SparseBooleanArray();
+    private int mScreenCaptureDisallowedUser = UserHandle.USER_NULL;
 
     @GuardedBy("mLock")
     private final SparseIntArray mPasswordQuality = new SparseIntArray();
@@ -57,7 +62,6 @@
 
     public void onUserRemoved(int userHandle) {
         synchronized (mLock) {
-            mScreenCaptureDisabled.delete(userHandle);
             mPasswordQuality.delete(userHandle);
             mPermissionPolicy.delete(userHandle);
             mCanGrantSensorsPermissions.delete(userHandle);
@@ -65,15 +69,22 @@
     }
 
     @Override
-    public boolean isScreenCaptureAllowed(int userHandle, boolean ownerCanAddInternalSystemWindow) {
+    public boolean isScreenCaptureAllowed(int userHandle) {
         synchronized (mLock) {
-            return !mScreenCaptureDisabled.get(userHandle) || ownerCanAddInternalSystemWindow;
+            return mScreenCaptureDisallowedUser != UserHandle.USER_ALL
+                    && mScreenCaptureDisallowedUser != userHandle;
         }
     }
 
-    public void setScreenCaptureAllowed(int userHandle, boolean allowed) {
+    public int getScreenCaptureDisallowedUser() {
         synchronized (mLock) {
-            mScreenCaptureDisabled.put(userHandle, !allowed);
+            return mScreenCaptureDisallowedUser;
+        }
+    }
+
+    public void setScreenCaptureDisallowedUser(int userHandle) {
+        synchronized (mLock) {
+            mScreenCaptureDisallowedUser = userHandle;
         }
     }
 
@@ -125,7 +136,7 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("Device policy cache:");
         pw.increaseIndent();
-        pw.println("Screen capture disabled: " + mScreenCaptureDisabled.toString());
+        pw.println("Screen capture disallowed user: " + mScreenCaptureDisallowedUser);
         pw.println("Password quality: " + mPasswordQuality.toString());
         pw.println("Permission policy: " + mPermissionPolicy.toString());
         pw.println("Admin can grant sensors permission: "
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 18bffeb..837d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1963,6 +1963,7 @@
 
             mOwners.removeProfileOwner(userHandle);
             mOwners.writeProfileOwner(userHandle);
+            pushScreenCapturePolicy(userHandle);
 
             DevicePolicyData policy = mUserData.get(userHandle);
             if (policy != null) {
@@ -3183,8 +3184,9 @@
 
     @Override
     void handleStartUser(int userId) {
-        updateScreenCaptureDisabled(userId,
-                getScreenCaptureDisabled(null, userId, false));
+        synchronized (getLockObject()) {
+            pushScreenCapturePolicy(userId);
+        }
         pushUserRestrictions(userId);
         // When system user is started (device boot), load cache for all users.
         // This is to mitigate the potential race between loading the cache and keyguard
@@ -6919,7 +6921,6 @@
             notifyResetProtectionPolicyChanged(frpAgentUid);
         }
         mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
-        updateScreenCaptureDisabled(parentId, getScreenCaptureDisabled(null, parentId, false));
 
         Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
     }
@@ -7686,10 +7687,7 @@
             if (ap.disableScreenCapture != disabled) {
                 ap.disableScreenCapture = disabled;
                 saveSettingsLocked(caller.getUserId());
-                final int affectedUserId = parent
-                        ? getProfileParentId(caller.getUserId())
-                        : caller.getUserId();
-                updateScreenCaptureDisabled(affectedUserId, disabled);
+                pushScreenCapturePolicy(caller.getUserId());
             }
         }
         DevicePolicyEventLogger
@@ -7699,6 +7697,38 @@
                 .write();
     }
 
+    // Push the screen capture policy for a given userId. If screen capture is disabled by the
+    // DO or COPE PO on the parent profile, then this takes precedence as screen capture will
+    // be disabled device-wide.
+    private void pushScreenCapturePolicy(int adminUserId) {
+        // Update screen capture device-wide if disabled by the DO or COPE PO on the parent profile.
+        ActiveAdmin admin =
+                getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceParentLocked(
+                        UserHandle.USER_SYSTEM);
+        if (admin != null && admin.disableScreenCapture) {
+            setScreenCaptureDisabled(UserHandle.USER_ALL);
+        } else {
+            // Otherwise, update screen capture only for the calling user.
+            admin = getProfileOwnerAdminLocked(adminUserId);
+            if (admin != null && admin.disableScreenCapture) {
+                setScreenCaptureDisabled(adminUserId);
+            } else {
+                setScreenCaptureDisabled(UserHandle.USER_NULL);
+            }
+        }
+    }
+
+    // Set the latest screen capture policy, overriding any existing ones.
+    // userHandle can be one of USER_ALL, USER_NULL or a concrete userId.
+    private void setScreenCaptureDisabled(int userHandle) {
+        int current = mPolicyCache.getScreenCaptureDisallowedUser();
+        if (userHandle == current) {
+            return;
+        }
+        mPolicyCache.setScreenCaptureDisallowedUser(userHandle);
+        updateScreenCaptureDisabled();
+    }
+
     /**
      * Returns whether or not screen capture is disabled for a given admin, or disabled for any
      * active admin (if given admin is null).
@@ -7708,7 +7738,6 @@
         if (!mHasFeature) {
             return false;
         }
-
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
 
@@ -7716,29 +7745,13 @@
             Preconditions.checkCallAuthorization(
                     isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
         }
-
-        synchronized (getLockObject()) {
-            if (who != null) {
-                ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
-                return (admin != null) && admin.disableScreenCapture;
-            }
-
-            final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
-            List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
-            for (ActiveAdmin admin: admins) {
-                if (admin.disableScreenCapture) {
-                    return true;
-                }
-            }
-            return false;
-        }
+        return !mPolicyCache.isScreenCaptureAllowed(userHandle);
     }
 
-    private void updateScreenCaptureDisabled(int userHandle, boolean disabled) {
-        mPolicyCache.setScreenCaptureAllowed(userHandle, !disabled);
+    private void updateScreenCaptureDisabled() {
         mHandler.post(() -> {
             try {
-                mInjector.getIWindowManager().refreshScreenCaptureDisabled(userHandle);
+                mInjector.getIWindowManager().refreshScreenCaptureDisabled();
             } catch (RemoteException e) {
                 Slogf.w(LOG_TAG, "Unable to notify WindowManager.", e);
             }
@@ -8504,6 +8517,13 @@
         }
     }
 
+    private boolean isDeviceOwnerUserId(int userId) {
+        synchronized (getLockObject()) {
+            return mOwners.hasDeviceOwner()
+                    && mOwners.getDeviceOwnerUserId() == userId;
+        }
+    }
+
     private boolean isDeviceOwnerPackage(String packageName, int userId) {
         synchronized (getLockObject()) {
             return mOwners.hasDeviceOwner()
@@ -8690,6 +8710,7 @@
     }
 
     ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+        ensureLocked();
         ActiveAdmin admin = getDeviceOwnerAdminLocked();
         if (admin == null) {
             admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
@@ -8697,6 +8718,16 @@
         return admin;
     }
 
+    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceParentLocked(int userId) {
+        ensureLocked();
+        ActiveAdmin admin = getDeviceOwnerAdminLocked();
+        if (admin != null) {
+            return admin;
+        }
+        admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+        return admin != null ? admin.getParentActiveAdmin() : null;
+    }
+
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
@@ -15151,6 +15182,7 @@
             saveSettingsLocked(userHandle);
             updateMaximumTimeToLockLocked(userHandle);
             policy.mRemovingAdmins.remove(adminReceiver);
+            pushScreenCapturePolicy(userHandle);
 
             Slogf.i(LOG_TAG, "Device admin " + adminReceiver + " removed from user " + userHandle);
         }
@@ -18236,7 +18268,7 @@
 
     private void updateNetworkPreferenceForUser(int userId,
             List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) {
-        if (!isManagedProfile(userId)) {
+        if (!isManagedProfile(userId) && !isDeviceOwnerUserId(userId)) {
             return;
         }
         List<ProfileNetworkPreference> preferences = new ArrayList<>();
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7b15224..9c0f713 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -30,6 +30,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.resolution.ComponentResolver
+import com.android.server.pm.snapshot.PackageDataSnapshot
 import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
 import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
@@ -361,8 +362,8 @@
             whenever(this.isCallerRecents(anyInt())) { false }
         }
         val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
-            whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
-                    any<PackageSetting>(), anyInt())) { false }
+            whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(), 
+                    any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
             whenever(this.snapshot()) { this@mockThrowOnUnmocked }
             whenever(registerObserver(any())).thenCallRealMethod()
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 353c8e2..55745cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -69,6 +69,7 @@
 import com.android.server.pm.pkg.parsing.ParsingPackage
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
+import com.android.server.pm.snapshot.PackageDataSnapshot
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
 import com.android.server.sdksandbox.SdkSandboxManagerLocal
 import com.android.server.testutils.TestHandler
@@ -329,7 +330,7 @@
         }
         whenever(mocks.injector.sharedLibrariesImpl) { mSharedLibraries }
         // everything visible by default
-        whenever(mocks.appsFilter.shouldFilterApplication(
+        whenever(mocks.appsFilter.shouldFilterApplication(any(PackageDataSnapshot::class.java),
                 anyInt(), nullable(), nullable(), anyInt())) { false }
 
         val displayManager: DisplayManager = mock()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 3ba9ca5..b9d6b2c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -23,6 +23,7 @@
 import android.util.ArrayMap
 import android.util.SparseArray
 import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.pm.snapshot.PackageDataSnapshot
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
 import com.android.server.testutils.nullable
@@ -389,6 +390,7 @@
 
     private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
         whenever(rule.mocks().appsFilter.getVisibilityAllowList(
+                any(PackageDataSnapshot::class.java),
             argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
             any() as ArrayMap<String, out PackageStateInternal>
         ))
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index aba93b0..f08d0ef6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
@@ -26,8 +27,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -53,6 +59,9 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @Presubmit
 @SmallTest
 public class FaceAuthenticationClientTest {
@@ -82,6 +91,10 @@
     private ClientMonitorCallback mCallback;
     @Mock
     private Sensor.HalSessionCallback mHalSessionCallback;
+    @Mock
+    private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private ICancellationSignal mCancellationSignal;
     @Captor
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
 
@@ -116,6 +129,25 @@
         verify(mHal, never()).authenticate(anyLong());
     }
 
+    @Test
+    public void cancelsAuthWhenNotInForeground() throws Exception {
+        final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
+        topTask.topActivity = new ComponentName("other", "thing");
+        when(mActivityTaskManager.getTasks(anyInt())).thenReturn(List.of(topTask));
+        when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
+
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+
+        verify(mCancellationSignal).cancel();
+    }
+
+    private FaceAuthenticationClient createClient() throws RemoteException {
+        return createClient(2 /* version */);
+    }
+
     private FaceAuthenticationClient createClient(int version) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
@@ -126,6 +158,11 @@
                 false /* requireConfirmation */, 9 /* sensorId */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
-                false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+                false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */) {
+            @Override
+            protected ActivityTaskManager getActivityTaskManager() {
+                return mActivityTaskManager;
+            }
+        };
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 7463022..1a49f8a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -31,7 +31,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
@@ -66,6 +70,7 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -111,6 +116,10 @@
     @Mock
     private Sensor.HalSessionCallback mHalSessionCallback;
     @Mock
+    private ActivityTaskManager mActivityTaskManager;
+    @Mock
+    private ICancellationSignal mCancellationSignal;
+    @Mock
     private Probe mLuxProbe;
     @Captor
     private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@@ -288,11 +297,36 @@
         verify(mSideFpsController).hide(anyInt());
     }
 
+    @Test
+    public void cancelsAuthWhenNotInForeground() throws Exception {
+        final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
+        topTask.topActivity = new ComponentName("other", "thing");
+        when(mActivityTaskManager.getTasks(anyInt())).thenReturn(List.of(topTask));
+        when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
+
+        final FingerprintAuthenticationClient client = createClientWithoutBackgroundAuth();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+
+        verify(mCancellationSignal).cancel();
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
-        return createClient(100);
+        return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
+    }
+
+    private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
+            throws RemoteException {
+        return createClient(100 /* version */, false /* allowBackgroundAuthentication */);
     }
 
     private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+        return createClient(version, true /* allowBackgroundAuthentication */);
+    }
+
+    private FingerprintAuthenticationClient createClient(int version,
+            boolean allowBackgroundAuthentication) throws RemoteException {
         when(mHal.getInterfaceVersion()).thenReturn(version);
 
         final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
@@ -302,7 +336,11 @@
         9 /* sensorId */, mBiometricLogger, mBiometricContext,
         true /* isStrongBiometric */,
         null /* taskStackListener */, mLockoutCache,
-        mUdfpsOverlayController, mSideFpsController,
-        true /* allowBackgroundAuthentication */, mSensorProps);
+        mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) {
+            @Override
+            protected ActivityTaskManager getActivityTaskManager() {
+                return mActivityTaskManager;
+            }
+        };
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 45d101a..545361c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4992,8 +4992,8 @@
                 .thenReturn(12345 /* some UID in user 0 */);
         // Make personal apps look suspended
         dpms.getUserData(UserHandle.USER_SYSTEM).mAppsSuspended = true;
-
-        clearInvocations(getServices().iwindowManager);
+        // Screen capture
+        dpm.setScreenCaptureDisabled(admin1, true);
 
         dpm.wipeData(0);
         verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(CALLER_USER_HANDLE);
@@ -5004,6 +5004,8 @@
         verify(getServices().userManager).setUserRestriction(
                 UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM);
 
+        clearInvocations(getServices().iwindowManager);
+
         // Some device-wide policies are getting cleaned-up after the user is removed.
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         sendBroadcastWithUser(dpms, Intent.ACTION_USER_REMOVED, CALLER_USER_HANDLE);
@@ -5020,9 +5022,10 @@
                 MockUtils.checkIntentAction(
                         DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
-        // Refresh strong auth timeout and screen capture
+        // Refresh strong auth timeout
         verify(getServices().lockSettingsInternal).refreshStrongAuthTimeout(UserHandle.USER_SYSTEM);
-        verify(getServices().iwindowManager).refreshScreenCaptureDisabled(UserHandle.USER_SYSTEM);
+        // Refresh screen capture
+        verify(getServices().iwindowManager).refreshScreenCaptureDisabled();
         // Unsuspend personal apps
         verify(getServices().packageManagerInternal)
                 .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index 3be2aac..c43e6ab 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
@@ -53,6 +54,7 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.WatchableTester;
 
 import org.junit.Before;
@@ -99,9 +101,11 @@
     @Mock
     AppsFilterImpl.FeatureConfig mFeatureConfigMock;
     @Mock
-    AppsFilterImpl.StateProvider mStateProvider;
+    PackageDataSnapshot mSnapshot;
     @Mock
     Executor mMockExecutor;
+    @Mock
+    PackageManagerInternal mPmInternal;
 
     private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>();
     private Collection<SharedUserSetting> mSharedUserSettings = new ArraySet<>();
@@ -201,12 +205,10 @@
         mExisting = new ArrayMap<>();
 
         MockitoAnnotations.initMocks(this);
-        doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
-                    .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST);
-            return new Object();
-        }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+        when(mSnapshot.getPackageStates()).thenAnswer(x -> mExisting);
+        when(mSnapshot.getAllSharedUsers()).thenReturn(mSharedUserSettings);
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
+        when(mPmInternal.snapshot()).thenReturn(mSnapshot);
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
@@ -223,11 +225,11 @@
     @Test
     public void testSystemReadyPropogates() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
         verify(mFeatureConfigMock).onSystemReady();
     }
@@ -235,13 +237,13 @@
     @Test
     public void testQueriesAction_FilterMatches() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter,
@@ -251,14 +253,16 @@
                 pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
         watcher.verifyChangeReported("add package");
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterAplication");
     }
+
     @Test
     public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
@@ -271,7 +275,7 @@
         simulateAddPackage(appsFilter, android, 1000,
                 b -> b.setSigningDetails(frameworkSigningDetails));
         watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         final int activityUid = DUMMY_TARGET_APPID;
@@ -292,14 +296,14 @@
                 pkg("com.calling.wildcard", new Intent("*")), wildcardUid);
         watcher.verifyChangeReported("addPackage");
 
-        assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity,
-                SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver,
-                SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+                targetActivity, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+                targetReceiver, SYSTEM_USER));
 
-        assertFalse(appsFilter.shouldFilterApplication(
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
                 wildcardUid, callingWildCard, targetActivity, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot,
                 wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterApplication");
     }
@@ -307,13 +311,13 @@
     @Test
     public void testQueriesProvider_FilterMatches() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter,
@@ -324,19 +328,19 @@
                 DUMMY_CALLING_APPID);
         watcher.verifyChangeReported("addPackage");
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterApplication");
     }
 
     @Test
     public void testOnUserUpdated_FilterMatches() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
 
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
@@ -346,41 +350,31 @@
 
         for (int subjectUserId : USER_ARRAY) {
             for (int otherUserId : USER_ARRAY) {
-                assertFalse(appsFilter.shouldFilterApplication(
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
                         UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
                         otherUserId));
             }
         }
 
         // adds new user
-        doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
-                    .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST_WITH_ADDED);
-            return new Object();
-        }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
-        appsFilter.onUserCreated(ADDED_USER);
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST_WITH_ADDED);
+        appsFilter.onUserCreated(mSnapshot, ADDED_USER);
 
         for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
             for (int otherUserId : USER_ARRAY_WITH_ADDED) {
-                assertFalse(appsFilter.shouldFilterApplication(
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
                         UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
                         otherUserId));
             }
         }
 
         // delete user
-        doAnswer(invocation -> {
-            ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
-                    .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST);
-            return new Object();
-        }).when(mStateProvider)
-                .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+        when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
         appsFilter.onUserDeleted(ADDED_USER);
 
         for (int subjectUserId : USER_ARRAY) {
             for (int otherUserId : USER_ARRAY) {
-                assertFalse(appsFilter.shouldFilterApplication(
+                assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
                         UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
                         otherUserId));
             }
@@ -390,13 +384,13 @@
     @Test
     public void testQueriesDifferentProvider_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addPackage");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter,
@@ -407,18 +401,18 @@
                 DUMMY_CALLING_APPID);
         watcher.verifyChangeReported("addPackage");
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterApplication");
     }
 
     @Test
     public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
@@ -427,34 +421,34 @@
                 pkgQueriesProvider("com.some.other.package", "com.some.authority"),
                 DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
     }
 
     @Test
     public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+                target, SYSTEM_USER));
     }
 
     @Test
     public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
@@ -465,35 +459,37 @@
                 DUMMY_CALLING_APPID);
 
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testNoQueries_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testNoUsesLibrary_Filters() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor);
 
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         final Signature mockSignature = Mockito.mock(Signature.class);
         final SigningDetails mockSigningDetails = new SigningDetails(
@@ -508,18 +504,19 @@
         final PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testUsesLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor);
 
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         final Signature mockSignature = Mockito.mock(Signature.class);
         final SigningDetails mockSigningDetails = new SigningDetails(
@@ -535,18 +532,19 @@
                 pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"),
                 DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor);
 
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         final Signature mockSignature = Mockito.mock(Signature.class);
         final SigningDetails mockSigningDetails = new SigningDetails(
@@ -562,18 +560,19 @@
                 pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"),
                 DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
-        final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+        final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
                 new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
                 mMockExecutor);
 
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         final Signature mockSignature = Mockito.mock(Signature.class);
         final SigningDetails mockSigningDetails = new SigningDetails(
@@ -589,22 +588,23 @@
                 pkg("com.some.other.package_a").setSharedUserId("com.some.uid"),
                 DUMMY_CALLING_APPID);
         simulateAddPackage(appsFilter, pkg("com.some.other.package_b")
-                .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
+                        .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
                 DUMMY_CALLING_APPID);
 
         // Although package_a doesn't use library, it should be granted visibility. It's because
         // package_a shares userId with package_b, and package_b uses that shared library.
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testForceQueryable_SystemDoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
@@ -612,36 +612,38 @@
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
 
     @Test
     public void testForceQueryable_NonSystemFilters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+                new AppsFilterImpl(mFeatureConfigMock,
                         new String[]{"com.some.package"}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID,
@@ -649,17 +651,18 @@
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
 
     @Test
     public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         final Signature frameworkSignature = Mockito.mock(Signature.class);
         final SigningDetails frameworkSigningDetails =
@@ -679,36 +682,38 @@
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID,
                 b -> b.setSigningDetails(otherSigningDetails));
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+                new AppsFilterImpl(mFeatureConfigMock,
                         new String[]{"com.some.package"}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
 
     @Test
     public void testSystemQueryable_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{},
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{},
                         true /* system force queryable */, null, mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID,
@@ -716,25 +721,27 @@
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testQueriesPackage_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
@@ -742,49 +749,52 @@
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
                 .thenReturn(false);
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(
                 appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID);
         PackageSetting calling = simulateAddPackage(
                 appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testSystemUid_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(SYSTEM_USER, null, target, SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
-                null, target, SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, SYSTEM_USER, null, target,
+                SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+                Process.FIRST_APPLICATION_UID - 1, null, target, SYSTEM_USER));
     }
 
     @Test
     public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
 
-        assertFalse(appsFilter.shouldFilterApplication(0, null, target, SECONDARY_USER));
-        assertFalse(appsFilter.shouldFilterApplication(
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, 0, null, target,
+                SECONDARY_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
                 UserHandle.getUid(SECONDARY_USER, Process.FIRST_APPLICATION_UID - 1),
                 null, target, SECONDARY_USER));
     }
@@ -792,25 +802,25 @@
     @Test
     public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, null, target,
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, null, target,
                 SYSTEM_USER));
     }
 
     @Test
     public void testNoTargetPackage_filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = new PackageSettingBuilder()
                 .setAppId(DUMMY_TARGET_APPID)
@@ -821,8 +831,9 @@
         PackageSetting calling = simulateAddPackage(appsFilter,
                 pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
@@ -838,7 +849,6 @@
         ParsingPackage actor = pkg("com.some.package.actor");
 
         final AppsFilterImpl appsFilter = new AppsFilterImpl(
-                mStateProvider,
                 mFeatureConfigMock,
                 new String[]{},
                 false,
@@ -868,7 +878,7 @@
                 },
                 mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         // Packages must be added in actor -> overlay -> target order so that the implicit
         // visibility of the actor into the overlay can be tested
@@ -878,33 +888,33 @@
                 simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
 
         // Actor can not see overlay (yet)
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
                 overlaySetting, SYSTEM_USER));
 
         PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
 
         // Actor can see both target and overlay
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
                 targetSetting, SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
                 overlaySetting, SYSTEM_USER));
 
         // But target/overlay can't see each other
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting,
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
                 overlaySetting, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
-                targetSetting, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+                overlaySetting, targetSetting, SYSTEM_USER));
 
         // And can't see the actor
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting,
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
                 actorSetting, SYSTEM_USER));
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
-                actorSetting, SYSTEM_USER));
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+                overlaySetting, actorSetting, SYSTEM_USER));
 
-        appsFilter.removePackage(targetSetting, false /* isReplace */);
+        appsFilter.removePackage(mSnapshot, targetSetting, false /* isReplace */);
 
         // Actor loses visibility to the overlay via removal of the target
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+        assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
                 overlaySetting, SYSTEM_USER));
     }
 
@@ -928,7 +938,6 @@
                 null /*settingBuilder*/);
 
         final AppsFilterImpl appsFilter = new AppsFilterImpl(
-                mStateProvider,
                 mFeatureConfigMock,
                 new String[]{},
                 false,
@@ -959,7 +968,7 @@
                 },
                 mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
         SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
@@ -971,19 +980,19 @@
         simulateAddPackage(ps2, appsFilter, actorSharedSetting);
 
         // actorTwo can see both target and overlay
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting,
-                targetSetting, SYSTEM_USER));
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting,
-                overlaySetting, SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+                actorSharedSetting, targetSetting, SYSTEM_USER));
+        assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+                actorSharedSetting, overlaySetting, SYSTEM_USER));
     }
 
     @Test
     public void testInitiatingApp_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
                 DUMMY_TARGET_APPID);
@@ -991,17 +1000,18 @@
                 DUMMY_CALLING_APPID,
                 withInstallSource(target.getPackageName(), null, null, null, false));
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testUninstalledInitiatingApp_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
                 DUMMY_TARGET_APPID);
@@ -1009,20 +1019,21 @@
                 DUMMY_CALLING_APPID,
                 withInstallSource(target.getPackageName(), null, null, null, true));
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
     }
 
     @Test
     public void testOriginatingApp_Filters() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1033,21 +1044,22 @@
                         false));
         watcher.verifyChangeReported("add package");
 
-        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertTrue(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterAplication");
     }
 
     @Test
     public void testInstallingApp_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1058,21 +1070,22 @@
                         false));
         watcher.verifyChangeReported("add package");
 
-        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
-                SYSTEM_USER));
+        assertFalse(
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+                        SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterAplication");
     }
 
     @Test
     public void testInstrumentation_DoesntFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1084,24 +1097,24 @@
         watcher.verifyChangeReported("add package");
 
         assertFalse(
-                appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
-                        SYSTEM_USER));
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target, SYSTEM_USER));
         assertFalse(
-                appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
-                        SYSTEM_USER));
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation, SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterAplication");
     }
 
     @Test
     public void testWhoCanSee() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasicAndroid");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
@@ -1123,14 +1136,14 @@
         watcher.verifyChangeReported("add package");
 
         final SparseArray<int[]> systemFilter =
-                appsFilter.getVisibilityAllowList(system, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
         watcher.verifyNoChangeReported("getVisibility");
         assertThat(toList(systemFilter.get(SYSTEM_USER)),
                 contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
         watcher.verifyNoChangeReported("getVisibility");
 
         final SparseArray<int[]> seesNothingFilter =
-                appsFilter.getVisibilityAllowList(seesNothing, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
         watcher.verifyNoChangeReported("getVisibility");
         assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
                 contains(seesNothingAppId));
@@ -1140,12 +1153,13 @@
         watcher.verifyNoChangeReported("getVisibility");
 
         final SparseArray<int[]> hasProviderFilter =
-                appsFilter.getVisibilityAllowList(hasProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
         assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
                 contains(hasProviderAppId, queriesProviderAppId));
 
         SparseArray<int[]> queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
         watcher.verifyNoChangeReported("getVisibility");
         assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
                 contains(queriesProviderAppId));
@@ -1158,7 +1172,8 @@
 
         // ensure implicit access is included in the filter
         queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
         watcher.verifyNoChangeReported("getVisibility");
         assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
                 contains(hasProviderAppId, queriesProviderAppId));
@@ -1168,13 +1183,13 @@
     @Test
     public void testOnChangeReport() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
         watcher.register();
         simulateAddBasicAndroid(appsFilter);
         watcher.verifyChangeReported("addBasic");
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         watcher.verifyChangeReported("systemReady");
 
         final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
@@ -1196,13 +1211,13 @@
         watcher.verifyChangeReported("addPackage");
 
         final SparseArray<int[]> systemFilter =
-                appsFilter.getVisibilityAllowList(system, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
         assertThat(toList(systemFilter.get(SYSTEM_USER)),
                 contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
         watcher.verifyNoChangeReported("get");
 
         final SparseArray<int[]> seesNothingFilter =
-                appsFilter.getVisibilityAllowList(seesNothing, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
         assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
                 contains(seesNothingAppId));
         assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
@@ -1210,13 +1225,14 @@
         watcher.verifyNoChangeReported("get");
 
         final SparseArray<int[]> hasProviderFilter =
-                appsFilter.getVisibilityAllowList(hasProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
         assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
                 contains(hasProviderAppId, queriesProviderAppId));
         watcher.verifyNoChangeReported("get");
 
         SparseArray<int[]> queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
         assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
                 contains(queriesProviderAppId));
         watcher.verifyNoChangeReported("get");
@@ -1228,23 +1244,24 @@
 
         // ensure implicit access is included in the filter
         queriesProviderFilter =
-                appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+                appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+                        mExisting);
         assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
                 contains(hasProviderAppId, queriesProviderAppId));
         watcher.verifyNoChangeReported("get");
 
         // remove a package
-        appsFilter.removePackage(seesNothing, false /* isReplace */);
+        appsFilter.removePackage(mSnapshot, seesNothing, false /* isReplace */);
         watcher.verifyChangeReported("removePackage");
     }
 
     @Test
     public void testOnChangeReportedFilter() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
         final WatchableTester watcher = new WatchableTester(appsFilter, "onChange filter");
         watcher.register();
 
@@ -1256,21 +1273,21 @@
         watcher.verifyChangeReported("addPackage");
 
         assertFalse(
-                appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
-                        SYSTEM_USER));
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target, SYSTEM_USER));
         assertFalse(
-                appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
-                        SYSTEM_USER));
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation, SYSTEM_USER));
         watcher.verifyNoChangeReported("shouldFilterApplication");
     }
 
     @Test
     public void testAppsFilterRead() throws Exception {
         final AppsFilterImpl appsFilter =
-                new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+                new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
                         mMockExecutor);
         simulateAddBasicAndroid(appsFilter);
-        appsFilter.onSystemReady();
+        appsFilter.onSystemReady(mPmInternal);
 
         PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
                 DUMMY_TARGET_APPID);
@@ -1288,25 +1305,29 @@
 
         AppsFilterSnapshot snapshot = appsFilter.snapshot();
         assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
                         SYSTEM_USER));
         assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+                        instrumentation,
                         SYSTEM_USER));
 
         SparseArray<int[]> queriesProviderFilter =
-                snapshot.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+                snapshot.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY, mExisting);
         assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
         assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
                 target.getPackageName()));
 
         // New changes don't affect the snapshot
-        appsFilter.removePackage(target, false);
+        appsFilter.removePackage(mSnapshot, target, false);
         assertTrue(
-                appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+                appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
                         SYSTEM_USER));
         assertFalse(
-                snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+                snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+                        target,
                         SYSTEM_USER));
 
     }
@@ -1343,7 +1364,7 @@
     }
 
     private PackageSetting simulateAddPackage(AppsFilterImpl filter,
-                ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
+            ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
             @Nullable SharedUserSetting sharedUserSetting) {
         final PackageSetting setting =
                 getPackageSettingFromParsingPackage(newPkgBuilder, appId, action);
@@ -1373,7 +1394,7 @@
             setting.setSharedUserAppId(sharedUserSetting.mAppId);
             mSharedUserSettings.add(sharedUserSetting);
         }
-        filter.addPackage(setting);
+        filter.addPackage(mSnapshot, setting);
     }
 
     private WithSettingBuilder withInstallSource(String initiatingPackageName,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index f4ab3db..39220a4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1516,7 +1516,7 @@
     private Settings makeSettings() {
         return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
                 mRuntimePermissionsPersistence, mPermissionDataProvider,
-                mDomainVerificationManager, new PackageManagerTracedLock());
+                mDomainVerificationManager, null, new PackageManagerTracedLock());
     }
 
     private void verifyKeySetMetaData(Settings settings)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 54fa4e4..240943c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -19,8 +19,10 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -30,6 +32,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 
 import android.content.res.Configuration;
@@ -257,6 +260,9 @@
                 .createActivityCount(1)
                 .build();
         final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
+        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+        activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
+        activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
         spyOn(mAtm.mTaskFragmentOrganizerController);
 
         // Move activity to pinned.
@@ -269,11 +275,47 @@
         final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
         assertTrue(info.isTaskFragmentClearedForPip());
         assertTrue(info.isEmpty());
+
+        // Notify organizer because the Task is still visible.
+        assertTrue(task.isVisibleRequested());
         verify(mAtm.mTaskFragmentOrganizerController)
                 .dispatchPendingInfoChangedEvent(taskFragment0);
     }
 
     @Test
+    public void testIsReadyToTransit() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+        final Task task = taskFragment.getTask();
+
+        // Not ready when it is empty.
+        assertFalse(taskFragment.isReadyToTransit());
+
+        // Ready when it is not empty.
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        doNothing().when(activity).setDropInputMode(anyInt());
+        activity.reparent(taskFragment, WindowContainer.POSITION_TOP);
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Ready when the Task is in PiP.
+        taskFragment.removeChild(activity);
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Ready when the TaskFragment is empty because of PiP, and the Task is invisible.
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskFragment.mClearedTaskFragmentForPip = true;
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Not ready if the task is still visible when the TaskFragment becomes empty.
+        doReturn(true).when(task).isVisibleRequested();
+        assertFalse(taskFragment.isReadyToTransit());
+    }
+
+    @Test
     public void testActivityHasOverlayOverUntrustedModeEmbedded() {
         final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
                 ACTIVITY_TYPE_STANDARD);