Merge "Create IMPROVED_HUN_ANIMATIONS flag" into udc-dev
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 02cd037..99a4f6b 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
+import android.graphics.HardwareRenderer;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -550,6 +551,11 @@
         ThreadedRenderer.trimMemory(level);
     }
 
+    /** @hide */
+    public void trimCaches(@HardwareRenderer.CacheTrimLevel int level) {
+        ThreadedRenderer.trimCaches(level);
+    }
+
     public void dumpGfxInfo(FileDescriptor fd, String[] args) {
         FileOutputStream fout = new FileOutputStream(fd);
         PrintWriter pw = new FastPrintWriter(fout);
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 9ed3d9c..9cde187 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -144,6 +144,32 @@
     public @interface DumpFlags {
     }
 
+
+    /**
+     * Trims all Skia caches.
+     * @hide
+     */
+    public static final int CACHE_TRIM_ALL = 0;
+    /**
+     * Trims Skia font caches.
+     * @hide
+     */
+    public static final int CACHE_TRIM_FONT = 1;
+    /**
+     * Trims Skia resource caches.
+     * @hide
+     */
+    public static final int CACHE_TRIM_RESOURCES = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"CACHE_TRIM_"}, value = {
+            CACHE_TRIM_ALL,
+            CACHE_TRIM_FONT,
+            CACHE_TRIM_RESOURCES
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CacheTrimLevel {}
+
     /**
      * Name of the file that holds the shaders cache.
      */
@@ -1131,6 +1157,20 @@
         nTrimMemory(level);
     }
 
+    /**
+     * Invoke this when all font caches should be flushed. This can cause jank on next render
+     * commands so use it only after expensive font allocation operations which would
+     * allocate large amount of temporary memory.
+     *
+     * @param level Hint about which caches to trim. See {@link #CACHE_TRIM_ALL},
+     *              {@link #CACHE_TRIM_FONT}, {@link #CACHE_TRIM_RESOURCES}
+     *
+     * @hide
+     */
+    public static void trimCaches(@CacheTrimLevel int level) {
+        nTrimCaches(level);
+    }
+
     /** @hide */
     public static void overrideProperty(@NonNull String name, @NonNull String value) {
         if (name == null || value == null) {
@@ -1497,6 +1537,8 @@
 
     private static native void nTrimMemory(int level);
 
+    private static native void nTrimCaches(int level);
+
     private static native void nOverrideProperty(String name, String value);
 
     private static native void nFence(long nativeProxy);
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 139cdde..347daf34 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -31,6 +31,12 @@
     RUNNING_MODERATE = 5,
 };
 
+enum class CacheTrimLevel {
+    ALL_CACHES = 0,
+    FONT_CACHE = 1,
+    RESOURCE_CACHE = 2,
+};
+
 struct MemoryPolicy {
     // The initial scale factor applied to the display resolution. The default is 1, but
     // lower values may be used to start with a smaller initial cache size. The cache will
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 6a7411f..d04de37 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -362,6 +362,10 @@
     RenderProxy::trimMemory(level);
 }
 
+static void android_view_ThreadedRenderer_trimCaches(JNIEnv* env, jobject clazz, jint level) {
+    RenderProxy::trimCaches(level);
+}
+
 static void android_view_ThreadedRenderer_overrideProperty(JNIEnv* env, jobject clazz,
         jstring name, jstring value) {
     const char* nameCharArray = env->GetStringUTFChars(name, NULL);
@@ -1018,6 +1022,7 @@
          (void*)android_view_ThreadedRenderer_notifyCallbackPending},
         {"nNotifyExpensiveFrame", "(J)V",
          (void*)android_view_ThreadedRenderer_notifyExpensiveFrame},
+        {"nTrimCaches", "(I)V", (void*)android_view_ThreadedRenderer_trimCaches},
 };
 
 static JavaVM* mJvm = nullptr;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index c00a270..babce88 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -139,6 +139,25 @@
     }
 }
 
