1 /* 2 * Copyright (C) 2020 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.navigationbar; 18 19 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; 20 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; 21 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; 22 import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; 23 import static android.inputmethodservice.InputMethodService.IME_VISIBLE; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; 26 import static android.view.WindowInsets.Type.ime; 27 28 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; 29 import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; 30 31 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertFalse; 33 import static org.junit.Assert.assertTrue; 34 import static org.mockito.ArgumentMatchers.any; 35 import static org.mockito.ArgumentMatchers.anyBoolean; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.Matchers.eq; 38 import static org.mockito.Mockito.doNothing; 39 import static org.mockito.Mockito.doReturn; 40 import static org.mockito.Mockito.mock; 41 import static org.mockito.Mockito.spy; 42 import static org.mockito.Mockito.times; 43 import static org.mockito.Mockito.verify; 44 import static org.mockito.Mockito.when; 45 46 import android.content.BroadcastReceiver; 47 import android.content.Context; 48 import android.content.IntentFilter; 49 import android.hardware.display.DisplayManagerGlobal; 50 import android.os.Handler; 51 import android.os.SystemClock; 52 import android.os.UserHandle; 53 import android.provider.DeviceConfig; 54 import android.provider.Settings; 55 import android.telecom.TelecomManager; 56 import android.testing.AndroidTestingRunner; 57 import android.testing.TestableLooper; 58 import android.testing.TestableLooper.RunWithLooper; 59 import android.view.Display; 60 import android.view.DisplayInfo; 61 import android.view.MotionEvent; 62 import android.view.View; 63 import android.view.WindowInsets; 64 import android.view.WindowManager; 65 import android.view.WindowMetrics; 66 import android.view.accessibility.AccessibilityManager; 67 import android.view.inputmethod.InputMethodManager; 68 69 import androidx.test.filters.SmallTest; 70 71 import com.android.internal.logging.MetricsLogger; 72 import com.android.internal.logging.UiEventLogger; 73 import com.android.systemui.SysuiTestCase; 74 import com.android.systemui.SysuiTestableContext; 75 import com.android.systemui.accessibility.AccessibilityButtonModeObserver; 76 import com.android.systemui.accessibility.SystemActions; 77 import com.android.systemui.assist.AssistManager; 78 import com.android.systemui.broadcast.BroadcastDispatcher; 79 import com.android.systemui.dump.DumpManager; 80 import com.android.systemui.model.SysUiState; 81 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; 82 import com.android.systemui.plugins.statusbar.StatusBarStateController; 83 import com.android.systemui.recents.OverviewProxyService; 84 import com.android.systemui.recents.Recents; 85 import com.android.systemui.settings.UserTracker; 86 import com.android.systemui.statusbar.CommandQueue; 87 import com.android.systemui.statusbar.NotificationRemoteInputManager; 88 import com.android.systemui.statusbar.NotificationShadeDepthController; 89 import com.android.systemui.statusbar.phone.AutoHideController; 90 import com.android.systemui.statusbar.phone.LightBarController; 91 import com.android.systemui.statusbar.phone.NotificationShadeWindowView; 92 import com.android.systemui.statusbar.phone.ShadeController; 93 import com.android.systemui.statusbar.phone.StatusBar; 94 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 95 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 96 import com.android.systemui.statusbar.policy.KeyguardStateController; 97 import com.android.systemui.utils.leaks.LeakCheckedTest; 98 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; 99 import com.android.wm.shell.pip.Pip; 100 101 import org.junit.After; 102 import org.junit.Before; 103 import org.junit.Rule; 104 import org.junit.Test; 105 import org.junit.runner.RunWith; 106 import org.mockito.Mock; 107 import org.mockito.MockitoAnnotations; 108 import org.mockito.Spy; 109 110 import java.util.Optional; 111 112 @RunWith(AndroidTestingRunner.class) 113 @RunWithLooper(setAsMainLooper = true) 114 @SmallTest 115 public class NavigationBarTest extends SysuiTestCase { 116 private static final int EXTERNAL_DISPLAY_ID = 2; 117 118 private NavigationBar mNavigationBar; 119 private NavigationBar mExternalDisplayNavigationBar; 120 121 private SysuiTestableContext mSysuiTestableContextExternal; 122 @Mock 123 private OverviewProxyService mOverviewProxyService; 124 @Mock 125 private StatusBarStateController mStatusBarStateController; 126 @Mock 127 private NavigationModeController mNavigationModeController; 128 @Mock 129 private CommandQueue mCommandQueue; 130 private SysUiState mMockSysUiState; 131 @Mock 132 private Handler mHandler; 133 @Mock 134 private BroadcastDispatcher mBroadcastDispatcher; 135 @Mock 136 private UiEventLogger mUiEventLogger; 137 @Mock 138 EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; 139 @Mock 140 EdgeBackGestureHandler mEdgeBackGestureHandler; 141 NavBarHelper mNavBarHelper; 142 @Mock 143 private LightBarController mLightBarController; 144 @Mock 145 private LightBarController.Factory mLightBarcontrollerFactory; 146 @Mock 147 private AutoHideController mAutoHideController; 148 @Mock 149 private AutoHideController.Factory mAutoHideControllerFactory; 150 @Mock 151 private WindowManager mWindowManager; 152 @Mock 153 private TelecomManager mTelecomManager; 154 @Mock 155 private InputMethodManager mInputMethodManager; 156 @Mock 157 private AssistManager mAssistManager; 158 @Mock 159 private StatusBar mStatusBar; 160 161 @Rule 162 public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); 163 164 @Before setup()165 public void setup() throws Exception { 166 MockitoAnnotations.initMocks(this); 167 168 when(mEdgeBackGestureHandlerFactory.create(any(Context.class))) 169 .thenReturn(mEdgeBackGestureHandler); 170 when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController); 171 when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController); 172 setupSysuiDependency(); 173 // This class inflates views that call Dependency.get, thus these injections are still 174 // necessary. 175 mDependency.injectTestDependency(AssistManager.class, mAssistManager); 176 mDependency.injectMockDependency(KeyguardStateController.class); 177 mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController); 178 mDependency.injectMockDependency(NavigationBarController.class); 179 mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class, 180 mEdgeBackGestureHandlerFactory); 181 mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); 182 mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); 183 TestableLooper.get(this).runWithLooper(() -> { 184 mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class), 185 mock(AccessibilityManagerWrapper.class), 186 mock(AccessibilityButtonModeObserver.class), mOverviewProxyService, 187 () -> mock(AssistManager.class), () -> Optional.of(mStatusBar), 188 mock(NavigationModeController.class), mock(UserTracker.class), 189 mock(DumpManager.class))); 190 mNavigationBar = createNavBar(mContext); 191 mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal); 192 }); 193 } 194 195 @After tearDown()196 public void tearDown() throws Exception { 197 DeviceConfig.resetToDefaults( 198 Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI); 199 } 200 setupSysuiDependency()201 private void setupSysuiDependency() { 202 Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID, 203 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); 204 mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext( 205 display); 206 207 Display defaultDisplay = mContext.getDisplay(); 208 when(mWindowManager.getDefaultDisplay()).thenReturn(defaultDisplay); 209 WindowMetrics metrics = mContext.getSystemService(WindowManager.class) 210 .getMaximumWindowMetrics(); 211 when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics); 212 WindowMetrics currentWindowMetrics = mContext.getSystemService(WindowManager.class) 213 .getCurrentWindowMetrics(); 214 when(mWindowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics); 215 doNothing().when(mWindowManager).addView(any(), any()); 216 doNothing().when(mWindowManager).removeViewImmediate(any()); 217 mMockSysUiState = mock(SysUiState.class); 218 when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState); 219 220 mContext.addMockSystemService(WindowManager.class, mWindowManager); 221 mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager); 222 } 223 224 @Test testHomeLongPress()225 public void testHomeLongPress() { 226 mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); 227 mNavigationBar.onHomeLongClick(mNavigationBar.getView()); 228 229 verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS); 230 } 231 232 @Test testHomeLongPressWithCustomDuration()233 public void testHomeLongPressWithCustomDuration() throws Exception { 234 DeviceConfig.setProperties( 235 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI) 236 .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) 237 .build()); 238 when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); 239 mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); 240 241 mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( 242 /*downTime=*/SystemClock.uptimeMillis(), 243 /*eventTime=*/SystemClock.uptimeMillis(), 244 /*action=*/MotionEvent.ACTION_DOWN, 245 0, 0, 0 246 )); 247 verify(mHandler, times(1)).postDelayed(any(), eq(100L)); 248 249 mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( 250 /*downTime=*/SystemClock.uptimeMillis(), 251 /*eventTime=*/SystemClock.uptimeMillis(), 252 /*action=*/MotionEvent.ACTION_UP, 253 0, 0, 0 254 )); 255 256 verify(mHandler, times(1)).removeCallbacks(any()); 257 } 258 259 @Test testRegisteredWithDispatcher()260 public void testRegisteredWithDispatcher() { 261 mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); 262 verify(mBroadcastDispatcher).registerReceiverWithHandler( 263 any(BroadcastReceiver.class), 264 any(IntentFilter.class), 265 any(Handler.class), 266 any(UserHandle.class)); 267 } 268 269 @Test testSetImeWindowStatusWhenImeSwitchOnDisplay()270 public void testSetImeWindowStatusWhenImeSwitchOnDisplay() { 271 // Create default & external NavBar fragment. 272 NavigationBar defaultNavBar = mNavigationBar; 273 NavigationBar externalNavBar = mExternalDisplayNavigationBar; 274 NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class); 275 WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); 276 doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); 277 doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView(); 278 doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); 279 doNothing().when(defaultNavBar).checkNavBarModes(); 280 doNothing().when(externalNavBar).checkNavBarModes(); 281 defaultNavBar.createView(null); 282 externalNavBar.createView(null); 283 284 defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, 285 BACK_DISPOSITION_DEFAULT, true); 286 287 // Verify IME window state will be updated in default NavBar & external NavBar state reset. 288 assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, 289 defaultNavBar.getNavigationIconHints()); 290 assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); 291 assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); 292 293 externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE, 294 BACK_DISPOSITION_DEFAULT, true); 295 defaultNavBar.setImeWindowStatus( 296 DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false); 297 // Verify IME window state will be updated in external NavBar & default NavBar state reset. 298 assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, 299 externalNavBar.getNavigationIconHints()); 300 assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); 301 assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); 302 } 303 304 @Test testSetImeWindowStatusWhenKeyguardLockingAndImeInsetsChange()305 public void testSetImeWindowStatusWhenKeyguardLockingAndImeInsetsChange() { 306 NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class); 307 doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView(); 308 doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); 309 doNothing().when(mNavigationBar).checkNavBarModes(); 310 mNavigationBar.createView(null); 311 WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); 312 doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); 313 314 // Verify navbar altered back icon when an app is showing IME 315 mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, 316 BACK_DISPOSITION_DEFAULT, true); 317 assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); 318 assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); 319 320 // Verify navbar didn't alter and showing back icon when the keyguard is showing without 321 // requesting IME insets visible. 322 doReturn(true).when(mStatusBar).isKeyguardShowing(); 323 mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, 324 BACK_DISPOSITION_DEFAULT, true); 325 assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); 326 assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); 327 328 // Verify navbar altered and showing back icon when the keyguard is showing and 329 // requesting IME insets visible. 330 windowInsets = new WindowInsets.Builder().setVisible(ime(), true).build(); 331 doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); 332 mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, 333 BACK_DISPOSITION_DEFAULT, true); 334 assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); 335 assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); 336 } 337 338 @Test testA11yEventAfterDetach()339 public void testA11yEventAfterDetach() { 340 View v = mNavigationBar.createView(null); 341 mNavigationBar.onViewAttachedToWindow(v); 342 verify(mNavBarHelper).registerNavTaskStateUpdater(any( 343 NavBarHelper.NavbarTaskbarStateUpdater.class)); 344 mNavigationBar.onViewDetachedFromWindow(v); 345 verify(mNavBarHelper).removeNavTaskStateUpdater(any( 346 NavBarHelper.NavbarTaskbarStateUpdater.class)); 347 348 // Should be safe even though the internal view is now null. 349 mNavigationBar.updateAcessibilityStateFlags(); 350 } 351 createNavBar(Context context)352 private NavigationBar createNavBar(Context context) { 353 DeviceProvisionedController deviceProvisionedController = 354 mock(DeviceProvisionedController.class); 355 when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true); 356 NavigationBar.Factory factory = new NavigationBar.Factory( 357 () -> mAssistManager, 358 mock(AccessibilityManager.class), 359 deviceProvisionedController, 360 new MetricsLogger(), 361 mOverviewProxyService, 362 mNavigationModeController, 363 mock(AccessibilityButtonModeObserver.class), 364 mStatusBarStateController, 365 mMockSysUiState, 366 mBroadcastDispatcher, 367 mCommandQueue, 368 Optional.of(mock(Pip.class)), 369 Optional.of(mock(LegacySplitScreen.class)), 370 Optional.of(mock(Recents.class)), 371 () -> Optional.of(mStatusBar), 372 mock(ShadeController.class), 373 mock(NotificationRemoteInputManager.class), 374 mock(NotificationShadeDepthController.class), 375 mock(SystemActions.class), 376 mHandler, 377 mock(NavigationBarOverlayController.class), 378 mUiEventLogger, 379 mNavBarHelper, 380 mock(UserTracker.class), 381 mLightBarController, 382 mLightBarcontrollerFactory, 383 mAutoHideController, 384 mAutoHideControllerFactory, 385 Optional.of(mTelecomManager), 386 mInputMethodManager); 387 return spy(factory.create(context)); 388 } 389 processAllMessages()390 private void processAllMessages() { 391 TestableLooper.get(this).processAllMessages(); 392 } 393 } 394