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.