1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.keyguard 18 19 import android.annotation.WorkerThread 20 import android.content.ComponentCallbacks2 21 import android.graphics.HardwareRenderer 22 import android.os.Trace 23 import android.util.Log 24 import com.android.systemui.CoreStartable 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.dagger.qualifiers.Application 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.flags.FeatureFlags 29 import com.android.systemui.flags.Flags 30 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 31 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 32 import com.android.systemui.keyguard.shared.model.TransitionState 33 import com.android.systemui.keyguard.shared.model.WakefulnessState 34 import com.android.systemui.utils.GlobalWindowManager 35 import javax.inject.Inject 36 import kotlinx.coroutines.CoroutineDispatcher 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.flow.combine 39 import kotlinx.coroutines.flow.distinctUntilChanged 40 import kotlinx.coroutines.flow.map 41 import kotlinx.coroutines.launch 42 43 /** 44 * Releases cached resources on allocated by keyguard. 45 * 46 * We release most resources when device goes idle since that's the least likely time it'll cause 47 * jank during use. Idle in this case means after lockscreen -> AoD transition completes or when the 48 * device screen is turned off, depending on settings. 49 */ 50 @SysUISingleton 51 class ResourceTrimmer 52 @Inject 53 constructor( 54 private val keyguardInteractor: KeyguardInteractor, 55 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 56 private val globalWindowManager: GlobalWindowManager, 57 @Application private val applicationScope: CoroutineScope, 58 @Background private val bgDispatcher: CoroutineDispatcher, 59 private val featureFlags: FeatureFlags, 60 ) : CoreStartable, WakefulnessLifecycle.Observer { 61 62 override fun start() { 63 Log.d(LOG_TAG, "Resource trimmer registered.") 64 if (featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) { 65 applicationScope.launch(bgDispatcher) { 66 // We need to wait for the AoD transition (and animation) to complete. 67 // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f 68 // signal. This is to make sure we don't clear font caches during animation which 69 // would jank and leave stale data in memory. 70 val isDozingFully = 71 keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged() 72 combine( 73 keyguardInteractor.wakefulnessModel.map { it.state }, 74 keyguardInteractor.isDreaming, 75 isDozingFully, 76 ::Triple 77 ) 78 .distinctUntilChanged() 79 .collect { onWakefulnessUpdated(it.first, it.second, it.third) } 80 } 81 } 82 83 applicationScope.launch(bgDispatcher) { 84 // We drop 1 to avoid triggering on initial collect(). 85 keyguardTransitionInteractor.anyStateToGoneTransition.collect { transition -> 86 if (transition.transitionState == TransitionState.FINISHED) { 87 onKeyguardGone() 88 } 89 } 90 } 91 } 92 93 @WorkerThread 94 private fun onKeyguardGone() { 95 // We want to clear temporary caches we've created while rendering and animating 96 // lockscreen elements, especially clocks. 97 Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.") 98 globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) 99 if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) { 100 if (DEBUG) { 101 Log.d(LOG_TAG, "Trimming font caches since keyguard went away.") 102 } 103 globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT) 104 } 105 } 106 107 @WorkerThread 108 private fun onWakefulnessUpdated( 109 wakefulness: WakefulnessState, 110 isDreaming: Boolean, 111 isDozingFully: Boolean 112 ) { 113 if (!featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) { 114 return 115 } 116 117 if (DEBUG) { 118 Log.d( 119 LOG_TAG, 120 "Wakefulness: $wakefulness Dreaming: $isDreaming DozeAmount: $isDozingFully" 121 ) 122 } 123 // There are three scenarios: 124 // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false 125 // and dozeAmount == 0f 126 // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps 127 // to 1f 128 // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases 129 // to 1f 130 val dozeDisabledAndScreenOff = wakefulness == WakefulnessState.ASLEEP && !isDreaming 131 val dozeEnabledAndDozeAnimationCompleted = 132 wakefulness == WakefulnessState.ASLEEP && isDreaming && isDozingFully 133 if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) { 134 Trace.beginSection("ResourceTrimmer#trimMemory") 135 Log.d(LOG_TAG, "SysUI asleep, trimming memory.") 136 globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) 137 globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL) 138 Trace.endSection() 139 } 140 } 141 142 companion object { 143 private const val LOG_TAG = "ResourceTrimmer" 144 private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) 145 } 146 } 147