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