Merge "[snapshot] skip onChanged() in Watched* snapshot()" into tm-dev
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 436eac3..7c83d58 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -45,7 +45,9 @@
  * referenced in the manifest via {@code android:localeConfig} on
  * {@code <application>}.
  *
- * For more information, see TODO(b/214154050): add link to guide
+ * <p>For more information, see
+ * <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
+ * the section on per-app language preferences</a>.
  *
  * @attr ref android.R.styleable#LocaleConfig_Locale_name
  * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
diff --git a/core/java/android/app/admin/ProvisioningIntentHelper.java b/core/java/android/app/admin/ProvisioningIntentHelper.java
index fbad90c..1c38559 100644
--- a/core/java/android/app/admin/ProvisioningIntentHelper.java
+++ b/core/java/android/app/admin/ProvisioningIntentHelper.java
@@ -17,8 +17,10 @@
 package android.app.admin;
 
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_NFC;
@@ -36,12 +38,14 @@
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.util.Log;
 
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.Enumeration;
 import java.util.Properties;
+import java.util.Set;
 
 /**
  * Utility class that provides functionality to create provisioning intents from nfc intents.
@@ -124,12 +128,46 @@
             ComponentName componentName = ComponentName.unflattenFromString(
                     properties.getProperty(propertyName));
             bundle.putParcelable(propertyName, componentName);
+        } else if (EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE.equals(propertyName)
+                || EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE.equals(propertyName)) {
+            try {
+                bundle.putParcelable(propertyName,
+                        deserializeExtrasBundle(properties, propertyName));
+            } catch (IOException e) {
+                Log.e(TAG,
+                        "Failed to parse " + propertyName + ".", e);
+            }
         }
         else {
             bundle.putString(propertyName, properties.getProperty(propertyName));
         }
     }
 
+    /**
+     * Get a {@link PersistableBundle} from a {@code String} property in a {@link Properties}
+     * object.
+     * @param properties the source of the extra
+     * @param extraName key into the {@link Properties} object
+     * @return the {@link PersistableBundle} or {@code null} if there was no property with the
+     * given name
+     * @throws IOException if there was an error parsing the property
+     */
+    private static PersistableBundle deserializeExtrasBundle(
+            Properties properties, String extraName) throws IOException {
+        String serializedExtras = properties.getProperty(extraName);
+        if (serializedExtras == null) {
+            return null;
+        }
+        Properties bundleProperties = new Properties();
+        bundleProperties.load(new StringReader(serializedExtras));
+        PersistableBundle extrasBundle = new PersistableBundle(bundleProperties.size());
+        Set<String> propertyNames = bundleProperties.stringPropertyNames();
+        for (String propertyName : propertyNames) {
+            extrasBundle.putString(propertyName, bundleProperties.getProperty(propertyName));
+        }
+        return extrasBundle;
+    }
+
     private static Intent createProvisioningIntentFromBundle(Bundle bundle) {
         requireNonNull(bundle);
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d..52e64e8 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2618,6 +2618,15 @@
             return Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                targetCode)) {
+            Slog.w(TAG, "Package requires development platform " + targetCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return Build.VERSION.SDK_INT;
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             outError[0] = "Requires development platform " + targetCode
@@ -2689,6 +2698,15 @@
             return Build.VERSION_CODES.CUR_DEVELOPMENT;
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                minCode)) {
+            Slog.w(TAG, "Package requires min development platform " + minCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return Build.VERSION.SDK_INT;
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index cb55e30..20a4fdf 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -567,9 +567,14 @@
                     targetCode = minCode;
                 }
 
+                boolean allowUnknownCodenames = false;
+                if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) {
+                    allowUnknownCodenames = true;
+                }
+
                 ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
                         targetVer, targetCode, SDK_CODENAMES, input,
-                        /* allowUnknownCodenames= */ false);
+                        allowUnknownCodenames);
                 if (targetResult.isError()) {
                     return input.error(targetResult);
                 }
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 6d74b81..8cc4cdb 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -58,6 +58,7 @@
     private static final int MAX_FILE_NAME_SIZE = 223;
 
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
+    public static final int PARSE_APK_IN_APEX = 1 << 9;
 
     /**
      * Check if the given name is valid.
@@ -315,6 +316,15 @@
             return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                        minCode)) {
+            Slog.w(TAG, "Parsed package requires min development platform " + minCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return input.success(Build.VERSION.SDK_INT);
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -367,16 +377,29 @@
             return input.success(targetVers);
         }
 
-        if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
-            return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
-        }
-
         // If it's a pre-release SDK and the codename matches this platform, it
         // definitely targets this SDK.
         if (matchTargetCode(platformSdkCodenames, targetCode)) {
             return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
         }
 
+        // STOPSHIP: hack for the pre-release SDK
+        if (platformSdkCodenames.length == 0
+                && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+                        targetCode)) {
+            Slog.w(TAG, "Parsed package requires development platform " + targetCode
+                    + ", returning current version " + Build.VERSION.SDK_INT);
+            return input.success(Build.VERSION.SDK_INT);
+        }
+
+        try {
+            if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+                return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+            }
+        } catch (IllegalArgumentException e) {
+            return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
+        }
+
         // Otherwise, we're looking at an incompatible pre-release SDK.
         if (platformSdkCodenames.length > 0) {
             return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 37f44e9..9a2f7ba 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -738,6 +738,14 @@
      */
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
 
+    /**
+     * Namespace for DevicePolicyManager related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
+            "device_policy_manager";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dac54cf..f35a458 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9695,6 +9695,40 @@
                 "active_unlock_on_biometric_fail";
 
         /**
+         * If active unlock triggers on biometric failures, include the following error codes
+         * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}.
+         * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock
+         * should never trigger on any face errors, this should be set to an empty string.
+         * A null value will use the system default value (TIMEOUT).
+         * @hide
+         */
+        public static final String ACTIVE_UNLOCK_ON_FACE_ERRORS =
+                "active_unlock_on_face_errors";
+
+        /**
+         * If active unlock triggers on biometric failures, include the following acquired info
+         * as a "biometric failure". See {@link android.hardware.biometrics.BiometricFaceConstants}.
+         * Acquired codes should be separated by a pipe. For example: "1|4|5". If active unlock
+         * should never on trigger on any acquired info messages, this should be
+         * set to an empty string. A null value will use the system default value (none).
+         * @hide
+         */
+        public static final String ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO =
+                "active_unlock_on_face_acquire_info";
+
+        /**
+         * If active unlock triggers on biometric failures, then also request active unlock on
+         * unlock intent when each setting (BiometricType) is the only biometric type enrolled.
+         * Biometric types should be separated by a pipe. For example: "0|3" or "0". If this
+         * setting should be disabled, then this should be set to an empty string. A null value
+         * will use the system default value (0 / None).
+         *   0 = None, 1 = Any face, 2 = Any fingerprint, 3 = Under display fingerprint
+         * @hide
+         */
+        public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED =
+                "active_unlock_on_unlock_intent_when_biometric_enrolled";
+
+        /**
          * Whether the assist gesture should be enabled.
          *
          * @hide
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5bc340b..00052f6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3108,10 +3108,14 @@
 
         /**
          * The preferred refresh rate for the window.
-         *
+         * <p>
          * This must be one of the supported refresh rates obtained for the display(s) the window
          * is on. The selected refresh rate will be applied to the display's default mode.
-         *
+         * <p>
+         * This should be used in favor of {@link LayoutParams#preferredDisplayModeId} for
+         * applications that want to specify the refresh rate, but do not want to specify a
+         * preference for any other displayMode properties (e.g., resolution).
+         * <p>
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          *
          * @see Display#getSupportedRefreshRates()
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 7db4243..0976f45 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,8 +21,10 @@
 
 import android.annotation.AnyThread;
 import android.annotation.BinderThread;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.app.IWindowToken;
 import android.app.ResourcesManager;
 import android.content.Context;
@@ -33,7 +35,6 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IWindowManager;
@@ -42,6 +43,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.lang.ref.WeakReference;
 
@@ -76,7 +78,7 @@
 
     private boolean mAttachToWindowContainer;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
 
     /**
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
@@ -188,8 +190,8 @@
     @BinderThread
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
-        mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
-                true /* shouldReportConfigChange */));
+        mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
+                newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
     }
 
     // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
@@ -279,12 +281,16 @@
     @BinderThread
     @Override
     public void onWindowTokenRemoved() {
-        mHandler.post(() -> {
-            final Context context = mContextRef.get();
-            if (context != null) {
-                context.destroy();
-                mContextRef.clear();
-            }
-        });
+        mHandler.post(PooledLambda.obtainRunnable(
+                WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse());
+    }
+
+    @MainThread
+    private void onWindowTokenRemovedInner() {
+        final Context context = mContextRef.get();
+        if (context != null) {
+            context.destroy();
+            mContextRef.clear();
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 957a636..e56d92b 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -91,7 +91,12 @@
         } else {
             builder.setPositiveButton(R.string.ok, null);
         }
-        builder.show();
+        final AlertDialog dialog = builder.create();
+        dialog.create();
+        // Prevents screen overlay attack.
+        getWindow().setHideOverlayWindows(true);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+        dialog.show();
     }
 
     private String getDialogTitle() {
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 3eb9804..5adaf4f 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -28,18 +28,15 @@
 public final class NotificationAccessConfirmationActivityContract {
     public static final String EXTRA_USER_ID = "user_id";
     public static final String EXTRA_COMPONENT_NAME = "component_name";
-    public static final String EXTRA_PACKAGE_TITLE = "package_title";
 
     /**
      * Creates a launcher intent for NotificationAccessConfirmationActivity.
      */
-    public static Intent launcherIntent(Context context, int userId, ComponentName component,
-            String packageTitle) {
+    public static Intent launcherIntent(Context context, int userId, ComponentName component) {
         return new Intent()
                 .setComponent(ComponentName.unflattenFromString(context.getString(
                         R.string.config_notificationAccessConfirmationActivity)))
                 .putExtra(EXTRA_USER_ID, userId)
-                .putExtra(EXTRA_COMPONENT_NAME, component)
-                .putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
+                .putExtra(EXTRA_COMPONENT_NAME, component);
     }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 3f87de2..b03a8cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 207;
+    static final int VERSION = 208;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -3981,8 +3981,7 @@
         if (idxObj != null) {
             idx = idxObj;
             if ((idx & TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                idx &= ~TAG_FIRST_OCCURRENCE_FLAG;
-                mHistoryTagPool.put(tag, idx);
+                mHistoryTagPool.put(tag, idx & ~TAG_FIRST_OCCURRENCE_FLAG);
             }
             return idx;
         } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1db4bbb..ea5797d 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1262,7 +1262,7 @@
             }
         }
 
-        if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
+        if (forceConsumingNavBar && !hideNavigation && !mDrawLegacyNavigationBarBackgroundHandled) {
             mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
         } else {
             mBackgroundInsets = Insets.NONE;
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index b6fbe20..f24c666 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1264,6 +1264,12 @@
     size_t numPositionMasks = 0;
     size_t numIndexMasks = 0;
 
+    int audioFormat = audioFormatFromNative(nAudioProfile->format);
+    if (audioFormat == ENCODING_INVALID) {
+        ALOGW("Unknown native audio format for JAVA API: %u", nAudioProfile->format);
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+
     // count up how many masks are positional and indexed
     for (size_t index = 0; index < nAudioProfile->num_channel_masks; index++) {
         const audio_channel_mask_t mask = nAudioProfile->channel_masks[index];
@@ -1306,10 +1312,9 @@
         ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
     }
 
-    *jAudioProfile =
-            env->NewObject(gAudioProfileClass, gAudioProfileCstor,
-                           audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
-                           jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
+    *jAudioProfile = env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
+                                    jSamplingRates.get(), jChannelMasks.get(),
+                                    jChannelIndexMasks.get(), encapsulationType);
 
     if (*jAudioProfile == nullptr) {
         return AUDIO_JAVA_ERROR;
@@ -1368,6 +1373,10 @@
         jobject jAudioProfile = nullptr;
         jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
                                                 useInMask);
+        if (jStatus == AUDIO_JAVA_BAD_VALUE) {
+            // skipping Java layer unsupported audio formats
+            continue;
+        }
         if (jStatus != NO_ERROR) {
             jStatus = (jint)AUDIO_JAVA_ERROR;
             goto exit;
@@ -2406,8 +2415,13 @@
         goto exit;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
-        jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor,
-                                                audioFormatFromNative(surroundFormats[i]));
+        int audioFormat = audioFormatFromNative(surroundFormats[i]);
+        if (audioFormat == ENCODING_INVALID) {
+            // skipping Java layer unsupported audio formats
+            ALOGW("Unknown surround native audio format for JAVA API: %u", surroundFormats[i]);
+            continue;
+        }
+        jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor, audioFormat);
         jobject enabled = env->NewObject(gBooleanClass, gBooleanCstor, surroundFormatsEnabled[i]);
         env->CallObjectMethod(jSurroundFormats, gMapPut, surroundFormat, enabled);
         env->DeleteLocalRef(surroundFormat);
@@ -2453,8 +2467,13 @@
         goto exit;
     }
     for (size_t i = 0; i < numSurroundFormats; i++) {
-        jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor,
-                                                audioFormatFromNative(surroundFormats[i]));
+        int audioFormat = audioFormatFromNative(surroundFormats[i]);
+        if (audioFormat == ENCODING_INVALID) {
+            // skipping Java layer unsupported audio formats
+            ALOGW("Unknown surround native audio format for JAVA API: %u", surroundFormats[i]);
+            continue;
+        }
+        jobject surroundFormat = env->NewObject(gIntegerClass, gIntegerCstor, audioFormat);
         env->CallObjectMethod(jSurroundFormats, gArrayListMethods.add, surroundFormat);
         env->DeleteLocalRef(surroundFormat);
     }
@@ -2919,6 +2938,10 @@
     for (const auto &audioProfile : audioProfiles) {
         jobject jAudioProfile;
         jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false);
+        if (jStatus == AUDIO_JAVA_BAD_VALUE) {
+            // skipping Java layer unsupported audio formats
+            continue;
+        }
         if (jStatus != AUDIO_JAVA_SUCCESS) {
             return jStatus;
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
index 2262c05..3858792 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryIteratorTest.java
@@ -35,6 +35,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class BatteryStatsHistoryIteratorTest {
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
@@ -124,7 +125,10 @@
         // More than 32k strings
         final int eventCount = 0x7FFF + 100;
         for (int i = 0; i < eventCount; i++) {
-            mBatteryStats.noteAlarmStartLocked("a" + i, null, APP_UID, 3_000_000, 2_000_000);
+            // Names repeat in order to verify de-duping of identical history tags.
+            String name = "a" + (i % 10);
+            mBatteryStats.noteAlarmStartLocked(name, null, APP_UID, 3_000_000, 2_000_000);
+            mBatteryStats.noteAlarmFinishLocked(name, null, APP_UID, 3_500_000, 2_500_000);
         }
 
         final BatteryStatsHistoryIterator iterator =
@@ -149,10 +153,23 @@
         assertThat(item.time).isEqualTo(2_000_000);
 
         for (int i = 0; i < eventCount; i++) {
+            String name = "a" + (i % 10);
             assertThat(iterator.next(item)).isTrue();
+            // Skip a blank event inserted at the start of every buffer
+            if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
+                assertThat(iterator.next(item)).isTrue();
+            }
             assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM
                     | BatteryStats.HistoryItem.EVENT_FLAG_START);
-            assertThat(item.eventTag.string).isEqualTo("a" + i);
+            assertThat(item.eventTag.string).isEqualTo(name);
+
+            assertThat(iterator.next(item)).isTrue();
+            if (item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
+                assertThat(iterator.next(item)).isTrue();
+            }
+            assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_ALARM
+                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH);
+            assertThat(item.eventTag.string).isEqualTo(name);
         }
 
         assertThat(iterator.next(item)).isFalse();
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 12d3d64..60da2e8 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2485,12 +2485,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "323235828": {
-      "message": "Delaying app transition for recents animation to finish",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/AppTransitionController.java"
-    },
     "327461496": {
       "message": "Complete pause: %s",
       "level": "VERBOSE",
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java
new file mode 100644
index 0000000..4855ad0
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPrivateKey.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.KeyDescriptor;
+
+import java.security.PrivateKey;
+import java.security.interfaces.EdECKey;
+import java.security.spec.NamedParameterSpec;
+
+/**
+ * EdEC private key (instance of {@link PrivateKey} and {@link EdECKey}) backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreEdECPrivateKey extends AndroidKeyStorePrivateKey implements EdECKey {
+    public AndroidKeyStoreEdECPrivateKey(
+            @NonNull KeyDescriptor descriptor, long keyId,
+            @NonNull Authorization[] authorizations,
+            @NonNull String algorithm,
+            @NonNull KeyStoreSecurityLevel securityLevel) {
+        super(descriptor, keyId, authorizations, algorithm, securityLevel);
+    }
+
+    @Override
+    public NamedParameterSpec getParams() {
+        return NamedParameterSpec.ED25519;
+    }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java
new file mode 100644
index 0000000..642e088
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreEdECPublicKey.java
@@ -0,0 +1,145 @@
+/*
+ * 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 android.security.keystore2;
+
+import android.annotation.NonNull;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import java.math.BigInteger;
+import java.security.interfaces.EdECPublicKey;
+import java.security.spec.EdECPoint;
+import java.security.spec.NamedParameterSpec;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * {@link EdECPublicKey} backed by keystore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreEdECPublicKey extends AndroidKeyStorePublicKey
+        implements EdECPublicKey {
+    /**
+     * DER sequence, as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-4 and
+     * https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.
+     * SEQUENCE (2 elem)
+     *  SEQUENCE (1 elem)
+     *    OBJECT IDENTIFIER 1.3.101.112 curveEd25519 (EdDSA 25519 signature algorithm)
+     *    as defined in https://datatracker.ietf.org/doc/html/rfc8410#section-3
+     *  BIT STRING (256 bit) as defined in
+     *  https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
+     */
+    private static final byte[] DER_KEY_PREFIX = new byte[] {
+            0x30,
+            0x2a,
+            0x30,
+            0x05,
+            0x06,
+            0x03,
+            0x2b,
+            0x65,
+            0x70,
+            0x03,
+            0x21,
+            0x00,
+    };
+    private static final int ED25519_KEY_SIZE_BYTES = 32;
+
+    private byte[] mEncodedKey;
+    private EdECPoint mPoint;
+
+    public AndroidKeyStoreEdECPublicKey(
+            @NonNull KeyDescriptor descriptor,
+            @NonNull KeyMetadata metadata,
+            @NonNull String algorithm,
+            @NonNull KeyStoreSecurityLevel iSecurityLevel,
+            @NonNull byte[] encodedKey) {
+        super(descriptor, metadata, encodedKey, algorithm, iSecurityLevel);
+        mEncodedKey = encodedKey;
+
+        int preambleLength = matchesPreamble(DER_KEY_PREFIX, encodedKey);
+        if (preambleLength == 0) {
+            throw new IllegalArgumentException("Key size is not correct size");
+        }
+
+        mPoint = pointFromKeyByteArray(
+                Arrays.copyOfRange(encodedKey, preambleLength, encodedKey.length));
+    }
+
+    @Override
+    AndroidKeyStorePrivateKey getPrivateKey() {
+        return new AndroidKeyStoreEdECPrivateKey(
+                getUserKeyDescriptor(),
+                getKeyIdDescriptor().nspace,
+                getAuthorizations(),
+                "EdDSA",
+                getSecurityLevel());
+    }
+
+    @Override
+    public NamedParameterSpec getParams() {
+        return NamedParameterSpec.ED25519;
+    }
+
+    @Override
+    public EdECPoint getPoint() {
+        return mPoint;
+    }
+
+    private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+        if (encoded.length != (preamble.length + ED25519_KEY_SIZE_BYTES)) {
+            return 0;
+        }
+        if (Arrays.compare(preamble, Arrays.copyOf(encoded, preamble.length)) != 0) {
+            return 0;
+        }
+        return preamble.length;
+    }
+
+    private static EdECPoint pointFromKeyByteArray(byte[] coordinates) {
+        Objects.requireNonNull(coordinates);
+
+        // Oddity of the key is the most-significant bit of the last byte.
+        boolean isOdd = (0x80 & coordinates[coordinates.length - 1]) != 0;
+        // Zero out the oddity bit.
+        coordinates[coordinates.length - 1] &= (byte) 0x7f;
+        // Representation of Y is in little-endian, according to rfc8032 section-3.1.
+        reverse(coordinates);
+        // The integer representing Y starts from the first bit in the coordinates array.
+        BigInteger y = new BigInteger(1, coordinates);
+        return new EdECPoint(isOdd, y);
+    }
+
+    private static void reverse(byte[] coordinateArray) {
+        int start = 0;
+        int end = coordinateArray.length - 1;
+        while (start < end) {
+            byte tmp = coordinateArray[start];
+            coordinateArray[start] = coordinateArray[end];
+            coordinateArray[end] = tmp;
+            start++;
+            end--;
+        }
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return mEncodedKey.clone();
+    }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index d31499e..0355628 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -224,7 +224,6 @@
 
         String jcaKeyAlgorithm = publicKey.getAlgorithm();
 
-        KeyStoreSecurityLevel securityLevel = iSecurityLevel;
         if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
             return new AndroidKeyStoreECPublicKey(descriptor, metadata,
                     iSecurityLevel, (ECPublicKey) publicKey);
@@ -232,8 +231,9 @@
             return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
                     iSecurityLevel, (RSAPublicKey) publicKey);
         } else if (ED25519_OID.equalsIgnoreCase(jcaKeyAlgorithm)) {
-            //TODO(b/214203951) missing classes in conscrypt
-            throw new ProviderException("Curve " + ED25519_OID + " not supported yet");
+            final byte[] publicKeyEncoded = publicKey.getEncoded();
+            return new AndroidKeyStoreEdECPublicKey(descriptor, metadata, ED25519_OID,
+                    iSecurityLevel, publicKeyEncoded);
         } else if (X25519_ALIAS.equalsIgnoreCase(jcaKeyAlgorithm)) {
             //TODO(b/214203951) missing classes in conscrypt
             throw new ProviderException("Curve " + X25519_ALIAS + " not supported yet");
diff --git a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java
new file mode 100644
index 0000000..5bd5797
--- /dev/null
+++ b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreEdECPublicKeyTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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 android.security.keystore2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.math.BigInteger;
+import java.util.Base64;
+
+@RunWith(AndroidJUnit4.class)
+public class AndroidKeyStoreEdECPublicKeyTest {
+    private static KeyDescriptor descriptor() {
+        final KeyDescriptor keyDescriptor = new KeyDescriptor();
+        keyDescriptor.alias = "key";
+        keyDescriptor.blob = null;
+        keyDescriptor.domain = Domain.APP;
+        keyDescriptor.nspace = -1;
+        return keyDescriptor;
+    }
+
+    private static KeyMetadata metadata(byte[] cert, byte[] certChain) {
+        KeyMetadata metadata = new KeyMetadata();
+        metadata.authorizations = new Authorization[0];
+        metadata.certificate = cert;
+        metadata.certificateChain = certChain;
+        metadata.key = descriptor();
+        metadata.modificationTimeMs = 0;
+        metadata.keySecurityLevel = 1;
+        return metadata;
+    }
+
+    @Mock
+    private KeyStoreSecurityLevel mKeystoreSecurityLevel;
+
+    private static class EdECTestVector {
+        public final byte[] encodedKeyBytes;
+        public final boolean isOdd;
+        public final BigInteger yValue;
+
+        EdECTestVector(String b64KeyBytes, boolean isOdd, String yValue) {
+            this.encodedKeyBytes = Base64.getDecoder().decode(b64KeyBytes);
+            this.isOdd = isOdd;
+            this.yValue = new BigInteger(yValue);
+        }
+    }
+
+    private static final EdECTestVector[] ED_EC_TEST_VECTORS = new EdECTestVector[]{
+            new EdECTestVector("MCowBQYDK2VwAyEADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSo=",
+                    false,
+                    "19147682157189290216699341180089409126316261024914226007941553249095116672780"
+                    ),
+            new EdECTestVector("MCowBQYDK2VwAyEA/0E1IRNzGj85Ot/TPeXqifkqTkdk4voleH0hIq59D9w=",
+                    true,
+                    "41640152188550647350742178040529506688513911269563908889464821205156322689535"
+                    ),
+            new EdECTestVector("MCowBQYDK2VwAyEAunOvGuenetl9GQSXGVo5L3RIr4OOIpFIv/Zre8qTc/8=",
+                    true,
+                    "57647939198144376128225770417635248407428273266444593100194116168980378907578"
+                    ),
+            new EdECTestVector("MCowBQYDK2VwAyEA2hHqaZ5IolswN1Yd58Y4hzhmUMCCqc4PW5A/SFLmTX8=",
+                    false,
+                    "57581368614046789120409806291852629847774713088410311752049592044694364885466"
+                    ),
+    };
+
+    @Test
+    public void testParsingOfValidKeys() {
+        for (EdECTestVector testVector : ED_EC_TEST_VECTORS) {
+            AndroidKeyStoreEdECPublicKey pkey = new AndroidKeyStoreEdECPublicKey(descriptor(),
+                    metadata(null, null), "EdDSA", mKeystoreSecurityLevel,
+                    testVector.encodedKeyBytes);
+
+            assertEquals(pkey.getPoint().isXOdd(), testVector.isOdd);
+            assertEquals(pkey.getPoint().getY(), testVector.yValue);
+        }
+    }
+
+    @Test
+    public void testFailedParsingOfKeysWithDifferentOid() {
+        final byte[] testVectorWithIncorrectOid = Base64.getDecoder().decode(
+                "MCowBQYDLGVwAyEADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSo=");
+        assertThrows("OID should be unrecognized", IllegalArgumentException.class,
+                () -> new AndroidKeyStoreEdECPublicKey(descriptor(), metadata(null, null), "EdDSA",
+                        mKeystoreSecurityLevel, testVectorWithIncorrectOid));
+    }
+
+    @Test
+    public void testFailedParsingOfKeysWithWrongSize() {
+        final byte[] testVectorWithIncorrectKeySize = Base64.getDecoder().decode(
+        "MCwwBQYDK2VwAyMADE+wvQqNHxaERPhAZ0rCFlgFbfWLs/YonPXdSTw0VSrOzg==");
+        assertThrows("Key length should be invalid", IllegalArgumentException.class,
+                () -> new AndroidKeyStoreEdECPublicKey(descriptor(), metadata(null, null), "EdDSA",
+                        mKeystoreSecurityLevel, testVectorWithIncorrectKeySize));
+    }
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 64017e1..d04c349 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -40,6 +40,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG,
             false, Consts.TAG_WM_SHELL),
+    WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_SHELL),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 91f9d25..d543aa7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -54,6 +54,9 @@
 import static com.android.wm.shell.transition.Transitions.isClosingType;
 import static com.android.wm.shell.transition.Transitions.isOpeningType;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -68,6 +71,7 @@
 import android.graphics.Rect;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -147,7 +151,9 @@
 
     private final int mDisplayId;
     private SplitLayout mSplitLayout;