+void CacheManager::trimCaches(CacheTrimLevel mode) {
+    switch (mode) {
+        case CacheTrimLevel::FONT_CACHE:
+            SkGraphics::PurgeFontCache();
+            break;
+        case CacheTrimLevel::RESOURCE_CACHE:
+            SkGraphics::PurgeResourceCache();
+            break;
+        case CacheTrimLevel::ALL_CACHES:
+            SkGraphics::PurgeAllCaches();
+            if (mGrContext) {
+                mGrContext->purgeUnlockedResources(false);
+            }
+            break;
+        default:
+            break;
+    }
+}
+
 void CacheManager::trimStaleResources() {
     if (!mGrContext) {
         return;
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index d21ac9b..5e43ac2 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -48,6 +48,7 @@
     void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
 #endif
     void trimMemory(TrimLevel mode);
+    void trimCaches(CacheTrimLevel mode);
     void trimStaleResources();
     void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
     void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 31b4b20..224c878 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -231,6 +231,15 @@
     }
 }
 
+void RenderProxy::trimCaches(int level) {
+    // Avoid creating a RenderThread to do a trimMemory.
+    if (RenderThread::hasInstance()) {
+        RenderThread& thread = RenderThread::getInstance();
+        const auto trimLevel = static_cast<CacheTrimLevel>(level);
+        thread.queue().post([&thread, trimLevel]() { thread.trimCaches(trimLevel); });
+    }
+}
+
 void RenderProxy::purgeCaches() {
     if (RenderThread::hasInstance()) {
         RenderThread& thread = RenderThread::getInstance();
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 82072a6..47c1b0c 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -105,6 +105,7 @@
 
     void destroyHardwareResources();
     static void trimMemory(int level);
+    static void trimCaches(int level);
     static void purgeCaches();
     static void overrideProperty(const char* name, const char* value);
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9ba67a2..eb28c08 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -521,6 +521,11 @@
     cacheManager().trimMemory(level);
 }
 
+void RenderThread::trimCaches(CacheTrimLevel level) {
+    ATRACE_CALL();
+    cacheManager().trimCaches(level);
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index c77cd41..79e57de 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -174,6 +174,7 @@
     }
 
     void trimMemory(TrimLevel level);
+    void trimCaches(CacheTrimLevel level);
 
     /**
      * isCurrent provides a way to query, if the caller is running on
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 271fab1..a62dead 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1730,15 +1730,15 @@
     <!-- Keyboard backlight indicator-->
     <dimen name="backlight_indicator_root_corner_radius">48dp</dimen>
     <dimen name="backlight_indicator_root_vertical_padding">8dp</dimen>
-    <dimen name="backlight_indicator_root_horizontal_padding">4dp</dimen>
+    <dimen name="backlight_indicator_root_horizontal_padding">6dp</dimen>
     <dimen name="backlight_indicator_icon_width">22dp</dimen>
     <dimen name="backlight_indicator_icon_height">11dp</dimen>
-    <dimen name="backlight_indicator_icon_left_margin">2dp</dimen>
+    <dimen name="backlight_indicator_icon_padding">10dp</dimen>
     <dimen name="backlight_indicator_step_width">52dp</dimen>
     <dimen name="backlight_indicator_step_height">40dp</dimen>
-    <dimen name="backlight_indicator_step_horizontal_margin">4dp</dimen>
+    <dimen name="backlight_indicator_step_horizontal_margin">2dp</dimen>
     <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
-    <dimen name="backlight_indicator_step_large_radius">48dp</dimen>
+    <dimen name="backlight_indicator_step_large_radius">28dp</dimen>
 
     <!-- Broadcast dialog -->
     <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index eaeaabe..e5c9461 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -203,5 +203,8 @@
 
     <item type="id" name="log_access_dialog_allow_button" />
     <item type="id" name="log_access_dialog_deny_button" />
+
+    <!-- keyboard backlight indicator-->
+    <item type="id" name="backlight_icon" />
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 1f3d26f..e118fdf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -217,6 +217,7 @@
             )
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
+    // TODO(b/275069969): Tracking bug.
     @JvmField
     val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
 
@@ -247,6 +248,11 @@
     @JvmField
     val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area")
 
+    /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
+    // TODO(b/283260512): Tracking bug.
+    @JvmField
+    val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -673,6 +679,10 @@
     val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
             unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
 
+    // TODO:(b/283203305): Tracking bug
+    @JvmField
+    val TRIM_FONT_CACHES_AT_UNLOCK = releasedFlag(2402, "trim_font_caches_on_unlock")
+
     // 2700 - unfold transitions
     // TODO(b/265764985): Tracking Bug
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index d3678b5..7078341 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -22,9 +22,12 @@
 import android.app.Dialog
 import android.content.Context
 import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.OvalShape
 import android.graphics.drawable.shapes.RoundRectShape
 import android.os.Bundle
 import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
 import android.view.Window
 import android.view.WindowManager
 import android.widget.FrameLayout
@@ -32,9 +35,10 @@
 import android.widget.LinearLayout
 import android.widget.LinearLayout.LayoutParams
 import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import androidx.annotation.IdRes
+import androidx.core.view.setPadding
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.util.children
 
 class KeyboardBacklightDialog(
     context: Context,
@@ -51,7 +55,7 @@
     private data class BacklightIconProperties(
         val width: Int,
         val height: Int,
-        val leftMargin: Int,
+        val padding: Int,
     )
 
     private data class StepViewProperties(
@@ -71,6 +75,7 @@
     private lateinit var rootProperties: RootProperties
     private lateinit var iconProperties: BacklightIconProperties
     private lateinit var stepProperties: StepViewProperties
+
     @ColorInt
     var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
     @ColorInt
@@ -78,7 +83,16 @@
         getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
     @ColorInt
     var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
-    @ColorInt var iconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
+    @ColorInt
+    var defaultIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
+    @ColorInt
+    var defaultIconBackgroundColor =
+        getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+    @ColorInt
+    var dimmedIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+    @ColorInt
+    var dimmedIconBackgroundColor =
+        getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceDim)
 
     init {
         currentLevel = initialCurrentLevel
@@ -111,8 +125,7 @@
                 BacklightIconProperties(
                     width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
                     height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
-                    leftMargin =
-                        getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
+                    padding = getDimensionPixelSize(R.dimen.backlight_indicator_icon_padding),
                 )
             stepProperties =
                 StepViewProperties(
@@ -139,23 +152,34 @@
         if (maxLevel != max || forceRefresh) {
             maxLevel = max
             rootView.removeAllViews()
+            rootView.addView(buildIconTile())
             buildStepViews().forEach { rootView.addView(it) }
         }
         currentLevel = current
-        updateLevel()
+        updateIconTile()
+        updateStepColors()
     }
 
-    private fun updateLevel() {
-        rootView.children.forEachIndexed(
-            action = { index, v ->
-                val drawable = v.background as ShapeDrawable
-                if (index <= currentLevel) {
-                    updateColor(drawable, filledRectangleColor)
-                } else {
-                    updateColor(drawable, emptyRectangleColor)
-                }
-            }
-        )
+    private fun updateIconTile() {
+        val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+        val backgroundDrawable = iconTile.background as ShapeDrawable
+        if (currentLevel == 0) {
+            iconTile.setColorFilter(dimmedIconColor)
+            updateColor(backgroundDrawable, dimmedIconBackgroundColor)
+        } else {
+            iconTile.setColorFilter(defaultIconColor)
+            updateColor(backgroundDrawable, defaultIconBackgroundColor)
+        }
+    }
+
+    private fun updateStepColors() {
+        (1 until rootView.childCount).forEach { index ->
+            val drawable = rootView.getChildAt(index).background as ShapeDrawable
+            updateColor(
+                drawable,
+                if (index <= currentLevel) filledRectangleColor else emptyRectangleColor,
+            )
+        }
     }
 
     private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
@@ -192,9 +216,33 @@
     }
 
     private fun buildStepViews(): List<FrameLayout> {
-        val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
-        stepViews[0].addView(createBacklightIconView())
-        return stepViews
+        return (1..maxLevel).map { i -> createStepViewAt(i) }
+    }
+
+    private fun buildIconTile(): View {
+        val diameter = stepProperties.height
+        val circleDrawable =
+            ShapeDrawable(OvalShape()).apply {
+                intrinsicHeight = diameter
+                intrinsicWidth = diameter
+            }
+
+        return ImageView(context).apply {
+            setImageResource(R.drawable.ic_keyboard_backlight)
+            id = BACKLIGHT_ICON_ID
+            setColorFilter(defaultIconColor)
+            setPadding(iconProperties.padding)
+            layoutParams =
+                MarginLayoutParams(diameter, diameter).apply {
+                    setMargins(
+                        /* left= */ stepProperties.horizontalMargin,
+                        /* top= */ 0,
+                        /* right= */ stepProperties.horizontalMargin,
+                        /* bottom= */ 0
+                    )
+                }
+            background = circleDrawable
+        }
     }
 
     private fun createStepViewAt(i: Int): FrameLayout {
@@ -221,18 +269,6 @@
         }
     }
 
