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.accessibility;
18 
19 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
20 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
21 
22 import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
23 import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
24 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
26 
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyBoolean;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.ArgumentMatchers.isNull;
32 import static org.mockito.Mockito.doAnswer;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.content.Context;
39 import android.graphics.Rect;
40 import android.hardware.display.DisplayManager;
41 import android.os.RemoteException;
42 import android.testing.AndroidTestingRunner;
43 import android.testing.TestableLooper;
44 import android.view.Display;
45 import android.view.accessibility.AccessibilityManager;
46 import android.view.accessibility.IWindowMagnificationConnection;
47 import android.view.accessibility.IWindowMagnificationConnectionCallback;
48 
49 import androidx.test.filters.SmallTest;
50 
51 import com.android.systemui.SysuiTestCase;
52 import com.android.systemui.model.SysUiState;
53 import com.android.systemui.recents.OverviewProxyService;
54 import com.android.systemui.settings.FakeDisplayTracker;
55 import com.android.systemui.statusbar.CommandQueue;
56 import com.android.systemui.util.settings.SecureSettings;
57 
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 
65 @SmallTest
66 @RunWith(AndroidTestingRunner.class)
67 @TestableLooper.RunWithLooper
68 public class WindowMagnificationTest extends SysuiTestCase {
69 
70     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
71     @Mock
72     private AccessibilityManager mAccessibilityManager;
73     @Mock
74     private ModeSwitchesController mModeSwitchesController;
75     @Mock
76     private SysUiState mSysUiState;
77     @Mock
78     private IWindowMagnificationConnectionCallback mConnectionCallback;
79     @Mock
80     private OverviewProxyService mOverviewProxyService;
81     @Mock
82     private SecureSettings mSecureSettings;
83 
84     private CommandQueue mCommandQueue;
85     private WindowMagnification mWindowMagnification;
86     private OverviewProxyListener mOverviewProxyListener;
87     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
88 
89     @Mock
90     private WindowMagnificationController mWindowMagnificationController;
91     @Mock
92     private MagnificationSettingsController mMagnificationSettingsController;
93     @Mock
94     private AccessibilityLogger mA11yLogger;
95 
96     @Before
setUp()97     public void setUp() throws Exception {
98         MockitoAnnotations.initMocks(this);
99         getContext().addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
100         doAnswer(invocation -> {
101             IWindowMagnificationConnection connection = invocation.getArgument(0);
102             connection.setConnectionCallback(mConnectionCallback);
103             return null;
104         }).when(mAccessibilityManager).setWindowMagnificationConnection(
105                 any(IWindowMagnificationConnection.class));
106 
107         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
108 
109         doAnswer(invocation -> {
110             mWindowMagnification.mMagnificationSettingsControllerCallback
111                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true);
112             return null;
113         }).when(mMagnificationSettingsController).toggleSettingsPanelVisibility();
114         doAnswer(invocation -> {
115             mWindowMagnification.mMagnificationSettingsControllerCallback
116                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false);
117             return null;
118         }).when(mMagnificationSettingsController).closeMagnificationSettings();
119 
120         mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
121         mWindowMagnification = new WindowMagnification(getContext(),
122                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
123                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
124                 getContext().getSystemService(DisplayManager.class), mA11yLogger);
125         mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
126                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
127         mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
128                 mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
129         mWindowMagnification.start();
130 
131         final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
132                 ArgumentCaptor.forClass(OverviewProxyListener.class);
133         verify(mOverviewProxyService).addCallback(listenerArgumentCaptor.capture());
134         mOverviewProxyListener = listenerArgumentCaptor.getValue();
135     }
136 
137     @Test
requestWindowMagnificationConnection_setConnectionAndListener()138     public void requestWindowMagnificationConnection_setConnectionAndListener() {
139         mCommandQueue.requestWindowMagnificationConnection(true);
140         waitForIdleSync();
141 
142         verify(mAccessibilityManager).setWindowMagnificationConnection(any(
143                 IWindowMagnificationConnection.class));
144 
145         mCommandQueue.requestWindowMagnificationConnection(false);
146         waitForIdleSync();
147 
148         verify(mAccessibilityManager).setWindowMagnificationConnection(isNull());
149     }
150 
151     @Test
onWindowMagnifierBoundsChanged()152     public void onWindowMagnifierBoundsChanged() throws RemoteException {
153         final Rect testBounds = new Rect(0, 0, 500, 600);
154         mCommandQueue.requestWindowMagnificationConnection(true);
155         waitForIdleSync();
156 
157         mWindowMagnification.mWindowMagnifierCallback
158                 .onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
159 
160         verify(mConnectionCallback).onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
161     }
162 
163     @Test
onPerformScaleAction_enabled_notifyCallback()164     public void onPerformScaleAction_enabled_notifyCallback() throws RemoteException {
165         final float newScale = 4.0f;
166         final boolean updatePersistence = true;
167         mCommandQueue.requestWindowMagnificationConnection(true);
168         waitForIdleSync();
169 
170         mWindowMagnification.mWindowMagnifierCallback
171                 .onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
172 
173         verify(mConnectionCallback).onPerformScaleAction(
174                 eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
175     }
176 
177     @Test
onAccessibilityActionPerformed_enabled_notifyCallback()178     public void onAccessibilityActionPerformed_enabled_notifyCallback() throws RemoteException {
179         mCommandQueue.requestWindowMagnificationConnection(true);
180         waitForIdleSync();
181 
182         mWindowMagnification.mWindowMagnifierCallback
183                 .onAccessibilityActionPerformed(TEST_DISPLAY);
184 
185         verify(mConnectionCallback).onAccessibilityActionPerformed(TEST_DISPLAY);
186     }
187 
188     @Test
onMove_enabled_notifyCallback()189     public void onMove_enabled_notifyCallback() throws RemoteException {
190         mCommandQueue.requestWindowMagnificationConnection(true);
191         waitForIdleSync();
192 
193         mWindowMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY);
194 
195         verify(mConnectionCallback).onMove(TEST_DISPLAY);
196     }
197 
198     @Test
onClickSettingsButton_enabled_showPanelForWindowMode()199     public void onClickSettingsButton_enabled_showPanelForWindowMode() {
200         mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
201         waitForIdleSync();
202 
203         verify(mMagnificationSettingsController).toggleSettingsPanelVisibility();
204         verify(mA11yLogger).log(
205                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED));
206     }
207 
208     @Test
onSetMagnifierSize_delegateToMagnifier()209     public void onSetMagnifierSize_delegateToMagnifier() {
210         final @MagnificationSize int index = MagnificationSize.SMALL;
211         mWindowMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize(
212                 TEST_DISPLAY, index);
213         waitForIdleSync();
214 
215         verify(mWindowMagnificationController).changeMagnificationSize(eq(index));
216         verify(mA11yLogger).log(
217                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED));
218     }
219 
220     @Test
onSetDiagonalScrolling_delegateToMagnifier()221     public void onSetDiagonalScrolling_delegateToMagnifier() {
222         mWindowMagnification.mMagnificationSettingsControllerCallback.onSetDiagonalScrolling(
223                 TEST_DISPLAY, /* enable= */ true);
224         waitForIdleSync();
225 
226         verify(mWindowMagnificationController).setDiagonalScrolling(eq(true));
227     }
228 
229     @Test
onEditMagnifierSizeMode_windowActivated_delegateToMagnifier()230     public void onEditMagnifierSizeMode_windowActivated_delegateToMagnifier() {
231         when(mWindowMagnificationController.isActivated()).thenReturn(true);
232         mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
233                 TEST_DISPLAY, /* enable= */ true);
234         waitForIdleSync();
235 
236         verify(mWindowMagnificationController).setEditMagnifierSizeMode(eq(true));
237         verify(mA11yLogger).log(
238                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED));
239 
240         mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
241                 TEST_DISPLAY, /* enable= */ false);
242         waitForIdleSync();
243         verify(mA11yLogger).log(
244                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED));
245         verify(mA11yLogger).log(
246                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED));
247     }
248 
249     @Test
onMagnifierScale_notifyCallback()250     public void onMagnifierScale_notifyCallback() throws RemoteException {
251         mCommandQueue.requestWindowMagnificationConnection(true);
252         waitForIdleSync();
253         final float scale = 3.0f;
254         final boolean updatePersistence = false;
255         mWindowMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale(
256                 TEST_DISPLAY, scale, updatePersistence);
257 
258         verify(mConnectionCallback).onPerformScaleAction(
259                 eq(TEST_DISPLAY), eq(scale), eq(updatePersistence));
260     }
261 
262     @Test
onModeSwitch_windowEnabledAndSwitchToFullscreen_hidePanelAndNotifyCallback()263     public void onModeSwitch_windowEnabledAndSwitchToFullscreen_hidePanelAndNotifyCallback()
264             throws RemoteException {
265         when(mWindowMagnificationController.isActivated()).thenReturn(true);
266         mCommandQueue.requestWindowMagnificationConnection(true);
267         waitForIdleSync();
268 
269         mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
270                 TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
271         waitForIdleSync();
272 
273         verify(mMagnificationSettingsController).closeMagnificationSettings();
274         verify(mA11yLogger).log(
275                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED));
276         verify(mConnectionCallback).onChangeMagnificationMode(eq(TEST_DISPLAY),
277                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
278     }
279 
280     @Test
onModeSwitch_switchToSameMode_doNothing()281     public void onModeSwitch_switchToSameMode_doNothing()
282             throws RemoteException {
283         when(mWindowMagnificationController.isActivated()).thenReturn(true);
284         mCommandQueue.requestWindowMagnificationConnection(true);
285         waitForIdleSync();
286 
287         mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
288                 TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
289         waitForIdleSync();
290 
291         verify(mMagnificationSettingsController, never()).closeMagnificationSettings();
292         verify(mConnectionCallback, never()).onChangeMagnificationMode(eq(TEST_DISPLAY),
293                 /* magnificationMode = */ anyInt());
294     }
295 
296     @Test
onSettingsPanelVisibilityChanged_windowActivated_delegateToMagnifier()297     public void onSettingsPanelVisibilityChanged_windowActivated_delegateToMagnifier() {
298         when(mWindowMagnificationController.isActivated()).thenReturn(true);
299         final boolean shown = false;
300         mWindowMagnification.mMagnificationSettingsControllerCallback
301                 .onSettingsPanelVisibilityChanged(TEST_DISPLAY, shown);
302         waitForIdleSync();
303 
304         verify(mWindowMagnificationController).updateDragHandleResourcesIfNeeded(eq(shown));
305         verify(mA11yLogger).log(
306                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED));
307     }
308 
309     @Test
overviewProxyIsConnected_noController_resetFlag()310     public void overviewProxyIsConnected_noController_resetFlag() {
311         mOverviewProxyListener.onConnectionChanged(true);
312 
313         verify(mSysUiState).setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false);
314         verify(mSysUiState).commitUpdate(mContext.getDisplayId());
315     }
316 
317     @Test
overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag()318     public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
319         final WindowMagnificationController mController = mock(WindowMagnificationController.class);
320         mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
321                 mContext.getSystemService(DisplayManager.class), mController);
322         mWindowMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
323 
324         mOverviewProxyListener.onConnectionChanged(true);
325 
326         verify(mController).updateSysUIStateFlag();
327     }
328 
329     private static class FakeControllerSupplier extends
330             DisplayIdIndexSupplier<WindowMagnificationController> {
331 
332         private final WindowMagnificationController mController;
333 
FakeControllerSupplier(DisplayManager displayManager, WindowMagnificationController controller)334         FakeControllerSupplier(DisplayManager displayManager,
335                 WindowMagnificationController controller) {
336             super(displayManager);
337             mController = controller;
338         }
339 
340         @Override
createInstance(Display display)341         protected WindowMagnificationController createInstance(Display display) {
342             return mController;
343         }
344     }
345 
346     private static class FakeSettingsSupplier extends
347             DisplayIdIndexSupplier<MagnificationSettingsController> {
348 
349         private final MagnificationSettingsController mController;
350 
FakeSettingsSupplier(DisplayManager displayManager, MagnificationSettingsController controller)351         FakeSettingsSupplier(DisplayManager displayManager,
352                 MagnificationSettingsController controller) {
353             super(displayManager);
354             mController = controller;
355         }
356 
357         @Override
createInstance(Display display)358         protected MagnificationSettingsController createInstance(Display display) {
359             return mController;
360         }
361     }
362 }
363