+    private ValueAnimator mDividerFadeInAnimator;
     private boolean mDividerVisible;
+    private boolean mKeyguardShowing;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -404,6 +410,7 @@
         mSplitLayout.init();
         // Set false to avoid record new bounds with old task still on top;
         mShouldUpdateRecents = false;
+        mIsDividerRemoteAnimating = true;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
@@ -417,7 +424,6 @@
                     RemoteAnimationTarget[] wallpapers,
                     RemoteAnimationTarget[] nonApps,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
-                mIsDividerRemoteAnimating = true;
                 RemoteAnimationTarget[] augmentedNonApps =
                         new RemoteAnimationTarget[nonApps.length + 1];
                 for (int i = 0; i < nonApps.length; ++i) {
@@ -494,8 +500,10 @@
         }
         // Using legacy transitions, so we can't use blast sync since it conflicts.
         mTaskOrganizer.applyTransaction(wct);
-        mSyncQueue.runInSync(t ->
-                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+        mSyncQueue.runInSync(t -> {
+            setDividerVisibility(true, t);
+            updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+        });
     }
 
     private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
@@ -510,10 +518,6 @@
                         ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
         } else {
             mSyncQueue.queue(evictWct);
-            mSyncQueue.runInSync(t -> {
-                setDividerVisibility(true, t);
-                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-            });
         }
     }
 
@@ -623,16 +627,12 @@
     }
 
     void onKeyguardVisibilityChanged(boolean showing) {
+        mKeyguardShowing = showing;
         if (!mMainStage.isActive()) {
             return;
         }
 
-        if (ENABLE_SHELL_TRANSITIONS) {
-            // Update divider visibility so it won't float on top of keyguard.
-            setDividerVisibility(!showing, null /* transaction */);
-        }
-
-        if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+        if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
             if (ENABLE_SHELL_TRANSITIONS) {
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
@@ -643,7 +643,10 @@
                         mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
                         EXIT_REASON_DEVICE_FOLDED);
             }
+            return;
         }
+
+        setDividerVisibility(!mKeyguardShowing, null);
     }
 
     void onFinishedWakingUp() {
@@ -727,6 +730,7 @@
             setResizingSplits(false /* resizing */);
             t.setWindowCrop(mMainStage.mRootLeash, null)
                     .setWindowCrop(mSideStage.mRootLeash, null);
+            setDividerVisibility(false, t);
         });
 
         // Hide divider and reset its position.
@@ -1055,8 +1059,31 @@
     }
 
     private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
+        if (visible == mDividerVisible) {
+            return;
+        }
+
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                "%s: Request to %s divider bar from %s.", TAG,
+                (visible ? "show" : "hide"), Debug.getCaller());
+
+        // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
+        // dismissing animation.
+        if (visible && mKeyguardShowing) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "%s:   Defer showing divider bar due to keyguard showing.", TAG);
+            return;
+        }
+
         mDividerVisible = visible;
         sendSplitVisibilityChanged();
+
+        if (mIsDividerRemoteAnimating) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "%s:   Skip animating divider bar due to it's remote animating.", TAG);
+            return;
+        }
+
         if (t != null) {
             applyDividerVisibility(t);
         } else {
@@ -1066,15 +1093,56 @@
 
     private void applyDividerVisibility(SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
-        if (mIsDividerRemoteAnimating || dividerLeash == null) return;
+        if (dividerLeash == null) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "%s:   Skip animating divider bar due to divider leash not ready.", TAG);
+            return;
+        }
+        if (mIsDividerRemoteAnimating) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "%s:   Skip animating divider bar due to it's remote animating.", TAG);
+            return;
+        }
+
+        if (mDividerFadeInAnimator != null && mDividerFadeInAnimator.isRunning()) {
+            mDividerFadeInAnimator.cancel();
+        }
 
         if (mDividerVisible) {
-            t.show(dividerLeash);
-            t.setAlpha(dividerLeash, 1);
-            t.setLayer(dividerLeash, Integer.MAX_VALUE);
-            t.setPosition(dividerLeash,
-                    mSplitLayout.getRefDividerBounds().left,
-                    mSplitLayout.getRefDividerBounds().top);
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+            mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
+            mDividerFadeInAnimator.addUpdateListener(animation -> {
+                if (dividerLeash == null) {
+                    mDividerFadeInAnimator.cancel();
+                    return;
+                }
+                transaction.setAlpha(dividerLeash, (float) animation.getAnimatedValue());
+                transaction.apply();
+            });
+            mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (dividerLeash == null) {
+                        mDividerFadeInAnimator.cancel();
+                        return;
+                    }
+                    transaction.show(dividerLeash);
+                    transaction.setAlpha(dividerLeash, 0);
+                    transaction.setLayer(dividerLeash, Integer.MAX_VALUE);
+                    transaction.setPosition(dividerLeash,
+                                    mSplitLayout.getRefDividerBounds().left,
+                                    mSplitLayout.getRefDividerBounds().top);
+                    transaction.apply();
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransactionPool.release(transaction);
+                    mDividerFadeInAnimator = null;
+                }
+            });
+
+            mDividerFadeInAnimator.start();
         } else {
             t.hide(dividerLeash);
         }
@@ -1096,10 +1164,8 @@
             mSplitLayout.init();
             prepareEnterSplitScreen(wct);
             mSyncQueue.queue(wct);
