Merge changes from topic "revert-20691780-cherrypick-modernize_alt_bouncer-2av1xvqe7q-EXOZAWOBTI"

* changes:
  Revert "Modernize alternate bouncer logic"
  Revert "Remove ScrimController from BiometricUnlockController"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 830031e..85b762d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -91,12 +91,23 @@
         }
         final int priority = job.getEffectivePriority();
         if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
-            // For moderate throttling, only let expedited jobs and high priority regular jobs that
-            // haven't been running for a long time run.
-            return !job.shouldTreatAsExpeditedJob()
-                    && !(priority == JobInfo.PRIORITY_HIGH
-                        && mService.isCurrentlyRunningLocked(job)
-                        && !mService.isJobInOvertimeLocked(job));
+            // For moderate throttling:
+            // Only let expedited & user-initiated jobs run if:
+            // 1. They haven't previously run
+            // 2. They're already running and aren't yet in overtime
+            // Only let high priority jobs run if:
+            //   They are already running and aren't yet in overtime
+            // Don't let any other job run.
+            if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated()) {
+                return job.getNumPreviousAttempts() > 0
+                        || (mService.isCurrentlyRunningLocked(job)
+                                && mService.isJobInOvertimeLocked(job));
+            }
+            if (priority == JobInfo.PRIORITY_HIGH) {
+                return !mService.isCurrentlyRunningLocked(job)
+                        || mService.isJobInOvertimeLocked(job);
+            }
+            return true;
         }
         if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
             // For light throttling, throttle all min priority jobs and all low priority jobs that
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index de0f752..411d157 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -458,8 +458,7 @@
                 && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
             // Add onBackPressed as default back behavior.
             mDefaultBackCallback = this::onBackPressed;
-            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mDefaultBackCallback);
+            getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
             mDefaultBackCallback = null;
         }
     }
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
index 79e3d3d..00532f4 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
@@ -18,12 +18,17 @@
 
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
 import androidx.compose.material3.dynamicDarkColorScheme
 import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.compose.theme.typography.TypeScaleTokens
+import com.android.systemui.compose.theme.typography.TypefaceNames
+import com.android.systemui.compose.theme.typography.TypefaceTokens
+import com.android.systemui.compose.theme.typography.TypographyTokens
+import com.android.systemui.compose.theme.typography.systemUITypography
 
 /** The Material 3 theme that should wrap all SystemUI Composables. */
 @Composable