-    private fun createBacklightIconView(): ImageView {
-        return ImageView(context).apply {
-            setImageResource(R.drawable.ic_keyboard_backlight)
-            setColorFilter(iconColor)
-            layoutParams =
-                FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
-                    gravity = Gravity.CENTER
-                    leftMargin = iconProperties.leftMargin
-                }
-        }
-    }
-
     private fun setWindowPosition() {
         window?.apply {
             setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
@@ -262,30 +298,29 @@
     private fun radiiForIndex(i: Int, last: Int): FloatArray {
         val smallRadius = stepProperties.smallRadius
         val largeRadius = stepProperties.largeRadius
-        return when (i) {
-            0 -> // left radii bigger
-            floatArrayOf(
-                    largeRadius,
-                    largeRadius,
-                    smallRadius,
-                    smallRadius,
-                    smallRadius,
-                    smallRadius,
-                    largeRadius,
-                    largeRadius
-                )
-            last -> // right radii bigger
-            floatArrayOf(
-                    smallRadius,
-                    smallRadius,
-                    largeRadius,
-                    largeRadius,
-                    largeRadius,
-                    largeRadius,
-                    smallRadius,
-                    smallRadius
-                )
-            else -> FloatArray(8) { smallRadius } // all radii equal
+        val radii = FloatArray(8) { smallRadius }
+        if (i == 1) {
+            radii.setLeftCorners(largeRadius)
         }
+        // note "first" and "last" might be the same tile
+        if (i == last) {
+            radii.setRightCorners(largeRadius)
+        }
+        return radii
+    }
+
+    private fun FloatArray.setLeftCorners(radius: Float) {
+        LEFT_CORNERS_INDICES.forEach { this[it] = radius }
+    }
+    private fun FloatArray.setRightCorners(radius: Float) {
+        RIGHT_CORNERS_INDICES.forEach { this[it] = radius }
+    }
+
+    private companion object {
+        @IdRes val BACKLIGHT_ICON_ID = R.id.backlight_icon
+
+        // indices used to define corners radii in ShapeDrawable
+        val LEFT_CORNERS_INDICES: IntArray = intArrayOf(0, 1, 6, 7)
+        val RIGHT_CORNERS_INDICES: IntArray = intArrayOf(2, 3, 4, 5)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 8386a05b..d8affa4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.WorkerThread
 import android.content.ComponentCallbacks2
+import android.graphics.HardwareRenderer
 import android.os.Trace
 import android.util.Log
 import com.android.systemui.CoreStartable
@@ -27,12 +28,13 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.utils.GlobalWindowManager
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -50,6 +52,7 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val globalWindowManager: GlobalWindowManager,
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
@@ -58,7 +61,10 @@
 
     override fun start() {
         Log.d(LOG_TAG, "Resource trimmer registered.")
-        if (!featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+        if (
+            !(featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) ||
+                featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK))
+        ) {
             return
         }
 
@@ -78,6 +84,30 @@
                 .distinctUntilChanged()
                 .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
         }
+
+        applicationScope.launch(bgDispatcher) {
+            // We drop 1 to avoid triggering on initial collect().
+            keyguardTransitionInteractor.anyStateToGoneTransition.collect { transition ->
+                if (transition.transitionState == TransitionState.FINISHED) {
+                    onKeyguardGone()
+                }
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun onKeyguardGone() {
+        if (!featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
+            return
+        }
+
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
+        }
+        // We want to clear temporary caches we've created while rendering and animating
+        // lockscreen elements, especially clocks.
+        globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+        globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
     }
 
     @WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index df68e7e..0414a14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -23,7 +23,6 @@
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 
-import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.hardware.biometrics.BiometricSourceType;
@@ -36,7 +35,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
-import android.view.WindowManagerGlobal;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -985,8 +983,6 @@
         mShadeViewController.resetViewGroupFade();
         mCentralSurfaces.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
-        WindowManagerGlobal.getInstance().trimMemory(
-                ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
     }
 
     private void wakeAndUnlockDejank() {
diff --git a/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
index 038fddc..4111850 100644
--- a/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.utils
 
+import android.graphics.HardwareRenderer.CacheTrimLevel
 import android.view.WindowManagerGlobal
 import javax.inject.Inject
 
@@ -13,4 +14,9 @@
     fun trimMemory(level: Int) {
         WindowManagerGlobal.getInstance().trimMemory(level)
     }
+
+    /** Sends a trim caches command to [WindowManagerGlobal]. */
+    fun trimCaches(@CacheTrimLevel level: Int) {
+        WindowManagerGlobal.getInstance().trimCaches(level)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 367d206..548d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.keyguard
 
 import android.content.ComponentCallbacks2
+import android.graphics.HardwareRenderer
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -9,7 +10,11 @@
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
@@ -25,6 +30,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
@@ -37,6 +43,7 @@
     private val testScope = TestScope(testDispatcher)
     private val keyguardRepository = FakeKeyguardRepository()
     private val featureFlags = FakeFeatureFlags()
+    private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
 
     @Mock private lateinit var globalWindowManager: GlobalWindowManager
     private lateinit var resourceTrimmer: ResourceTrimmer
@@ -45,13 +52,15 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
+        featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
         featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
         keyguardRepository.setWakefulnessModel(
             WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
         )
         keyguardRepository.setDozeAmount(0f)
+        keyguardRepository.setKeyguardGoingAway(false)
 
-        val interactor =
+        val keyguardInteractor =
             KeyguardInteractor(
                 keyguardRepository,
                 FakeCommandQueue(),
@@ -60,7 +69,8 @@
             )
         resourceTrimmer =
             ResourceTrimmer(
-                interactor,
+                keyguardInteractor,
+                KeyguardTransitionInteractor(keyguardTransitionRepository),
                 globalWindowManager,
                 testScope.backgroundScope,
                 testDispatcher,
@@ -191,4 +201,26 @@
             verifyZeroInteractions(globalWindowManager)
         }
     }
+
+    @Test
+    fun keyguardTransitionsToGone_trimsFontCache() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+            )
+            verify(globalWindowManager, times(1))
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+            verifyNoMoreInteractions(globalWindowManager)
+        }
+
+    @Test
+    fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
+        testScope.runTest {
+            featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+            )
+            verifyNoMoreInteractions(globalWindowManager)
+        }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f4c9d05..d7a5ee9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -420,6 +420,20 @@
             mBtHelper.stopBluetoothSco(eventSource);
         }
 
+        // In BT classic for communication, the device changes from a2dp to sco device, but for
+        // LE Audio it stays the same and we must trigger the proper stream volume alignment, if
+        // LE Audio communication device is activated after the audio system has already switched to
+        // MODE_IN_CALL mode.
+        if (isBluetoothLeAudioRequested()) {
+            final int streamType = mAudioService.getBluetoothContextualVolumeStream();
+            final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
+            final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
+            if (AudioService.DEBUG_COMM_RTE) {
+                Log.v(TAG, "setCommunicationRouteForClient restoring LE Audio device volume lvl.");
+            }
+            postSetLeAudioVolumeIndex(leAudioVolIndex, leAudioMaxVolIndex, streamType);
+        }
+
         updateCommunicationRoute(eventSource);
     }
 
