summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt190
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt4
6 files changed, 359 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
new file mode 100644
index 000000000000..4eb7d44ebfa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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.keyguard
+
+import android.annotation.WorkerThread
+import android.content.ComponentCallbacks2
+import android.os.Trace
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+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
+import kotlinx.coroutines.launch
+
+/**
+ * Releases cached resources on allocated by keyguard.
+ *
+ * We release most resources when device goes idle since that's the least likely time it'll cause
+ * jank during use. Idle in this case means after lockscreen -> AoD transition completes or when the
+ * device screen is turned off, depending on settings.
+ */
+@SysUISingleton
+class ResourceTrimmer
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val globalWindowManager: GlobalWindowManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : CoreStartable, WakefulnessLifecycle.Observer {
+
+ override fun start() {
+ Log.d(LOG_TAG, "Resource trimmer registered.")
+ applicationScope.launch(bgDispatcher) {
+ // We need to wait for the AoD transition (and animation) to complete.
+ // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
+ // signal. This is to make sure we don't clear font caches during animation which
+ // would jank and leave stale data in memory.
+ val isDozingFully =
+ keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
+ combine(
+ keyguardInteractor.wakefulnessModel.map { it.state },
+ keyguardInteractor.isDreaming,
+ isDozingFully,
+ ::Triple
+ )
+ .distinctUntilChanged()
+ .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+ }
+ }
+
+ @WorkerThread
+ private fun onWakefulnessUpdated(
+ wakefulness: WakefulnessState,
+ isDreaming: Boolean,
+ isDozingFully: Boolean
+ ) {
+ if (DEBUG) {
+ Log.d(
+ LOG_TAG,
+ "Wakefulness: $wakefulness Dreaming: $isDreaming DozeAmount: $isDozingFully"
+ )
+ }
+ // There are three scenarios:
+ // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
+ // and dozeAmount == 0f
+ // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps
+ // to 1f
+ // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
+ // to 1f
+ val dozeDisabledAndScreenOff = wakefulness == WakefulnessState.ASLEEP && !isDreaming
+ val dozeEnabledAndDozeAnimationCompleted =
+ wakefulness == WakefulnessState.ASLEEP && isDreaming && isDozingFully
+ if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
+ Trace.beginSection("ResourceTrimmer#trimMemory")
+ Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
+ globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ Trace.endSection()
+ }
+ }
+
+ companion object {
+ private const val LOG_TAG = "ResourceTrimmer"
+ private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 255556c80121..d7c039d9b519 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -87,6 +87,7 @@ import java.util.concurrent.Executor;
KeyguardRepositoryModule.class,
KeyguardFaceAuthModule.class,
StartKeyguardTransitionModule.class,
+ ResourceTrimmerModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java
new file mode 100644
index 000000000000..d693326f0dba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.keyguard.dagger;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.keyguard.ResourceTrimmer;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Binds {@link ResourceTrimmer} into {@link CoreStartable} set.
+ */
+@Module
+public abstract class ResourceTrimmerModule {
+
+ /** Bind ResourceTrimmer into CoreStarteables. */
+ @Binds
+ @IntoMap
+ @ClassKey(ResourceTrimmer.class)
+ public abstract CoreStartable bindResourceTrimmer(ResourceTrimmer resourceTrimmer);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
new file mode 100644
index 000000000000..038fddc1f7a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.utils
+
+import android.view.WindowManagerGlobal
+import javax.inject.Inject
+
+/** Interface to talk to [WindowManagerGlobal] */
+class GlobalWindowManager @Inject constructor() {
+ /**
+ * Sends a trim memory command to [WindowManagerGlobal].
+ *
+ * @param level One of levels from [ComponentCallbacks2] starting with TRIM_
+ */
+ fun trimMemory(level: Int) {
+ WindowManagerGlobal.getInstance().trimMemory(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
new file mode 100644
index 000000000000..931f82ce9f6b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -0,0 +1,190 @@
+package com.android.systemui.keyguard
+
+import android.content.ComponentCallbacks2
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+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.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.utils.GlobalWindowManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ResourceTrimmerTest : SysuiTestCase() {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val keyguardRepository = FakeKeyguardRepository()
+
+ @Mock private lateinit var globalWindowManager: GlobalWindowManager
+ @Mock private lateinit var featureFlags: FeatureFlags
+ private lateinit var resourceTrimmer: ResourceTrimmer
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ )
+ keyguardRepository.setDozeAmount(0f)
+
+ val interactor =
+ KeyguardInteractor(
+ keyguardRepository,
+ FakeCommandQueue(),
+ featureFlags,
+ FakeKeyguardBouncerRepository()
+ )
+ resourceTrimmer =
+ ResourceTrimmer(
+ interactor,
+ globalWindowManager,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+ resourceTrimmer.start()
+ }
+
+ @Test
+ fun noChange_noOutputChanges() =
+ testScope.runTest {
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ fun dozeAodDisabled_sleep_trimsMemory() =
+ testScope.runTest {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+
+ @Test
+ fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(1f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+
+ @Test
+ fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0f) { it + 0.1f }
+ .takeWhile { it < 1f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+ verifyZeroInteractions(globalWindowManager)
+
+ keyguardRepository.setDozeAmount(1f)
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+ }
+
+ @Test
+ fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0f) { it + 0.1f }
+ .takeWhile { it < 0.8f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0.8f) { it - 0.1f }
+ .takeWhile { it >= 0f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+
+ keyguardRepository.setDozeAmount(0f)
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index fd8c4b81063d..b52a76839a99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -147,6 +147,10 @@ class FakeKeyguardRepository : KeyguardRepository {
_isAodAvailable.value = isAodAvailable
}
+ fun setDreaming(isDreaming: Boolean) {
+ _isDreaming.value = isDreaming
+ }
+
fun setDreamingWithOverlay(isDreaming: Boolean) {
_isDreamingWithOverlay.value = isDreaming
}