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