@@ -633,6 +647,16 @@
     }
 
     /**
+     * Helper method on top of isDeviceRequestedForCommunication() indicating if
+     * Bluetooth LE Audio communication device is currently requested or not.
+     * @return true if Bluetooth LE Audio device is requested, false otherwise.
+     */
+    /*package*/ boolean isBluetoothLeAudioRequested() {
+        return isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BLE_HEADSET)
+                || isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BLE_SPEAKER);
+    }
+
+    /**
      * Indicates if preferred route selection for communication is Bluetooth SCO.
      * @return true if Bluetooth SCO is preferred , false otherwise.
      */
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index a97126c..5324acd 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -72,6 +72,7 @@
 import android.view.ContentRecordingSession;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -111,7 +112,11 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long MEDIA_PROJECTION_PREVENTS_REUSING_CONSENT = 266201607L; // buganizer id
 
-    private final Object mLock = new Object(); // Protects the list of media projections
+    // Protects access to state at service level & IMediaProjection level.
+    // Invocation order while holding locks must follow below to avoid deadlock:
+    // WindowManagerService -> MediaProjectionManagerService -> DisplayManagerService
+    // See mediaprojection.md
+    private final Object mLock = new Object();
     private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
     private final CallbackDelegate mCallbackDelegate;
 
