1 /* 2 * Copyright (C) 2021 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.WINDOW_NAVIGATION_BAR; 20 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; 21 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; 22 23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; 24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; 25 26 import static com.google.common.truth.Truth.assertThat; 27 28 import static org.mockito.ArgumentMatchers.any; 29 import static org.mockito.ArgumentMatchers.anyBoolean; 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.Mockito.doAnswer; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.content.ComponentName; 38 import android.content.res.Configuration; 39 import android.view.IWindowManager; 40 import android.view.accessibility.AccessibilityManager; 41 42 import androidx.test.filters.SmallTest; 43 import androidx.test.runner.AndroidJUnit4; 44 45 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; 46 import com.android.systemui.SysuiTestCase; 47 import com.android.systemui.accessibility.AccessibilityButtonModeObserver; 48 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; 49 import com.android.systemui.accessibility.SystemActions; 50 import com.android.systemui.assist.AssistManager; 51 import com.android.systemui.dump.DumpManager; 52 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; 53 import com.android.systemui.recents.OverviewProxyService; 54 import com.android.systemui.settings.DisplayTracker; 55 import com.android.systemui.settings.UserTracker; 56 import com.android.systemui.statusbar.CommandQueue; 57 import com.android.systemui.statusbar.NotificationShadeWindowController; 58 import com.android.systemui.statusbar.phone.CentralSurfaces; 59 import com.android.systemui.statusbar.policy.ConfigurationController; 60 import com.android.systemui.statusbar.policy.FakeConfigurationController; 61 import com.android.systemui.statusbar.policy.KeyguardStateController; 62 63 import dagger.Lazy; 64 65 import org.junit.Before; 66 import org.junit.Test; 67 import org.junit.runner.RunWith; 68 import org.mockito.Mock; 69 import org.mockito.MockitoAnnotations; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.Optional; 74 import java.util.concurrent.Executor; 75 76 /** 77 * Tests for {@link NavBarHelper}. 78 */ 79 @RunWith(AndroidJUnit4.class) 80 @SmallTest 81 public class NavBarHelperTest extends SysuiTestCase { 82 83 private static final int DISPLAY_ID = 0; 84 private static final int WINDOW = WINDOW_NAVIGATION_BAR; 85 private static final int STATE_ID = 0; 86 87 @Mock 88 AccessibilityManager mAccessibilityManager; 89 @Mock 90 AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; 91 @Mock 92 AccessibilityButtonTargetsObserver mAccessibilityButtonTargetObserver; 93 @Mock 94 SystemActions mSystemActions; 95 @Mock 96 OverviewProxyService mOverviewProxyService; 97 @Mock 98 Lazy<AssistManager> mAssistManagerLazy; 99 @Mock 100 AssistManager mAssistManager; 101 @Mock 102 NavigationModeController mNavigationModeController; 103 @Mock 104 UserTracker mUserTracker; 105 @Mock 106 ComponentName mAssistantComponent; 107 @Mock 108 DumpManager mDumpManager; 109 @Mock 110 NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater; 111 @Mock 112 CommandQueue mCommandQueue; 113 @Mock 114 IWindowManager mWm; 115 @Mock 116 DisplayTracker mDisplayTracker; 117 @Mock 118 EdgeBackGestureHandler mEdgeBackGestureHandler; 119 @Mock 120 EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; 121 @Mock 122 NotificationShadeWindowController mNotificationShadeWindowController; 123 ConfigurationController mConfigurationController = new FakeConfigurationController(); 124 125 private AccessibilityManager.AccessibilityServicesStateChangeListener 126 mAccessibilityServicesStateChangeListener; 127 128 private static final long ACCESSIBILITY_BUTTON_CLICKABLE_STATE = 129 SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; 130 private NavBarHelper mNavBarHelper; 131 132 private final Executor mSynchronousExecutor = runnable -> runnable.run(); 133 134 @Before setup()135 public void setup() { 136 MockitoAnnotations.initMocks(this); 137 when(mAssistManagerLazy.get()).thenReturn(mAssistManager); 138 when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent); 139 when(mUserTracker.getUserId()).thenReturn(1); 140 when(mDisplayTracker.getDefaultDisplayId()).thenReturn(0); 141 when(mEdgeBackGestureHandlerFactory.create(any())).thenReturn(mEdgeBackGestureHandler); 142 143 doAnswer((invocation) -> mAccessibilityServicesStateChangeListener = 144 invocation.getArgument(0)).when( 145 mAccessibilityManager).addAccessibilityServicesStateChangeListener(any()); 146 mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager, 147 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver, 148 mSystemActions, mOverviewProxyService, mAssistManagerLazy, 149 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class), 150 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker, 151 mDisplayTracker, mNotificationShadeWindowController, mConfigurationController, 152 mDumpManager, mCommandQueue, mSynchronousExecutor); 153 } 154 155 @Test registerListenersInCtor()156 public void registerListenersInCtor() { 157 verify(mNavigationModeController, times(1)).addListener(mNavBarHelper); 158 verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper); 159 verify(mCommandQueue, times(1)).addCallback(any()); 160 } 161 162 @Test testSetupBarsRegistersListeners()163 public void testSetupBarsRegistersListeners() throws Exception { 164 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 165 verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper); 166 verify(mAccessibilityButtonTargetObserver, times(1)).addListener(mNavBarHelper); 167 verify(mAccessibilityManager, times(1)).addAccessibilityServicesStateChangeListener( 168 mNavBarHelper); 169 verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); 170 verify(mWm, times(1)).watchRotation(any(), anyInt()); 171 verify(mWm, times(1)).registerWallpaperVisibilityListener(any(), anyInt()); 172 verify(mEdgeBackGestureHandler, times(1)).onNavBarAttached(); 173 } 174 175 @Test testCleanupBarsUnregistersListeners()176 public void testCleanupBarsUnregistersListeners() throws Exception { 177 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 178 mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 179 verify(mAccessibilityButtonModeObserver, times(1)).removeListener(mNavBarHelper); 180 verify(mAccessibilityButtonTargetObserver, times(1)).removeListener(mNavBarHelper); 181 verify(mAccessibilityManager, times(1)).removeAccessibilityServicesStateChangeListener( 182 mNavBarHelper); 183 verify(mWm, times(1)).removeRotationWatcher(any()); 184 verify(mWm, times(1)).unregisterWallpaperVisibilityListener(any(), anyInt()); 185 verify(mEdgeBackGestureHandler, times(1)).onNavBarDetached(); 186 } 187 188 @Test replacingBarsHint()189 public void replacingBarsHint() { 190 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 191 mNavBarHelper.setTogglingNavbarTaskbar(true); 192 mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 193 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 194 mNavBarHelper.setTogglingNavbarTaskbar(false); 195 // Use any state in cleanup to verify it was not called 196 verify(mAccessibilityButtonModeObserver, times(0)).removeListener(mNavBarHelper); 197 } 198 199 @Test callbacksFiredWhenRegistering()200 public void callbacksFiredWhenRegistering() { 201 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 202 verify(mNavbarTaskbarStateUpdater, times(1)) 203 .updateAccessibilityServicesState(); 204 verify(mNavbarTaskbarStateUpdater, times(1)) 205 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 206 verify(mNavbarTaskbarStateUpdater, times(1)) 207 .updateRotationWatcherState(anyInt()); 208 verify(mNavbarTaskbarStateUpdater, times(1)) 209 .updateWallpaperVisibility(anyBoolean(), anyInt()); 210 } 211 212 @Test assistantCallbacksFiredAfterConnecting()213 public void assistantCallbacksFiredAfterConnecting() { 214 // 1st set of callbacks get called when registering 215 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 216 217 mNavBarHelper.onConnectionChanged(false); 218 // assert no more callbacks fired 219 verify(mNavbarTaskbarStateUpdater, times(1)) 220 .updateAccessibilityServicesState(); 221 verify(mNavbarTaskbarStateUpdater, times(1)) 222 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 223 224 mNavBarHelper.onConnectionChanged(true); 225 // assert no more callbacks fired 226 verify(mNavbarTaskbarStateUpdater, times(1)) 227 .updateAccessibilityServicesState(); 228 verify(mNavbarTaskbarStateUpdater, times(2)) 229 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 230 } 231 232 @Test a11yCallbacksFiredAfterModeChange()233 public void a11yCallbacksFiredAfterModeChange() { 234 // 1st set of callbacks get called when registering 235 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 236 237 mNavBarHelper.onAccessibilityButtonModeChanged(0); 238 verify(mNavbarTaskbarStateUpdater, times(2)) 239 .updateAccessibilityServicesState(); 240 verify(mNavbarTaskbarStateUpdater, times(1)) 241 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 242 } 243 244 @Test assistantCallbacksFiredAfterNavModeChange()245 public void assistantCallbacksFiredAfterNavModeChange() { 246 // 1st set of callbacks get called when registering 247 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 248 249 mNavBarHelper.onNavigationModeChanged(0); 250 verify(mNavbarTaskbarStateUpdater, times(1)) 251 .updateAccessibilityServicesState(); 252 verify(mNavbarTaskbarStateUpdater, times(2)) 253 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 254 } 255 256 @Test removeListenerNoCallbacksFired()257 public void removeListenerNoCallbacksFired() { 258 // 1st set of callbacks get called when registering 259 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 260 261 // Remove listener 262 mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 263 264 // Would have fired 2nd callback if not removed 265 mNavBarHelper.onAccessibilityButtonModeChanged(0); 266 267 // assert no more callbacks fired 268 verify(mNavbarTaskbarStateUpdater, times(1)) 269 .updateAccessibilityServicesState(); 270 verify(mNavbarTaskbarStateUpdater, times(1)) 271 .updateAssistantAvailable(anyBoolean(), anyBoolean()); 272 } 273 274 @Test initNavBarHelper_buttonModeNavBar_a11yButtonClickableState()275 public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() { 276 when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE)) 277 .thenReturn(createFakeShortcutTargets()); 278 279 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 280 281 assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo( 282 ACCESSIBILITY_BUTTON_CLICKABLE_STATE); 283 } 284 285 @Test initAccessibilityStateWithFloatingMenuModeAndTargets_disableClickableState()286 public void initAccessibilityStateWithFloatingMenuModeAndTargets_disableClickableState() { 287 when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( 288 ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); 289 290 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 291 292 assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(/* disable_clickable_state */ 0); 293 } 294 295 @Test onA11yServicesStateChangedWithMultipleServices_a11yButtonClickableState()296 public void onA11yServicesStateChangedWithMultipleServices_a11yButtonClickableState() { 297 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 298 when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn( 299 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); 300 301 when(mAccessibilityManager.getAccessibilityShortcutTargets(UserShortcutType.SOFTWARE)) 302 .thenReturn(createFakeShortcutTargets()); 303 mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged( 304 mAccessibilityManager); 305 306 assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo( 307 ACCESSIBILITY_BUTTON_CLICKABLE_STATE); 308 } 309 310 @Test saveMostRecentSysuiState()311 public void saveMostRecentSysuiState() { 312 mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID); 313 NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState(); 314 315 // Update window state 316 int newState = STATE_ID + 1; 317 mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, newState); 318 NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState(); 319 320 // Ensure we get most recent state back 321 assertThat(state1.mWindowState).isNotEqualTo(state2.mWindowState); 322 assertThat(state1.mWindowStateDisplayId).isEqualTo(state2.mWindowStateDisplayId); 323 assertThat(state2.mWindowState).isEqualTo(newState); 324 } 325 326 @Test ignoreNonNavbarSysuiState()327 public void ignoreNonNavbarSysuiState() { 328 mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID); 329 NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState(); 330 331 // Update window state for other window type 332 int newState = STATE_ID + 1; 333 mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW + 1, newState); 334 NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState(); 335 336 // Ensure we get first state back 337 assertThat(state2.mWindowState).isEqualTo(state1.mWindowState); 338 assertThat(state2.mWindowState).isNotEqualTo(newState); 339 } 340 341 @Test configUpdatePropagatesToEdgeBackGestureHandler()342 public void configUpdatePropagatesToEdgeBackGestureHandler() { 343 mConfigurationController.onConfigurationChanged(Configuration.EMPTY); 344 verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any()); 345 } 346 createFakeShortcutTargets()347 private List<String> createFakeShortcutTargets() { 348 return new ArrayList<>(List.of("a", "b", "c", "d")); 349 } 350 } 351