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 
18 package com.android.systemui.keyguard.domain.interactor
19 
20 import android.content.Intent
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.SmallTest
23 import com.android.internal.logging.UiEventLogger
24 import com.android.systemui.R
25 import com.android.systemui.RoboPilotTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.coroutines.collectLastValue
28 import com.android.systemui.flags.FakeFeatureFlags
29 import com.android.systemui.flags.Flags
30 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
31 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
32 import com.android.systemui.keyguard.shared.model.KeyguardState
33 import com.android.systemui.keyguard.shared.model.TransitionStep
34 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
35 import com.android.systemui.util.mockito.whenever
36 import com.google.common.truth.Truth.assertThat
37 import kotlinx.coroutines.ExperimentalCoroutinesApi
38 import kotlinx.coroutines.runBlocking
39 import kotlinx.coroutines.test.TestScope
40 import kotlinx.coroutines.test.advanceTimeBy
41 import kotlinx.coroutines.test.runCurrent
42 import kotlinx.coroutines.test.runTest
43 import org.junit.After
44 import org.junit.Before
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 import org.mockito.ArgumentMatchers.anyInt
48 import org.mockito.Mock
49 import org.mockito.Mockito.verify
50 import org.mockito.MockitoAnnotations
51 
52 @OptIn(ExperimentalCoroutinesApi::class)
53 @SmallTest
54 @RoboPilotTest
55 @RunWith(AndroidJUnit4::class)
56 class KeyguardLongPressInteractorTest : SysuiTestCase() {
57 
58     @Mock private lateinit var logger: UiEventLogger
59     @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
60 
61     private lateinit var underTest: KeyguardLongPressInteractor
62 
63     private lateinit var testScope: TestScope
64     private lateinit var keyguardRepository: FakeKeyguardRepository
65     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
66 
67     @Before
68     fun setUp() {
69         MockitoAnnotations.initMocks(this)
70         overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true)
71         whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
72             it.arguments[0]
73         }
74 
75         testScope = TestScope()
76         keyguardRepository = FakeKeyguardRepository()
77         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
78 
79         runBlocking { createUnderTest() }
80     }
81 
82     @After
83     fun tearDown() {
84         mContext
85             .getOrCreateTestableResources()
86             .removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled)
87     }
88 
89     @Test
90     fun isEnabled() =
91         testScope.runTest {
92             val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
93             KeyguardState.values().forEach { keyguardState ->
94                 setUpState(
95                     keyguardState = keyguardState,
96                 )
97 
98                 if (keyguardState == KeyguardState.LOCKSCREEN) {
99                     assertThat(isEnabled()).isTrue()
100                 } else {
101                     assertThat(isEnabled()).isFalse()
102                 }
103             }
104         }
105 
106     @Test
107     fun isEnabled_alwaysFalseWhenQuickSettingsAreVisible() =
108         testScope.runTest {
109             val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
110             KeyguardState.values().forEach { keyguardState ->
111                 setUpState(
112                     keyguardState = keyguardState,
113                     isQuickSettingsVisible = true,
114                 )
115 
116                 assertThat(isEnabled()).isFalse()
117             }
118         }
119 
120     @Test
121     fun isEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() =
122         testScope.runTest {
123             overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, false)
124             createUnderTest()
125             val isEnabled by collectLastValue(underTest.isLongPressHandlingEnabled)
126             runCurrent()
127 
128             assertThat(isEnabled).isFalse()
129         }
130 
131     @Test
132     fun longPressed_menuClicked_showsSettings() =
133         testScope.runTest {
134             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
135             val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
136             runCurrent()
137 
138             underTest.onLongPress()
139             assertThat(isMenuVisible).isTrue()
140 
141             underTest.onMenuTouchGestureEnded(/* isClick= */ true)
142 
143             assertThat(isMenuVisible).isFalse()
144             assertThat(shouldOpenSettings).isTrue()
145         }
146 
147     @Test
148     fun onSettingsShown_consumesSettingsShowEvent() =
149         testScope.runTest {
150             val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
151             runCurrent()
152 
153             underTest.onLongPress()
154             underTest.onMenuTouchGestureEnded(/* isClick= */ true)
155             assertThat(shouldOpenSettings).isTrue()
156 
157             underTest.onSettingsShown()
158             assertThat(shouldOpenSettings).isFalse()
159         }
160 
161     @Test
162     fun onTouchedOutside_neverShowsSettings() =
163         testScope.runTest {
164             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
165             val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
166             runCurrent()
167 
168             underTest.onTouchedOutside()
169 
170             assertThat(isMenuVisible).isFalse()
171             assertThat(shouldOpenSettings).isFalse()
172         }
173 
174     @Test
175     fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() =
176         testScope.runTest {
177             createUnderTest(isOpenWppDirectlyEnabled = true)
178             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
179             val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
180             runCurrent()
181 
182             underTest.onLongPress()
183 
184             assertThat(isMenuVisible).isFalse()
185             assertThat(shouldOpenSettings).isTrue()
186         }
187 
188     @Test
189     fun longPressed_closeDialogsBroadcastReceived_popupDismissed() =
190         testScope.runTest {
191             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
192             runCurrent()
193 
194             underTest.onLongPress()
195             assertThat(isMenuVisible).isTrue()
196 
197             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
198                 context,
199                 Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
200             )
201 
202             assertThat(isMenuVisible).isFalse()
203         }
204 
205     @Test
206     fun closesDialogAfterTimeout() =
207         testScope.runTest {
208             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
209             runCurrent()
210 
211             underTest.onLongPress()
212             assertThat(isMenuVisible).isTrue()
213 
214             advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
215 
216             assertThat(isMenuVisible).isFalse()
217         }
218 
219     @Test
220     fun closesDialogAfterTimeout_onlyAfterTouchGestureEnded() =
221         testScope.runTest {
222             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
223             runCurrent()
224 
225             underTest.onLongPress()
226             assertThat(isMenuVisible).isTrue()
227             underTest.onMenuTouchGestureStarted()
228 
229             advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
230             assertThat(isMenuVisible).isTrue()
231 
232             underTest.onMenuTouchGestureEnded(/* isClick= */ false)
233             advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
234             assertThat(isMenuVisible).isFalse()
235         }
236 
237     @Test
238     fun logsWhenMenuIsShown() =
239         testScope.runTest {
240             collectLastValue(underTest.isMenuVisible)
241             runCurrent()
242 
243             underTest.onLongPress()
244 
245             verify(logger)
246                 .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
247         }
248 
249     @Test
250     fun logsWhenMenuIsClicked() =
251         testScope.runTest {
252             collectLastValue(underTest.isMenuVisible)
253             runCurrent()
254 
255             underTest.onLongPress()
256             underTest.onMenuTouchGestureEnded(/* isClick= */ true)
257 
258             verify(logger)
259                 .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
260         }
261 
262     @Test
263     fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
264         testScope.runTest {
265             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
266             runCurrent()
267             underTest.onLongPress()
268             assertThat(isMenuVisible).isTrue()
269 
270             keyguardTransitionRepository.sendTransitionStep(
271                 TransitionStep(
272                     to = KeyguardState.GONE,
273                 ),
274             )
275             assertThat(isMenuVisible).isFalse()
276 
277             keyguardTransitionRepository.sendTransitionStep(
278                 TransitionStep(
279                     to = KeyguardState.LOCKSCREEN,
280                 ),
281             )
282             assertThat(isMenuVisible).isFalse()
283         }
284 
285     private suspend fun createUnderTest(
286         isLongPressFeatureEnabled: Boolean = true,
287         isRevampedWppFeatureEnabled: Boolean = true,
288         isOpenWppDirectlyEnabled: Boolean = false,
289     ) {
290         underTest =
291             KeyguardLongPressInteractor(
292                 appContext = mContext,
293                 scope = testScope.backgroundScope,
294                 transitionInteractor =
295                     KeyguardTransitionInteractorFactory.create(
296                             scope = testScope.backgroundScope,
297                             repository = keyguardTransitionRepository,
298                         )
299                         .keyguardTransitionInteractor,
300                 repository = keyguardRepository,
301                 logger = logger,
302                 featureFlags =
303                     FakeFeatureFlags().apply {
304                         set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
305                         set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
306                         set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
307                     },
308                 broadcastDispatcher = fakeBroadcastDispatcher,
309                 accessibilityManager = accessibilityManager
310             )
311         setUpState()
312     }
313 
314     private suspend fun setUpState(
315         keyguardState: KeyguardState = KeyguardState.LOCKSCREEN,
316         isQuickSettingsVisible: Boolean = false,
317     ) {
318         keyguardTransitionRepository.sendTransitionStep(
319             TransitionStep(
320                 to = keyguardState,
321             ),
322         )
323         keyguardRepository.setQuickSettingsVisible(isVisible = isQuickSettingsVisible)
324     }
325 }
326