@@ -127,7 +132,9 @@
     private final MediaRouterCallback mMediaRouterCallback;
     private MediaRouter.RouteInfo mMediaRouteInfo;
 
+    @GuardedBy("mLock")
     private IBinder mProjectionToken;
+    @GuardedBy("mLock")
     private MediaProjection mProjectionGrant;
 
     public MediaProjectionManagerService(Context context) {
@@ -314,9 +321,11 @@
      */
     @VisibleForTesting
     boolean setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
+        // NEVER lock while calling into WindowManagerService, since WindowManagerService is
+        // ALWAYS locked when it invokes MediaProjectionManagerService.
+        final boolean setSessionSucceeded = mWmInternal.setContentRecordingSession(incomingSession);
         synchronized (mLock) {
-            if (!mWmInternal.setContentRecordingSession(
-                    incomingSession)) {
+            if (!setSessionSucceeded) {
                 // Unable to start mirroring, so tear down this projection.
                 if (mProjectionGrant != null) {
                     mProjectionGrant.stop();
@@ -359,13 +368,20 @@
      */
     @VisibleForTesting
     void requestConsentForInvalidProjection() {
+        Intent reviewConsentIntent;
+        int uid;
         synchronized (mLock) {
-            Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
-            // Trigger the permission dialog again in SysUI
-            // Do not handle the result; SysUI will update us when the user has consented.
-            mContext.startActivityAsUser(buildReviewGrantedConsentIntent(),
-                    UserHandle.getUserHandleForUid(mProjectionGrant.uid));
+            reviewConsentIntent = buildReviewGrantedConsentIntentLocked();
+            uid = mProjectionGrant.uid;
         }
+        // NEVER lock while calling into a method that eventually acquires the WindowManagerService
+        // lock, since WindowManagerService is ALWAYS locked when it invokes
+        // MediaProjectionManagerService.
+        Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
+        // Trigger the permission dialog again in SysUI
+        // Do not handle the result; SysUI will update us when the user has consented.
+        mContext.startActivityAsUser(reviewConsentIntent,
+                UserHandle.getUserHandleForUid(uid));
     }
 
     /**
@@ -375,7 +391,7 @@
      * <p>Consent dialog result handled in
      * {@link BinderService#setUserReviewGrantedConsentResult(int)}.
      */
-    private Intent buildReviewGrantedConsentIntent() {
+    private Intent buildReviewGrantedConsentIntentLocked() {
         final String permissionDialogString = mContext.getResources().getString(
                 R.string.config_mediaProjectionPermissionDialogComponent);
         final ComponentName mediaProjectionPermissionDialogComponent =
@@ -388,7 +404,8 @@
     }
 
     /**
-     * Handles result of dialog shown from {@link BinderService#buildReviewGrantedConsentIntent()}.
+     * Handles result of dialog shown from
+     * {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
      *
      * <p>Tears down session if user did not consent, or starts mirroring if user did consent.
      */
@@ -490,23 +507,26 @@
     MediaProjection getProjectionInternal(int uid, String packageName) {
         final long callingToken = Binder.clearCallingIdentity();
         try {
-            // Supposedly the package has re-used the user's consent; confirm the provided details
-            // against the current projection token before re-using the current projection.
-            if (mProjectionGrant == null || mProjectionGrant.mSession == null
-                    || !mProjectionGrant.mSession.isWaitingForConsent()) {
-                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
-                        + "instance");
-                return null;
-            }
+            synchronized (mLock) {
+                // Supposedly the package has re-used the user's consent; confirm the provided
+                // details against the current projection token before re-using the current
+                // projection.
+                if (mProjectionGrant == null || mProjectionGrant.mSession == null
+                        || !mProjectionGrant.mSession.isWaitingForConsent()) {
+                    Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
+                            + "instance");
+                    return null;
+                }
                 // The package matches, go ahead and re-use the token for this request.
-            if (mProjectionGrant.uid == uid
-                    && Objects.equals(mProjectionGrant.packageName, packageName)) {
-                Slog.v(TAG, "Reusing token: getProjection can reuse the current projection");
-                return mProjectionGrant;
-            } else {
-                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
-                        + "instance due to package details mismatching");
-                return null;
+                if (mProjectionGrant.uid == uid
+                        && Objects.equals(mProjectionGrant.packageName, packageName)) {
+                    Slog.v(TAG, "Reusing token: getProjection can reuse the current projection");
+                    return mProjectionGrant;
+                } else {
+                    Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
+                            + "instance due to package details mismatching");
+                    return null;
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(callingToken);
@@ -626,8 +646,10 @@
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                if (mProjectionGrant != null) {
-                    mProjectionGrant.stop();
+                synchronized (mLock) {
+                    if (mProjectionGrant != null) {
+                        mProjectionGrant.stop();
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -641,13 +663,17 @@
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
                         + "on captured content resize");
             }
-            if (!isCurrentProjection(mProjectionGrant)) {
-                return;
+            synchronized (mLock) {
+                if (!isCurrentProjection(mProjectionGrant)) {
+                    return;
+                }
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                if (mProjectionGrant != null && mCallbackDelegate != null) {
-                    mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+                synchronized (mLock) {
+                    if (mProjectionGrant != null && mCallbackDelegate != null) {
+                        mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -661,13 +687,17 @@
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
                         + "on captured content visibility changed");
             }
-            if (!isCurrentProjection(mProjectionGrant)) {
-                return;
+            synchronized (mLock) {
+                if (!isCurrentProjection(mProjectionGrant)) {
+                    return;
+                }
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                if (mProjectionGrant != null && mCallbackDelegate != null) {
-                    mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+                synchronized (mLock) {
+                    if (mProjectionGrant != null && mCallbackDelegate != null) {
+                        mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -712,9 +742,11 @@
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to set session "
                         + "details.");
             }
-            if (!isCurrentProjection(projection)) {
-                throw new SecurityException("Unable to set ContentRecordingSession on "
-                        + "non-current MediaProjection");
+            synchronized (mLock) {
+                if (!isCurrentProjection(projection)) {
+                    throw new SecurityException("Unable to set ContentRecordingSession on "
+                            + "non-current MediaProjection");
+                }
             }
             final long origId = Binder.clearCallingIdentity();
             try {
@@ -732,10 +764,12 @@
                 throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
                         + "projection is valid.");
             }
-            if (!isCurrentProjection(projection)) {
-                Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
-                        + "isn't current");
-                return;
+            synchronized (mLock) {
+                if (!isCurrentProjection(projection)) {
+                    Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
+                            + "isn't current");
+                    return;
+                }
             }
 
             // Remove calling app identity before performing any privileged operations.
diff --git a/services/core/java/com/android/server/media/projection/mediaprojection.md b/services/core/java/com/android/server/media/projection/mediaprojection.md
new file mode 100644
index 0000000..bccdf34
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/mediaprojection.md
@@ -0,0 +1,30 @@
+# MediaProjection
+
+## Locking model
+`MediaProjectionManagerService` needs to have consistent lock ordering with its interactions with
+`WindowManagerService` to prevent deadlock.
+
+### TLDR
+`MediaProjectionManagerService` must lock when updating its own fields.
+
+Calls must follow the below invocation order while holding locks:
+
+`WindowManagerService -> MediaProjectionManagerService -> DisplayManagerService`
+
+### Justification
+
+`MediaProjectionManagerService` calls into `WindowManagerService` in the below cases. While handling
+each invocation, `WindowManagerService` acquires its own lock:
+* setting a `ContentRecordingSession`
+  * starting a new `MediaProjection` recording session through
+`MediaProjection#createVirtualDisplay`
+  * indicating the user has granted consent to reuse the consent token
+
+`WindowManagerService` calls into `MediaProjectionManagerService`, always while holding
+`WindowManagerGlobalLock`:
+* `ContentRecorder` handling various events such as resizing recorded content
+
+
+Since `WindowManagerService -> MediaProjectionManagerService` is guaranteed to always hold the
+`WindowManagerService` lock, we must ensure that `MediaProjectionManagerService ->
+WindowManagerService` is NEVER holding the `MediaProjectionManagerService` lock.