-            mSyncQueue.runInSync(t -> {
-                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                setDividerVisibility(true, t);
-            });
+            mSyncQueue.runInSync(t ->
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
             mShouldUpdateRecents = true;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index cf4ea46..41cd31a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -37,7 +37,7 @@
         val displayBounds = WindowUtils.displayBounds
         val secondaryAppBounds = Region.from(0,
                 dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
-                displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
+                displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
         return secondaryAppBounds
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index a510d69..e2da1a4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -171,7 +171,7 @@
             val bottomAppBounds = Region.from(0,
                 dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
                 displayBounds.right,
-                displayBounds.bottom - WindowUtils.navigationBarHeight)
+                displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
             visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
                 .coversExactly(topAppBounds)
             visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
@@ -192,7 +192,7 @@
             val bottomAppBounds = Region.from(0,
                 dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
                 displayBounds.right,
-                displayBounds.bottom - WindowUtils.navigationBarHeight)
+                displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
 
             visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
                 .coversExactly(topAppBounds)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index a55f737..ffaab65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -139,6 +139,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testLaunchToSide() {
         ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
@@ -173,6 +174,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testLaunchPair() {
         TransitionInfo info = createEnterPairInfo();
 
@@ -195,6 +197,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testMonitorInSplit() {
         enterSplit();
 
@@ -251,6 +254,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testEnterRecents() {
         enterSplit();
 
@@ -288,6 +292,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testDismissFromBeingOccluded() {
         enterSplit();
 
@@ -325,6 +330,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testDismissFromMultiWindowSupport() {
         enterSplit();
 
@@ -346,6 +352,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testDismissSnap() {
         enterSplit();
 
@@ -370,6 +377,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testDismissFromAppFinish() {
         enterSplit();
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 37cbf30..5e91864 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -484,6 +484,14 @@
         if (deviceFilterPairs.isEmpty()) return;
 
         mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+        // No need to show user consent dialog if it is a singleDevice
+        // and isSkipPrompt(true) AssociationRequest.
+        // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            mSingleDeviceSpinner.setVisibility(View.GONE);
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
 
         final String deviceName = mSelectedDevice.getDisplayName();
         final Spanned title = getHtmlFromResources(
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
index 25f0771..72b569f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
@@ -25,7 +25,7 @@
         android:fitsSystemWindows="true"
         android:outlineAmbientShadowColor="@android:color/transparent"
         android:outlineSpotShadowColor="@android:color/transparent"
-        android:background="?android:attr/colorPrimary"
+        android:background="@android:color/transparent"
         android:theme="@style/Theme.CollapsingToolbar.Settings">
 
         <com.google.android.material.appbar.CollapsingToolbarLayout
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 72383fe..dbb4b50 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -20,6 +20,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -29,6 +30,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 
@@ -41,6 +43,7 @@
  * This widget is wrapping the collapsing toolbar and can be directly used by the
  * {@link AppCompatActivity}.
  */
+@RequiresApi(Build.VERSION_CODES.S)
 public class CollapsingCoordinatorLayout extends CoordinatorLayout {
     private static final String TAG = "CollapsingCoordinatorLayout";
     private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index ac30636..6766cdd 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -40,6 +40,8 @@
     static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
     @VisibleForTesting
     View.OnClickListener mLearnMoreListener;
+    @VisibleForTesting
+    int mIconVisibility = View.VISIBLE;
     private CharSequence mContentDescription;
     private CharSequence mLearnMoreText;
     private CharSequence mLearnMoreContentDescription;
@@ -84,6 +86,9 @@
         } else {
             learnMore.setVisibility(View.GONE);
         }
+
+        View icon = holder.itemView.findViewById(R.id.icon_frame);
+        icon.setVisibility(mIconVisibility);
     }
 
     @Override
@@ -165,6 +170,17 @@
         }
     }
 
+    /**
+     * Set visibility of footer icon.
+     */
+    public void setIconVisibility(int iconVisibility) {
+        if (mIconVisibility == iconVisibility) {
+            return;
+        }
+        mIconVisibility = iconVisibility;
+        notifyChanged();
+    }
+
     private void init() {
         setLayoutResource(R.layout.preference_footer);
         if (getIcon() == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fdb0607..6b9daa3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -863,6 +863,30 @@
             }
         }
 
+        /**
+         *  Activate session to enable a class that implements Callbacks to receive the callback.
+         */
+        public void activateSession() {
+            synchronized (mEntriesMap) {
+                if (!mResumed) {
+                    mResumed = true;
+                    mSessionsChanged = true;
+                }
+            }
+        }
+
+        /**
+         *  Deactivate session to disable a class that implements Callbacks to get the callback.
+         */
+        public void deactivateSession() {
+            synchronized (mEntriesMap) {
+                if (mResumed) {
+                    mResumed = false;
+                    mSessionsChanged = true;
+                }
+            }
+        }
+
         public ArrayList<AppEntry> getAllApps() {
             synchronized (mEntriesMap) {
                 return new ArrayList<>(mAppEntries);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 4ee2122..6919cf2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1376,7 +1376,7 @@
     /**
      * Store the member devices that are in the same coordinated set.
      */
-    public void setMemberDevice(CachedBluetoothDevice memberDevice) {
+    public void addMemberDevice(CachedBluetoothDevice memberDevice) {
         mMemberDevices.add(memberDevice);
     }
 
@@ -1393,24 +1393,24 @@
      * device and member devices.
      *
      * @param prevMainDevice the previous Main device, it will be added into the member device set.
-     * @param newMainDevie the new Main device, it will be removed from the member device set.
+     * @param newMainDevice the new Main device, it will be removed from the member device set.
      */
     public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice,
-            CachedBluetoothDevice newMainDevie) {
+            CachedBluetoothDevice newMainDevice) {
         // Backup from main device
         final BluetoothDevice tmpDevice = mDevice;
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
         // Set main device from sub device
-        mDevice = newMainDevie.mDevice;
-        mRssi = newMainDevie.mRssi;
-        mJustDiscovered = newMainDevie.mJustDiscovered;
-        setMemberDevice(prevMainDevice);
-        mMemberDevices.remove(newMainDevie);
+        mDevice = newMainDevice.mDevice;
+        mRssi = newMainDevice.mRssi;
+        mJustDiscovered = newMainDevice.mJustDiscovered;
+        addMemberDevice(prevMainDevice);
+        mMemberDevices.remove(newMainDevice);
         // Set sub device from backup
-        newMainDevie.mDevice = tmpDevice;
-        newMainDevie.mRssi = tmpRssi;
-        newMainDevie.mJustDiscovered = tmpJustDiscovered;
+        newMainDevice.mDevice = tmpDevice;
+        newMainDevice.mRssi = tmpRssi;
+        newMainDevice.mJustDiscovered = tmpJustDiscovered;
         fetchActiveDevices();
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index cc56a21..89e10c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -85,7 +85,7 @@
             // Once there is other devices with the same groupId, to add new device as member
             // devices.
             if (CsipDevice != null) {
-                CsipDevice.setMemberDevice(newDevice);
+                CsipDevice.addMemberDevice(newDevice);
                 newDevice.setName(CsipDevice.getName());
                 return true;
             }
@@ -148,7 +148,7 @@
             log("onGroupIdChanged: removed from UI device =" + cachedDevice
                     + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
 
-            mainDevice.setMemberDevice(cachedDevice);
+            mainDevice.addMemberDevice(cachedDevice);
             mCachedDevices.remove(i);
             mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
             break;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 298ee90..bef1d9c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -40,7 +40,6 @@
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
 
 @RunWith(RobolectricTestRunner.class)
@@ -503,8 +502,8 @@
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
         CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
-        cachedDevice1.setMemberDevice(cachedDevice2);
-        cachedDevice1.setMemberDevice(cachedDevice3);
+        cachedDevice1.addMemberDevice(cachedDevice2);
+        cachedDevice1.addMemberDevice(cachedDevice3);
 
         assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
         assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
@@ -524,7 +523,7 @@
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
         cachedDevice1.setGroupId(1);
         cachedDevice2.setGroupId(1);
-        cachedDevice1.setMemberDevice(cachedDevice2);
+        cachedDevice1.addMemberDevice(cachedDevice2);
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
@@ -541,7 +540,7 @@
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
         cachedDevice1.setGroupId(1);
         cachedDevice2.setGroupId(1);
-        cachedDevice1.setMemberDevice(cachedDevice2);
+        cachedDevice1.addMemberDevice(cachedDevice2);
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 61a28aa..9abb27e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.widget.TextView;
 
 import androidx.preference.PreferenceViewHolder;
@@ -61,7 +62,7 @@
         mFooterPreference.onBindViewHolder(holder);
 
         assertThat(((TextView) holder.findViewById(
-                        R.id.settingslib_learn_more)).getText().toString())
+                R.id.settingslib_learn_more)).getText().toString())
                 .isEqualTo("Custom learn more");
     }
 
@@ -86,4 +87,11 @@
 
         assertThat(mFooterPreference.mLearnMoreListener).isNotNull();
     }
+
+    @Test
+    public void setIconVisibility_shouldReturnSameVisibilityType() {
+        mFooterPreference.setIconVisibility(View.GONE);
+
+        assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3029781..5eaf553 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -120,6 +120,9 @@
         Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
         Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
         Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+        Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+        Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+        Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
         Settings.Secure.VR_DISPLAY_MODE,
         Settings.Secure.NOTIFICATION_BADGING,
         Settings.Secure.NOTIFICATION_DISMISS_RTL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a4da497..9ee7b65 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -18,6 +18,7 @@
 
 import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
@@ -176,6 +177,10 @@
         VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+                ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume.xml b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
new file mode 100644
index 0000000..fce4e00
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@color/media_dialog_item_main_content"
+      android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.125,8.475 15.812,9.575Q16.5,10.675 16.5,12Q16.5,13.325 15.812,14.4Q15.125,15.475 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 1d6f279..e6af6f4 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
 
 <resources>
     <!-- Minimum margin between clock and top of screen or ambient indication -->
-    <dimen name="keyguard_clock_top_margin">38dp</dimen>
+    <dimen name="keyguard_clock_top_margin">26dp</dimen>
 
     <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
     <dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a014efb..5c69cef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1134,7 +1134,7 @@
 
     <!-- Output switcher panel related dimensions -->
     <dimen name="media_output_dialog_list_margin">12dp</dimen>
-    <dimen name="media_output_dialog_list_max_height">364dp</dimen>
+    <dimen name="media_output_dialog_list_max_height">355dp</dimen>
     <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
     <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
     <dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index f195d20..38fa354 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -16,14 +16,20 @@
 
 package com.android.keyguard
 
+import android.annotation.IntDef
 import android.content.ContentResolver
 import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
+import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
@@ -44,6 +50,20 @@
     dumpManager: DumpManager
 ) : Dumpable {
 
+    companion object {
+        const val TAG = "ActiveUnlockConfig"
+
+        const val BIOMETRIC_TYPE_NONE = 0
+        const val BIOMETRIC_TYPE_ANY_FACE = 1
+        const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
+        const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
+    }
+
+    @Retention(AnnotationRetention.SOURCE)
+    @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
+            BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
+    annotation class BiometricType
+
     /**
      * Indicates the origin for an active unlock request.
      */
@@ -51,35 +71,50 @@
         WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
     }
 
+    var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
     private var requestActiveUnlockOnWakeup = false
     private var requestActiveUnlockOnUnlockIntent = false
     private var requestActiveUnlockOnBioFail = false
 
+    private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
+    private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
+    private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
+
     private val settingsObserver = object : ContentObserver(handler) {
-        private val wakeUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
-        private val unlockIntentUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
-        private val bioFailUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+        private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
+        private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
+        private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+        private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)
+        private val faceAcquireInfoUri =
+                secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
+        private val unlockIntentWhenBiometricEnrolledUri =
+                secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
 
         fun register() {
-            contentResolver.registerContentObserver(
-                    wakeUri,
-                    false,
-                    this,
-                    UserHandle.USER_ALL)
-            contentResolver.registerContentObserver(
-                    unlockIntentUri,
-                    false,
-                    this,
-                    UserHandle.USER_ALL)
-            contentResolver.registerContentObserver(
-                    bioFailUri,
-                    false,
-                    this,
-                    UserHandle.USER_ALL)
+            registerUri(
+                    listOf(
+                            wakeUri,
+                            unlockIntentUri,
+                            bioFailUri,
+                            faceErrorsUri,
+                            faceAcquireInfoUri,
+                            unlockIntentWhenBiometricEnrolledUri
+                    )
+            )
 
             onChange(true, ArrayList(), 0, getCurrentUser())
         }
 
+        private fun registerUri(uris: Collection<Uri>) {
+            for (uri in uris) {
+                contentResolver.registerContentObserver(
+                        uri,
+                        false,
+                        this,
+                        UserHandle.USER_ALL)
+            }
+        }
+
         override fun onChange(
             selfChange: Boolean,
             uris: Collection<Uri>,
@@ -104,6 +139,55 @@
                 requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
                         ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
             }
+
+            if (selfChange || uris.contains(faceErrorsUri)) {
+                processStringArray(
+                        secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
+                                getCurrentUser()),
+                        faceErrorsToTriggerBiometricFailOn,
+                        setOf(FACE_ERROR_TIMEOUT))
+            }
+
+            if (selfChange || uris.contains(faceAcquireInfoUri)) {
+                processStringArray(
+                        secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+                                getCurrentUser()),
+                        faceAcquireInfoToTriggerBiometricFailOn,
+                        setOf<Int>())
+            }
+
+            if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
+                processStringArray(
+                        secureSettings.getStringForUser(
+                                ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+                                getCurrentUser()),
+                        onUnlockIntentWhenBiometricEnrolled,
+                        setOf(BIOMETRIC_TYPE_NONE))
+            }
+        }
+
+        /**
+         * Convert a pipe-separated set of integers into a set of ints.
+         * @param stringSetting expected input are integers delineated by a pipe. For example,
+         * it may look something like this: "1|5|3".
+         * @param out updates the "out" Set will the integers between the pipes.
+         * @param default If stringSetting is null, "out" will be populated with values in "default"
+         */
+        private fun processStringArray(
+            stringSetting: String?,
+            out: MutableSet<Int>,
+            default: Set<Int>
+        ) {
+            out.clear()
+            stringSetting?.let {
+                for (code: String in stringSetting.split("|")) {
+                    try {
+                        out.add(code.toInt())
+                    } catch (e: NumberFormatException) {
+                        Log.e(TAG, "Passed an invalid setting=$code")
+                    }
+                }
+            } ?: out.addAll(default)
         }
     }
 
@@ -113,6 +197,30 @@
     }
 
     /**
+     * If any active unlock triggers are enabled.
+     */
+    fun isActiveUnlockEnabled(): Boolean {
+        return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
+                requestActiveUnlockOnBioFail
+    }
+
+    /**
+     * Whether the face error code from {@link BiometricFaceConstants} should trigger
+     * active unlock on biometric failure.
+     */
+    fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean {
+        return faceErrorsToTriggerBiometricFailOn.contains(errorCode)
+    }
+
+    /**
+     * Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger
+     * active unlock on biometric failure.
+     */
+    fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean {
+        return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo)
+    }
+
+    /**
      * Whether to trigger active unlock based on where the request is coming from and
      * the current settings.
      */
@@ -121,7 +229,8 @@
             ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
 
             ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
-                requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup
+                requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
+                        (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
 
             ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
                 requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
@@ -131,17 +240,55 @@
         }
     }
 
-    /**
-     * If any active unlock triggers are enabled.
-     */
-    fun isActiveUnlockEnabled(): Boolean {
-        return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
-                requestActiveUnlockOnBioFail
+    private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean {
+        if (!requestActiveUnlockOnBioFail) {
+            return false
+        }
+
+        keyguardUpdateMonitor?.let {
+            val anyFaceEnrolled = it.isFaceEnrolled
+            val anyFingerprintEnrolled =
+                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+            val udfpsEnrolled = it.isUdfpsEnrolled
+
+            if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
+                return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
+            }
+
+            if (!anyFaceEnrolled && anyFingerprintEnrolled) {
+                return onUnlockIntentWhenBiometricEnrolled.contains(
+                        BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
+                        (udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
+                                BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
+            }
+
+            if (!anyFingerprintEnrolled && anyFaceEnrolled) {
+                return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
+            }
+        }
+
+        return false
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("Settings:")
         pw.println("   requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
         pw.println("   requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
         pw.println("   requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
+        pw.println("   requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
+                "$onUnlockIntentWhenBiometricEnrolled")
+        pw.println("   requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
+        pw.println("   requestActiveUnlockOnFaceAcquireInfo=" +
+                "$faceAcquireInfoToTriggerBiometricFailOn")
+
+        pw.println("Current state:")
+        keyguardUpdateMonitor?.let {
+            pw.println("   shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
+                    "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
+            pw.println("   faceEnrolled=${it.isFaceEnrolled}")
+            pw.println("   fpEnrolled=${
+                    it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+            pw.println("   udfpsEnrolled=${it.isUdfpsEnrolled}")
+        } ?: pw.println("   keyguardUpdateMonitor is uninitialized")
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bbe9a362..121ac29 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -56,7 +56,6 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -1615,7 +1614,7 @@
                         mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
                     }
 
-                    if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
+                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
                         requestActiveUnlock(
                                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
                                 "faceError-" + errMsgId);
@@ -1625,6 +1624,13 @@
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
                     handleFaceAcquired(acquireInfo);
+
+                    if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                            acquireInfo)) {
+                        requestActiveUnlock(
+                                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+                                "faceAcquireInfo-" + acquireInfo);
+                    }
                 }
     };
 
@@ -1639,6 +1645,7 @@
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
     private boolean mFaceLockedOutPermanent;
+    private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
     private TelephonyManager mTelephonyManager;
 
     /**
@@ -1889,6 +1896,7 @@
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mActiveUnlockConfig = activeUnlockConfiguration;
+        mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2329,7 +2337,7 @@
         }
 
         if (shouldTriggerActiveUnlock()) {
-            if (DEBUG) {
+            if (DEBUG_ACTIVE_UNLOCK) {
                 Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
             }
             mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
@@ -2359,7 +2367,7 @@
         }
 
         if (allowRequest && shouldTriggerActiveUnlock()) {
-            if (DEBUG) {
+            if (DEBUG_ACTIVE_UNLOCK) {
                 Log.d("ActiveUnlock", "reportUserRequestedUnlock"
                         + " origin=" + requestOrigin.name()
                         + " reason=" + reason
@@ -2777,8 +2785,17 @@
     }
 
     private boolean isUnlockWithFingerprintPossible(int userId) {
-        return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
-                && mFpm.hasEnrolledTemplates(userId);
+        mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
+                && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
+        return mIsUnlockWithFingerprintPossible.get(userId);
+    }
+
+    /**
+     * Cached value for whether fingerprint is enrolled and possible to use for authentication.
+     * Note: checking fingerprint enrollment directly with the AuthController requires an IPC.
+     */
+    public boolean getCachedIsUnlockWithFingerprintPossible(int userId) {
+        return mIsUnlockWithFingerprintPossible.get(userId);
     }
 
     private boolean isUnlockWithFacePossible(int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index e191365..fdde402 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -55,7 +55,7 @@
 
     @NonNull private final RectF mSensorRect;
     @NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
-    private int mRadius;
+    private float mRadius;
     private int mLockIconPadding;
 
     private ImageView mLockIcon;
@@ -126,7 +126,7 @@
      * Set the location of the lock icon.
      */
     @VisibleForTesting
-    public void setCenterLocation(@NonNull PointF center, int radius, int drawablePadding) {
+    public void setCenterLocation(@NonNull PointF center, float radius, int drawablePadding) {
         mLockIconCenter = center;
         mRadius = radius;
         mLockIconPadding = drawablePadding;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2b217f0..d79b145 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -188,6 +188,7 @@
     protected void onViewAttached() {
         updateIsUdfpsEnrolled();
         updateConfiguration();
+        updateLockIconLocation();
         updateKeyguardShowing();
         mUserUnlockedWithBiometric = false;
 
@@ -340,20 +341,17 @@
         mHeightPixels = bounds.bottom;
         mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
 
-        final int defaultPaddingPx =
-                getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
-        mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
-
         mUnlockedLabel = mView.getContext().getResources().getString(
                 R.string.accessibility_unlock_button);
         mLockedLabel = mView.getContext()
                 .getResources().getString(R.string.accessibility_lock_icon);
-
-        updateLockIconLocation();
     }
 
     private void updateLockIconLocation() {
         if (mUdfpsSupported) {
+            final int defaultPaddingPx =
+                    getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
+            mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
             mView.setCenterLocation(mAuthController.getUdfpsLocation(),
                     mAuthController.getUdfpsRadius(), mScaledPaddingPx);
         } else {
@@ -362,8 +360,6 @@
                         mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx),
                         sLockIconRadiusPx, mScaledPaddingPx);
         }
-
-        mView.getHitRect(mSensorTouchLocation);
     }
 
     @Override
@@ -386,6 +382,7 @@
         pw.println("  mCanDismissLockScreen: " + mCanDismissLockScreen);
         pw.println("  mStatusBarState: " + StatusBarState.toString(mStatusBarState));
         pw.println("  mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+        pw.println("  mSensorTouchLocation: " + mSensorTouchLocation);
 
         if (mView != null) {
             mView.dump(pw, args);
@@ -672,6 +669,7 @@
     }
 
     private boolean inLockIconArea(MotionEvent event) {
+        mView.getHitRect(mSensorTouchLocation);
         return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
                 && mView.getVisibility() == View.VISIBLE;
     }
@@ -692,6 +690,7 @@
         mExecutor.execute(() -> {
             updateIsUdfpsEnrolled();
             updateConfiguration();
+            updateLockIconLocation();
         });
     }
 
@@ -705,6 +704,11 @@
         public void onEnrollmentsChanged() {
             updateUdfpsConfig();
         }
+
+        @Override
+        public void onUdfpsLocationChanged() {
+            updateLockIconLocation();
+        }
     };
 
     private final View.OnClickListener mA11yClickListener = v -> onLongPress();
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index cbce854..dd31218 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -47,6 +47,7 @@
 import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.os.Handler;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.util.DisplayUtils;
@@ -1067,15 +1068,22 @@
     }
 
     private void updateLayoutParams() {
-        if (mOverlays == null) {
-            return;
+        //ToDo: We should skip unnecessary call to update view layout.
+        Trace.beginSection("ScreenDecorations#updateLayoutParams");
+        if (mScreenDecorHwcWindow != null) {
+            mWindowManager.updateViewLayout(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
         }
-        for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-            if (mOverlays[i] == null) {
-                continue;
+
+        if (mOverlays != null) {
+            for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+                if (mOverlays[i] == null) {
+                    continue;
+                }
+                mWindowManager.updateViewLayout(
+                        mOverlays[i].getRootView(), getWindowLayoutParams(i));
             }
-            mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i));
         }
+        Trace.endSection();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 9324893..75339aa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -79,6 +79,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -446,11 +447,11 @@
     /**
      * @return the radius of UDFPS on the screen in pixels
      */
-    public int getUdfpsRadius() {
+    public float getUdfpsRadius() {
         if (mUdfpsController == null || mUdfpsBounds == null) {
             return -1;
         }
-        return mUdfpsBounds.height() / 2;
+        return mUdfpsBounds.height() / 2f;
     }
 
     /**
@@ -634,11 +635,17 @@
                     displayInfo.getNaturalHeight());
 
             final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+            final Rect previousUdfpsBounds = mUdfpsBounds;
             mUdfpsBounds = udfpsProp.getLocation().getRect();
             mUdfpsBounds.scale(scaleFactor);
             mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
                     new UdfpsOverlayParams(mUdfpsBounds, displayInfo.getNaturalWidth(),
                             displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation));
+            if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
+                for (Callback cb : mCallbacks) {
+                    cb.onUdfpsLocationChanged();
+                }
+            }
         }
     }
 
@@ -1054,5 +1061,10 @@
          * Called when the biometric prompt is no longer showing.
          */
         default void onBiometricPromptDismissed() {}
+
+        /**
+         * The location in pixels can change due to resolution changes.
+         */
+        default void onUdfpsLocationChanged() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index d9aa1ba..86e5016 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -335,15 +335,17 @@
                 updateSensorLocation()
             }
 
-            override fun onEnrollmentsChanged() {
+            override fun onUdfpsLocationChanged() {
+                updateUdfpsDependentParams()
+                updateSensorLocation()
             }
         }
 
     private fun updateUdfpsDependentParams() {
         authController.udfpsProps?.let {
             if (it.size > 0) {
-                udfpsRadius = it[0].location.sensorRadius.toFloat()
                 udfpsController = udfpsControllerProvider.get()
+                udfpsRadius = authController.udfpsRadius
 
                 if (mView.isAttachedToWindow) {
                     udfpsController?.addCallback(udfpsControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 9139699..f28fedb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -32,6 +32,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
@@ -44,6 +45,8 @@
 import com.airbnb.lottie.model.KeyPath;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * View corresponding with udfps_keyguard_view.xml
@@ -52,7 +55,6 @@
     private UdfpsDrawable mFingerprintDrawable; // placeholder
     private LottieAnimationView mAodFp;
     private LottieAnimationView mLockScreenFp;
-    private int mStatusBarState;
 
     // used when highlighting fp icon:
     private int mTextColorPrimary;
@@ -70,7 +72,7 @@
     private float mBurnInOffsetY;
     private float mBurnInProgress;
     private float mInterpolatedDarkAmount;
-    private boolean mAnimatingBetweenAodAndLockscreen; // As opposed to Unlocked => AOD
+    private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
 
     public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
@@ -117,8 +119,10 @@
             return;
         }
 
-        final float darkAmountForAnimation = mAnimatingBetweenAodAndLockscreen
-                ? mInterpolatedDarkAmount : 1f /* animating from unlocked to AOD */;
+        // if we're animating from screen off, we can immediately place the icon in the
+        // AoD-burn in location, else we need to translate the icon from LS => AoD.
+        final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
+                ? 1f : mInterpolatedDarkAmount;
         mBurnInOffsetX = MathUtils.lerp(0f,
             getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
                 - mMaxBurnInOffsetX, darkAmountForAnimation);
@@ -127,12 +131,12 @@
                 - mMaxBurnInOffsetY, darkAmountForAnimation);
         mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation);
 
-        if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) {
+        if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) {
             mLockScreenFp.setTranslationX(mBurnInOffsetX);
             mLockScreenFp.setTranslationY(mBurnInOffsetY);
             mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
             mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
-        } else if (mInterpolatedDarkAmount == 0f) {
+        } else if (darkAmountForAnimation == 0f) {
             mLockScreenFp.setTranslationX(0);
             mLockScreenFp.setTranslationY(0);
             mBgProtection.setAlpha(mAlpha / 255f);
@@ -148,9 +152,15 @@
         mAodFp.setProgress(mBurnInProgress);
         mAodFp.setAlpha(mInterpolatedDarkAmount);
 
-        // done animating between AoD & LS
-        if (mInterpolatedDarkAmount == 1f || mInterpolatedDarkAmount == 0f) {
-            mAnimatingBetweenAodAndLockscreen = false;
+        // done animating
+        final boolean doneAnimatingBetweenAodAndLS =
+                mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+                        && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f);
+        final boolean doneAnimatingUnlockedScreenOff =
+                mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
+                        && (mInterpolatedDarkAmount == 1f);
+        if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) {
+            mAnimationType = ANIMATION_NONE;
         }
     }
 
@@ -158,10 +168,6 @@
         mUdfpsRequested = request;
     }
 
-    void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
-    }
-
     void updateColor() {
         if (!mFullyInflated) {
             return;
@@ -219,8 +225,16 @@
         return mAlpha;
     }
 
-    void onDozeAmountChanged(float linear, float eased, boolean animatingBetweenAodAndLockscreen) {
-        mAnimatingBetweenAodAndLockscreen = animatingBetweenAodAndLockscreen;
+    static final int ANIMATION_NONE = 0;
+    static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1;
+    static final int ANIMATION_UNLOCKED_SCREEN_OFF = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATION_UNLOCKED_SCREEN_OFF})
+    private @interface AnimationType {}
+
+    void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) {
+        mAnimationType = animationType;
         mInterpolatedDarkAmount = eased;
         updateAlpha();
     }
@@ -262,7 +276,7 @@
         pw.println("    mUnpausedAlpha=" + getUnpausedAlpha());
         pw.println("    mUdfpsRequested=" + mUdfpsRequested);
         pw.println("    mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
-        pw.println("    mAnimatingBetweenAodAndLockscreen=" + mAnimatingBetweenAodAndLockscreen);
+        pw.println("    mAnimationType=" + mAnimationType);
     }
 
     private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8b0f36f..ec4cf2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -121,7 +121,7 @@
                         mView.onDozeAmountChanged(
                                 animation.getAnimatedFraction(),
                                 (float) animation.getAnimatedValue(),
-                                /* animatingBetweenAodAndLockScreen */ false);
+                                UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
                     }
                 });
     }
