1 package com.android.systemui.keyguard
2 
3 import android.content.ComponentCallbacks2
4 import android.graphics.HardwareRenderer
5 import android.testing.AndroidTestingRunner
6 import androidx.test.filters.SmallTest
7 import com.android.systemui.SysuiTestCase
8 import com.android.systemui.flags.FakeFeatureFlags
9 import com.android.systemui.flags.Flags
10 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
11 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
12 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
13 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
14 import com.android.systemui.keyguard.shared.model.KeyguardState
15 import com.android.systemui.keyguard.shared.model.TransitionStep
16 import com.android.systemui.keyguard.shared.model.WakeSleepReason
17 import com.android.systemui.keyguard.shared.model.WakefulnessModel
18 import com.android.systemui.keyguard.shared.model.WakefulnessState
19 import com.android.systemui.util.mockito.any
20 import com.android.systemui.utils.GlobalWindowManager
21 import kotlinx.coroutines.ExperimentalCoroutinesApi
22 import kotlinx.coroutines.test.TestScope
23 import kotlinx.coroutines.test.UnconfinedTestDispatcher
24 import kotlinx.coroutines.test.runCurrent
25 import kotlinx.coroutines.test.runTest
26 import org.junit.Before
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import org.mockito.Mock
30 import org.mockito.Mockito.times
31 import org.mockito.Mockito.verify
32 import org.mockito.Mockito.verifyNoMoreInteractions
33 import org.mockito.Mockito.verifyZeroInteractions
34 import org.mockito.MockitoAnnotations
35 
36 @OptIn(ExperimentalCoroutinesApi::class)
37 @RunWith(AndroidTestingRunner::class)
38 @SmallTest
39 class ResourceTrimmerTest : SysuiTestCase() {
40 
41     private val testDispatcher = UnconfinedTestDispatcher()
42     private val testScope = TestScope(testDispatcher)
43     private val keyguardRepository = FakeKeyguardRepository()
44     private val featureFlags = FakeFeatureFlags()
45     private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
46 
47     @Mock private lateinit var globalWindowManager: GlobalWindowManager
48     private lateinit var resourceTrimmer: ResourceTrimmer
49 
50     @Before
51     fun setUp() {
52         MockitoAnnotations.initMocks(this)
53         featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
54         featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
55         featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
56         keyguardRepository.setWakefulnessModel(
57             WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
58         )
59         keyguardRepository.setDozeAmount(0f)
60         keyguardRepository.setKeyguardGoingAway(false)
61 
62         val withDeps =
63             KeyguardInteractorFactory.create(
64                 repository = keyguardRepository,
65                 featureFlags = featureFlags,
66             )
67         val keyguardInteractor = withDeps.keyguardInteractor
68         resourceTrimmer =
69             ResourceTrimmer(
70                 keyguardInteractor,
71                 KeyguardTransitionInteractorFactory.create(
72                         scope = TestScope().backgroundScope,
73                         repository = keyguardTransitionRepository,
74                     )
75                     .keyguardTransitionInteractor,
76                 globalWindowManager,
77                 testScope.backgroundScope,
78                 testDispatcher,
79                 featureFlags
80             )
81         resourceTrimmer.start()
82     }
83 
84     @Test
85     fun noChange_noOutputChanges() =
86         testScope.runTest {
87             testScope.runCurrent()
88             verifyZeroInteractions(globalWindowManager)
89         }
90 
91     @Test
92     fun dozeAodDisabled_sleep_trimsMemory() =
93         testScope.runTest {
94             keyguardRepository.setWakefulnessModel(
95                 WakefulnessModel(
96                     WakefulnessState.ASLEEP,
97                     WakeSleepReason.OTHER,
98                     WakeSleepReason.OTHER
99                 )
100             )
101             testScope.runCurrent()
102             verify(globalWindowManager, times(1))
103                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
104             verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
105         }
106 
107     @Test
108     fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
109         testScope.runTest {
110             keyguardRepository.setDreaming(true)
111             keyguardRepository.setDozeAmount(1f)
112             keyguardRepository.setWakefulnessModel(
113                 WakefulnessModel(
114                     WakefulnessState.ASLEEP,
115                     WakeSleepReason.OTHER,
116                     WakeSleepReason.OTHER
117                 )
118             )
119             testScope.runCurrent()
120             verify(globalWindowManager, times(1))
121                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
122             verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
123         }
124 
125     @Test
126     fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
127         testScope.runTest {
128             keyguardRepository.setDreaming(true)
129             keyguardRepository.setDozeAmount(0f)
130             keyguardRepository.setWakefulnessModel(
131                 WakefulnessModel(
132                     WakefulnessState.ASLEEP,
133                     WakeSleepReason.OTHER,
134                     WakeSleepReason.OTHER
135                 )
136             )
137             testScope.runCurrent()
138             verifyZeroInteractions(globalWindowManager)
139         }
140 
141     @Test
142     fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
143         testScope.runTest {
144             keyguardRepository.setDreaming(true)
145             keyguardRepository.setDozeAmount(0f)
146             keyguardRepository.setWakefulnessModel(
147                 WakefulnessModel(
148                     WakefulnessState.ASLEEP,
149                     WakeSleepReason.OTHER,
150                     WakeSleepReason.OTHER
151                 )
152             )
153 
154             testScope.runCurrent()
155             verifyZeroInteractions(globalWindowManager)
156 
157             generateSequence(0f) { it + 0.1f }
158                 .takeWhile { it < 1f }
159                 .forEach {
160                     keyguardRepository.setDozeAmount(it)
161                     testScope.runCurrent()
162                 }
163             verifyZeroInteractions(globalWindowManager)
164 
165             keyguardRepository.setDozeAmount(1f)
166             testScope.runCurrent()
167             verify(globalWindowManager, times(1))
168                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
169             verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
170         }
171     }
172 
173     @Test
174     fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
175         testScope.runTest {
176             keyguardRepository.setDreaming(true)
177             keyguardRepository.setDozeAmount(0f)
178             keyguardRepository.setWakefulnessModel(
179                 WakefulnessModel(
180                     WakefulnessState.ASLEEP,
181                     WakeSleepReason.OTHER,
182                     WakeSleepReason.OTHER
183                 )
184             )
185 
186             testScope.runCurrent()
187             verifyZeroInteractions(globalWindowManager)
188 
189             generateSequence(0f) { it + 0.1f }
190                 .takeWhile { it < 0.8f }
191                 .forEach {
192                     keyguardRepository.setDozeAmount(it)
193                     testScope.runCurrent()
194                 }
195             verifyZeroInteractions(globalWindowManager)
196 
197             generateSequence(0.8f) { it - 0.1f }
198                 .takeWhile { it >= 0f }
199                 .forEach {
200                     keyguardRepository.setDozeAmount(it)
201                     testScope.runCurrent()
202                 }
203 
204             keyguardRepository.setDozeAmount(0f)
205             testScope.runCurrent()
206             verifyZeroInteractions(globalWindowManager)
207         }
208     }
209 
210     @Test
211     fun keyguardTransitionsToGone_trimsFontCache() =
212         testScope.runTest {
213             keyguardTransitionRepository.sendTransitionStep(
214                 TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
215             )
216             verify(globalWindowManager, times(1))
217                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
218             verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
219             verifyNoMoreInteractions(globalWindowManager)
220         }
221 
222     @Test
223     fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
224         testScope.runTest {
225             featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
226             keyguardTransitionRepository.sendTransitionStep(
227                 TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
228             )
229             // Memory hidden should still be called.
230             verify(globalWindowManager, times(1))
231                 .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
232             verify(globalWindowManager, times(0)).trimCaches(any())
233         }
234 }
235