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