@@ -394,7 +394,7 @@
                 mUnlockedScreenOffDozeAnimator.start();
             } else {
                 mView.onDozeAmountChanged(linear, eased,
-                    /* animatingBetweenAodAndLockScreen */ true);
+                        UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
             }
 
             mLastDozeAmount = linear;
@@ -404,7 +404,6 @@
         @Override
         public void onStateChanged(int statusBarState) {
             mStatusBarState = statusBarState;
-            mView.setStatusBarState(statusBarState);
             updateAlpha();
             updatePauseAuth();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 74044e2..1cc5df5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -39,7 +39,6 @@
 import com.android.systemui.doze.DozeMachine.State;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
@@ -94,7 +93,6 @@
     private final AuthController mAuthController;
     private final DelayableExecutor mMainExecutor;
     private final KeyguardStateController mKeyguardStateController;
-    private final BatteryController mBatteryController;
     private final UiEventLogger mUiEventLogger;
     private final DevicePostureController mDevicePostureController;
 
@@ -186,8 +184,7 @@
             @Main DelayableExecutor mainExecutor,
             UiEventLogger uiEventLogger,
             KeyguardStateController keyguardStateController,
-            DevicePostureController devicePostureController,
-            BatteryController batteryController) {
+            DevicePostureController devicePostureController) {
         mContext = context;
         mDozeHost = dozeHost;
         mConfig = config;
@@ -208,7 +205,6 @@
         mMainExecutor = mainExecutor;
         mUiEventLogger = uiEventLogger;
         mKeyguardStateController = keyguardStateController;
-        mBatteryController = batteryController;
     }
     private final DevicePostureController.Callback mDevicePostureCallback =
             posture -> {
@@ -320,12 +316,7 @@
                     gentleWakeUp(pulseReason);
                 } else if (isPickup) {
                     if (shouldDropPickupEvent())  {
-                        mDozeLog.traceSensorEventDropped(
-                                pulseReason,
-                                "keyguardOccluded="
-                                        + mKeyguardStateController.isOccluded()
-                                        + " pluggedInWireless="
-                                        + mBatteryController.isPluggedInWireless());
+                        mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
                         return;
                     }
                     gentleWakeUp(pulseReason);
@@ -356,7 +347,7 @@
     }
 
     private boolean shouldDropPickupEvent() {
-        return mKeyguardStateController.isOccluded() || mBatteryController.isPluggedInWireless();
+        return mKeyguardStateController.isOccluded();
     }
 
     private void gentleWakeUp(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 2fec499..8c2cb6d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -97,7 +97,7 @@
  * A view controller used for Media Playback.
  */
 public class MediaControlPanel {
-    private static final String TAG = "MediaControlPanel";
+    protected static final String TAG = "MediaControlPanel";
 
     private static final float DISABLED_ALPHA = 0.38f;
     private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
@@ -106,7 +106,7 @@
             "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
     private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
     private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
-    private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
+    protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
 
     // Event types logged by smartspace
     private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
@@ -149,6 +149,7 @@
     private RecommendationViewHolder mRecommendationViewHolder;
     private String mKey;
     private MediaData mMediaData;
+    private SmartspaceMediaData mRecommendationData;
     private MediaViewController mMediaViewController;
     private MediaSession.Token mToken;
     private MediaController mController;
@@ -448,6 +449,7 @@
 
         bindOutputSwitcherChip(data);
         bindGutsMenuForPlayer(data);
+        bindPlayerContentDescription(data);
         bindScrubbingTime(data);
         bindActionButtons(data);
 
@@ -541,12 +543,6 @@
     }
 
     private boolean bindSongMetadata(MediaData data) {
-        // Accessibility label
-        mMediaViewHolder.getPlayer().setContentDescription(
-                mContext.getString(
-                        R.string.controls_media_playing_item_description,
-                        data.getSong(), data.getArtist(), data.getApp()));
-
         TextView titleText = mMediaViewHolder.getTitleText();
         TextView artistText = mMediaViewHolder.getArtistText();
         return mMetadataAnimationHandler.setNext(
@@ -568,6 +564,48 @@
             });
     }
 
+    // We may want to look into unifying this with bindRecommendationContentDescription if/when we
+    // do a refactor of this class.
+    private void bindPlayerContentDescription(MediaData data) {
+        if (mMediaViewHolder == null) {
+            return;
+        }
+
+        CharSequence contentDescription;
+        if (mMediaViewController.isGutsVisible()) {
+            contentDescription = mMediaViewHolder.getGutsViewHolder().getGutsText().getText();
+        } else if (data != null) {
+            contentDescription = mContext.getString(
+                    R.string.controls_media_playing_item_description,
+                    data.getSong(),
+                    data.getArtist(),
+                    data.getApp());
+        } else {
+            contentDescription = null;
+        }
+        mMediaViewHolder.getPlayer().setContentDescription(contentDescription);
+    }
+
+    private void bindRecommendationContentDescription(SmartspaceMediaData data) {
+        if (mRecommendationViewHolder == null) {
+            return;
+        }
+
+       CharSequence contentDescription;
+        if (mMediaViewController.isGutsVisible()) {
+            contentDescription =
+                    mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
+        } else if (data != null) {
+            contentDescription = mContext.getString(
+                    R.string.controls_media_smartspace_rec_description,
+                    data.getAppName(mContext));
+        } else {
+            contentDescription = null;
+        }
+
+        mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription);
+    }
+
     private void bindArtworkAndColors(MediaData data, boolean updateBackground) {
         final int reqId = mArtworkNextBindRequestId++;
         if (updateBackground) {
@@ -636,6 +674,10 @@
                     mIsArtworkBound = isArtworkBound;
                 }
 
+                // Scrim bounds are set manually so it scales as expected
+                albumView.getForeground().setBounds(0, 0,
+                        Math.max(width, height), Math.max(width, height));
+
                 // Transition Colors to current color scheme
                 mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound);
 
@@ -950,6 +992,7 @@
             return;
         }
 
+        mRecommendationData = data;
         mSmartspaceId = SmallHash.hash(data.getTargetId());
         mPackageName = data.getPackageName();
         mInstanceId = data.getInstanceId();
@@ -965,6 +1008,12 @@
             return;
         }
 
+        CharSequence appName = data.getAppName(mContext);
+        if (appName == null) {
+            Log.w(TAG, "Fail to get media recommendation's app name");
+            return;
+        }
+
         PackageManager packageManager = mContext.getPackageManager();
         // Set up media source app's logo.
         Drawable icon = packageManager.getApplicationIcon(applicationInfo);
@@ -972,28 +1021,11 @@
         headerLogoImageView.setImageDrawable(icon);
         fetchAndUpdateRecommendationColors(icon);
 
-        // Set up media source app's label text.
-        CharSequence appName = getAppName(data.getCardAction());
-        if (TextUtils.isEmpty(appName)) {
-            Intent launchIntent =
-                    packageManager.getLaunchIntentForPackage(data.getPackageName());
-            if (launchIntent != null) {
-                ActivityInfo launchActivity = launchIntent.resolveActivityInfo(packageManager, 0);
-                appName = launchActivity.loadLabel(packageManager);
-            } else {
-                Log.w(TAG, "Package " + data.getPackageName()
-                        +  " does not have a main launcher activity. Fallback to full app name");
-                appName = packageManager.getApplicationLabel(applicationInfo);
-            }
-        }
-
         // Set up media rec card's tap action if applicable.
         TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
         setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
                 /* interactedSubcardRank */ -1);
-        // Set up media rec card's accessibility label.
-        recommendationCard.setContentDescription(
-                mContext.getString(R.string.controls_media_smartspace_rec_description, appName));
+        bindRecommendationContentDescription(data);
 
         List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
         List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
@@ -1175,6 +1207,11 @@
             mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
         }
         mMediaViewController.closeGuts(immediate);
+        if (mMediaViewHolder != null) {
+            bindPlayerContentDescription(mMediaData);
+        } else if (mRecommendationViewHolder != null) {
+            bindRecommendationContentDescription(mRecommendationData);
+        }
     }
 
     private void closeGuts() {
@@ -1188,6 +1225,11 @@
             mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
         }
         mMediaViewController.openGuts();
+        if (mMediaViewHolder != null) {
+            bindPlayerContentDescription(mMediaData);
+        } else if (mRecommendationViewHolder != null) {
+            bindRecommendationContentDescription(mRecommendationData);
+        }
         mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
     }
 
@@ -1302,17 +1344,6 @@
         });
     }
 
-    /** Returns the upstream app name if available. */
-    @Nullable
-    private String getAppName(SmartspaceAction action) {
-        if (action == null || action.getIntent() == null
-                || action.getIntent().getExtras() == null) {
-            return null;
-        }
-
-        return action.getIntent().getExtras().getString(KEY_SMARTSPACE_APP_NAME);
-    }
-
     /** Returns if the Smartspace action will open the activity in foreground. */
     private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
         if (action == null || action.getIntent() == null
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
index 50a96f6..c8f17d9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -17,8 +17,13 @@
 package com.android.systemui.media
 
 import android.app.smartspace.SmartspaceAction
+import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
+import android.text.TextUtils
+import android.util.Log
 import com.android.internal.logging.InstanceId
+import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
 
 /** State of a Smartspace media recommendations view. */
 data class SmartspaceMediaData(
@@ -67,6 +72,32 @@
      * Returns the list of [recommendations] that have valid data.
      */
     fun getValidRecommendations() = recommendations.filter { it.icon != null }
+
+    /** Returns the upstream app name if available. */
+    fun getAppName(context: Context): CharSequence? {
+        val nameFromAction = cardAction?.intent?.extras?.getString(KEY_SMARTSPACE_APP_NAME)
+        if (!TextUtils.isEmpty(nameFromAction)) {
+            return nameFromAction
+        }
+
+        val packageManager = context.packageManager
+        packageManager.getLaunchIntentForPackage(packageName)?.let {
+            val launchActivity = it.resolveActivityInfo(packageManager, 0)
+            return launchActivity.loadLabel(packageManager)
+        }
+
+        Log.w(
+            TAG,
+            "Package $packageName does not have a main launcher activity. " +
+                    "Fallback to full app name")
+        return try {
+            val applicationInfo = packageManager.getApplicationInfo(packageName,  /* flags= */ 0)
+            packageManager.getApplicationLabel(applicationInfo)
+        } catch (e: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
 }
 
 const val NUM_REQUIRED_RECOMMENDATIONS = 3
+private val TAG = SmartspaceMediaData::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index a397f32..58a35ff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -143,6 +143,7 @@
             if (mController.isTransferring()) {
                 if (device.getState() == MediaDeviceState.STATE_CONNECTING
                         && !mController.hasAdjustVolumeUserRestriction()) {
+                    setUpDeviceIcon(device);
                     mProgressBar.getIndeterminateDrawable().setColorFilter(
                             new PorterDuffColorFilter(
                                     mController.getColorItemContent(),
@@ -151,11 +152,13 @@
                             false /* showSeekBar*/,
                             true /* showProgressBar */, false /* showStatus */);
                 } else {
+                    setUpDeviceIcon(device);
                     setSingleLineLayout(getItemTitle(device), false /* bFocused */);
                 }
             } else {
                 // Set different layout for each device
                 if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
+                    setUpDeviceIcon(device);
                     mTitleText.setAlpha(DEVICE_CONNECTED_ALPHA);
                     mTitleIcon.setAlpha(DEVICE_CONNECTED_ALPHA);
                     mStatusIcon.setImageDrawable(
@@ -167,6 +170,7 @@
                     mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
                     mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
                 } else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
+                    setUpDeviceIcon(device);
                     mProgressBar.getIndeterminateDrawable().setColorFilter(
                             new PorterDuffColorFilter(
                                     mController.getColorItemContent(),
@@ -176,7 +180,12 @@
                             true /* showProgressBar */, false /* showStatus */);
                 } else if (mController.getSelectedMediaDevice().size() > 1
                         && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+                    boolean isDeviceDeselectable = isDeviceIncluded(
+                            mController.getDeselectableMediaDevice(), device);
                     mTitleText.setTextColor(mController.getColorItemContent());
+                    mTitleIcon.setImageDrawable(
+                            mContext.getDrawable(R.drawable.media_output_icon_volume));
+                    mTitleIcon.setColorFilter(mController.getColorItemContent());
                     setSingleLineLayout(getItemTitle(device), true /* bFocused */,
                             true /* showSeekBar */,
                             false /* showProgressBar */, false /* showStatus */);
@@ -184,13 +193,20 @@
                     mCheckBox.setOnCheckedChangeListener(null);
                     mCheckBox.setVisibility(View.VISIBLE);
                     mCheckBox.setChecked(true);
-                    mCheckBox.setOnCheckedChangeListener(
-                            (buttonView, isChecked) -> onGroupActionTriggered(false, device));
+                    mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable
+                            ? (buttonView, isChecked) -> onGroupActionTriggered(false, device)
+                            : null);
+                    mCheckBox.setEnabled(isDeviceDeselectable);
+                    mCheckBox.setAlpha(
+                            isDeviceDeselectable ? DEVICE_CONNECTED_ALPHA
+                                    : DEVICE_DISCONNECTED_ALPHA
+                    );
                     setCheckBoxColor(mCheckBox, mController.getColorItemContent());
                     initSeekbar(device, isCurrentSeekbarInvisible);
                     mEndTouchArea.setVisibility(View.VISIBLE);
                     mEndTouchArea.setOnClickListener(null);
-                    mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick());
+                    mEndTouchArea.setOnClickListener(
+                            isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
                     mEndTouchArea.setImportantForAccessibility(
                             View.IMPORTANT_FOR_ACCESSIBILITY_YES);
                     setUpContentDescriptionForView(mEndTouchArea, true, device);
@@ -198,6 +214,9 @@
                     mStatusIcon.setImageDrawable(
                             mContext.getDrawable(R.drawable.media_output_status_check));
                     mStatusIcon.setColorFilter(mController.getColorItemContent());
+                    mTitleIcon.setImageDrawable(
+                            mContext.getDrawable(R.drawable.media_output_icon_volume));
+                    mTitleIcon.setColorFilter(mController.getColorItemContent());
                     mTitleText.setTextColor(mController.getColorItemContent());
                     setSingleLineLayout(getItemTitle(device), true /* bFocused */,
                             true /* showSeekBar */,
@@ -206,6 +225,7 @@
                     setUpContentDescriptionForView(mContainerLayout, false, device);
                     mCurrentActivePosition = position;
                 } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+                    setUpDeviceIcon(device);
                     mCheckBox.setOnCheckedChangeListener(null);
                     mCheckBox.setVisibility(View.VISIBLE);
                     mCheckBox.setChecked(false);
@@ -218,6 +238,7 @@
                             false /* showSeekBar */,
                             false /* showProgressBar */, false /* showStatus */);
                 } else {
+                    setUpDeviceIcon(device);
                     setSingleLineLayout(getItemTitle(device), false /* bFocused */);
                     mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 5c2cc0b..b407e76f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -166,15 +166,6 @@
 
         void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
             mDeviceId = device.getId();
-            ThreadUtils.postOnBackgroundThread(() -> {
-                Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
-                ThreadUtils.postOnMainThread(() -> {
-                    if (!TextUtils.equals(mDeviceId, device.getId())) {
-                        return;
-                    }
-                    mTitleIcon.setImageIcon(icon);
-                });
-            });
         }
 
         abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
@@ -414,5 +405,17 @@
             mSeekBar.setEnabled(false);
             mSeekBar.setOnTouchListener((v, event) -> true);
         }
+
+        protected void setUpDeviceIcon(MediaDevice device) {
+            ThreadUtils.postOnBackgroundThread(() -> {
+                Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
+                ThreadUtils.postOnMainThread(() -> {
+                    if (!TextUtils.equals(mDeviceId, device.getId())) {
+                        return;
+                    }
+                    mTitleIcon.setImageIcon(icon);
+                });
+            });
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 83970dc..629aa03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -220,7 +220,7 @@
         }
     }
 
-    public float getMinStackScrollerPadding() {
+    public float getLockscreenMinStackScrollerPadding() {
         if (mBypassEnabled) {
             return mUnlockedStackScrollerPadding;
         } else if (mIsSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 7da3238..77b9efa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -326,6 +326,11 @@
     private boolean mShouldUseSplitNotificationShade;
     // The bottom padding reserved for elements of the keyguard measuring notifications
     private float mKeyguardNotificationBottomPadding;
+    /**
+     * The top padding from where notification should start in lockscreen.
+     * Should be static also during animations and should match the Y of the first notification.
+     */
+    private float mKeyguardNotificationTopPadding;
     // Current max allowed keyguard notifications determined by measuring the panel
     private int mMaxAllowedKeyguardNotifications;
 
@@ -1514,7 +1519,10 @@
      */
     @VisibleForTesting
     float getSpaceForLockscreenNotifications() {
-        float topPadding = mNotificationStackScrollLayoutController.getTopPadding();
+        float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
+                // getMinStackScrollerPadding is from the top of the screen,
+                // but we need it from the top of the NSSL.
+                - mNotificationStackScrollLayoutController.getTop();
 
         // Space between bottom of notifications and top of lock icon or udfps background.
         float lockIconPadding = mLockIconViewController.getTop();
@@ -1526,11 +1534,15 @@
 
         float bottomPadding = Math.max(lockIconPadding,
                 Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));
-        mKeyguardNotificationBottomPadding = bottomPadding;
 
+        mKeyguardNotificationBottomPadding = bottomPadding;
+        mKeyguardNotificationTopPadding = staticTopPadding;
+
+        // To debug the available space, enable debug lines in this class. If you change how the
+        // available space is calculated, please also update those lines.
         float availableSpace =
                 mNotificationStackScrollLayoutController.getHeight()
-                        - topPadding
+                        - staticTopPadding
                         - bottomPadding;
         return availableSpace;
     }
@@ -3198,6 +3210,8 @@
         updateTrackingHeadsUp(null);
         mExpandingFromHeadsUp = false;
         setPanelScrimMinFraction(0.0f);
+        // Reset status bar alpha so alpha can be calculated upon updating view state.
+        setKeyguardStatusBarAlpha(-1f);
     }
 
     private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
@@ -4924,6 +4938,20 @@
             drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
                     "mLockIconViewController.getTop()");
 
+            if (mKeyguardShowing) {
+                // Notifications have the space between those two lines.
+                drawDebugInfo(canvas,
+                        mNotificationStackScrollLayoutController.getTop() +
+                                (int) mKeyguardNotificationTopPadding,
+                        Color.RED,
+                        "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+                drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
+                                (int) mKeyguardNotificationBottomPadding,
+                        Color.RED,
+                        "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+            }
+
             mDebugPaint.setColor(Color.CYAN);
             canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
                     mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 3329eab..ad73491 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -71,6 +71,11 @@
     private var popupMenu: UserSwitcherPopupMenu? = null
     private lateinit var addButton: View
     private var addUserRecords = mutableListOf<UserRecord>()
+    private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            finish()
+        }
+    }
     // When the add users options become available, insert another option to manage users
     private val manageUserRecord = UserRecord(
         null /* info */,
@@ -215,11 +220,7 @@
         initBroadcastReceiver()
 
         parent.post { buildUserViews() }
-        userTracker.addCallback(object : UserTracker.Callback {
-            override fun onUserChanged(newUser: Int, userContext: Context) {
-                finish()
-            }
-        }, mainExecutor)
+        userTracker.addCallback(userSwitchedCallback, mainExecutor)
     }
 
     private fun showPopupMenu() {
@@ -340,6 +341,7 @@
         super.onDestroy()
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+        userTracker.removeCallback(userSwitchedCallback)
     }
 
     private fun initBroadcastReceiver() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 7476490..39cc34b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.ContentResolver
 import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
@@ -44,18 +45,20 @@
     private val fakeWakeUri = Uri.Builder().appendPath("wake").build()
     private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build()
     private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build()
+    private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
+    private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
+    private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
 
     @Mock
     private lateinit var secureSettings: SecureSettings
-
     @Mock
     private lateinit var contentResolver: ContentResolver
-
     @Mock
     private lateinit var handler: Handler
-
     @Mock
     private lateinit var dumpManager: DumpManager
+    @Mock
+    private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     @Captor
     private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@@ -72,6 +75,13 @@
                 .thenReturn(fakeUnlockIntentUri)
         `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
                 .thenReturn(fakeBioFailUri)
+        `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS))
+                .thenReturn(fakeFaceErrorsUri)
+        `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
+                .thenReturn(fakeFaceAcquiredUri)
+        `when`(secureSettings.getUriFor(
+                Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
+                .thenReturn(fakeUnlockIntentBioEnroll)
 
         activeUnlockConfig = ActiveUnlockConfig(
                 handler,
@@ -99,12 +109,7 @@
         // WHEN unlock on wake is allowed
         `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
                 0, 0)).thenReturn(1)