@@ -33,7 +38,7 @@
 ) {
     val context = LocalContext.current
 
-    // TODO(b/230605885): Define our typography and color scheme.
+    // TODO(b/230605885): Define our color scheme.
     val colorScheme =
         if (isDarkTheme) {
             dynamicDarkColorScheme(context)
@@ -41,7 +46,11 @@
             dynamicLightColorScheme(context)
         }
     val androidColorScheme = AndroidColorScheme(context)
-    val typography = Typography()
+    val typefaceNames = remember(context) { TypefaceNames.get(context) }
+    val typography =
+        remember(typefaceNames) {
+            systemUITypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
+        }
 
     MaterialTheme(colorScheme, typography = typography) {
         CompositionLocalProvider(
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt
new file mode 100644
index 0000000..365f4bb
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt
@@ -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 com.android.systemui.compose.theme.typography
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+
+/**
+ * The SystemUI typography.
+ *
+ * Do not use directly and call [MaterialTheme.typography] instead to access the different text
+ * styles.
+ */
+internal fun systemUITypography(typographyTokens: TypographyTokens): Typography {
+    return Typography(
+        displayLarge = typographyTokens.displayLarge,
+        displayMedium = typographyTokens.displayMedium,
+        displaySmall = typographyTokens.displaySmall,
+        headlineLarge = typographyTokens.headlineLarge,
+        headlineMedium = typographyTokens.headlineMedium,
+        headlineSmall = typographyTokens.headlineSmall,
+        titleLarge = typographyTokens.titleLarge,
+        titleMedium = typographyTokens.titleMedium,
+        titleSmall = typographyTokens.titleSmall,
+        bodyLarge = typographyTokens.bodyLarge,
+        bodyMedium = typographyTokens.bodyMedium,
+        bodySmall = typographyTokens.bodySmall,
+        labelLarge = typographyTokens.labelLarge,
+        labelMedium = typographyTokens.labelMedium,
+        labelSmall = typographyTokens.labelSmall,
+    )
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt
new file mode 100644
index 0000000..537ba0b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.theme.typography
+
+import androidx.compose.ui.unit.sp
+
+internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
+    val bodyLargeFont = typefaceTokens.plain
+    val bodyLargeLineHeight = 24.0.sp
+    val bodyLargeSize = 16.sp
+    val bodyLargeTracking = 0.0.sp
+    val bodyLargeWeight = TypefaceTokens.WeightRegular
+    val bodyMediumFont = typefaceTokens.plain
+    val bodyMediumLineHeight = 20.0.sp
+    val bodyMediumSize = 14.sp
+    val bodyMediumTracking = 0.0.sp
+    val bodyMediumWeight = TypefaceTokens.WeightRegular
+    val bodySmallFont = typefaceTokens.plain
+    val bodySmallLineHeight = 16.0.sp
+    val bodySmallSize = 12.sp
+    val bodySmallTracking = 0.1.sp
+    val bodySmallWeight = TypefaceTokens.WeightRegular
+    val displayLargeFont = typefaceTokens.brand
+    val displayLargeLineHeight = 64.0.sp
+    val displayLargeSize = 57.sp
+    val displayLargeTracking = 0.0.sp
+    val displayLargeWeight = TypefaceTokens.WeightRegular
+    val displayMediumFont = typefaceTokens.brand
+    val displayMediumLineHeight = 52.0.sp
+    val displayMediumSize = 45.sp
+    val displayMediumTracking = 0.0.sp
+    val displayMediumWeight = TypefaceTokens.WeightRegular
+    val displaySmallFont = typefaceTokens.brand
+    val displaySmallLineHeight = 44.0.sp
+    val displaySmallSize = 36.sp
+    val displaySmallTracking = 0.0.sp
+    val displaySmallWeight = TypefaceTokens.WeightRegular
+    val headlineLargeFont = typefaceTokens.brand
+    val headlineLargeLineHeight = 40.0.sp
+    val headlineLargeSize = 32.sp
+    val headlineLargeTracking = 0.0.sp
+    val headlineLargeWeight = TypefaceTokens.WeightRegular
+    val headlineMediumFont = typefaceTokens.brand
+    val headlineMediumLineHeight = 36.0.sp
+    val headlineMediumSize = 28.sp
+    val headlineMediumTracking = 0.0.sp
+    val headlineMediumWeight = TypefaceTokens.WeightRegular
+    val headlineSmallFont = typefaceTokens.brand
+    val headlineSmallLineHeight = 32.0.sp
+    val headlineSmallSize = 24.sp
+    val headlineSmallTracking = 0.0.sp
+    val headlineSmallWeight = TypefaceTokens.WeightRegular
+    val labelLargeFont = typefaceTokens.plain
+    val labelLargeLineHeight = 20.0.sp
+    val labelLargeSize = 14.sp
+    val labelLargeTracking = 0.0.sp
+    val labelLargeWeight = TypefaceTokens.WeightMedium
+    val labelMediumFont = typefaceTokens.plain
+    val labelMediumLineHeight = 16.0.sp
+    val labelMediumSize = 12.sp
+    val labelMediumTracking = 0.1.sp
+    val labelMediumWeight = TypefaceTokens.WeightMedium
+    val labelSmallFont = typefaceTokens.plain
+    val labelSmallLineHeight = 16.0.sp
+    val labelSmallSize = 11.sp
+    val labelSmallTracking = 0.1.sp
+    val labelSmallWeight = TypefaceTokens.WeightMedium
+    val titleLargeFont = typefaceTokens.brand
+    val titleLargeLineHeight = 28.0.sp
+    val titleLargeSize = 22.sp
+    val titleLargeTracking = 0.0.sp
+    val titleLargeWeight = TypefaceTokens.WeightRegular
+    val titleMediumFont = typefaceTokens.plain
+    val titleMediumLineHeight = 24.0.sp
+    val titleMediumSize = 16.sp
+    val titleMediumTracking = 0.0.sp
+    val titleMediumWeight = TypefaceTokens.WeightMedium
+    val titleSmallFont = typefaceTokens.plain
+    val titleSmallLineHeight = 20.0.sp
+    val titleSmallSize = 14.sp
+    val titleSmallTracking = 0.0.sp
+    val titleSmallWeight = TypefaceTokens.WeightMedium
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt
new file mode 100644
index 0000000..f3d554f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package com.android.systemui.compose.theme.typography
+
+import android.content.Context
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+internal class TypefaceTokens(typefaceNames: TypefaceNames) {
+    companion object {
+        val WeightMedium = FontWeight.Medium
+        val WeightRegular = FontWeight.Normal
+    }
+
+    private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
+    private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
+
+    val brand =
+        FontFamily(
+            Font(brandFont, weight = WeightMedium),
+            Font(brandFont, weight = WeightRegular),
+        )
+    val plain =
+        FontFamily(
+            Font(plainFont, weight = WeightMedium),
+            Font(plainFont, weight = WeightRegular),
+        )
+}
+
+internal data class TypefaceNames
+private constructor(
+    val brand: String,
+    val plain: String,
+) {
+    private enum class Config(val configName: String, val default: String) {
+        Brand("config_headlineFontFamily", "sans-serif"),
+        Plain("config_bodyFontFamily", "sans-serif"),
+    }
+
+    companion object {
+        fun get(context: Context): TypefaceNames {
+            return TypefaceNames(
+                brand = getTypefaceName(context, Config.Brand),
+                plain = getTypefaceName(context, Config.Plain),
+            )
+        }
+
+        private fun getTypefaceName(context: Context, config: Config): String {
+            return context
+                .getString(context.resources.getIdentifier(config.configName, "string", "android"))
+                .takeIf { it.isNotEmpty() }
+                ?: config.default
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt
new file mode 100644
index 0000000..55f3d1f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.theme.typography
+
+import androidx.compose.ui.text.TextStyle
+
+internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
+    val bodyLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodyLargeFont,
+            fontWeight = typeScaleTokens.bodyLargeWeight,
+            fontSize = typeScaleTokens.bodyLargeSize,
+            lineHeight = typeScaleTokens.bodyLargeLineHeight,
+            letterSpacing = typeScaleTokens.bodyLargeTracking,
+        )
+    val bodyMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodyMediumFont,
+            fontWeight = typeScaleTokens.bodyMediumWeight,
+            fontSize = typeScaleTokens.bodyMediumSize,
+            lineHeight = typeScaleTokens.bodyMediumLineHeight,
+            letterSpacing = typeScaleTokens.bodyMediumTracking,
+        )
+    val bodySmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.bodySmallFont,
+            fontWeight = typeScaleTokens.bodySmallWeight,
+            fontSize = typeScaleTokens.bodySmallSize,
+            lineHeight = typeScaleTokens.bodySmallLineHeight,
+            letterSpacing = typeScaleTokens.bodySmallTracking,
+        )
+    val displayLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.displayLargeFont,
+            fontWeight = typeScaleTokens.displayLargeWeight,
+            fontSize = typeScaleTokens.displayLargeSize,
+            lineHeight = typeScaleTokens.displayLargeLineHeight,
+            letterSpacing = typeScaleTokens.displayLargeTracking,
+        )
+    val displayMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.displayMediumFont,
+            fontWeight = typeScaleTokens.displayMediumWeight,
+            fontSize = typeScaleTokens.displayMediumSize,
+            lineHeight = typeScaleTokens.displayMediumLineHeight,
+            letterSpacing = typeScaleTokens.displayMediumTracking,
+        )
+    val displaySmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.displaySmallFont,
+            fontWeight = typeScaleTokens.displaySmallWeight,
+            fontSize = typeScaleTokens.displaySmallSize,
+            lineHeight = typeScaleTokens.displaySmallLineHeight,
+            letterSpacing = typeScaleTokens.displaySmallTracking,
+        )
+    val headlineLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineLargeFont,
+            fontWeight = typeScaleTokens.headlineLargeWeight,
+            fontSize = typeScaleTokens.headlineLargeSize,
+            lineHeight = typeScaleTokens.headlineLargeLineHeight,
+            letterSpacing = typeScaleTokens.headlineLargeTracking,
+        )
+    val headlineMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineMediumFont,
+            fontWeight = typeScaleTokens.headlineMediumWeight,
+            fontSize = typeScaleTokens.headlineMediumSize,
+            lineHeight = typeScaleTokens.headlineMediumLineHeight,
+            letterSpacing = typeScaleTokens.headlineMediumTracking,
+        )
+    val headlineSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.headlineSmallFont,
+            fontWeight = typeScaleTokens.headlineSmallWeight,
+            fontSize = typeScaleTokens.headlineSmallSize,
+            lineHeight = typeScaleTokens.headlineSmallLineHeight,
+            letterSpacing = typeScaleTokens.headlineSmallTracking,
+        )
+    val labelLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelLargeFont,
+            fontWeight = typeScaleTokens.labelLargeWeight,
+            fontSize = typeScaleTokens.labelLargeSize,
+            lineHeight = typeScaleTokens.labelLargeLineHeight,
+            letterSpacing = typeScaleTokens.labelLargeTracking,
+        )
+    val labelMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelMediumFont,
+            fontWeight = typeScaleTokens.labelMediumWeight,
+            fontSize = typeScaleTokens.labelMediumSize,
+            lineHeight = typeScaleTokens.labelMediumLineHeight,
+            letterSpacing = typeScaleTokens.labelMediumTracking,
+        )
+    val labelSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.labelSmallFont,
+            fontWeight = typeScaleTokens.labelSmallWeight,
+            fontSize = typeScaleTokens.labelSmallSize,
+            lineHeight = typeScaleTokens.labelSmallLineHeight,
+            letterSpacing = typeScaleTokens.labelSmallTracking,
+        )
+    val titleLarge =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleLargeFont,
+            fontWeight = typeScaleTokens.titleLargeWeight,
+            fontSize = typeScaleTokens.titleLargeSize,
+            lineHeight = typeScaleTokens.titleLargeLineHeight,
+            letterSpacing = typeScaleTokens.titleLargeTracking,
+        )
+    val titleMedium =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleMediumFont,
+            fontWeight = typeScaleTokens.titleMediumWeight,
+            fontSize = typeScaleTokens.titleMediumSize,
+            lineHeight = typeScaleTokens.titleMediumLineHeight,
+            letterSpacing = typeScaleTokens.titleMediumTracking,
+        )
+    val titleSmall =
+        TextStyle(
+            fontFamily = typeScaleTokens.titleSmallFont,
+            fontWeight = typeScaleTokens.titleSmallWeight,
+            fontSize = typeScaleTokens.titleSmallSize,
+            lineHeight = typeScaleTokens.titleSmallLineHeight,
+            letterSpacing = typeScaleTokens.titleSmallTracking,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 87e9d56..8f38e58 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,7 @@
  */
 package com.android.keyguard
 
+import android.app.WallpaperManager
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -100,9 +101,13 @@
     private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
 
     private fun updateColors() {
+
         if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
-            smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
-            largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+            val wallpaperManager = WallpaperManager.getInstance(context)
+            if (!wallpaperManager.lockScreenWallpaperExists()) {
+                smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
+                largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+            }
         } else {
             val isLightTheme = TypedValue()
             context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8b4b30c..3644d42 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -36,6 +36,8 @@
 import android.app.role.RoleManager;
 import android.app.smartspace.SmartspaceManager;
 import android.app.trust.TrustManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
 import android.content.ClipboardManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -616,4 +618,16 @@
     static CameraManager provideCameraManager(Context context) {
         return context.getSystemService(CameraManager.class);
     }
+
+    @Provides
+    @Singleton
+    static BluetoothManager provideBluetoothManager(Context context) {
+        return context.getSystemService(BluetoothManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
+        return bluetoothManager.getAdapter();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6dc4f5c..68f4dbe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -125,10 +125,10 @@
     default void init() {
         // Initialize components that have no direct tie to the dagger dependency graph,
         // but are critical to this component's operation
-        // TODO(b/205034537): I think this is a good idea?
         getSysUIUnfoldComponent().ifPresent(c -> {
             c.getUnfoldLightRevealOverlayAnimation().init();
             c.getUnfoldTransitionWallpaperController().init();
+            c.getUnfoldHapticsPlayer();
         });
         getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
         // No init method needed, just needs to be gotten so that it's created.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ff3714f..4d0edc5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -251,7 +251,7 @@
 
     // 801 - region sampling
     // TODO(b/254512848): Tracking Bug
-    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
+    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
 
     // 802 - wallpaper rendering
     // TODO(b/254512923): Tracking Bug
@@ -430,6 +430,7 @@
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+    @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index ac7c70b..f60d7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -55,6 +55,7 @@
 import android.view.WindowInsetsController.Behavior;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.internal.view.AppearanceRegion;
@@ -125,7 +126,7 @@
     private final DisplayManager mDisplayManager;
     private Context mWindowContext;
     private ScreenPinningNotify mScreenPinningNotify;
-    private int mNavigationMode;
+    private int mNavigationMode = -1;
     private final Consumer<Rect> mPipListener;
 
     /**
@@ -217,8 +218,7 @@
         parseCurrentSysuiState();
         mCommandQueue.addCallback(this);
         mOverviewProxyService.addCallback(this);
-        mEdgeBackGestureHandler.onNavigationModeChanged(
-                mNavigationModeController.addListener(this));
+        onNavigationModeChanged(mNavigationModeController.addListener(this));
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mNavBarHelper.init();
         mEdgeBackGestureHandler.onNavBarAttached();
@@ -492,6 +492,11 @@
                 !QuickStepContract.isGesturalMode(mNavigationMode));
     }
 
+    @VisibleForTesting
+    int getNavigationMode() {
+        return mNavigationMode;
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 8bb2d46..56d3b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.lockscreen
 
 import android.app.PendingIntent
+import android.app.WallpaperManager
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
@@ -390,7 +391,8 @@
     }
 
     private fun updateTextColorFromWallpaper() {
-        if (!regionSamplingEnabled) {
+        val wallpaperManager = WallpaperManager.getInstance(context)
+        if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) {
             val wallpaperTextColor =
                     Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
             smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
new file mode 100644
index 0000000..d9e3f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.statusbar.notification.fsi
+
+class FsiDebug {
+
+    companion object {
+        private const val debugTag = "FsiDebug"
+        private const val debug = true
+
+        fun log(s: Any) {
+            if (!debug) {
+                return
+            }
+            android.util.Log.d(debugTag, "$s")
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
new file mode 100644
index 0000000..3e111e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stylus
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.util.ArrayMap
+import android.util.Log
+import android.view.InputDevice
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A class which keeps track of InputDevice events related to stylus devices, and notifies
+ * registered callbacks of stylus events.
+ */
+@SysUISingleton
+class StylusManager
+@Inject
+constructor(
+    private val inputManager: InputManager,
+    private val bluetoothAdapter: BluetoothAdapter,
+    @Background private val handler: Handler,
+    @Background private val executor: Executor,
+) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
+
+    private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
+    private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
+        CopyOnWriteArrayList()
+    // This map should only be accessed on the handler
+    private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
+
+    /**
+     * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
+     * at time of starting.
+     */
+    fun startListener() {
+        addExistingStylusToMap()
+        inputManager.registerInputDeviceListener(this, handler)
+    }
+
+    /** Registers a StylusCallback to listen to stylus events. */
+    fun registerCallback(callback: StylusCallback) {
+        stylusCallbacks.add(callback)
+    }
+
+    /** Unregisters a StylusCallback. If StylusCallback is not registered, is a no-op. */
+    fun unregisterCallback(callback: StylusCallback) {
+        stylusCallbacks.remove(callback)
+    }
+
+    fun registerBatteryCallback(callback: StylusBatteryCallback) {
+        stylusBatteryCallbacks.add(callback)
+    }
+
+    fun unregisterBatteryCallback(callback: StylusBatteryCallback) {
+        stylusBatteryCallbacks.remove(callback)
+    }
+
+    override fun onInputDeviceAdded(deviceId: Int) {
+        val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
+        if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+        // TODO(b/257936830): get address once input api available
+        val btAddress: String? = null
+        inputDeviceAddressMap[deviceId] = btAddress
+        executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) }
+
+        if (btAddress != null) {
+            onStylusBluetoothConnected(btAddress)
+            executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
+        }
+    }
+
+    override fun onInputDeviceChanged(deviceId: Int) {
+        val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
+        if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+        // TODO(b/257936830): get address once input api available
+        val currAddress: String? = null
+        val prevAddress: String? = inputDeviceAddressMap[deviceId]
+        inputDeviceAddressMap[deviceId] = currAddress
+
+        if (prevAddress == null && currAddress != null) {
+            onStylusBluetoothConnected(currAddress)
+            executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) }
+        }
+
+        if (prevAddress != null && currAddress == null) {
+            onStylusBluetoothDisconnected(prevAddress)
+            executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) }
+        }
+    }
+
+    override fun onInputDeviceRemoved(deviceId: Int) {
+        if (!inputDeviceAddressMap.contains(deviceId)) return
+
+        val btAddress: String? = inputDeviceAddressMap[deviceId]
+        inputDeviceAddressMap.remove(deviceId)
+        if (btAddress != null) {
+            onStylusBluetoothDisconnected(btAddress)
+            executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) }
+        }
+        executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) }
+    }
+
+    override fun onMetadataChanged(device: BluetoothDevice, key: Int, value: ByteArray?) {
+        handler.post executeMetadataChanged@{
+            if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null)
+                return@executeMetadataChanged
+
+            val inputDeviceId: Int =
+                inputDeviceAddressMap.filterValues { it == device.address }.keys.firstOrNull()
+                    ?: return@executeMetadataChanged
+
+            val isCharging = String(value) == "true"
+
+            executeStylusBatteryCallbacks { cb ->
+                cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
+            }
+        }
+    }
+
+    private fun onStylusBluetoothConnected(btAddress: String) {
+        val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+        try {
+            bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
+        } catch (e: IllegalArgumentException) {
+            Log.e(TAG, "$e: Metadata listener already registered for device. Ignoring.")
+        }
+    }
+
+    private fun onStylusBluetoothDisconnected(btAddress: String) {
+        val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+        try {
+            bluetoothAdapter.removeOnMetadataChangedListener(device, this)
+        } catch (e: IllegalArgumentException) {
+            Log.e(TAG, "$e: Metadata listener does not exist for device. Ignoring.")
+        }
+    }
+
+    private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
+        stylusCallbacks.forEach(run)
+    }
+
+    private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) {
+        stylusBatteryCallbacks.forEach(run)
+    }
+
+    private fun addExistingStylusToMap() {
+        for (deviceId: Int in inputManager.inputDeviceIds) {
+            val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue
+            if (device.supportsSource(InputDevice.SOURCE_STYLUS)) {
+                // TODO(b/257936830): get address once input api available
+                inputDeviceAddressMap[deviceId] = null
+            }
+        }
+    }
+
+    /** Callback interface to receive events from the StylusManager. */
+    interface StylusCallback {
+        fun onStylusAdded(deviceId: Int) {}
+        fun onStylusRemoved(deviceId: Int) {}
+        fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {}
+        fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {}
+    }
+
+    /** Callback interface to receive stylus battery events from the StylusManager. */
+    interface StylusBatteryCallback {
+        fun onStylusBluetoothChargingStateChanged(
+            inputDeviceId: Int,
+            btDevice: BluetoothDevice,
+            isCharging: Boolean
+        ) {}
+    }
+
+    companion object {
+        private val TAG = StylusManager::class.simpleName.orEmpty()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 13ac39c..209d93f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -92,5 +92,7 @@
 
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
 
+    fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
+
     fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
new file mode 100644
index 0000000..7726d09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.unfold
+
+import android.os.SystemProperties
+import android.os.VibrationEffect
+import android.os.Vibrator
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+/**
+ * Class that plays a haptics effect during unfolding a foldable device
+ */
+@SysUIUnfoldScope
+class UnfoldHapticsPlayer
+@Inject
+constructor(
+    unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    private val vibrator: Vibrator?
+) : TransitionProgressListener {
+
+    init {
+        if (vibrator != null) {
+            // We don't need to remove the callback because we should listen to it
+            // the whole time when SystemUI process is alive
+            unfoldTransitionProgressProvider.addCallback(this)
+        }
+    }
+
+    private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+
+    override fun onTransitionStarted() {
+        lastTransitionProgress = TRANSITION_PROGRESS_CLOSED
+    }
+
+    override fun onTransitionProgress(progress: Float) {
+        lastTransitionProgress = progress
+    }
+
+    override fun onTransitionFinishing() {
+        // Run haptics only if the animation is long enough to notice
+        if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) {
+            playHaptics()
+        }
+    }
+
+    override fun onTransitionFinished() {
+        lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+    }
+
+    private fun playHaptics() {
+        vibrator?.vibrate(effect)
+    }
+
+    private val hapticsScale: Float
+        get() {
+            val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
+            return intensityString.toFloatOrNull() ?: 0.1f
+        }
+
+    private val hapticsScaleTick: Float
+        get() {
+            val intensityString =
+                SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
+            return intensityString.toFloatOrNull() ?: 0.6f
+        }
+
+    private val primitivesCount: Int
+        get() {
+            val count = SystemProperties.get("persist.unfold.primitives_count", "18")
+            return count.toIntOrNull() ?: 18
+        }
+
+    private val effect: VibrationEffect by lazy {
+        val composition =
+            VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0F, 0)
+
+        repeat(primitivesCount) {
+            composition.addPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+                hapticsScale,
+                0
+            )
+        }
+
+        composition
+            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, hapticsScaleTick)
+            .compose()
+    }
+}
+
+private const val TRANSITION_PROGRESS_CLOSED = 0f
+private const val TRANSITION_PROGRESS_FULL_OPEN = 1f
+private const val TRANSITION_NOTICEABLE_THRESHOLD = 0.9f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
new file mode 100644
index 0000000..1742c69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.navigationbar
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.model.SysUiState
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarTransitionsController
+import com.android.wm.shell.back.BackAnimation
+import com.android.wm.shell.pip.Pip
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+class TaskbarDelegateTest : SysuiTestCase() {
+    val DISPLAY_ID = 0;
+    val MODE_GESTURE = 0;
+    val MODE_THREE_BUTTON = 1;
+
+    private lateinit var mTaskbarDelegate: TaskbarDelegate
+    @Mock
+    lateinit var mEdgeBackGestureHandlerFactory : EdgeBackGestureHandler.Factory
+    @Mock
+    lateinit var mEdgeBackGestureHandler : EdgeBackGestureHandler
+    @Mock
+    lateinit var mLightBarControllerFactory : LightBarTransitionsController.Factory
+    @Mock
+    lateinit var mLightBarTransitionController: LightBarTransitionsController
+    @Mock
+    lateinit var mCommandQueue: CommandQueue
+    @Mock
+    lateinit var mOverviewProxyService: OverviewProxyService
+    @Mock
+    lateinit var mNavBarHelper: NavBarHelper
+    @Mock
+    lateinit var mNavigationModeController: NavigationModeController
+    @Mock
+    lateinit var mSysUiState: SysUiState
+    @Mock
+    lateinit var mDumpManager: DumpManager
+    @Mock
+    lateinit var mAutoHideController: AutoHideController
+    @Mock
+    lateinit var mLightBarController: LightBarController
+    @Mock
+    lateinit var mOptionalPip: Optional<Pip>
+    @Mock
+    lateinit var mBackAnimation: BackAnimation
+    @Mock
+    lateinit var mCurrentSysUiState: NavBarHelper.CurrentSysuiState
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(mEdgeBackGestureHandlerFactory.create(context)).thenReturn(mEdgeBackGestureHandler)
+        `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
+        `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
+        `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
+        mTaskbarDelegate = TaskbarDelegate(context, mEdgeBackGestureHandlerFactory,
+                mLightBarControllerFactory)
+        mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper,
+        mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController,
+                mLightBarController, mOptionalPip, mBackAnimation)
+    }
+
+    @Test
+    fun navigationModeInitialized() {
+        `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_THREE_BUTTON)
+        assert(mTaskbarDelegate.navigationMode == -1)
+        mTaskbarDelegate.init(DISPLAY_ID)
+        assert(mTaskbarDelegate.navigationMode == MODE_THREE_BUTTON)
+    }
+
+    @Test
+    fun navigationModeInitialized_notifyEdgeBackHandler() {
+        `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_GESTURE)
+        mTaskbarDelegate.init(DISPLAY_ID)
+        verify(mEdgeBackGestureHandler, times(1)).onNavigationModeChanged(MODE_GESTURE)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
new file mode 100644
index 0000000..58b5560
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.stylus
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@Ignore("b/257936830 until bt APIs")
+class StylusManagerTest : SysuiTestCase() {
+    @Mock lateinit var inputManager: InputManager
+
+    @Mock lateinit var stylusDevice: InputDevice
+
+    @Mock lateinit var btStylusDevice: InputDevice
+
+    @Mock lateinit var otherDevice: InputDevice
+
+    @Mock lateinit var bluetoothAdapter: BluetoothAdapter
+
+    @Mock lateinit var bluetoothDevice: BluetoothDevice
+
+    @Mock lateinit var handler: Handler
+
+    @Mock lateinit var stylusCallback: StylusManager.StylusCallback
+
+    @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
+
+    @Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback
+
+    @Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback
+
+    private lateinit var stylusManager: StylusManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(handler.post(any())).thenAnswer {
+            (it.arguments[0] as Runnable).run()
+            true
+        }
+
+        stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR)
+
+        stylusManager.registerCallback(stylusCallback)
+
+        stylusManager.registerBatteryCallback(stylusBatteryCallback)
+
+        whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
+        whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+        whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+
+        // whenever(stylusDevice.bluetoothAddress).thenReturn(null)
+        // whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+
+        whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
+        whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
+        whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
+        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
+
+        whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
+        whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
+    }
+
+    @Test
+    fun startListener_registersInputDeviceListener() {
+        stylusManager.startListener()
+
+        verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
+    }
+
+    @Test
+    fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() {
+        stylusManager.registerCallback(otherStylusCallback)
+
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+        verifyNoMoreInteractions(stylusCallback)
+        verify(otherStylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+        verifyNoMoreInteractions(otherStylusCallback)
+    }
+
+    @Test
+    fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+        verifyNoMoreInteractions(stylusCallback)
+    }
+
+    @Test
+    fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        inOrder(stylusCallback).let {
+            it.verify(stylusCallback, times(1)).onStylusAdded(BT_STYLUS_DEVICE_ID)
+            it.verify(stylusCallback, times(1))
+                .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+        }
+    }
+
+    @Test
+    fun onInputDeviceAdded_notStylus_doesNotCallCallbacks() {
+        stylusManager.onInputDeviceAdded(OTHER_DEVICE_ID)
+
+        verifyNoMoreInteractions(stylusCallback)
+    }
+
+    @Test
+    fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+        // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+        stylusManager.registerCallback(otherStylusCallback)
+
+        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1))
+            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+        verify(otherStylusCallback, times(1))
+            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+    }
+
+    @Test
+    fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+        // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+
+        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1))
+            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+    }
+
+    @Test
+    fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+        // whenever(btStylusDevice.bluetoothAddress).thenReturn(null)
+
+        stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1))
+            .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+    }
+
+    @Test
+    fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1))
+            .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+    }
+
+    @Test
+    fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
+    }
+
+    @Test
+    fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+        stylusManager.registerCallback(otherStylusCallback)
+
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+        verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+    }
+
+    @Test
+    fun onInputDeviceRemoved_stylus_callsCallbacks() {
+        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
+
+        verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+        verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
+    }
+
+    @Test
+    fun onInputDeviceRemoved_btStylus_callsCallbacks() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
+
+        inOrder(stylusCallback).let {
+            it.verify(stylusCallback, times(1))
+                .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+            it.verify(stylusCallback, times(1)).onStylusRemoved(BT_STYLUS_DEVICE_ID)
+        }
+    }
+
+    @Test
+    fun onStylusBluetoothConnected_registersMetadataListener() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        verify(bluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any())
+    }
+
+    @Test
+    fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
+        whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)
+
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
+    }
+
+    @Test
+    fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
+
+        verify(bluetoothAdapter, times(1)).removeOnMetadataChangedListener(any(), any())
+    }
+
+    @Test
+    fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+        stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
+
+        stylusManager.onMetadataChanged(
+            bluetoothDevice,
+            BluetoothDevice.METADATA_MAIN_CHARGING,
+            "true".toByteArray()
+        )
+
+        verify(stylusBatteryCallback, times(1))
+            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+        verify(otherStylusBatteryCallback, times(1))
+            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+    }
+
+    @Test
+    fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onMetadataChanged(
+            bluetoothDevice,
+            BluetoothDevice.METADATA_MAIN_CHARGING,
+            "true".toByteArray()
+        )
+
+        verify(stylusBatteryCallback, times(1))
+            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+    }
+
+    @Test
+    fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onMetadataChanged(
+            bluetoothDevice,
+            BluetoothDevice.METADATA_MAIN_CHARGING,
+            "false".toByteArray()
+        )
+
+        verify(stylusBatteryCallback, times(1))
+            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
+    }
+
+    @Test
+    fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
+        stylusManager.onMetadataChanged(
+            bluetoothDevice,
+            BluetoothDevice.METADATA_MAIN_CHARGING,
+            "true".toByteArray()
+        )
+
+        verifyNoMoreInteractions(stylusBatteryCallback)
+    }
+
+    @Test
+    fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onMetadataChanged(
+            bluetoothDevice,
+            BluetoothDevice.METADATA_DEVICE_TYPE,
+            "true".toByteArray()
+        )
+
+        verify(stylusBatteryCallback, never())
+            .onStylusBluetoothChargingStateChanged(any(), any(), any())
+    }
+
+    companion object {
+        private val EXECUTOR = Executor { r -> r.run() }
+
+        private const val OTHER_DEVICE_ID = 0
+        private const val STYLUS_DEVICE_ID = 1
+        private const val BT_STYLUS_DEVICE_ID = 2
+
+        private const val STYLUS_BT_ADDRESS = "SOME:ADDRESS"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 03fd624..abbdab0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -69,6 +69,22 @@
     }
 
     @Test
+    fun testUnfold_emitsFinishingEvent() {
+        runOnMainThreadWithInterval(
+            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
+            { foldStateProvider.sendHingeAngleUpdate(10f) },
+            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+            { foldStateProvider.sendHingeAngleUpdate(90f) },
+            { foldStateProvider.sendHingeAngleUpdate(180f) },
+            { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
+        )
+
+        with(listener.ensureTransitionFinished()) {
+            assertHasSingleFinishingEvent()
+        }
+    }
+
+    @Test
     fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
         runOnMainThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
@@ -157,6 +173,12 @@
             currentRecording!!.addProgress(progress)
         }
 
+        override fun onTransitionFinishing() {
+            assertWithMessage("Received transition finishing event when it's not started")
+                    .that(currentRecording).isNotNull()
+            currentRecording!!.onFinishing()
+        }
+
         override fun onTransitionFinished() {
             assertWithMessage("Received transition finish event when it's not started")
                 .that(currentRecording).isNotNull()
@@ -171,6 +193,7 @@
 
         class UnfoldTransitionRecording {
             private val progressHistory: MutableList<Float> = arrayListOf()
+            private var finishingInvocations: Int = 0
 
             fun addProgress(progress: Float) {
                 assertThat(progress).isAtMost(1.0f)
@@ -179,6 +202,10 @@
                 progressHistory += progress
             }
 
+            fun onFinishing() {
+                finishingInvocations++
+            }
+
             fun assertIncreasingProgress() {
                 assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
                 assertThat(progressHistory).isInOrder()
@@ -206,6 +233,11 @@
                     .isInOrder(Comparator.reverseOrder<Float>())
                 assertThat(progressHistory.last()).isEqualTo(0.0f)
             }
+
+            fun assertHasSingleFinishingEvent() {
+                assertWithMessage("onTransitionFinishing callback should be invoked exactly " +
+                        "one time").that(finishingInvocations).isEqualTo(1)
+            }
         }
 
         private companion object {
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 108295b..180b611 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -33,6 +33,7 @@
         "dagger2",
         "jsr330",
     ],
+    kotlincflags: ["-Xjvm-default=enable"],
     java_version: "1.8",
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index 7117aaf..fee485d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -34,8 +34,28 @@
     fun destroy()
 
     interface TransitionProgressListener {
+        /** Called when transition is started */
+        @JvmDefault
         fun onTransitionStarted() {}
-        fun onTransitionFinished() {}
+
+        /**
+         * Called whenever transition progress is updated, [progress] is a value of the animation
+         * where 0 is fully folded, 1 is fully unfolded
+         */
+        @JvmDefault
         fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
+
+        /**
+         * Called when the progress provider determined that the transition is about to finish soon.
+         *
+         * For example, in [PhysicsBasedUnfoldTransitionProgressProvider] this could happen when the
+         * animation is not tied to the hinge angle anymore and it is about to run fixed animation.
+         */
+        @JvmDefault
+        fun onTransitionFinishing() {}
+
+        /** Called when transition is completely finished */
+        @JvmDefault
+        fun onTransitionFinished() {}
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 4c85b05..fa59cb4 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -88,6 +88,7 @@
 
         override fun onAnimationStart(animator: Animator) {
             listeners.forEach { it.onTransitionStarted() }
+            listeners.forEach { it.onTransitionFinishing() }
         }
 
         override fun onAnimationEnd(animator: Animator) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 52fb0a7..ecc029d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -125,6 +125,10 @@
 
     private fun cancelTransition(endValue: Float, animate: Boolean) {
         if (isTransitionRunning && animate) {
+            if (endValue == 1.0f && !isAnimatedCancelRunning) {
+                listeners.forEach { it.onTransitionFinishing() }
+            }
+
             isAnimatedCancelRunning = true
             springAnimation.animateToFinalPosition(endValue)
         } else {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 8491f83..b7bab3e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -110,6 +110,12 @@
         lastTransitionProgress = progress
     }
 
+    override fun onTransitionFinishing() {
+        if (isReadyToHandleTransition) {
+            listeners.forEach { it.onTransitionFinishing() }
+        }
+    }
+
     override fun onTransitionFinished() {
         if (isReadyToHandleTransition) {
             listeners.forEach { it.onTransitionFinished() }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index ea09629..b822541 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -378,6 +378,10 @@
                 return false;
             }
 
+            if (!dreamsEnabledForUser(ActivityManager.getCurrentUser())) {
+                return false;
+            }
+
             if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
                 return mIsCharging;
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 399b94c..7919e19 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1054,6 +1054,19 @@
             return;
         }
 
+        // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
+        // the lock screen before the dream appears. Note that this locking behavior needs to
+        // happen regardless of whether we end up dreaming (below) or not.
+        // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
+        lockNow(null);
+
+        // Don't dream if the user isn't user zero.
+        // TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal().
+        if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
+            noDreamAction.run();
+            return;
+        }
+
         final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
         if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
             noDreamAction.run();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9cf1e2f..f725d1a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7672,6 +7672,10 @@
         // This activity may relaunch or perform configuration change so once it has reported drawn,
         // the screen can be unfrozen.
         ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+        if (mTransitionController.isCollecting(this)) {
+            // In case the task was changed from PiP but still keeps old transform.
+            task.resetSurfaceControlTransforms();
+        }
     }
 
     void setRequestedOrientation(int requestedOrientation) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d5802cf..c6dc24f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3392,6 +3392,9 @@
             if (!controller.isCollecting(this)) {
                 controller.collect(this);
                 startAsyncRotationIfNeeded();
+                if (mFixedRotationLaunchingApp != null) {
+                    setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
+                }
             }
             return;
         }
@@ -3401,12 +3404,8 @@
             mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
             if (mFixedRotationLaunchingApp != null) {
                 // A fixed-rotation transition is done, then continue to start a seamless display
-                // transition. And be fore the start transaction is applied, the non-app windows
-                // need to keep in previous rotation to avoid showing inconsistent content.
-                t.setSeamlessRotation(this);
-                if (mAsyncRotationController != null) {
-                    mAsyncRotationController.keepAppearanceInPreviousRotation();
-                }
+                // transition.
+                setSeamlessTransitionForFixedRotation(t);
             } else if (isRotationChanging()) {
                 if (displayChange != null) {
                     final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
@@ -3425,6 +3424,15 @@
         }
     }
 
+    private void setSeamlessTransitionForFixedRotation(Transition t) {
+        t.setSeamlessRotation(this);
+        // Before the start transaction is applied, the non-app windows need to keep in previous
+        // rotation to avoid showing inconsistent content.
+        if (mAsyncRotationController != null) {
+            mAsyncRotationController.keepAppearanceInPreviousRotation();
+        }
+    }
+
     /** If the display is in transition, there should be a screenshot covering it. */
     @Override
     boolean inTransition() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0be4191..03e3e93 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1125,11 +1125,12 @@
         if (inMultiWindowMode() || !hasChild()) return false;
         if (intent != null) {
             final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+            if ((intent.getFlags() & returnHomeFlags) != returnHomeFlags) {
+                return false;
+            }
             final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null;
-            final boolean isLockTaskModeViolation = task != null
-                    && mAtmService.getLockTaskController().isLockTaskModeViolation(task);
-            return (intent.getFlags() & returnHomeFlags) == returnHomeFlags
-                    && !isLockTaskModeViolation;
+            return !(task != null
+                    && mAtmService.getLockTaskController().isLockTaskModeViolation(task));
         }
         final Task bottomTask = getBottomMostTask();
         return bottomTask != this && bottomTask.returnsToHomeRootTask();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0c2cc43..1d17cd4 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1032,10 +1032,11 @@
      * @param dc The display this container is on after changes.
      */
     void onDisplayChanged(DisplayContent dc) {
-        if (mDisplayContent != null) {
+        if (mDisplayContent != null && mDisplayContent != dc) {
+            // Cancel any change transition queued-up for this container on the old display when
+            // this container is moved from the old display.
             mDisplayContent.mClosingChangingContainers.remove(this);
             if (mDisplayContent.mChangingContainers.remove(this)) {
-                // Cancel any change transition queued-up for this container on the old display.
                 mSurfaceFreezer.unfreeze(getSyncTransaction());
             }
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index f88e18b..4942690 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -180,14 +180,47 @@
                 createJobBuilder(7).setExpedited(true).build());
         final JobStatus ej = spy(createJobStatus("testIsJobRestricted",
                 createJobBuilder(8).setExpedited(true).build()));
+        final JobStatus ejRetried = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(11).setExpedited(true).build()));
+        final JobStatus ejRunning = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(12).setExpedited(true).build()));
+        final JobStatus ejRunningLong = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(13).setExpedited(true).build()));
+        final JobStatus ui = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(14).build()));
+        final JobStatus uiRetried = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(15).build()));
+        final JobStatus uiRunning = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(16).build()));
+        final JobStatus uiRunningLong = spy(createJobStatus("testIsJobRestricted",
+                createJobBuilder(17).build()));
         when(ej.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true);
+        when(ui.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(uiRetried.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(uiRunning.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(uiRunningLong.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(ejRetried.getNumPreviousAttempts()).thenReturn(1);
+        when(uiRetried.getNumPreviousAttempts()).thenReturn(2);
         when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
         when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
                 .thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong))
+                .thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong))
+                .thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true);
+        when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true);
         when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
                 .thenReturn(true);
         when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
                 .thenReturn(true);
+        when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true);
+        when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true);
 
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
@@ -199,6 +232,13 @@
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
 
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT);
 
@@ -212,6 +252,13 @@
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
 
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE);
 
@@ -225,6 +272,13 @@
         assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
         assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
 
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
 
@@ -238,6 +292,13 @@
         assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
 
         mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL);
 
@@ -251,6 +312,13 @@
         assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
         assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
+        assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
     }
 
     private JobInfo.Builder createJobBuilder(int jobId) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 50fcafc..98a28cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -869,6 +869,28 @@
     }
 
     @Test
+    public void testOnDisplayChanged_cleanupChanging() {
+        final Task task = createTask(mDisplayContent);
+        spyOn(task.mSurfaceFreezer);
+        mDisplayContent.mChangingContainers.add(task);
+
+        // Don't remove the changing transition of this window when it is still the old display.
+        // This happens on display info changed.
+        task.onDisplayChanged(mDisplayContent);
+
+        assertTrue(mDisplayContent.mChangingContainers.contains(task));
+        verify(task.mSurfaceFreezer, never()).unfreeze(any());
+
+        // Remove the changing transition of this window when it is moved or reparented from the old
+        // display.
+        final DisplayContent newDc = createNewDisplay();
+        task.onDisplayChanged(newDc);
+
+        assertFalse(mDisplayContent.mChangingContainers.contains(task));
+        verify(task.mSurfaceFreezer).unfreeze(any());
+    }
+
+    @Test
     public void testHandleCompleteDeferredRemoval() {
         final DisplayContent displayContent = createNewDisplay();
         // Do not reparent activity to default display when removing the display.