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.back.domain.interactor
18 
19 import android.platform.test.annotations.RequiresFlagsDisabled
20 import android.platform.test.annotations.RequiresFlagsEnabled
21 import android.platform.test.flag.junit.DeviceFlagsValueProvider
22 import android.view.ViewRootImpl
23 import android.window.BackEvent
24 import android.window.BackEvent.EDGE_LEFT
25 import android.window.OnBackAnimationCallback
26 import android.window.OnBackInvokedCallback
27 import android.window.OnBackInvokedDispatcher
28 import android.window.WindowOnBackInvokedDispatcher
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import com.android.internal.statusbar.IStatusBarService
32 import com.android.systemui.Flags
33 import com.android.systemui.SysuiTestCase
34 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
35 import com.android.systemui.kosmos.Kosmos
36 import com.android.systemui.kosmos.testScope
37 import com.android.systemui.plugins.statusbar.StatusBarStateController
38 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
39 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
40 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
41 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
42 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
43 import com.android.systemui.scene.domain.interactor.sceneInteractor
44 import com.android.systemui.scene.ui.view.WindowRootView
45 import com.android.systemui.shade.QuickSettingsController
46 import com.android.systemui.shade.ShadeController
47 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
48 import com.android.systemui.statusbar.NotificationShadeWindowController
49 import com.android.systemui.statusbar.StatusBarState
50 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
51 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
52 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
53 import com.android.systemui.util.concurrency.FakeExecutor
54 import com.android.systemui.util.mockito.any
55 import com.android.systemui.util.mockito.argumentCaptor
56 import com.android.systemui.util.mockito.eq
57 import com.android.systemui.util.mockito.whenever
58 import com.android.systemui.util.time.FakeSystemClock
59 import com.google.common.truth.Truth.assertThat
60 import junit.framework.Assert.assertFalse
61 import junit.framework.Assert.assertTrue
62 import kotlinx.coroutines.ExperimentalCoroutinesApi
63 import kotlinx.coroutines.test.runCurrent
64 import org.junit.Before
65 import org.junit.Rule
66 import org.junit.Test
67 import org.junit.runner.RunWith
68 import org.mockito.Mock
69 import org.mockito.Mockito.anyBoolean
70 import org.mockito.Mockito.atLeastOnce
71 import org.mockito.Mockito.never
72 import org.mockito.Mockito.verify
73 import org.mockito.junit.MockitoJUnit
74 
75 @SmallTest
76 @RunWith(AndroidJUnit4::class)
77 @OptIn(ExperimentalCoroutinesApi::class)
78 class BackActionInteractorTest : SysuiTestCase() {
79     private val kosmos = Kosmos()
80     private val testScope = kosmos.testScope
81     private val executor = FakeExecutor(FakeSystemClock())
82 
83     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
84 
85     @Mock private lateinit var statusBarStateController: StatusBarStateController
86     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
87     @Mock private lateinit var shadeController: ShadeController
88     @Mock private lateinit var qsController: QuickSettingsController
89     @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
90     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
91     @Mock private lateinit var windowRootView: WindowRootView
92     @Mock private lateinit var viewRootImpl: ViewRootImpl
93     @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
94     @Mock private lateinit var iStatusBarService: IStatusBarService
95     @Mock private lateinit var headsUpManager: HeadsUpManager
96 
97     private val keyguardRepository = FakeKeyguardRepository()
<lambda>null98     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
99         WindowRootViewVisibilityInteractor(
100             testScope.backgroundScope,
101             WindowRootViewVisibilityRepository(iStatusBarService, executor),
102             keyguardRepository,
103             headsUpManager,
104             powerInteractor,
105             kosmos.activeNotificationsInteractor,
106             kosmos::sceneInteractor,
107         )
108     }
109 
<lambda>null110     private val backActionInteractor: BackActionInteractor by lazy {
111         BackActionInteractor(
112             testScope.backgroundScope,
113             statusBarStateController,
114             statusBarKeyguardViewManager,
115             shadeController,
116             notificationShadeWindowController,
117             windowRootViewVisibilityInteractor,
118             shadeBackActionInteractor,
119             qsController,
120         )
121     }
122 
123     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
124 
125     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
126 
127     @Before
setUpnull128     fun setUp() {
129         whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
130         whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
131         whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
132     }
133 
134     @Test
testOnBackRequested_keyguardCanHandleBackPressednull135     fun testOnBackRequested_keyguardCanHandleBackPressed() {
136         whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
137 
138         val result = backActionInteractor.onBackRequested()
139 
140         assertTrue(result)
141         verify(statusBarKeyguardViewManager, atLeastOnce()).onBackPressed()
142     }
143 
144     @Test
testOnBackRequested_quickSettingsIsCustomizingnull145     fun testOnBackRequested_quickSettingsIsCustomizing() {
146         whenever(qsController.isCustomizing).thenReturn(true)
147 
148         val result = backActionInteractor.onBackRequested()
149 
150         assertTrue(result)
151         verify(qsController, atLeastOnce()).closeQsCustomizer()
152         verify(statusBarKeyguardViewManager, never()).onBackPressed()
153     }
154 
155     @Test
testOnBackRequested_quickSettingsExpandednull156     fun testOnBackRequested_quickSettingsExpanded() {
157         whenever(qsController.expanded).thenReturn(true)
158 
159         val result = backActionInteractor.onBackRequested()
160 
161         assertTrue(result)
162         verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
163         verify(statusBarKeyguardViewManager, never()).onBackPressed()
164     }
165 
166     @Test
testOnBackRequested_closeUserSwitcherIfOpennull167     fun testOnBackRequested_closeUserSwitcherIfOpen() {
168         whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
169 
170         val result = backActionInteractor.onBackRequested()
171 
172         assertTrue(result)
173         verify(statusBarKeyguardViewManager, never()).onBackPressed()
174         verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
175     }
176 
177     @Test
testOnBackRequested_returnsFalsenull178     fun testOnBackRequested_returnsFalse() {
179         // make shouldBackBeHandled return false
180         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
181 
182         val result = backActionInteractor.onBackRequested()
183 
184         assertFalse(result)
185         verify(statusBarKeyguardViewManager, never()).onBackPressed()
186         verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
187     }
188 
189     @Test
shadeVisibleAndDeviceAwake_callbackRegisterednull190     fun shadeVisibleAndDeviceAwake_callbackRegistered() {
191         backActionInteractor.start()
192         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
193         powerInteractor.setAwakeForTest()
194 
195         testScope.runCurrent()
196 
197         verify(onBackInvokedDispatcher)
198             .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
199     }
200 
201     @Test
noWindowRootView_noCrashAttemptingCallbackRegistrationnull202     fun noWindowRootView_noCrashAttemptingCallbackRegistration() {
203         whenever(notificationShadeWindowController.windowRootView).thenReturn(null)
204 
205         backActionInteractor.start()
206         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
207         powerInteractor.setAwakeForTest()
208 
209         testScope.runCurrent()
210         // No assert necessary, just testing no crash
211     }
212 
213     @Test
shadeNotVisible_callbackUnregisterednull214     fun shadeNotVisible_callbackUnregistered() {
215         backActionInteractor.start()
216         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
217         powerInteractor.setAwakeForTest()
218         val callback = getBackInvokedCallback()
219 
220         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
221         testScope.runCurrent()
222 
223         verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
224     }
225 
226     @Test
deviceAsleep_callbackUnregisterednull227     fun deviceAsleep_callbackUnregistered() {
228         backActionInteractor.start()
229         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
230         powerInteractor.setAwakeForTest()
231         val callback = getBackInvokedCallback()
232 
233         powerInteractor.setAsleepForTest()
234         testScope.runCurrent()
235 
236         verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
237     }
238 
239     @Test
240     @RequiresFlagsDisabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
animationFlagOff_onBackInvoked_keyguardNotifiednull241     fun animationFlagOff_onBackInvoked_keyguardNotified() {
242         backActionInteractor.start()
243         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
244         powerInteractor.setAwakeForTest()
245         val callback = getBackInvokedCallback()
246         whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
247 
248         callback.onBackInvoked()
249 
250         verify(statusBarKeyguardViewManager).onBackPressed()
251     }
252 
253     @Test
254     @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
animationFlagOn_onBackInvoked_keyguardNotifiednull255     fun animationFlagOn_onBackInvoked_keyguardNotified() {
256         backActionInteractor.start()
257         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
258         powerInteractor.setAwakeForTest()
259         val callback = getBackInvokedCallback()
260         whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
261 
262         callback.onBackInvoked()
263 
264         verify(statusBarKeyguardViewManager).onBackPressed()
265     }
266 
267     @Test
268     @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
animationFlagOn_callbackIsAnimationCallbacknull269     fun animationFlagOn_callbackIsAnimationCallback() {
270         backActionInteractor.start()
271         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
272         powerInteractor.setAwakeForTest()
273 
274         val callback = getBackInvokedCallback()
275 
276         assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java)
277     }
278 
279     @Test
280     @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotifiednull281     fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
282         backActionInteractor.start()
283         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
284         powerInteractor.setAwakeForTest()
285         val callback = getBackInvokedCallback() as OnBackAnimationCallback
286 
287         whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
288 
289         callback.onBackProgressed(createBackEvent(0.3f))
290 
291         verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
292     }
293 
294     @Test
295     @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotifiednull296     fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
297         backActionInteractor.start()
298         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
299         powerInteractor.setAwakeForTest()
300         val callback = getBackInvokedCallback() as OnBackAnimationCallback
301 
302         whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
303 
304         callback.onBackProgressed(createBackEvent(0.4f))
305 
306         verify(shadeBackActionInteractor).onBackProgressed(0.4f)
307     }
308 
getBackInvokedCallbacknull309     private fun getBackInvokedCallback(): OnBackInvokedCallback {
310         testScope.runCurrent()
311         val captor = argumentCaptor<OnBackInvokedCallback>()
312         verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture())
313         return captor.value!!
314     }
315 
createBackEventnull316     private fun createBackEvent(progress: Float): BackEvent =
317         BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT)
318 }
319