-        settingsObserverCaptor.value.onChange(
-                false,
-                listOf(fakeWakeUri),
-                0,
-                0
-        )
+        updateSetting(fakeWakeUri)
 
         // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
         assertTrue(
@@ -134,12 +139,7 @@
         // WHEN unlock on biometric failed is allowed
         `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
                 0, 0)).thenReturn(1)
-        settingsObserverCaptor.value.onChange(
-                false,
-                listOf(fakeUnlockIntentUri),
-                0,
-                0
-        )
+        updateSetting(fakeUnlockIntentUri)
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
         assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -154,19 +154,19 @@
     fun testOnBioFailSettingChanged() {
         verifyRegisterSettingObserver()
 
-        // GIVEN no active unlock settings enabled
+        // GIVEN no active unlock settings enabled and triggering unlock intent on biometric
+        // enrollment setting is disabled (empty string is disabled, null would use the default)
+        `when`(secureSettings.getStringForUser(
+                Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+                0)).thenReturn("")
+        updateSetting(fakeUnlockIntentBioEnroll)
         assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
 
         // WHEN unlock on biometric failed is allowed
         `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
                 0, 0)).thenReturn(1)
-        settingsObserverCaptor.value.onChange(
-                false,
-                listOf(fakeBioFailUri),
-                0,
-                0
-        )
+        updateSetting(fakeBioFailUri)
 
         // THEN active unlock triggers allowed on: biometric failure ONLY
         assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -177,21 +177,146 @@
                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
     }
 
+    @Test
+    fun testFaceErrorSettingsChanged() {
+        verifyRegisterSettingObserver()
+
+        // GIVEN unlock on biometric fail
+        `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+                0, 0)).thenReturn(1)
+        updateSetting(fakeBioFailUri)
+
+        // WHEN face error timeout (3), allow trigger active unlock
+        `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+                0)).thenReturn("3")
+        updateSetting(fakeFaceAcquiredUri)
+
+        // THEN active unlock triggers allowed on error TIMEOUT
+        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+
+        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+                BiometricFaceConstants.FACE_ERROR_CANCELED))
+    }
+
+    @Test
+    fun testFaceAcquiredSettingsChanged() {
+        verifyRegisterSettingObserver()
+
+        // GIVEN unlock on biometric fail
+        `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+                0, 0)).thenReturn(1)
+        updateSetting(fakeBioFailUri)
+
+        // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
+        `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+                0)).thenReturn(
+                "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+                        "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}")
+        updateSetting(fakeFaceAcquiredUri)
+
+        // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
+        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
+        assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+
+        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_GOOD))
+        assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+                BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+    }
+
+    @Test
+    fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() {
+        verifyRegisterSettingObserver()
+
+        // GIVEN unlock on biometric fail
+        `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+                0, 0)).thenReturn(1)
+        updateSetting(fakeBioFailUri)
+
+        // GIVEN fingerprint and face are NOT enrolled
+        activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+        `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+        // WHEN unlock intent is allowed when NO biometrics are enrolled (0)
+        `when`(secureSettings.getStringForUser(
+                Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+                0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}")
+        updateSetting(fakeUnlockIntentBioEnroll)
+
+        // THEN active unlock triggers allowed on unlock intent
+        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+    }
+
+    @Test
+    fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
+        verifyRegisterSettingObserver()
+
+        // GIVEN unlock on biometric fail
+        `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+                0, 0)).thenReturn(1)
+        updateSetting(fakeBioFailUri)
+
+        // GIVEN fingerprint and face are both enrolled
+        activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+        `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+        // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
+        // are enrolled
+        `when`(secureSettings.getStringForUser(
+                Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+                0)).thenReturn(
+                "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" +
+                        "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}")
+        updateSetting(fakeUnlockIntentBioEnroll)
+
+        // THEN active unlock triggers NOT allowed on unlock intent
+        assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+        // WHEN fingerprint ONLY enrolled
+        `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+        // THEN active unlock triggers allowed on unlock intent
+        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+        // WHEN face ONLY enrolled
+        `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+        `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+        // THEN active unlock triggers allowed on unlock intent
+        assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+    }
+
+    private fun updateSetting(uri: Uri) {
+        settingsObserverCaptor.value.onChange(
+                false,
+                listOf(uri),
+                0,
+                0 /* flags */
+        )
+    }
+
     private fun verifyRegisterSettingObserver() {
-        verify(contentResolver).registerContentObserver(
-                eq(fakeWakeUri),
-                eq(false),
-                capture(settingsObserverCaptor),
-                eq(UserHandle.USER_ALL))
+        verifyRegisterSettingObserver(fakeWakeUri)
+        verifyRegisterSettingObserver(fakeUnlockIntentUri)
+        verifyRegisterSettingObserver(fakeBioFailUri)
+        verifyRegisterSettingObserver(fakeFaceErrorsUri)
+        verifyRegisterSettingObserver(fakeFaceAcquiredUri)
+        verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
+    }
 
+    private fun verifyRegisterSettingObserver(uri: Uri) {
         verify(contentResolver).registerContentObserver(
-                eq(fakeUnlockIntentUri),
-                eq(false),
-                capture(settingsObserverCaptor),
-                eq(UserHandle.USER_ALL))
-
-        verify(contentResolver).registerContentObserver(
-                eq(fakeBioFailUri),
+                eq(uri),
                 eq(false),
                 capture(settingsObserverCaptor),
                 eq(UserHandle.USER_ALL))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index b87a7e7..0fdd905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -168,7 +168,8 @@
 
         mController.onViewAttached();
         verify(mView, atLeast(1)).setPauseAuth(true);
-        verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount, true);
+        verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
+                UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
     }
 
     @Test
@@ -195,7 +196,8 @@
         final float eased = .65f;
         mStatusBarStateListener.onDozeAmountChanged(linear, eased);
 
-        verify(mView).onDozeAmountChanged(linear, eased, true);
+        verify(mView).onDozeAmountChanged(linear, eased,
+                UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index ae387e8..4eeb4ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,7 +45,6 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -91,8 +90,6 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DevicePostureController mDevicePostureController;
-    @Mock
-    private BatteryController mBatteryController;
 
     private DozeTriggers mTriggers;
     private FakeSensorManager mSensors;
@@ -125,7 +122,7 @@
                 asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
                 mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
                 mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController,
-                mDevicePostureController, mBatteryController);
+                mDevicePostureController);
         mTriggers.setDozeMachine(mMachine);
         waitForSensorManager();
     }
@@ -233,9 +230,7 @@
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
 
         // WHEN the pick up gesture is triggered and keyguard isn't occluded
-        // and device isn't on a wireless charger
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
-        when(mBatteryController.isPluggedInWireless()).thenReturn(false);
         mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
 
         // THEN wakeup
@@ -249,22 +244,6 @@
 
         // WHEN the pick up gesture is triggered and keyguard IS occluded
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        when(mBatteryController.isPluggedInWireless()).thenReturn(false);
-        mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
-
-        // THEN never wakeup
-        verify(mMachine, never()).wakeUp();
-    }
-
-    @Test
-    public void testPickupGestureWirelessCharger() {
-        // GIVEN device is in doze (screen blank, but running doze sensors)
-        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
-
-        // WHEN the pick up gesture is triggered
-        // and device IS on a wireless charger
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
-        when(mBatteryController.isPluggedInWireless()).thenReturn(true);
         mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
 
         // THEN never wakeup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 5ff316e..c532ed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -34,8 +34,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedStateListDrawable;
 import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
@@ -77,9 +75,6 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
-import java.util.ArrayList;
-import java.util.List;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -182,7 +177,7 @@
     @Test
     public void testUpdateFingerprintLocationOnInit() {
         // GIVEN fp sensor location is available pre-attached
-        Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+        Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
 
         // WHEN lock icon view controller is initialized and attached
         mLockIconViewController.init();
@@ -197,7 +192,7 @@
     @Test
     public void testUpdatePaddingBasedOnResolutionScale() {
         // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
-        Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+        Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
         when(mAuthController.getScaleFactor()).thenReturn(5f);
 
         // WHEN lock icon view controller is initialized and attached
@@ -215,20 +210,19 @@
         // GIVEN fp sensor location is not available pre-init
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
         when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
-        when(mAuthController.getUdfpsProps()).thenReturn(null);
         mLockIconViewController.init();
         captureAttachListener();
         mAttachListener.onViewAttachedToWindow(mLockIconView);
 
-        // GIVEN fp sensor location is available post-atttached
+        // GIVEN fp sensor location is available post-attached
         captureAuthControllerCallback();
-        Pair<Integer, PointF> udfps = setupUdfps();
+        Pair<Float, PointF> udfps = setupUdfps();
 
         // WHEN all authenticators are registered
         mAuthControllerCallback.onAllAuthenticatorsRegistered();
         mDelayableExecutor.runAllReady();
 
-        // THEN lock icon view location is updated with the same coordinates as fpProps
+        // THEN lock icon view location is updated with the same coordinates as auth controller vals
         verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
                 eq(PADDING));
     }
@@ -402,21 +396,10 @@
         verify(mLockIconView).setTranslationX(0);
 
     }
-    private Pair<Integer, PointF> setupUdfps() {
+    private Pair<Float, PointF> setupUdfps() {
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         final PointF udfpsLocation = new PointF(50, 75);
-        final int radius = 33;
-        final FingerprintSensorPropertiesInternal fpProps =
-                new FingerprintSensorPropertiesInternal(
-                        /* sensorId */ 0,
-                        /* strength */ 0,
-                        /* max enrollments per user */ 5,
-                        /* component info */ new ArrayList<>(),
-                        /* sensorType */ 3,
-                        /* halControlsIllumination */ true,
-                        /* resetLockoutRequiresHwToken */ false,
-                        List.of(new SensorLocationInternal("" /* displayId */,
-                                (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
+        final float radius = 33f;
         when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
         when(mAuthController.getUdfpsRadius()).thenReturn(radius);
 
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 f9fb865..18aae0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -21,7 +21,6 @@
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
 import android.content.Context
-import org.mockito.Mockito.`when` as whenever
 import android.content.Intent
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
@@ -59,6 +58,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -67,8 +67,8 @@
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
@@ -92,6 +92,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val KEY = "TEST_KEY"
 private const val PACKAGE = "PKG"
@@ -102,6 +103,7 @@
 private const val SESSION_ARTIST = "SESSION_ARTIST"
 private const val SESSION_TITLE = "SESSION_TITLE"
 private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
+private const val REC_APP_NAME = "REC APP NAME"
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -262,6 +264,7 @@
 
         // Set valid recommendation data
         val extras = Bundle()
+        extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
         val intent = Intent().apply {
             putExtras(extras)
             setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -339,6 +342,7 @@
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
         whenever(viewHolder.albumView).thenReturn(albumView)
+        whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
         whenever(viewHolder.titleText).thenReturn(titleText)
         whenever(viewHolder.artistText).thenReturn(artistText)
         whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
@@ -1121,6 +1125,91 @@
         verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
     }
 
+    @Test
+    fun player_gutsOpen_contentDescriptionIsForGuts() {
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        player.attachPlayer(viewHolder)
+
+        val gutsTextString = "gutsText"
+        whenever(gutsText.text).thenReturn(gutsTextString)
+        player.bindPlayer(mediaData, KEY)
+
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).isEqualTo(gutsTextString)
+    }
+
+    @Test
+    fun player_gutsClosed_contentDescriptionIsForPlayer() {
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        player.attachPlayer(viewHolder)
+
+        val app = "appName"
+        player.bindPlayer(mediaData.copy(app = app), KEY)
+
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).contains(mediaData.song!!)
+        assertThat(description).contains(mediaData.artist!!)
+        assertThat(description).contains(app)
+    }
+
+    @Test
+    fun player_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
+        // Start out open
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        whenever(gutsText.text).thenReturn("gutsText")
+        player.attachPlayer(viewHolder)
+        val app = "appName"
+        player.bindPlayer(mediaData.copy(app = app), KEY)
+
+        // Update to closed by long pressing
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+        reset(viewHolder.player)
+
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        captor.value.onLongClick(viewHolder.player)
+
+        // Then content description is now the player content description
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).contains(mediaData.song!!)
+        assertThat(description).contains(mediaData.artist!!)
+        assertThat(description).contains(app)
+    }
+
+    @Test
+    fun player_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
+        // Start out closed
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        val gutsTextString = "gutsText"
+        whenever(gutsText.text).thenReturn(gutsTextString)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(mediaData.copy(app = "appName"), KEY)
+
+        // Update to open by long pressing
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+        reset(viewHolder.player)
+
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        captor.value.onLongClick(viewHolder.player)
+
+        // Then content description is now the guts content description
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).isEqualTo(gutsTextString)
+    }
+
     /* ***** END guts tests for the player ***** */
 
     /* ***** Guts tests for the recommendations ***** */
@@ -1189,6 +1278,85 @@
         verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong())
     }
 
+    @Test
+    fun recommendation_gutsOpen_contentDescriptionIsForGuts() {
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        player.attachRecommendation(recommendationViewHolder)
+
+        val gutsTextString = "gutsText"
+        whenever(gutsText.text).thenReturn(gutsTextString)
+        player.bindRecommendation(smartspaceData)
+
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).isEqualTo(gutsTextString)
+    }
+
+    @Test
+    fun recommendation_gutsClosed_contentDescriptionIsForPlayer() {
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        player.attachRecommendation(recommendationViewHolder)
+
+        player.bindRecommendation(smartspaceData)
+
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).contains(REC_APP_NAME)
+    }
+
+    @Test
+    fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
+        // Start out open
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        whenever(gutsText.text).thenReturn("gutsText")
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(smartspaceData)
+
+        // Update to closed by long pressing
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+        reset(viewHolder.player)
+
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        captor.value.onLongClick(viewHolder.player)
+
+        // Then content description is now the player content description
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).contains(REC_APP_NAME)
+    }
+
+    @Test
+    fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
+        // Start out closed
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+        val gutsTextString = "gutsText"
+        whenever(gutsText.text).thenReturn(gutsTextString)
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(smartspaceData)
+
+        // Update to open by long pressing
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(viewHolder.player).onLongClickListener = captor.capture()
+        reset(viewHolder.player)
+
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+        captor.value.onLongClick(viewHolder.player)
+
+        // Then content description is now the guts content description
+        val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
+        verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
+        val description = descriptionCaptor.value.toString()
+
+        assertThat(description).isEqualTo(gutsTextString)
+    }
+
     /* ***** END guts tests for the recommendations ***** */
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 6d3a5fe..ec20271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -336,7 +336,7 @@
         // WHEN the position algorithm is run
         positionClock();
         // THEN the padding DOESN'T adjust for keyguard status height.
-        assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
+        assertThat(mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding())
                 .isEqualTo(mKeyguardStatusBarHeaderHeight);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 94e6b9a..11f96ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -610,13 +610,13 @@
         when(mLockIconViewController.getTop()).thenReturn(80f);
         when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5);
 
-        // Available space (100 - 10 - 15 = 75)
+        // Available space (100 - 0 - 15 = 85)
         when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100);
-        when(mNotificationStackScrollLayoutController.getTopPadding()).thenReturn(10);
+        when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0);
         mNotificationPanelViewController.updateResources();
 
         assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications())
-                .isEqualTo(75);
+                .isEqualTo(85);
     }
 
     @Test
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 649328d..9b29bae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -242,6 +242,9 @@
 
         int getCurrentUserIdLocked();
 
+        Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+                int windowId);
+
         boolean isAccessibilityButtonShown();
 
         /**
@@ -551,8 +554,6 @@
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
-        final MagnificationSpec spec;
-        final float[] transformMatrix;
         synchronized (mLock) {
             mUsesAccessibilityCache = true;
             if (!hasRightsToCurrentUserLocked()) {
@@ -576,11 +577,11 @@
                 partialInteractiveRegion.recycle();
                 partialInteractiveRegion = null;
             }
-            final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
-                    getTransformMatrixAndSpecLocked(resolvedWindowId);
-            transformMatrix = transformMatrixAndSpec.first;
-            spec = transformMatrixAndSpec.second;
         }
+        final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+                getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+        final float[] transformMatrix = transformMatrixAndSpec.first;
+        final MagnificationSpec spec = transformMatrixAndSpec.second;
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
             return null;
         }
@@ -628,8 +629,6 @@
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
-        final MagnificationSpec spec;
-        final float [] transformMatrix;
         synchronized (mLock) {
             mUsesAccessibilityCache = true;
             if (!hasRightsToCurrentUserLocked()) {
@@ -653,11 +652,11 @@
                 partialInteractiveRegion.recycle();
                 partialInteractiveRegion = null;
             }
-            final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
-                    getTransformMatrixAndSpecLocked(resolvedWindowId);
-            transformMatrix = transformMatrixAndSpec.first;
-            spec = transformMatrixAndSpec.second;
         }
+        final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+                getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+        final float[] transformMatrix = transformMatrixAndSpec.first;
+        final MagnificationSpec spec = transformMatrixAndSpec.second;
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
             return null;
         }
@@ -706,8 +705,6 @@
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
-        final MagnificationSpec spec;
-        final float[] transformMatrix;
         synchronized (mLock) {
             mUsesAccessibilityCache = true;
             if (!hasRightsToCurrentUserLocked()) {
@@ -731,11 +728,11 @@
                 partialInteractiveRegion.recycle();
                 partialInteractiveRegion = null;
             }
-            final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
-                    getTransformMatrixAndSpecLocked(resolvedWindowId);
-            transformMatrix = transformMatrixAndSpec.first;
-            spec = transformMatrixAndSpec.second;
         }
+        final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+                getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+        final float[] transformMatrix = transformMatrixAndSpec.first;
+        final MagnificationSpec spec = transformMatrixAndSpec.second;
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
             return null;
         }
@@ -786,8 +783,6 @@
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
-        final MagnificationSpec spec;
-        final float[] transformMatrix;
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -811,11 +806,11 @@
                 partialInteractiveRegion.recycle();
                 partialInteractiveRegion = null;
             }
-            final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
-                    getTransformMatrixAndSpecLocked(resolvedWindowId);
-            transformMatrix = transformMatrixAndSpec.first;
-            spec = transformMatrixAndSpec.second;
         }
+        final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+                getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+        final float[] transformMatrix = transformMatrixAndSpec.first;
+        final MagnificationSpec spec = transformMatrixAndSpec.second;
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
             return null;
         }
@@ -865,8 +860,6 @@
         final int resolvedWindowId;
         RemoteAccessibilityConnection connection;
         Region partialInteractiveRegion = Region.obtain();
-        final MagnificationSpec spec;
-        final float[] transformMatrix;
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -889,12 +882,11 @@
                 partialInteractiveRegion.recycle();
                 partialInteractiveRegion = null;
             }
-
-            final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
-                    getTransformMatrixAndSpecLocked(resolvedWindowId);
-            transformMatrix = transformMatrixAndSpec.first;
-            spec = transformMatrixAndSpec.second;
         }
+        final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+                getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+        final float[] transformMatrix = transformMatrixAndSpec.first;
+        final MagnificationSpec spec = transformMatrixAndSpec.second;
         if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
             return null;
         }
@@ -1672,21 +1664,10 @@
         mInvocationHandler.startInputLocked(connection, editorInfo, restarting);
     }
 
-
-
     @Nullable
-    Pair<float[], MagnificationSpec> getTransformMatrixAndSpecLocked(int resolvedWindowId) {
-        final WindowInfo windowInfo =
-                mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId);
-        if (windowInfo == null) {
-            Slog.w(LOG_TAG, "getTransformMatrixAndSpec, windowInfo is null window id = "
-                    + resolvedWindowId);
-            return new Pair<>(null, null);
-        }
-
-        final MagnificationSpec spec = new MagnificationSpec();
-        spec.setTo(windowInfo.mMagnificationSpec);
-        return new Pair<>(windowInfo.mTransformMatrix, spec);
+    private Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+            int resolvedWindowId) {
+        return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4806514..99c8495 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -68,6 +68,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -99,6 +100,7 @@
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -458,6 +460,41 @@
     }
 
     @Override
+    public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+            int windowId) {
+        WindowInfo windowInfo;
+        synchronized (mLock) {
+            windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(windowId);
+        }
+        if (windowInfo != null) {
+            final MagnificationSpec spec = new MagnificationSpec();
+            spec.setTo(windowInfo.mMagnificationSpec);
+            return new Pair<>(windowInfo.mTransformMatrix, spec);
+        } else {
+            // If the framework doesn't track windows, we fall back to get the pair of
+            // transformation matrix and MagnificationSpe from the WindowManagerService's
+            // WindowState.
+            IBinder token;
+            synchronized (mLock) {
+                token = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(mCurrentUserId,
+                        windowId);
+            }
+            Pair<Matrix, MagnificationSpec> pair =
+                    mWindowManagerService.getWindowTransformationMatrixAndMagnificationSpec(token);
+            final float[] outTransformationMatrix = new float[9];
+            final Matrix tmpMatrix = pair.first;
+            final MagnificationSpec spec = pair.second;
+            if (!spec.isNop()) {
+                tmpMatrix.postScale(spec.scale, spec.scale);
+                tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+            }
+            tmpMatrix.getValues(outTransformationMatrix);
+
+            return new Pair<>(outTransformationMatrix, pair.second);
+        }
+    }
+
+    @Override
     public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
         mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
                 userState.mBoundServices);
@@ -3750,12 +3787,12 @@
                         boundsInScreenBeforeMagnification.centerY());
 
                 // Invert magnification if needed.
-                final WindowInfo windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(
-                        focus.getWindowId());
+                final Pair<float[], MagnificationSpec> pair =
+                        getWindowTransformationMatrixAndMagnificationSpec(focus.getWindowId());
                 MagnificationSpec spec = null;
-                if (windowInfo != null) {
+                if (pair != null && pair.second != null) {
                     spec = new MagnificationSpec();
-                    spec.setTo(windowInfo.mMagnificationSpec);
+                    spec.setTo(pair.second);
                 }
 
                 if (spec != null && !spec.isNop()) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8e32a7a..6d3620f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3225,10 +3225,15 @@
                     currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
                     mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
                 }
+                // Just show fill dialog once, so disabled after shown.
+                // Note: Cannot disable before requestShowFillDialog() because the method
+                //       need to check whether fill dialog enabled.
+                setFillDialogDisabled();
                 return;
             } else {
                 setFillDialogDisabled();
             }
+
         }
 
         if (response.supportsInlineSuggestions()) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 62bb9f1..2a22cac 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -62,7 +62,6 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
@@ -81,7 +80,6 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.text.BidiFormatter;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Log;
@@ -538,20 +536,12 @@
             String callingPackage = component.getPackageName();
             checkCanCallNotificationApi(callingPackage);
             // TODO: check userId.
-            String packageTitle = BidiFormatter.getInstance().unicodeWrap(
-                    getPackageInfo(getContext(), userId, callingPackage)
-                            .applicationInfo
-                            .loadSafeLabel(getContext().getPackageManager(),
-                                    PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
-                                    PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
-                            .toString());
             final long identity = Binder.clearCallingIdentity();
             try {
                 return PendingIntent.getActivityAsUser(getContext(),
                         0 /* request code */,
                         NotificationAccessConfirmationActivityContract.launcherIntent(
-                                getContext(), userId, component, packageTitle),
+                                getContext(), userId, component),
                         PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
                                 | PendingIntent.FLAG_CANCEL_CURRENT,
                         null /* options */,
@@ -732,9 +722,12 @@
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
             enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
-            new CompanionDeviceShellCommand(
-                    CompanionDeviceManagerService.this, mAssociationStore)
-                    .exec(this, in, out, err, args, callback, resultReceiver);
+
+            final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
+                    CompanionDeviceManagerService.this,
+                    mAssociationStore,
+                    mDevicePresenceMonitor);
+            cmd.exec(this, in, out, err, args, callback, resultReceiver);
         }
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index fd13085..6a19a42 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -21,6 +21,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -29,20 +31,24 @@
 
     private final CompanionDeviceManagerService mService;
     private final AssociationStore mAssociationStore;
+    private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
 
     CompanionDeviceShellCommand(CompanionDeviceManagerService service,
-            AssociationStore associationStore) {
+            AssociationStore associationStore,
+            CompanionDevicePresenceMonitor devicePresenceMonitor) {
         mService = service;
         mAssociationStore = associationStore;
+        mDevicePresenceMonitor = devicePresenceMonitor;
     }
 
     @Override
     public int onCommand(String cmd) {
         final PrintWriter out = getOutPrintWriter();
+        final int associationId;
         try {
             switch (cmd) {
                 case "list": {
-                    final int userId = getNextArgInt();
+                    final int userId = getNextIntArgRequired();
                     final List<AssociationInfo> associationsForUser =
                             mAssociationStore.getAssociationsForUser(userId);
                     for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@
                 break;
 
                 case "associate": {
-                    int userId = getNextArgInt();
+                    int userId = getNextIntArgRequired();
                     String packageName = getNextArgRequired();
                     String address = getNextArgRequired();
                     mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@
                 break;
 
                 case "disassociate": {
-                    final int userId = getNextArgInt();
+                    final int userId = getNextIntArgRequired();
                     final String packageName = getNextArgRequired();
                     final String address = getNextArgRequired();
                     final AssociationInfo association =
@@ -80,6 +86,16 @@
                 }
                 break;
 
+                case "simulate-device-appeared":
+                    associationId = getNextIntArgRequired();
+                    mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
+                    break;
+
+                case "simulate-device-disappeared":
+                    associationId = getNextIntArgRequired();
+                    mDevicePresenceMonitor.simulateDeviceDisappeared(associationId);
+                    break;
+
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -91,10 +107,6 @@
         }
     }
 
-    private int getNextArgInt() {
-        return Integer.parseInt(getNextArgRequired());
-    }
-
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -108,7 +120,31 @@
         pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
         pw.println("      Remove an existing Association.");
         pw.println("  clear-association-memory-cache");
-        pw.println("      Clear the in-memory association cache and reload all association "
-                + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+        pw.println("      Clear the in-memory association cache and reload all association ");
+        pw.println("      information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+        pw.println("  simulate-device-appeared ASSOCIATION_ID");
+        pw.println("      Make CDM act as if the given companion device has appeared.");
+        pw.println("      I.e. bind the associated companion application's");
+        pw.println("      CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
+        pw.println("      The CDM will consider the devices as present for 60 seconds and then");
+        pw.println("      will act as if device disappeared, unless 'simulate-device-disappeared'");
+        pw.println("      or 'simulate-device-appeared' is called again before 60 seconds run out"
+                + ".");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+        pw.println("  simulate-device-disappeared ASSOCIATION_ID");
+        pw.println("      Make CDM act as if the given companion device has disappeared.");
+        pw.println("      I.e. unbind the associated companion application's");
+        pw.println("      CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
+        pw.println("      NOTE: This will only have effect if 'simulate-device-appeared' was");
+        pw.println("      invoked for the same device (same ASSOCIATION_ID) no longer than");
+        pw.println("      60 seconds ago.");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+    }
+
+    private int getNextIntArgRequired() {
+        return Integer.parseInt(getNextArgRequired());
     }
 }
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 24be1b6..37e8369 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,11 +16,19 @@
 
 package com.android.server.companion.presence;
 
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.bluetooth.BluetoothAdapter;
 import android.companion.AssociationInfo;
 import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.util.Log;
 
 import com.android.server.companion.AssociationStore;
@@ -72,6 +80,11 @@
     private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
     private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
 
+    // Tracking "simulated" presence. Used for debugging and testing only.
+    private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+    private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+            new SimulatedDevicePresenceSchedulerHelper();
+
     public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
             @NonNull Callback callback) {
         mAssociationStore = associationStore;
@@ -106,7 +119,8 @@
     public boolean isDevicePresent(int associationId) {
         return mReportedSelfManagedDevices.contains(associationId)
                 || mConnectedBtDevices.contains(associationId)
-                || mNearbyBleDevices.contains(associationId);
+                || mNearbyBleDevices.contains(associationId)
+                || mSimulated.contains(associationId);
     }
 
     /**
@@ -155,6 +169,45 @@
         onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
     }
 
+    /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+    @TestApi
+    public void simulateDeviceAppeared(int associationId) {
+        // IMPORTANT: this API should only be invoked via the
+        // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+        // make this call are SHELL and ROOT.
+        // No other caller (including SYSTEM!) should be allowed.
+        enforceCallerShellOrRoot();
+        // Make sure the association exists.
+        enforceAssociationExists(associationId);
+
+        onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+
+        mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+    }
+
+    /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+    @TestApi
+    public void simulateDeviceDisappeared(int associationId) {
+        // IMPORTANT: this API should only be invoked via the
+        // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+        // make this call are SHELL and ROOT.
+        // No other caller (including SYSTEM!) should be allowed.
+        enforceCallerShellOrRoot();
+        // Make sure the association exists.
+        enforceAssociationExists(associationId);
+
+        mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+
+        onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+    }
+
+    private void enforceAssociationExists(int associationId) {
+        if (mAssociationStore.getAssociationById(associationId) == null) {
+            throw new IllegalArgumentException(
+                    "Association with id " + associationId + " does not exist.");
+        }
+    }
+
     private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
             int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
         if (DEBUG) {
@@ -212,6 +265,7 @@
         mConnectedBtDevices.remove(associationId);
         mNearbyBleDevices.remove(associationId);
         mReportedSelfManagedDevices.remove(associationId);
+        mSimulated.remove(associationId);
     }
 
     /**
@@ -232,4 +286,36 @@
         // CompanionDeviceManagerService will know that the association is removed, and will do
         // what's needed.
     }
+
+    private static void enforceCallerShellOrRoot() {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+        throw new SecurityException("Caller is neither Shell nor Root");
+    }
+
+    private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+        SimulatedDevicePresenceSchedulerHelper() {
+            super(Looper.getMainLooper());
+        }
+
+        void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+            // First, unschedule if it was scheduled previously.
+            if (hasMessages(/* what */ associationId)) {
+                removeMessages(/* what */ associationId);
+            }
+
+            sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+        }
+
+        void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+            removeMessages(/* what */ associationId);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            final int associationId = msg.what;
+            onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 5a234f5..c09bb2d 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -67,6 +67,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -360,6 +361,7 @@
                     mUidBatteryUsageInWindow.removeAt(i);
                 }
             }
+            mInjector.getPolicy().onUserRemovedLocked(userId);
         }
     }
 
@@ -368,6 +370,7 @@
         synchronized (mLock) {
             mUidBatteryUsage.delete(uid);
             mUidBatteryUsageInWindow.delete(uid);
+            mInjector.getPolicy().onUidRemovedLocked(uid);
         }
     }
 
@@ -1208,6 +1211,14 @@
                 DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window";
 
         /**
+         * The grace period after an interaction event with the app, if the background current
+         * drain goes beyond the threshold within that period, the system won't apply the
+         * restrictions.
+         */
+        static final String KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD =
+                DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_interaction_grace_period";
+
+        /**
          * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher
          * value for the legitimate cases with higher background current drain.
          */
@@ -1310,6 +1321,11 @@
         final long mDefaultBgCurrentDrainWindowMs;
 
         /**
+         * Default value to {@link #mBgCurrentDrainInteractionGracePeriodMs}.
+         */
+        final long mDefaultBgCurrentDrainInteractionGracePeriodMs;
+
+        /**
          * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
          * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
          */
@@ -1394,6 +1410,11 @@
         volatile long mBgCurrentDrainWindowMs;
 
         /**
+         * @see #KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD.
+         */
+        volatile long mBgCurrentDrainInteractionGracePeriodMs;
+
+        /**
          * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
          */
         volatile long mBgCurrentDrainMediaPlaybackMinDuration;
@@ -1455,6 +1476,12 @@
         private final SparseArray<Pair<long[], ImmutableBatteryUsage[]>> mHighBgBatteryPackages =
                 new SparseArray<>();
 
+        /**
+         * The timestamp of the last interaction, key is the UID.
+         */
+        @GuardedBy("mLock")
+        private final SparseLongArray mLastInteractionTime = new SparseLongArray();
+
         @NonNull
         private final Object mLock;
 
@@ -1478,6 +1505,7 @@
                     isLowRamDeviceStatic() ? val[1] : val[0];
             mDefaultBgCurrentDrainWindowMs = resources.getInteger(
                     R.integer.config_bg_current_drain_window) * 1_000;
+            mDefaultBgCurrentDrainInteractionGracePeriodMs = mDefaultBgCurrentDrainWindowMs;
             val = getFloatArray(resources.obtainTypedArray(
                     R.array.config_bg_current_drain_high_threshold_to_restricted_bucket));
             mDefaultBgCurrentDrainRestrictedBucketHighThreshold =
@@ -1511,6 +1539,8 @@
             mBgCurrentDrainBgRestrictedThreshold[1] =
                     mDefaultBgCurrentDrainBgRestrictedHighThreshold;
             mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs;
+            mBgCurrentDrainInteractionGracePeriodMs =
+                    mDefaultBgCurrentDrainInteractionGracePeriodMs;
             mBgCurrentDrainMediaPlaybackMinDuration =
                     mDefaultBgCurrentDrainMediaPlaybackMinDuration;
             mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration;
@@ -1542,6 +1572,9 @@
                 case KEY_BG_CURRENT_DRAIN_WINDOW:
                     updateCurrentDrainWindow();
                     break;
+                case KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD:
+                    updateCurrentDrainInteractionGracePeriod();
+                    break;
                 case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION:
                     updateCurrentDrainMediaPlaybackMinDuration();
                     break;
@@ -1626,6 +1659,13 @@
                     mDefaultBgCurrentDrainWindowMs);
         }
 
+        private void updateCurrentDrainInteractionGracePeriod() {
+            mBgCurrentDrainInteractionGracePeriodMs = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+                    mDefaultBgCurrentDrainInteractionGracePeriodMs);
+        }
+
         private void updateCurrentDrainMediaPlaybackMinDuration() {
             mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1668,6 +1708,7 @@
             super.onSystemReady();
             updateCurrentDrainThreshold();
             updateCurrentDrainWindow();
+            updateCurrentDrainInteractionGracePeriod();
             updateCurrentDrainMediaPlaybackMinDuration();
             updateCurrentDrainLocationMinDuration();
             updateCurrentDrainEventDurationBasedThresholdEnabled();
@@ -1685,8 +1726,10 @@
             synchronized (mLock) {
                 final Pair<long[], ImmutableBatteryUsage[]> pair = mHighBgBatteryPackages.get(uid);
                 if (pair != null) {
+                    final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
                     final long[] ts = pair.first;
-                    final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0
+                    final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
+                            > (lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs)
                             && mTracker.mAppRestrictionController.isAutoRestrictAbusiveAppEnabled()
                             ? RESTRICTION_LEVEL_RESTRICTED_BUCKET
                             : RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
@@ -1777,6 +1820,7 @@
                     // We're already in the background restricted level, nothing more we could do.
                     return;
                 }
+                final long lastInteractionTime = mLastInteractionTime.get(uid, 0L);
                 final long now = SystemClock.elapsedRealtime();
                 final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
                         mBgCurrentDrainWindowMs);
@@ -1788,13 +1832,17 @@
                     long[] ts = null;
                     ImmutableBatteryUsage[] usages = null;
                     if (rbPercentage >= rbThreshold) {
-                        // New findings to us, track it and let the controller know.
-                        ts = new long[TIME_STAMP_INDEX_LAST];
-                        ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
-                        usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
-                        usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
-                        mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
-                        notifyController = excessive = true;
+                        if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
+                            // New findings to us, track it and let the controller know.
+                            ts = new long[TIME_STAMP_INDEX_LAST];
+                            ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+                            usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST];
+                            usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+                            mHighBgBatteryPackages.put(uid, Pair.create(ts, usages));
+                            // It's beeen long enough since last interaction with this app.
+                            notifyController = true;
+                        }
+                        excessive = true;
                     }
                     if (decoupleThresholds && brPercentage >= brThreshold) {
                         if (ts == null) {
@@ -1812,11 +1860,15 @@
                     final long[] ts = pair.first;
                     final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
                     if (rbPercentage >= rbThreshold) {
-                        if (lastRestrictBucketTs == 0) {
-                            ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
-                            pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+                        if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) {
+                            if (lastRestrictBucketTs == 0) {
+                                ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+                                pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+                            }
+                            // It's been long enough since last interaction with this app.
+                            notifyController = true;
                         }
-                        notifyController = excessive = true;
+                        excessive = true;
                     } else {
                         // It's actually back to normal, but we don't untrack it until
                         // explicit user interactions, because the restriction could be the cause
@@ -1833,7 +1885,7 @@
                                 && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs));
                         if (notifyController) {
                             ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
-                            pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage;
+                            pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = usage;
                         }
                         excessive = true;
                     } else {
@@ -1841,7 +1893,7 @@
                         // user consent to unrestrict it; or if it's in restricted bucket level,
                         // resetting this won't lift it from that level.
                         ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
-                        pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = null;
+                        pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null;
                         // Now need to notify the controller.
                     }
                 }
@@ -1902,6 +1954,7 @@
         void onUserInteractionStarted(String packageName, int uid) {
             boolean changed = false;
             synchronized (mLock) {
+                mLastInteractionTime.put(uid, SystemClock.elapsedRealtime());
                 final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(
                         uid, packageName);
                 if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
@@ -1940,9 +1993,30 @@
         @VisibleForTesting
         void reset() {
             mHighBgBatteryPackages.clear();
+            mLastInteractionTime.clear();
             mTracker.reset();
         }
 
+        @GuardedBy("mLock")
+        void onUserRemovedLocked(final @UserIdInt int userId) {
+            for (int i = mHighBgBatteryPackages.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mHighBgBatteryPackages.keyAt(i)) == userId) {
+                    mHighBgBatteryPackages.removeAt(i);
+                }
+            }
+            for (int i = mLastInteractionTime.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mLastInteractionTime.keyAt(i)) == userId) {
+                    mLastInteractionTime.removeAt(i);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void onUidRemovedLocked(final int uid) {
+            mHighBgBatteryPackages.remove(uid);
+            mLastInteractionTime.delete(uid);
+        }
+
         @Override
         void dump(PrintWriter pw, String prefix) {
             pw.print(prefix);
@@ -1976,6 +2050,10 @@
                 pw.print('=');
                 pw.println(mBgCurrentDrainWindowMs);
                 pw.print(prefix);
+                pw.print(KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD);
+                pw.print('=');
+                pw.println(mBgCurrentDrainInteractionGracePeriodMs);
+                pw.print(prefix);
                 pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
                 pw.print('=');
                 pw.println(mBgCurrentDrainMediaPlaybackMinDuration);
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 18fb6a4..08a6719 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -24,9 +24,14 @@
 
 /** Rate limiter for adding errors into dropbox. */
 public class DropboxRateLimiter {
-    private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
-    private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
-    private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;
+    // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of
+    // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has
+    // elapsed, after which the current count for this breakdown will be reset.
+    private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS;
+    // The time duration after which the rate limit buffer will be cleared.
+    private static final long RATE_LIMIT_BUFFER_EXPIRY = 3 * RATE_LIMIT_BUFFER_DURATION;
+    // The number of entries to keep per breakdown of process/eventType.
+    private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6;
 
     @GuardedBy("mErrorClusterRecords")
     private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index db17c105..8180e66 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -64,7 +64,9 @@
                         | InputConfig.INTERCEPTS_STYLUS
                         | InputConfig.TRUSTED_OVERLAY;
 
-        // The touchable region of this input surface is not initially configured.
+        // Configure the surface to receive stylus events across the entire display.
+        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
@@ -81,10 +83,6 @@
         mWindowHandle.ownerUid = imeUid;
         mWindowHandle.inputConfig &= ~InputConfig.SPY;
 
-        // Update the touchable region so that the IME can intercept stylus events
-        // across the entire display.
-        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
-
         new SurfaceControl.Transaction()
                 .setInputWindowInfo(mInputSurface, mWindowHandle)
                 .apply();
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index a706772..f89b6ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -91,7 +91,7 @@
      * InputEventReceiver that batches events according to the current thread's Choreographer.
      */
     @UiThread
-    void initializeHandwritingSpy(int displayId, IBinder focusedWindowToken) {
+    void initializeHandwritingSpy(int displayId) {
         // When resetting, reuse resources if we are reinitializing on the same display.
         reset(displayId == mCurrentDisplayId);
         mCurrentDisplayId = displayId;
@@ -115,12 +115,6 @@
         mHandwritingSurface = new HandwritingEventReceiverSurface(
                 name, displayId, surface, channel);
 
-        // Configure the handwriting window to receive events over the focused window's bounds.
-        mWindowManagerInternal.replaceInputSurfaceTouchableRegionWithWindowCrop(
-                mHandwritingSurface.getSurface(),
-                mHandwritingSurface.getInputWindowHandle(),
-                focusedWindowToken);
-
         // Use a dup of the input channel so that event processing can be paused by disposing the
         // event receiver without causing a fd hangup.
         mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
@@ -149,7 +143,8 @@
      */
     @UiThread
     @Nullable
-    HandwritingSession startHandwritingSession(int requestId, int imePid, int imeUid) {
+    HandwritingSession startHandwritingSession(
+            int requestId, int imePid, int imeUid, IBinder focusedWindowToken) {
         if (mHandwritingSurface == null) {
             Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
             return null;
@@ -158,12 +153,20 @@
             Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
             return null;
         }
-        if (!mRecordingGesture) {
+        if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) {
             Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded.");
             return null;
         }
         Objects.requireNonNull(mHandwritingEventReceiver,
                 "Handwriting session was already transferred to IME.");
+        final MotionEvent downEvent = mHandwritingBuffer.get(0);
+        assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN);
+        if (!mWindowManagerInternal.isPointInsideWindow(
+                focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) {
+            Slog.e(TAG, "Cannot start handwriting session: "
+                    + "Stylus gesture did not start inside the focused window.");
+            return null;
+        }
         if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
 
         mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
@@ -226,13 +229,17 @@
         }
 
         if (!(ev instanceof MotionEvent)) {
-            Slog.e("Stylus", "Received non-motion event in stylus monitor.");
+            Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
             return false;
         }
         final MotionEvent event = (MotionEvent) ev;
         if (!isStylusEvent(event)) {
             return false;
         }
+        if (event.getDisplayId() != mCurrentDisplayId) {
+            Slog.wtf(TAG, "Received stylus event associated with the incorrect display.");
+            return false;
+        }
 
         onStylusEvent(event);
         return true;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c759c64..ea2b157 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5118,9 +5118,8 @@
             case MSG_RESET_HANDWRITING: {
                 synchronized (ImfLock.class) {
                     if (mBindingController.supportsStylusHandwriting()
-                            && getCurMethodLocked() != null && mCurFocusedWindow != null) {
-                        mHwController.initializeHandwritingSpy(
-                                mCurTokenDisplayId, mCurFocusedWindow);
+                            && getCurMethodLocked() != null) {
+                        mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
                     } else {
                         mHwController.reset();
                     }
@@ -5130,14 +5129,15 @@
             case MSG_START_HANDWRITING:
                 synchronized (ImfLock.class) {
                     IInputMethodInvoker curMethod = getCurMethodLocked();
-                    if (curMethod == null) {
+                    if (curMethod == null || mCurFocusedWindow == null) {
                         return true;
                     }
                     final HandwritingModeController.HandwritingSession session =
                             mHwController.startHandwritingSession(
                                     msg.arg1 /*requestId*/,
                                     msg.arg2 /*pid*/,
-                                    mBindingController.getCurMethodUid());
+                                    mBindingController.getCurMethodUid(),
+                                    mCurFocusedWindow);
                     if (session == null) {
                         Slog.e(TAG,
                                 "Failed to start handwriting session for requestId: " + msg.arg1);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 83c576e..dbe04b0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -58,6 +58,7 @@
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_NOT_PERCEPTIBLE;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -2161,6 +2162,11 @@
         mAccessibilityManager = am;
     }
 
+    @VisibleForTesting
+    void setTelecomManager(TelecomManager tm) {
+        mTelecomManager = tm;
+    }
+
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
     void init(WorkerHandler handler, RankingHandler rankingHandler,
@@ -6978,8 +6984,13 @@
     private boolean isCallNotification(String pkg, int uid) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            return mTelecomManager.isInManagedCall() || mTelecomManager.isInSelfManagedCall(
-                    pkg, UserHandle.getUserHandleForUid(uid));
+            if (mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)
+                    && mTelecomManager != null) {
+                return mTelecomManager.isInManagedCall()
+                        || mTelecomManager.isInSelfManagedCall(
+                                pkg, UserHandle.getUserHandleForUid(uid));
+            }
+            return false;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -7405,6 +7416,7 @@
         @Override
         public void run() {
             boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
+            boolean isCallNotification = isCallNotification(pkg, uid);
             synchronized (mNotificationLock) {
                 try {
                     NotificationRecord r = null;
@@ -7423,8 +7435,10 @@
 
                     final StatusBarNotification n = r.getSbn();
                     final Notification notification = n.getNotification();
+                    boolean isCallNotificationAndCorrectStyle = isCallNotification
+                            && notification.isStyle(Notification.CallStyle.class);
 
-                    if (!notification.isMediaNotification()
+                    if (!(notification.isMediaNotification() || isCallNotificationAndCorrectStyle)
                             && (appBanned || isRecordBlockedLocked(r))) {
                         mUsageStats.registerBlocked(r);
                         if (DBG) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 320b06f..32f0f10 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -119,7 +119,7 @@
             IInstalld.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
 
     private final boolean mIsolated;
-
+    private volatile boolean mDeferSetFirstBoot;
     private volatile IInstalld mInstalld;
     private volatile Object mWarnIfHeld;
 
@@ -171,6 +171,7 @@
             mInstalld = IInstalld.Stub.asInterface(binder);
             try {
                 invalidateMounts();
+                executeDeferredActions();
             } catch (InstallerException ignored) {
             }
         } else {
@@ -180,6 +181,15 @@
     }
 
     /**
+     * Perform any deferred actions on mInstalld while the connection could not be made.
+     */
+    private void executeDeferredActions() throws InstallerException {
+        if (mDeferSetFirstBoot) {
+            setFirstBoot();
+        }
+    }
+
+    /**
      * Do several pre-flight checks before making a remote call.
      *
      * @return if the remote call should continue.
@@ -291,8 +301,15 @@
             return;
         }
         try {
-            mInstalld.setFirstBoot();
-        } catch (RemoteException e) {
+            // mInstalld might be null if the connection could not be established.
+            if (mInstalld != null) {
+                mInstalld.setFirstBoot();
+            } else {
+                // if it is null while trying to set the first boot, set a flag to try and set the
+                // first boot when the connection is eventually established
+                mDeferSetFirstBoot = true;
+            }
+        } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index e1ff9ea..6ee9c66 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -392,6 +392,9 @@
         if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
             liteParseFlags = flags;
         }
+        if ((flags & PARSE_APK_IN_APEX) != 0) {
+            liteParseFlags |= PARSE_APK_IN_APEX;
+        }
         final ParseResult<PackageLite> liteResult =
                 ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
                         liteParseFlags);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b142141..d8e7fbe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -27,7 +27,6 @@
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
 import static android.os.Build.VERSION_CODES.M;
 import static android.os.Build.VERSION_CODES.O;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -130,7 +129,6 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.session.MediaSessionLegacyHelper;
-import android.os.BatteryManagerInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.DeviceIdleManager;
@@ -396,7 +394,6 @@
     PowerManagerInternal mPowerManagerInternal;
     IStatusBarService mStatusBarService;
     StatusBarManagerInternal mStatusBarManagerInternal;
-    BatteryManagerInternal mBatteryManagerInternal;
     AudioManagerInternal mAudioManagerInternal;
     DisplayManager mDisplayManager;
     DisplayManagerInternal mDisplayManagerInternal;
@@ -791,8 +788,7 @@
         @Override
         public void onWakeUp() {
             synchronized (mLock) {
-                if (shouldEnableWakeGestureLp()
-                        && getBatteryManagerInternal().getPlugType() != BATTERY_PLUGGED_WIRELESS) {
+                if (shouldEnableWakeGestureLp()) {
                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
                             "Wake Up");
                     wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture,
@@ -849,15 +845,6 @@
         }
     }
 
-    BatteryManagerInternal getBatteryManagerInternal() {
-        synchronized (mServiceAcquireLock) {
-            if (mBatteryManagerInternal == null) {
-                mBatteryManagerInternal =
-                        LocalServices.getService(BatteryManagerInternal.class);
-            }
-            return mBatteryManagerInternal;
-        }
-    }
 
     // returns true if the key was handled and should not be passed to the user
     private boolean backKeyPress() {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 63619e5..f1dbad61 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -446,6 +446,43 @@
         // Not relevant for the window observer.
     }
 
+    public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+            IBinder token) {
+        synchronized (mService.mGlobalLock) {
+            final Matrix transformationMatrix = new Matrix();
+            final MagnificationSpec magnificationSpec = new MagnificationSpec();
+
+            final WindowState windowState = mService.mWindowMap.get(token);
+            if (windowState != null) {
+                windowState.getTransformationMatrix(new float[9], transformationMatrix);
+
+                if (hasCallbacks()) {
+                    final MagnificationSpec otherMagnificationSpec =
+                            getMagnificationSpecForWindow(windowState);
+                    if (otherMagnificationSpec != null && !otherMagnificationSpec.isNop()) {
+                        magnificationSpec.setTo(otherMagnificationSpec);
+                    }
+                }
+            }
+
+            return new Pair<>(transformationMatrix, magnificationSpec);
+        }
+    }
+
+    MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow",
+                    FLAGS_MAGNIFICATION_CALLBACK,
+                    "windowState={" + windowState + "}");
+        }
+        final int displayId = windowState.getDisplayId();
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            return displayMagnifier.getMagnificationSpecForWindow(windowState);
+        }
+        return null;
+    }
+
     boolean hasCallbacks() {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index cb65597..701fc94 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -1143,17 +1142,13 @@
             if (activity == null) {
                 continue;
             }
-            if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                        "Delaying app transition for recents animation to finish");
-                return false;
-            }
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                             + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
                     activity, activity.allDrawn, activity.startingDisplayed,
                     activity.startingMoved, activity.isRelaunching(),
                     activity.mStartingWindow);
+
             final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
             if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
                 return false;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index ee7d9a9..46ce433 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -153,10 +153,10 @@
             for (WindowContainer wc : mRootMembers) {
                 wc.waitForSyncTransactionCommit(wcAwaitingCommit);
             }
-            final Runnable callback = new Runnable() {
+            class CommitCallback implements Runnable {
                 // Can run a second time if the action completes after the timeout.
                 boolean ran = false;
-                public void run() {
+                public void onCommitted() {
                     synchronized (mWm.mGlobalLock) {
                         if (ran) {
                             return;
@@ -171,8 +171,23 @@
                         wcAwaitingCommit.clear();
                     }
                 }
+
+                // Called in timeout
+                @Override
+                public void run() {
+                    // Sometimes we get a trace, sometimes we get a bugreport without
+                    // a trace. Since these kind of ANRs can trigger such an issue,
+                    // try and ensure we will have some visibility in both cases.
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
+                    Slog.e(TAG, "WM sent Transaction to organized, but never received" +
+                           " commit callback. Application ANR likely to follow.");
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    onCommitted();
+
+                }
             };
-            merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::run);
+            CommitCallback callback = new CommitCallback();
+            merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
             mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a480c37..2f00bc8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1584,14 +1584,6 @@
         return true;
     }
 
-    void forAllWindowContainers(Consumer<WindowContainer> callback) {
-        callback.accept(this);
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
-            mChildren.get(i).forAllWindowContainers(callback);
-        }
-    }
-
     /**
      * For all windows at or below this container call the callback.
      * @param   callback Calls the {@link ToBooleanFunction#apply} method for each window found and
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f8bc26a..c0d7d13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -23,17 +23,18 @@
 import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.util.Pair;
 import android.view.Display;
 import android.view.IInputFilter;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IWindow;
 import android.view.InputChannel;
-import android.view.InputWindowHandle;
 import android.view.MagnificationSpec;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -459,6 +460,17 @@
     public abstract void getWindowFrame(IBinder token, Rect outBounds);
 
     /**
+     * Get the transformation matrix and MagnificationSpec given its token.
+     *
+     * @param token The token.
+     * @return The pair of the transformation matrix and magnification spec.
+     */
+    // TODO (b/231663133): Long term solution for tracking window when the
+    //                     FLAG_RETRIEVE_INTERACTIVE_WINDOWS is unset.
+    public abstract Pair<Matrix, MagnificationSpec>
+            getWindowTransformationMatrixAndMagnificationSpec(IBinder token);
+
+    /**
      * Opens the global actions dialog.
      */
     public abstract void showGlobalActions();
@@ -849,24 +861,12 @@
     public abstract SurfaceControl getHandwritingSurfaceForDisplay(int displayId);
 
     /**
-     * Replaces the touchable region of the provided input surface with the crop of the window with
-     * the provided token. This method will associate the inputSurface with a copy of
-     * the given inputWindowHandle, where the copy is configured using
-     * {@link InputWindowHandle#replaceTouchableRegionWithCrop(SurfaceControl)} with the surface
-     * of the provided windowToken.
+     * Returns {@code true} if the given point is within the window bounds of the given window.
      *
-     * This is a no-op if windowToken is not valid or the window is not found.
-     *
-     * This does not change any other properties of the inputSurface.
-     *
-     * This method exists to avoid leaking the window's SurfaceControl outside WindowManagerService.
-     *
-     * @param inputSurface The surface for which the touchable region should be set.
-     * @param inputWindowHandle The {@link InputWindowHandle} for the input surface.
-     * @param windowToken The window whose bounds should be used as the touchable region for the
-     *                    inputSurface.
+     * @param windowToken the window whose bounds should be used for the hit test.
+     * @param displayX the x coordinate of the test point in the display's coordinate space.
+     * @param displayY the y coordinate of the test point in the display's coordinate space.
      */
-    public abstract void replaceInputSurfaceTouchableRegionWithWindowCrop(
-            @NonNull SurfaceControl inputSurface, @NonNull InputWindowHandle inputWindowHandle,
-            @NonNull IBinder windowToken);
+    public abstract boolean isPointInsideWindow(
+            @NonNull IBinder windowToken, int displayId, float displayX, float displayY);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a9f56d3..8f1f7ec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -118,7 +118,6 @@
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -173,6 +172,7 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -224,6 +224,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.MergedConfiguration;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
@@ -3051,22 +3052,13 @@
         }
     }
 
-
     void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
         if (mRecentsAnimationController != null) {
             final RecentsAnimationController controller = mRecentsAnimationController;
             mRecentsAnimationController = null;
             controller.cleanupAnimation(reorderMode);
-            // TODO(multi-display): currently only default display support recents animation.
-            // Cancel any existing app transition animation running in the legacy transition
-            // framework.
-            final DisplayContent dc = getDefaultDisplayContentLocked();
-            dc.mAppTransition.freeze();
-            dc.forAllWindowContainers((wc) -> {
-                if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
-                    wc.cancelAnimation();
-                }
-            });
+            // TODO(mult-display): currently only default display support recents animation.
+            getDefaultDisplayContentLocked().mAppTransition.updateBooster();
         }
     }
 
@@ -7769,6 +7761,13 @@
         }
 
         @Override
+        public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+                IBinder token) {
+            return mAccessibilityController
+                    .getWindowTransformationMatrixAndMagnificationSpec(token);
+        }
+
+        @Override
         public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
             final WindowContainer container = displayId == INVALID_DISPLAY
                     ? mRoot : mRoot.getDisplayContent(displayId);
@@ -8241,23 +8240,15 @@
         }
 
         @Override
-        public void replaceInputSurfaceTouchableRegionWithWindowCrop(
-                @NonNull SurfaceControl inputSurface,
-                @NonNull InputWindowHandle inputWindowHandle,
-                @NonNull IBinder windowToken) {
+        public boolean isPointInsideWindow(@NonNull IBinder windowToken, int displayId,
+                float displayX, float displayY) {
             synchronized (mGlobalLock) {
                 final WindowState w = mWindowMap.get(windowToken);
-                if (w == null) {
-                    return;
+                if (w == null || w.getDisplayId() != displayId) {
+                    return false;
                 }
-                // Make a copy of the InputWindowHandle to avoid leaking the window's
-                // SurfaceControl.
-                final InputWindowHandle localHandle = new InputWindowHandle(inputWindowHandle);
-                localHandle.replaceTouchableRegionWithCrop(w.getSurfaceControl());
-                final SurfaceControl.Transaction t = mTransactionFactory.get();
-                t.setInputWindowInfo(inputSurface, localHandle);
-                t.apply();
-                t.close();
+
+                return w.getBounds().contains((int) displayX, (int) displayY);
             }
         }
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index ac54293..009dae5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -582,6 +582,7 @@
 
         DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
         DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+        DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null;
         DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
         DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
         DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null;
@@ -617,6 +618,14 @@
                             R.integer.config_bg_current_drain_window));
             bgCurrentDrainWindow.set(windowMs);
 
+            bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+                    DeviceConfig::getLong,
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_window));
+            bgCurrentDrainInteractionGracePeriod.set(windowMs);
+
             bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
@@ -768,6 +777,32 @@
 
             clearInvocations(mInjector.getAppStandbyInternal());
 
+            // It won't be restricted since user just interacted with it.
+            runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+                    zeros, new double[]{0, restrictBucketThresholdMah - 1},
+                    zeros, new double[]{restrictBucketThresholdMah + 1, 0},
+                    () -> {
+                        doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+                        doReturn(mCurrentTimeMillis + windowMs)
+                                .when(stats).getStatsEndTimestamp();
+                        mCurrentTimeMillis += windowMs + 1;
+                        try {
+                            listener.verify(timeout, testUid, testPkgName,
+                                    RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+                            fail("There shouldn't be any level change events");
+                        } catch (Exception e) {
+                            // Expected.
+                        }
+                        verify(mInjector.getAppStandbyInternal(), never()).restrictApp(
+                                eq(testPkgName),
+                                eq(testUser),
+                                anyInt(), anyInt());
+                    });
+
+            // Sleep a while.
+            Thread.sleep(windowMs);
+            clearInvocations(mInjector.getAppStandbyInternal());
+            // Now it should have been restricted.
             runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
                     zeros, new double[]{0, restrictBucketThresholdMah - 1},
                     zeros, new double[]{restrictBucketThresholdMah + 1, 0},
@@ -1061,6 +1096,7 @@
         } finally {
             closeIfNotNull(bgCurrentDrainMonitor);
             closeIfNotNull(bgCurrentDrainWindow);
+            closeIfNotNull(bgCurrentDrainInteractionGracePeriod);
             closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
             closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
             closeIfNotNull(bgPromptFgsWithNotiToBgRestricted);
@@ -1610,6 +1646,7 @@
 
         DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
         DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+        DeviceConfigSession<Long> bgCurrentDrainInteractionGracePeriod = null;
         DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
         DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
         DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketHighThreshold = null;
@@ -1655,6 +1692,14 @@
                             R.integer.config_bg_current_drain_window));
             bgCurrentDrainWindow.set(windowMs);
 
+            bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD,
+                    DeviceConfig::getLong,
+                    (long) mContext.getResources().getInteger(
+                            R.integer.config_bg_current_drain_window));
+            bgCurrentDrainInteractionGracePeriod.set(windowMs);
+
             bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
                     DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
@@ -2176,6 +2221,7 @@
         } finally {
             closeIfNotNull(bgCurrentDrainMonitor);
             closeIfNotNull(bgCurrentDrainWindow);
+            closeIfNotNull(bgCurrentDrainInteractionGracePeriod);
             closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
             closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
             closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 08df438..19df5a2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -89,8 +89,10 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.util.Pair;
 import android.view.Display;
 import android.view.KeyEvent;
+import android.view.MagnificationSpec;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -145,6 +147,8 @@
     private static final int USER_ID = 1;
     private static final int USER_ID2 = 2;
     private static final int INTERACTION_ID = 199;
+    private static final Pair<float[], MagnificationSpec> FAKE_MATRIX_AND_MAG_SPEC =
+            new Pair<>(new float[9], new MagnificationSpec());
     private static final int PID = Process.myPid();
     private static final long TID = Process.myTid();
     private static final int UID = Process.myUid();
@@ -188,6 +192,8 @@
                 .thenReturn(mMockFingerprintGestureDispatcher);
         when(mMockSystemSupport.getMagnificationProcessor())
                 .thenReturn(mMockMagnificationProcessor);
+        when(mMockSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(anyInt()))
+                .thenReturn(FAKE_MATRIX_AND_MAG_SPEC);
 
         PowerManager powerManager =
                 new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
index d1390c6..e68a8a0 100644
--- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
@@ -49,10 +49,11 @@
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
         // Different processes and tags should not get rate limited either.
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process2").shouldRateLimit());
         assertFalse(mRateLimiter.shouldRateLimit("tag2", "process").shouldRateLimit());
-        // The 6th entry of the same process should be rate limited.
+        // The 7th entry of the same process should be rate limited.
         assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
     }
 
@@ -64,12 +65,13 @@
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
-        // The 6th entry of the same process should be rate limited.
+        assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+        // The 7th entry of the same process should be rate limited.
         assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
 
-        // After 11 seconds there should be nothing left in the buffer and the same type of entry
+        // After 11 minutes there should be nothing left in the buffer and the same type of entry
         // should not get rate limited anymore.
-        mClock.setOffsetMillis(11000);
+        mClock.setOffsetMillis(11 * 60 * 1000);
 
         assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
     }
@@ -86,13 +88,15 @@
                 mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
         assertEquals(0,
                 mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
+        assertEquals(0,
+                mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
         assertEquals(1,
                 mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
         assertEquals(2,
                 mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
 
-        // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop.
-        mClock.setOffsetMillis(11000);
+        // After 11 minutes the rate limiting buffer will be cleared and rate limiting will stop.
+        mClock.setOffsetMillis(11 * 60 * 1000);
 
         // The first call after rate limiting stops will still return the number of dropped events.
         assertEquals(2,
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 20486b3..8167b44 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -454,14 +454,14 @@
                         + "    <library \n"
                         + "        name=\"foo\"\n"
                         + "        file=\"" + mFooJar + "\"\n"
-                        + "        on-bootclasspath-before=\"Q\"\n"
+                        + "        on-bootclasspath-before=\"A\"\n"
                         + "        on-bootclasspath-since=\"W\"\n"
                         + "     />\n\n"
                         + " </permissions>";
         parseSharedLibraries(contents);
         assertFooIsOnlySharedLibrary();
         SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
-        assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
+        assertThat(entry.onBootclasspathBefore).isEqualTo("A");
         assertThat(entry.onBootclasspathSince).isEqualTo("W");
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c0cd7a7..850c9e3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -52,6 +52,7 @@
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -448,6 +449,8 @@
         mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
         when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0});
 
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
+
         ActivityManager.AppTask task = mock(ActivityManager.AppTask.class);
         List<ActivityManager.AppTask> taskList = new ArrayList<>();
         ActivityManager.RecentTaskInfo taskInfo = new ActivityManager.RecentTaskInfo();
@@ -9194,6 +9197,118 @@
                 r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+
+        // set telecom manager to null - blocked
+        mService.setTelecomManager(null);
+        assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+                           r.getSbn().getId(), r.getSbn().getTag(), r, false))
+                .isFalse();
+
+        // set telecom feature to false - blocked
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false);
+        assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+                           r.getSbn().getId(), r.getSbn().getTag(), r, false))
+                .isFalse();
+    }
+
+    @Test
+    public void testCallNotificationsBypassBlock_atPost() throws Exception {
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+
+        Notification.Builder nb =
+                new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                        .setContentTitle("foo")
+                        .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                        .addAction(new Notification.Action.Builder(null, "test", null).build());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.setNotificationsEnabledForPackage(
+                r.getSbn().getPackageName(), r.getUid(), false);
+
+        // normal blocked notifications - blocked
+        mService.addEnqueuedNotification(r);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                        r.getUid(), SystemClock.elapsedRealtime());
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats).registerBlocked(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
+
+        // just using the style - blocked
+        mService.clearNotifications();
+        reset(mUsageStats);
+        Person person = new Person.Builder().setName("caller").build();
+        nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
+        nb.setFullScreenIntent(mock(PendingIntent.class), true);
+        sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, nb.build(),
+                UserHandle.getUserHandleForUid(mUid), null, 0);
+        r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addEnqueuedNotification(r);
+        runnable = mService.new PostNotificationRunnable(
+                r.getKey(), r.getSbn().getPackageName(), r.getUid(), SystemClock.elapsedRealtime());
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats).registerBlocked(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
+
+        // style + managed call - bypasses block
+        mService.clearNotifications();
+        reset(mUsageStats);
+        when(mTelecomManager.isInManagedCall()).thenReturn(true);
+
+        mService.addEnqueuedNotification(r);
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats, never()).registerBlocked(any());
+        verify(mUsageStats).registerPostedByApp(any());
+
+        // style + self managed call - bypasses block
+        mService.clearNotifications();
+        reset(mUsageStats);
+        when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), r.getUser()))
+                .thenReturn(true);
+
+        mService.addEnqueuedNotification(r);
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats, never()).registerBlocked(any());
+        verify(mUsageStats).registerPostedByApp(any());
+
+        // set telecom manager to null - notifications should be blocked
+        // but post notifications runnable should not crash
+        mService.clearNotifications();
+        reset(mUsageStats);
+        mService.setTelecomManager(null);
+
+        mService.addEnqueuedNotification(r);
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats).registerBlocked(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
+
+        // set FEATURE_TELECOM to false - notifications should be blocked
+        // but post notifications runnable should not crash
+        mService.setTelecomManager(mTelecomManager);
+        when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false);
+        reset(mUsageStats);
+        mService.setTelecomManager(null);
+
+        mService.addEnqueuedNotification(r);
+        runnable.run();
+        waitForIdle();
+
+        verify(mUsageStats).registerBlocked(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 3592158..eb6395b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 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.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -50,9 +48,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.graphics.Rect;
 import android.os.Binder;
@@ -380,7 +376,7 @@
         doReturn(false).when(dc).onDescendantOrientationChanged(any());
         final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
                 dc, "exiting app");
-        final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
+        final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord;
         // Wait until everything in animation handler get executed to prevent the exiting window
         // from being removed during WindowSurfacePlacer Traversal.
         waitUntilHandlersIdle();
@@ -409,41 +405,6 @@
     }
 
     @Test
-    public void testDelayWhileRecents() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        doReturn(false).when(dc).onDescendantOrientationChanged(any());
-        final Task task = createTask(dc);
-
-        // Simulate activity1 launches activity2.
-        final ActivityRecord activity1 = createActivityRecord(task);
-        activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
-        activity1.allDrawn = true;
-        final ActivityRecord activity2 = createActivityRecord(task);
-        activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
-        activity2.allDrawn = true;
-
-        dc.mClosingApps.add(activity1);
-        dc.mOpeningApps.add(activity2);
-        dc.prepareAppTransition(TRANSIT_OPEN);
-        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
-        // Wait until everything in animation handler get executed to prevent the exiting window
-        // from being removed during WindowSurfacePlacer Traversal.
-        waitUntilHandlersIdle();
-
-        // Start recents
-        doReturn(true).when(task)
-                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
-        dc.mAppTransitionController.handleAppTransitionReady();
-
-        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-    }
-
-    @Test
     public void testGetAnimationStyleResId() {
         // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
         // specifying window type.
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index e3485de..ec94f8a 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -46,6 +46,14 @@
  * See {@link PhoneAccount}, {@link TelecomManager}.
  */
 public final class PhoneAccountHandle implements Parcelable {
+    /**
+     * Expected component name of Telephony phone accounts; ONLY used to determine if we should log
+     * the phone account handle ID.
+     */
+    private static final ComponentName TELEPHONY_COMPONENT_NAME =
+            new ComponentName("com.android.phone",
+                    "com.android.services.telephony.TelephonyConnectionService");
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196)
     private final ComponentName mComponentName;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -136,14 +144,23 @@
 
     @Override
     public String toString() {
-        // Note: Log.pii called for mId as it can contain personally identifying phone account
-        // information such as SIP account IDs.
-        return new StringBuilder().append(mComponentName)
-                    .append(", ")
-                    .append(Log.pii(mId))
-                    .append(", ")
-                    .append(mUserHandle)
-                    .toString();
+        StringBuilder sb = new StringBuilder()
+                .append(mComponentName)
+                .append(", ");
+
+        if (TELEPHONY_COMPONENT_NAME.equals(mComponentName)) {
+            // Telephony phone account handles are now keyed by subscription id which is not
+            // sensitive.
+            sb.append(mId);
+        } else {
+            // Note: Log.pii called for mId as it can contain personally identifying phone account
+            // information such as SIP account IDs.
+            sb.append(Log.pii(mId));
+        }
+        sb.append(", ");
+        sb.append(mUserHandle);
+
+        return sb.toString();
     }
 
     @Override
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index f47cf33..e7d95e4 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+
 import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_ANOMALY_DETECTED;
 
 import android.annotation.NonNull;
@@ -73,6 +75,7 @@
      *
      * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is
      * system protected. Invoking this method unless you are the system will result in an error.
+     * Carrier Id will be set as UNKNOWN_CARRIER_ID.
      *
      * @param eventId a fixed event ID that will be sent for each instance of the same event. This
      *        ID should be generated randomly.
@@ -81,6 +84,23 @@
      *        static and must not contain any sensitive information (especially PII).
      */
     public static void reportAnomaly(@NonNull UUID eventId, String description) {
+        reportAnomaly(eventId, description, UNKNOWN_CARRIER_ID);
+    }
+
+    /**
+     * If enabled, build and send an intent to a Debug Service for logging.
+     *
+     * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is
+     * system protected. Invoking this method unless you are the system will result in an error.
+     *
+     * @param eventId a fixed event ID that will be sent for each instance of the same event. This
+     *        ID should be generated randomly.
+     * @param description an optional description, that if included will be used as the subject for
+     *        identification and discussion of this event. This description should ideally be
+     *        static and must not contain any sensitive information (especially PII).
+     * @param carrierId the carrier of the id associated with this event.
+     */
+    public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
         if (sContext == null) {
             Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
             return;
@@ -88,7 +108,7 @@
 
         TelephonyStatsLog.write(
                 TELEPHONY_ANOMALY_DETECTED,
-                0, // TODO: carrier id needs to be populated
+                carrierId,
                 eventId.getLeastSignificantBits(),
                 eventId.getMostSignificantBits());
 
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index d4fa1dd..62e16a5 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -37,8 +37,8 @@
         "general-tests",
         "vts",
     ],
-    data_device_bins: [
-        "block_device_writer",
+    target_required: [
+        "block_device_writer_module",
     ],
     data: [
         ":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index e5d009d..fdfa41f 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,7 +24,12 @@
 }
 
 cc_test {
-    name: "block_device_writer",
+    // Depending on how the test runs, the executable may be uploaded to different location.
+    // Before the bug in the file pusher is fixed, workaround by making the name unique.
+    // See b/124718249#comment12.
+    name: "block_device_writer_module",
+    stem: "block_device_writer",
+
     srcs: ["block_device_writer.cpp"],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
@@ -37,7 +42,22 @@
         "libbase",
         "libutils",
     ],
-    compile_multilib: "first",
+    // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
+    // the uploader does not pick up the executable from correct output location. The following
+    // workaround allows the test to:
+    //  * upload the 32-bit exectuable for both 32 and 64 bits devices to use
+    //  * refer to the same executable name in Java
+    //  * no need to force the Java test to be archiecture specific.
+    //
+    // See b/145573317 for details.
+    multilib: {
+        lib32: {
+            suffix: "",
+        },
+        lib64: {
+            suffix: "64", // not really used
+        },
+    },
 
     auto_gen_config: false,
     test_suites: [
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 730daf3..5c2c15b 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@
  * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
  * 1. In Android.bp, add:
  * <pre>
- *      data_device_bins: ["block_device_writer"],
+ *     target_required: ["block_device_writer_module"],
  * </pre>
  * 2. In AndroidText.xml, add:
  * <pre>