1 /*
2  * Copyright (C) 2019 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.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
20 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
21 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
22 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
23 import static android.view.Choreographer.FrameCallback;
24 import static android.view.MotionEvent.ACTION_DOWN;
25 import static android.view.MotionEvent.ACTION_UP;
26 import static android.view.WindowInsets.Type.systemGestures;
27 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
28 
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
30 
31 import static org.hamcrest.Matchers.containsString;
32 import static org.hamcrest.Matchers.hasItems;
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertNotEquals;
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertThat;
38 import static org.junit.Assert.assertTrue;
39 import static org.mockito.AdditionalAnswers.returnsSecondArg;
40 import static org.mockito.ArgumentMatchers.any;
41 import static org.mockito.ArgumentMatchers.anyFloat;
42 import static org.mockito.ArgumentMatchers.anyInt;
43 import static org.mockito.ArgumentMatchers.anyString;
44 import static org.mockito.ArgumentMatchers.eq;
45 import static org.mockito.Mockito.atLeast;
46 import static org.mockito.Mockito.atLeastOnce;
47 import static org.mockito.Mockito.doAnswer;
48 import static org.mockito.Mockito.spy;
49 import static org.mockito.Mockito.timeout;
50 import static org.mockito.Mockito.times;
51 import static org.mockito.Mockito.verify;
52 import static org.mockito.Mockito.when;
53 
54 import android.animation.ValueAnimator;
55 import android.annotation.IdRes;
56 import android.app.Instrumentation;
57 import android.content.Context;
58 import android.content.pm.ActivityInfo;
59 import android.content.res.Configuration;
60 import android.content.res.Resources;
61 import android.graphics.Insets;
62 import android.graphics.PointF;
63 import android.graphics.Rect;
64 import android.graphics.Region;
65 import android.graphics.RegionIterator;
66 import android.os.Handler;
67 import android.os.RemoteException;
68 import android.os.SystemClock;
69 import android.testing.AndroidTestingRunner;
70 import android.testing.TestableLooper;
71 import android.testing.TestableResources;
72 import android.text.TextUtils;
73 import android.view.Display;
74 import android.view.IWindowSession;
75 import android.view.MotionEvent;
76 import android.view.Surface;
77 import android.view.SurfaceControl;
78 import android.view.View;
79 import android.view.WindowInsets;
80 import android.view.WindowManager;
81 import android.view.WindowManagerGlobal;
82 import android.view.accessibility.AccessibilityNodeInfo;
83 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
84 
85 import androidx.test.InstrumentationRegistry;
86 import androidx.test.filters.LargeTest;
87 
88 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
89 import com.android.systemui.R;
90 import com.android.systemui.SysuiTestCase;
91 import com.android.systemui.model.SysUiState;
92 import com.android.systemui.settings.FakeDisplayTracker;
93 import com.android.systemui.util.leak.ReferenceTestUtils;
94 import com.android.systemui.util.settings.SecureSettings;
95 import com.android.systemui.utils.os.FakeHandler;
96 
97 import com.google.common.util.concurrent.AtomicDouble;
98 
99 import org.junit.After;
100 import org.junit.Assume;
101 import org.junit.Before;
102 import org.junit.Test;
103 import org.junit.runner.RunWith;
104 import org.mockito.Answers;
105 import org.mockito.ArgumentCaptor;
106 import org.mockito.Mock;
107 import org.mockito.Mockito;
108 import org.mockito.MockitoAnnotations;
109 
110 import java.util.List;
111 import java.util.concurrent.CountDownLatch;
112 import java.util.concurrent.TimeUnit;
113 import java.util.concurrent.atomic.AtomicInteger;
114 
115 @LargeTest
116 @TestableLooper.RunWithLooper
117 @RunWith(AndroidTestingRunner.class)
118 public class WindowMagnificationControllerTest extends SysuiTestCase {
119 
120     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
121     private static final long ANIMATION_DURATION_MS = 300;
122     private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
123     @Mock
124     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
125     @Mock
126     private MirrorWindowControl mMirrorWindowControl;
127     @Mock
128     private WindowMagnifierCallback mWindowMagnifierCallback;
129     @Mock
130     IRemoteMagnificationAnimationCallback mAnimationCallback;
131     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
132     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
133     @Mock
134     private SecureSettings mSecureSettings;
135 
136     private Handler mHandler;
137     private TestableWindowManager mWindowManager;
138     private SysUiState mSysUiState;
139     private Resources mResources;
140     private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
141     private WindowMagnificationController mWindowMagnificationController;
142     private Instrumentation mInstrumentation;
143     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
144     private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
145 
146     private IWindowSession mWindowSessionSpy;
147 
148     private View mSpyView;
149     private View.OnTouchListener mTouchListener;
150     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
151 
152     /**
153      *  return whether window magnification is supported for current test context.
154      */
isWindowModeSupported()155     private boolean isWindowModeSupported() {
156         return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
157     }
158 
159     @Before
setUp()160     public void setUp() throws Exception {
161         MockitoAnnotations.initMocks(this);
162         mContext = Mockito.spy(getContext());
163         mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
164         mInstrumentation = InstrumentationRegistry.getInstrumentation();
165         final WindowManager wm = mContext.getSystemService(WindowManager.class);
166         mWindowManager = spy(new TestableWindowManager(wm));
167 
168         mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession());
169 
170         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
171         doAnswer(invocation -> {
172             FrameCallback callback = invocation.getArgument(0);
173             callback.doFrame(0);
174             return null;
175         }).when(mSfVsyncFrameProvider).postFrameCallback(
176                 any(FrameCallback.class));
177         mSysUiState = new SysUiState(mDisplayTracker);
178         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
179         when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
180                 returnsSecondArg());
181         when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
182                 returnsSecondArg());
183 
184         mResources = getContext().getOrCreateTestableResources().getResources();
185         // prevent the config orientation from undefined, which may cause config.diff method
186         // neglecting the orientation update.
187         if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
188             mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
189         }
190 
191         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
192                 mContext, mValueAnimator);
193         mWindowMagnificationController =
194                 new WindowMagnificationController(
195                         mContext,
196                         mHandler,
197                         mWindowMagnificationAnimationController,
198                         mSfVsyncFrameProvider,
199                         mMirrorWindowControl,
200                         mTransaction,
201                         mWindowMagnifierCallback,
202                         mSysUiState,
203                         () -> mWindowSessionSpy,
204                         mSecureSettings);
205 
206         verify(mMirrorWindowControl).setWindowDelegate(
207                 any(MirrorWindowControl.MirrorWindowDelegate.class));
208         mSpyView = Mockito.spy(new View(mContext));
209         doAnswer((invocation) -> {
210             mTouchListener = invocation.getArgument(0);
211             return null;
212         }).when(mSpyView).setOnTouchListener(
213                 any(View.OnTouchListener.class));
214 
215         // skip test if window magnification is not supported to prevent fail results. (b/279820875)
216         Assume.assumeTrue(isWindowModeSupported());
217     }
218 
219     @After
tearDown()220     public void tearDown() {
221         mInstrumentation.runOnMainSync(
222                 () -> mWindowMagnificationController.deleteWindowMagnification());
223         mValueAnimator.cancel();
224     }
225 
226     @Test
enableWindowMagnification_showControlAndNotifyBoundsChanged()227     public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
228         mInstrumentation.runOnMainSync(() -> {
229             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
230                     Float.NaN);
231         });
232 
233         verify(mMirrorWindowControl).showControl();
234         verify(mWindowMagnifierCallback,
235                 timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
236                 eq(mContext.getDisplayId()), any(Rect.class));
237     }
238 
239     @Test
enableWindowMagnification_notifySourceBoundsChanged()240     public void enableWindowMagnification_notifySourceBoundsChanged() {
241         mInstrumentation.runOnMainSync(
242                 () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
243                         Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
244                 /* magnificationFrameOffsetRatioY= */ 0, null));
245 
246         // Waits for the surface created
247         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
248                 (eq(mContext.getDisplayId())), any());
249     }
250 
251     @Test
enableWindowMagnification_disabled_notifySourceBoundsChanged()252     public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
253         enableWindowMagnification_notifySourceBoundsChanged();
254         mInstrumentation.runOnMainSync(
255                 () -> mWindowMagnificationController.deleteWindowMagnification(null));
256         Mockito.reset(mWindowMagnifierCallback);
257 
258         enableWindowMagnification_notifySourceBoundsChanged();
259     }
260 
261     @Test
enableWindowMagnification_withAnimation_schedulesFrame()262     public void enableWindowMagnification_withAnimation_schedulesFrame() {
263         mInstrumentation.runOnMainSync(() -> {
264             mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
265                     10, /* magnificationFrameOffsetRatioX= */ 0,
266                     /* magnificationFrameOffsetRatioY= */ 0,
267                     Mockito.mock(IRemoteMagnificationAnimationCallback.class));
268         });
269 
270         verify(mSfVsyncFrameProvider,
271                 timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any());
272     }
273 
274     @Test
moveWindowMagnifier_enabled_notifySourceBoundsChanged()275     public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
276         mInstrumentation.runOnMainSync(() -> {
277             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
278                     Float.NaN, 0, 0, null);
279         });
280 
281         mInstrumentation.runOnMainSync(() -> {
282             mWindowMagnificationController.moveWindowMagnifier(10, 10);
283         });
284 
285         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
286         verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
287                 (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
288         assertEquals(mWindowMagnificationController.getCenterX(),
289                 sourceBoundsCaptor.getValue().exactCenterX(), 0);
290         assertEquals(mWindowMagnificationController.getCenterY(),
291                 sourceBoundsCaptor.getValue().exactCenterY(), 0);
292     }
293 
294     @Test
enableWindowMagnification_systemGestureExclusionRectsIsSet()295     public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
296         mInstrumentation.runOnMainSync(() -> {
297             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
298                     Float.NaN);
299         });
300         // Wait for Rects updated.
301         waitForIdleSync();
302 
303         List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects();
304         assertFalse(rects.isEmpty());
305     }
306 
307     @Test
enableWindowMagnification_LargeScreen_windowSizeIsConstrained()308     public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
309         final int screenSize = mContext.getResources().getDimensionPixelSize(
310                 R.dimen.magnification_max_frame_size) * 10;
311         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
312 
313         mInstrumentation.runOnMainSync(() -> {
314             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
315                     Float.NaN);
316         });
317 
318         final int halfScreenSize = screenSize / 2;
319         WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
320         // The frame size should be the half of smaller value of window height/width unless it
321         //exceed the max frame size.
322         assertTrue(params.width < halfScreenSize);
323         assertTrue(params.height < halfScreenSize);
324     }
325 
326     @Test
327     public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
328         mInstrumentation.runOnMainSync(
329                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
330                         Float.NaN,
331                         Float.NaN));
332 
333         mInstrumentation.runOnMainSync(
334                 () -> mWindowMagnificationController.deleteWindowMagnification());
335 
336         verify(mMirrorWindowControl).destroyControl();
337         verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
338     }
339 
340     @Test
deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse()341     public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
342         final WindowManager wm = mContext.getSystemService(WindowManager.class);
343         final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
344         setSystemGestureInsets();
345 
346         mInstrumentation.runOnMainSync(() -> {
347             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
348                     bounds.bottom);
349         });
350         ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
351 
352         mInstrumentation.runOnMainSync(() -> {
353             mWindowMagnificationController.deleteWindowMagnification();
354         });
355 
356         verify(mMirrorWindowControl).destroyControl();
357         assertFalse(hasMagnificationOverlapFlag());
358     }
359 
360     @Test
deleteWindowMagnification_notifySourceBoundsChanged()361     public void deleteWindowMagnification_notifySourceBoundsChanged() {
362         mInstrumentation.runOnMainSync(
363                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
364                         Float.NaN,
365                         Float.NaN));
366 
367         mInstrumentation.runOnMainSync(
368                 () -> mWindowMagnificationController.deleteWindowMagnification());
369 
370         // The first time is for notifying magnification enabled and the second time is for
371         // notifying magnification disabled.
372         verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
373                 (eq(mContext.getDisplayId())), any());
374     }
375 
376     @Test
moveMagnifier_schedulesFrame()377     public void moveMagnifier_schedulesFrame() {
378         mInstrumentation.runOnMainSync(() -> {
379             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
380                     Float.NaN);
381             mWindowMagnificationController.moveWindowMagnifier(100f, 100f);
382         });
383 
384         verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
385     }
386 
387     @Test
moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()388     public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
389             throws InterruptedException {
390         mInstrumentation.runOnMainSync(() -> {
391             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
392                     Float.NaN, 0, 0, null);
393         });
394         final CountDownLatch countDownLatch = new CountDownLatch(1);
395         final MockMagnificationAnimationCallback animationCallback =
396                 new MockMagnificationAnimationCallback(countDownLatch);
397         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
398         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
399                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
400         final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
401         final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
402 
403         mInstrumentation.runOnMainSync(() -> {
404             mWindowMagnificationController.moveWindowMagnifierToPosition(
405                     targetCenterX, targetCenterY, animationCallback);
406         });
407 
408         assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
409         assertEquals(1, animationCallback.getSuccessCount());
410         assertEquals(0, animationCallback.getFailedCount());
411         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
412                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
413         assertEquals(mWindowMagnificationController.getCenterX(),
414                 sourceBoundsCaptor.getValue().exactCenterX(), 0);
415         assertEquals(mWindowMagnificationController.getCenterY(),
416                 sourceBoundsCaptor.getValue().exactCenterY(), 0);
417         assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
418         assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
419     }
420 
421     @Test
moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()422     public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
423             throws InterruptedException {
424         mInstrumentation.runOnMainSync(() -> {
425             mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
426                     Float.NaN, 0, 0, null);
427         });
428         final CountDownLatch countDownLatch = new CountDownLatch(4);
429         final MockMagnificationAnimationCallback animationCallback =
430                 new MockMagnificationAnimationCallback(countDownLatch);
431         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
432         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
433                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
434         final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
435         final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
436 
437         mInstrumentation.runOnMainSync(() -> {
438             mWindowMagnificationController.moveWindowMagnifierToPosition(
439                     centerX + 10, centerY + 10, animationCallback);
440             mWindowMagnificationController.moveWindowMagnifierToPosition(
441                     centerX + 20, centerY + 20, animationCallback);
442             mWindowMagnificationController.moveWindowMagnifierToPosition(
443                     centerX + 30, centerY + 30, animationCallback);
444             mWindowMagnificationController.moveWindowMagnifierToPosition(
445                     centerX + 40, centerY + 40, animationCallback);
446         });
447 
448         assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
449         // only the last one callback will return true
450         assertEquals(1, animationCallback.getSuccessCount());
451         // the others will return false
452         assertEquals(3, animationCallback.getFailedCount());
453         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
454                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
455         assertEquals(mWindowMagnificationController.getCenterX(),
456                 sourceBoundsCaptor.getValue().exactCenterX(), 0);
457         assertEquals(mWindowMagnificationController.getCenterY(),
458                 sourceBoundsCaptor.getValue().exactCenterY(), 0);
459         assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
460         assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
461     }
462 
463     @Test
setScale_enabled_expectedValueAndUpdateStateDescription()464     public void setScale_enabled_expectedValueAndUpdateStateDescription() {
465         mInstrumentation.runOnMainSync(
466                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
467                         Float.NaN, Float.NaN));
468 
469         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
470 
471         assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
472         final View mirrorView = mWindowManager.getAttachedView();
473         assertNotNull(mirrorView);
474         assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
475     }
476 
477     @Test
onConfigurationChanged_disabled_withoutException()478     public void onConfigurationChanged_disabled_withoutException() {
479         Display display = Mockito.spy(mContext.getDisplay());
480         when(display.getRotation()).thenReturn(Surface.ROTATION_90);
481         when(mContext.getDisplay()).thenReturn(display);
482 
483         mInstrumentation.runOnMainSync(() -> {
484             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
485             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
486             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
487             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
488         });
489     }
490 
491     @Test
onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition()492     public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
493         final int newRotation = simulateRotateTheDevice();
494         final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
495         final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
496         final float displayWidth = windowBounds.width();
497         final PointF magnifiedCenter = new PointF(center, center + 5f);
498         mInstrumentation.runOnMainSync(() -> {
499             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
500                     magnifiedCenter.x, magnifiedCenter.y);
501             // Get the center again in case the center we set is out of screen.
502             magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
503                     mWindowMagnificationController.getCenterY());
504         });
505         // Rotate the window clockwise 90 degree.
506         windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
507                 windowBounds.right);
508         mWindowManager.setWindowBounds(windowBounds);
509 
510         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
511                 ActivityInfo.CONFIG_ORIENTATION));
512 
513         assertEquals(newRotation, mWindowMagnificationController.mRotation);
514         final PointF expectedCenter = new PointF(magnifiedCenter.y,
515                 displayWidth - magnifiedCenter.x);
516         final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
517                 mWindowMagnificationController.getCenterY());
518         assertEquals(expectedCenter, actualCenter);
519     }
520 
521     @Test
onOrientationChanged_disabled_updateDisplayRotation()522     public void onOrientationChanged_disabled_updateDisplayRotation() {
523         final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
524         // Rotate the window clockwise 90 degree.
525         windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
526                 windowBounds.right);
527         mWindowManager.setWindowBounds(windowBounds);
528         final int newRotation = simulateRotateTheDevice();
529 
530         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
531                 ActivityInfo.CONFIG_ORIENTATION));
532 
533         assertEquals(newRotation, mWindowMagnificationController.mRotation);
534     }
535 
536     @Test
onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio()537     public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
538         // The default position is at the center of the screen.
539         final float expectedRatio = 0.5f;
540         final Rect testWindowBounds = new Rect(
541                 mWindowManager.getCurrentWindowMetrics().getBounds());
542         testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
543                 testWindowBounds.right + 100, testWindowBounds.bottom + 100);
544         mInstrumentation.runOnMainSync(() -> {
545             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
546                     Float.NaN);
547         });
548         mWindowManager.setWindowBounds(testWindowBounds);
549 
550         mInstrumentation.runOnMainSync(() -> {
551             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
552         });
553 
554         // The ratio of center to window size should be the same.
555         assertEquals(expectedRatio,
556                 mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
557                 0);
558         assertEquals(expectedRatio,
559                 mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
560                 0);
561     }
562     @Test
screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained()563     public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
564         mInstrumentation.runOnMainSync(() -> {
565             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
566                     Float.NaN);
567         });
568         final int screenSize = mContext.getResources().getDimensionPixelSize(
569                 R.dimen.magnification_max_frame_size) * 10;
570         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
571 
572         mInstrumentation.runOnMainSync(() -> {
573             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
574         });
575 
576         final int halfScreenSize = screenSize / 2;
577         WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
578         // The frame size should be the half of smaller value of window height/width unless it
579         //exceed the max frame size.
580         assertTrue(params.width < halfScreenSize);
581         assertTrue(params.height < halfScreenSize);
582     }
583 
584     @Test
585     public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
586         mInstrumentation.runOnMainSync(() -> {
587             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
588                     Float.NaN);
589             Mockito.reset(mWindowManager);
590             Mockito.reset(mMirrorWindowControl);
591         });
592 
593         mInstrumentation.runOnMainSync(() -> {
594             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
595         });
596 
597         verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
598         verify(mWindowManager).removeView(any());
599         verify(mMirrorWindowControl).destroyControl();
600         verify(mWindowManager).addView(any(), any());
601         verify(mMirrorWindowControl).showControl();
602     }
603 
604     @Test
onDensityChanged_disabled_updateDimensions()605     public void onDensityChanged_disabled_updateDimensions() {
606         mInstrumentation.runOnMainSync(() -> {
607             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
608         });
609 
610         verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
611     }
612 
613     @Test
initializeA11yNode_enabled_expectedValues()614     public void initializeA11yNode_enabled_expectedValues() {
615         mInstrumentation.runOnMainSync(() -> {
616             mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
617                     Float.NaN);
618         });
619         final View mirrorView = mWindowManager.getAttachedView();
620         assertNotNull(mirrorView);
621         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
622 
623         mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
624 
625         assertNotNull(nodeInfo.getContentDescription());
626         assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
627         assertThat(nodeInfo.getActionList(),
628                 hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
629                         new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
630                         new AccessibilityAction(R.id.accessibility_action_move_right, null),
631                         new AccessibilityAction(R.id.accessibility_action_move_left, null),
632                         new AccessibilityAction(R.id.accessibility_action_move_down, null),
633                         new AccessibilityAction(R.id.accessibility_action_move_up, null)));
634     }
635 
636     @Test
performA11yActions_visible_expectedResults()637     public void performA11yActions_visible_expectedResults() {
638         final int displayId = mContext.getDisplayId();
639         mInstrumentation.runOnMainSync(() -> {
640             mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
641                     Float.NaN);
642         });
643 
644         final View mirrorView = mWindowManager.getAttachedView();
645         assertTrue(
646                 mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
647         // Minimum scale is 1.0.
648         verify(mWindowMagnifierCallback).onPerformScaleAction(
649                 eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
650 
651         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
652         verify(mWindowMagnifierCallback).onPerformScaleAction(
653                 eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
654 
655         // TODO: Verify the final state when the mirror surface is visible.
656         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
657         assertTrue(
658                 mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
659         assertTrue(
660                 mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
661         assertTrue(
662                 mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
663         verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
664 
665         assertTrue(mirrorView.performAccessibilityAction(
666                 AccessibilityAction.ACTION_CLICK.getId(), null));
667         verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
668     }
669 
670     @Test
performA11yActions_visible_notifyAccessibilityActionPerformed()671     public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
672         final int displayId = mContext.getDisplayId();
673         mInstrumentation.runOnMainSync(() -> {
674             mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
675                     Float.NaN);
676         });
677 
678         final View mirrorView = mWindowManager.getAttachedView();
679         mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
680 
681         verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
682     }
683 
684     @Test
windowMagnifierEditMode_performA11yClickAction_exitEditMode()685     public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
686         mInstrumentation.runOnMainSync(() -> {
687             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
688                     Float.NaN);
689             mWindowMagnificationController.setEditMagnifierSizeMode(true);
690         });
691 
692         View closeButton = getInternalView(R.id.close_button);
693         View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
694         View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
695         View topRightCorner = getInternalView(R.id.top_right_corner);
696         View topLeftCorner = getInternalView(R.id.top_left_corner);
697 
698         assertEquals(View.VISIBLE, closeButton.getVisibility());
699         assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
700         assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
701         assertEquals(View.VISIBLE, topRightCorner.getVisibility());
702         assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
703 
704         final View mirrorView = mWindowManager.getAttachedView();
705         mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
706 
707         assertEquals(View.GONE, closeButton.getVisibility());
708         assertEquals(View.GONE, bottomRightCorner.getVisibility());
709         assertEquals(View.GONE, bottomLeftCorner.getVisibility());
710         assertEquals(View.GONE, topRightCorner.getVisibility());
711         assertEquals(View.GONE, topLeftCorner.getVisibility());
712     }
713 
714     @Test
windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased()715     public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
716         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
717         final int startingWidth = (int) (windowBounds.width() * 0.8);
718         final int startingHeight = (int) (windowBounds.height() * 0.8);
719         final float changeWindowSizeAmount = mContext.getResources().getFraction(
720                 R.fraction.magnification_resize_window_size_amount,
721                 /* base= */ 1,
722                 /* pbase= */ 1);
723 
724         mInstrumentation.runOnMainSync(() -> {
725             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
726                     Float.NaN);
727             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
728             mWindowMagnificationController.setEditMagnifierSizeMode(true);
729         });
730 
731         final View mirrorView = mWindowManager.getAttachedView();
732         final AtomicInteger actualWindowHeight = new AtomicInteger();
733         final AtomicInteger actualWindowWidth = new AtomicInteger();
734 
735         mInstrumentation.runOnMainSync(
736                 () -> {
737                     mirrorView.performAccessibilityAction(
738                             R.id.accessibility_action_increase_window_width, null);
739                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
740                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
741                 });
742 
743         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
744                 R.dimen.magnification_mirror_surface_margin);
745         // Window width includes the magnifier frame and the margin. Increasing the window size
746         // will be increasing the amount of the frame size only.
747         int newWindowWidth =
748                 (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
749                         + 2 * mirrorSurfaceMargin;
750         assertEquals(newWindowWidth, actualWindowWidth.get());
751         assertEquals(startingHeight, actualWindowHeight.get());
752     }
753 
754     @Test
windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased()755     public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
756         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
757         final int startingWidth = (int) (windowBounds.width() * 0.8);
758         final int startingHeight = (int) (windowBounds.height() * 0.8);
759         final float changeWindowSizeAmount = mContext.getResources().getFraction(
760                 R.fraction.magnification_resize_window_size_amount,
761                 /* base= */ 1,
762                 /* pbase= */ 1);
763 
764         mInstrumentation.runOnMainSync(() -> {
765             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
766                     Float.NaN);
767             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
768             mWindowMagnificationController.setEditMagnifierSizeMode(true);
769         });
770 
771         final View mirrorView = mWindowManager.getAttachedView();
772         final AtomicInteger actualWindowHeight = new AtomicInteger();
773         final AtomicInteger actualWindowWidth = new AtomicInteger();
774 
775         mInstrumentation.runOnMainSync(
776                 () -> {
777                     mirrorView.performAccessibilityAction(
778                             R.id.accessibility_action_increase_window_height, null);
779                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
780                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
781                 });
782 
783         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
784                 R.dimen.magnification_mirror_surface_margin);
785         // Window height includes the magnifier frame and the margin. Increasing the window size
786         // will be increasing the amount of the frame size only.
787         int newWindowHeight =
788                 (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
789                         + 2 * mirrorSurfaceMargin;
790         assertEquals(startingWidth, actualWindowWidth.get());
791         assertEquals(newWindowHeight, actualWindowHeight.get());
792     }
793 
794     @Test
windowWidthIsMax_noIncreaseWindowWidthA11yAction()795     public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
796         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
797         final int startingWidth = windowBounds.width();
798         final int startingHeight = windowBounds.height();
799 
800         mInstrumentation.runOnMainSync(() -> {
801             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
802                     Float.NaN);
803             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
804             mWindowMagnificationController.setEditMagnifierSizeMode(true);
805         });
806 
807         final View mirrorView = mWindowManager.getAttachedView();
808         final AccessibilityNodeInfo accessibilityNodeInfo =
809                 mirrorView.createAccessibilityNodeInfo();
810         assertFalse(accessibilityNodeInfo.getActionList().contains(
811                 new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
812     }
813 
814     @Test
windowHeightIsMax_noIncreaseWindowHeightA11yAction()815     public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
816         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
817         final int startingWidth = windowBounds.width();
818         final int startingHeight = windowBounds.height();
819 
820         mInstrumentation.runOnMainSync(() -> {
821             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
822                     Float.NaN);
823             mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
824             mWindowMagnificationController.setEditMagnifierSizeMode(true);
825         });
826 
827         final View mirrorView = mWindowManager.getAttachedView();
828         final AccessibilityNodeInfo accessibilityNodeInfo =
829                 mirrorView.createAccessibilityNodeInfo();
830         assertFalse(accessibilityNodeInfo.getActionList().contains(
831                 new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
832     }
833 
834     @Test
windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased()835     public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
836         int mMinWindowSize = mResources.getDimensionPixelSize(
837                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
838         final int startingSize = (int) (mMinWindowSize * 1.1);
839         final float changeWindowSizeAmount = mContext.getResources().getFraction(
840                 R.fraction.magnification_resize_window_size_amount,
841                 /* base= */ 1,
842                 /* pbase= */ 1);
843         mInstrumentation.runOnMainSync(() -> {
844             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
845                     Float.NaN);
846             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
847             mWindowMagnificationController.setEditMagnifierSizeMode(true);
848         });
849 
850         final View mirrorView = mWindowManager.getAttachedView();
851         final AtomicInteger actualWindowHeight = new AtomicInteger();
852         final AtomicInteger actualWindowWidth = new AtomicInteger();
853 
854         mInstrumentation.runOnMainSync(
855                 () -> {
856                     mirrorView.performAccessibilityAction(
857                             R.id.accessibility_action_decrease_window_width, null);
858                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
859                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
860                 });
861 
862         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
863                 R.dimen.magnification_mirror_surface_margin);
864         // Window width includes the magnifier frame and the margin. Decreasing the window size
865         // will be decreasing the amount of the frame size only.
866         int newWindowWidth =
867                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
868                         + 2 * mirrorSurfaceMargin;
869         assertEquals(newWindowWidth, actualWindowWidth.get());
870         assertEquals(startingSize, actualWindowHeight.get());
871     }
872 
873     @Test
windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased()874     public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
875         int mMinWindowSize = mResources.getDimensionPixelSize(
876                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
877         final int startingSize = (int) (mMinWindowSize * 1.1);
878         final float changeWindowSizeAmount = mContext.getResources().getFraction(
879                 R.fraction.magnification_resize_window_size_amount,
880                 /* base= */ 1,
881                 /* pbase= */ 1);
882 
883         mInstrumentation.runOnMainSync(() -> {
884             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
885                     Float.NaN);
886             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
887             mWindowMagnificationController.setEditMagnifierSizeMode(true);
888         });
889 
890         final View mirrorView = mWindowManager.getAttachedView();
891         final AtomicInteger actualWindowHeight = new AtomicInteger();
892         final AtomicInteger actualWindowWidth = new AtomicInteger();
893 
894         mInstrumentation.runOnMainSync(
895                 () -> {
896                     mirrorView.performAccessibilityAction(
897                             R.id.accessibility_action_decrease_window_height, null);
898                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
899                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
900                 });
901 
902         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
903                 R.dimen.magnification_mirror_surface_margin);
904         // Window height includes the magnifier frame and the margin. Decreasing the window size
905         // will be decreasing the amount of the frame size only.
906         int newWindowHeight =
907                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
908                         + 2 * mirrorSurfaceMargin;
909         assertEquals(startingSize, actualWindowWidth.get());
910         assertEquals(newWindowHeight, actualWindowHeight.get());
911     }
912 
913     @Test
windowWidthIsMin_noDecreaseWindowWidthA11yAction()914     public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
915         int mMinWindowSize = mResources.getDimensionPixelSize(
916                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
917         final int startingSize = mMinWindowSize;
918 
919         mInstrumentation.runOnMainSync(() -> {
920             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
921                     Float.NaN);
922             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
923             mWindowMagnificationController.setEditMagnifierSizeMode(true);
924         });
925 
926         final View mirrorView = mWindowManager.getAttachedView();
927         final AccessibilityNodeInfo accessibilityNodeInfo =
928                 mirrorView.createAccessibilityNodeInfo();
929         assertFalse(accessibilityNodeInfo.getActionList().contains(
930                 new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
931     }
932 
933     @Test
windowHeightIsMin_noDecreaseWindowHeightA11yAcyion()934     public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() {
935         int mMinWindowSize = mResources.getDimensionPixelSize(
936                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
937         final int startingSize = mMinWindowSize;
938 
939         mInstrumentation.runOnMainSync(() -> {
940             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
941                     Float.NaN);
942             mWindowMagnificationController.setWindowSize(startingSize, startingSize);
943             mWindowMagnificationController.setEditMagnifierSizeMode(true);
944         });
945 
946         final View mirrorView = mWindowManager.getAttachedView();
947         final AccessibilityNodeInfo accessibilityNodeInfo =
948                 mirrorView.createAccessibilityNodeInfo();
949         assertFalse(accessibilityNodeInfo.getActionList().contains(
950                 new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
951     }
952 
953     @Test
enableWindowMagnification_hasA11yWindowTitle()954     public void enableWindowMagnification_hasA11yWindowTitle() {
955         mInstrumentation.runOnMainSync(() -> {
956             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
957                     Float.NaN);
958         });
959 
960         assertEquals(getContext().getResources().getString(
961                 com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
962     }
963 
964     @Test
enableWindowMagnificationWithScaleLessThanOne_enabled_disabled()965     public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
966         mInstrumentation.runOnMainSync(() -> {
967             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
968                     Float.NaN);
969         });
970 
971         mInstrumentation.runOnMainSync(() -> {
972             mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
973                     Float.NaN);
974         });
975 
976         assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
977     }
978 
979     @Test
enableWindowMagnification_rotationIsChanged_updateRotationValue()980     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
981         // the config orientation should not be undefined, since it would cause config.diff
982         // returning 0 and thus the orientation changed would not be detected
983         assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
984 
985         final Configuration config = mResources.getConfiguration();
986         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
987                 : ORIENTATION_LANDSCAPE;
988         final int newRotation = simulateRotateTheDevice();
989 
990         mInstrumentation.runOnMainSync(
991                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
992                         Float.NaN, Float.NaN));
993 
994         assertEquals(newRotation, mWindowMagnificationController.mRotation);
995     }
996 
997     @Test
enableWindowMagnification_registerComponentCallback()998     public void enableWindowMagnification_registerComponentCallback() {
999         mInstrumentation.runOnMainSync(
1000                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1001                         Float.NaN,
1002                         Float.NaN));
1003 
1004         verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
1005     }
1006 
1007     @Test
onLocaleChanged_enabled_updateA11yWindowTitle()1008     public void onLocaleChanged_enabled_updateA11yWindowTitle() {
1009         final String newA11yWindowTitle = "new a11y window title";
1010         mInstrumentation.runOnMainSync(() -> {
1011             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
1012                     Float.NaN);
1013         });
1014         final TestableResources testableResources = getContext().getOrCreateTestableResources();
1015         testableResources.addOverride(com.android.internal.R.string.android_system_label,
1016                 newA11yWindowTitle);
1017 
1018         mInstrumentation.runOnMainSync(() -> {
1019             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
1020         });
1021 
1022         assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
1023     }
1024 
1025     @Test
onSingleTap_enabled_scaleAnimates()1026     public void onSingleTap_enabled_scaleAnimates() {
1027         mInstrumentation.runOnMainSync(() -> {
1028             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
1029                     Float.NaN);
1030         });
1031 
1032         mInstrumentation.runOnMainSync(() -> {
1033             mWindowMagnificationController.onSingleTap(mSpyView);
1034         });
1035 
1036         final View mirrorView = mWindowManager.getAttachedView();
1037 
1038         final long timeout = SystemClock.uptimeMillis() + 1000;
1039         final AtomicDouble maxScaleX = new AtomicDouble();
1040         final Runnable onAnimationFrame = new Runnable() {
1041             @Override
1042             public void run() {
1043                 // For some reason the fancy way doesn't compile...
1044 //                maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
1045                 final double oldMax = maxScaleX.get();
1046                 final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
1047                 assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
1048 
1049                 if (SystemClock.uptimeMillis() < timeout) {
1050                     mirrorView.postOnAnimation(this);
1051                 }
1052             }
1053         };
1054         mirrorView.postOnAnimation(onAnimationFrame);
1055 
1056         waitForIdleSync();
1057 
1058         ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0);
1059     }
1060 
1061     @Test
moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue()1062     public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
1063         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1064         setSystemGestureInsets();
1065         mInstrumentation.runOnMainSync(() -> {
1066             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
1067                     Float.NaN);
1068         });
1069 
1070         mInstrumentation.runOnMainSync(() -> {
1071             mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
1072         });
1073 
1074         ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
1075     }
1076 
1077     @Test
moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()1078     public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
1079             throws RemoteException {
1080         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1081         setSystemGestureInsets();
1082         mInstrumentation.runOnMainSync(
1083                 () -> {
1084                     mWindowMagnificationController.enableWindowMagnificationInternal(
1085                             Float.NaN, Float.NaN, Float.NaN);
1086                 });
1087 
1088         mInstrumentation.runOnMainSync(
1089                 () -> {
1090                     mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
1091                 });
1092 
1093         // Wait for Region updated.
1094         waitForIdleSync();
1095 
1096         final ArgumentCaptor<Region> tapExcludeRegionCapturer =
1097                 ArgumentCaptor.forClass(Region.class);
1098         verify(mWindowSessionSpy, times(2))
1099                 .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
1100         Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
1101         RegionIterator iterator = new RegionIterator(tapExcludeRegion);
1102 
1103         final Rect topRect = new Rect();
1104         final Rect bottomRect = new Rect();
1105         assertTrue(iterator.next(topRect));
1106         assertTrue(iterator.next(bottomRect));
1107         assertFalse(iterator.next(new Rect()));
1108 
1109         assertEquals(topRect.right, bottomRect.right);
1110         assertNotEquals(topRect.left, bottomRect.left);
1111     }
1112 
1113     @Test
moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()1114     public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
1115             throws RemoteException {
1116         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1117         setSystemGestureInsets();
1118         mInstrumentation.runOnMainSync(
1119                 () -> {
1120                     mWindowMagnificationController.enableWindowMagnificationInternal(
1121                             Float.NaN, Float.NaN, Float.NaN);
1122                 });
1123 
1124         mInstrumentation.runOnMainSync(
1125                 () -> {
1126                     mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
1127                 });
1128 
1129         // Wait for Region updated.
1130         waitForIdleSync();
1131 
1132         final ArgumentCaptor<Region> tapExcludeRegionCapturer =
1133                 ArgumentCaptor.forClass(Region.class);
1134         verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
1135         Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
1136         RegionIterator iterator = new RegionIterator(tapExcludeRegion);
1137 
1138         final Rect topRect = new Rect();
1139         final Rect bottomRect = new Rect();
1140         assertTrue(iterator.next(topRect));
1141         assertTrue(iterator.next(bottomRect));
1142         assertFalse(iterator.next(new Rect()));
1143 
1144         assertEquals(topRect.left, bottomRect.left);
1145         assertNotEquals(topRect.right, bottomRect.right);
1146     }
1147 
1148     @Test
setMinimumWindowSize_enabled_expectedWindowSize()1149     public void setMinimumWindowSize_enabled_expectedWindowSize() {
1150         final int minimumWindowSize = mResources.getDimensionPixelSize(
1151                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
1152         final int  expectedWindowHeight = minimumWindowSize;
1153         final int  expectedWindowWidth = minimumWindowSize;
1154         mInstrumentation.runOnMainSync(
1155                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1156                         Float.NaN, Float.NaN));
1157 
1158         final AtomicInteger actualWindowHeight = new AtomicInteger();
1159         final AtomicInteger actualWindowWidth = new AtomicInteger();
1160         mInstrumentation.runOnMainSync(() -> {
1161             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
1162             actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1163             actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1164 
1165         });
1166 
1167         assertEquals(expectedWindowHeight, actualWindowHeight.get());
1168         assertEquals(expectedWindowWidth, actualWindowWidth.get());
1169     }
1170 
1171     @Test
setMinimumWindowSizeThenEnable_expectedWindowSize()1172     public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
1173         final int minimumWindowSize = mResources.getDimensionPixelSize(
1174                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
1175         final int  expectedWindowHeight = minimumWindowSize;
1176         final int  expectedWindowWidth = minimumWindowSize;
1177 
1178         final AtomicInteger actualWindowHeight = new AtomicInteger();
1179         final AtomicInteger actualWindowWidth = new AtomicInteger();
1180         mInstrumentation.runOnMainSync(() -> {
1181             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
1182             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1183                     Float.NaN, Float.NaN);
1184             actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1185             actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1186         });
1187 
1188         assertEquals(expectedWindowHeight, actualWindowHeight.get());
1189         assertEquals(expectedWindowWidth, actualWindowWidth.get());
1190     }
1191 
1192     @Test
setWindowSizeLessThanMin_enabled_minimumWindowSize()1193     public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
1194         final int minimumWindowSize = mResources.getDimensionPixelSize(
1195                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
1196         mInstrumentation.runOnMainSync(
1197                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1198                         Float.NaN, Float.NaN));
1199 
1200         final AtomicInteger actualWindowHeight = new AtomicInteger();
1201         final AtomicInteger actualWindowWidth = new AtomicInteger();
1202         mInstrumentation.runOnMainSync(() -> {
1203             mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
1204                     minimumWindowSize - 10);
1205             actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1206             actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1207         });
1208 
1209         assertEquals(minimumWindowSize, actualWindowHeight.get());
1210         assertEquals(minimumWindowSize, actualWindowWidth.get());
1211     }
1212 
1213     @Test
setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize()1214     public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
1215         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1216         mInstrumentation.runOnMainSync(
1217                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1218                         Float.NaN, Float.NaN));
1219 
1220         final AtomicInteger actualWindowHeight = new AtomicInteger();
1221         final AtomicInteger actualWindowWidth = new AtomicInteger();
1222         mInstrumentation.runOnMainSync(() -> {
1223             mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
1224             actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1225             actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1226         });
1227 
1228         assertEquals(bounds.height(), actualWindowHeight.get());
1229         assertEquals(bounds.width(), actualWindowWidth.get());
1230     }
1231 
1232     @Test
changeMagnificationSize_expectedWindowSize()1233     public void changeMagnificationSize_expectedWindowSize() {
1234         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1235 
1236         final float magnificationScaleLarge = 2.5f;
1237         final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
1238         final int magnificationSize = (int) (initSize * magnificationScaleLarge);
1239 
1240         final int expectedWindowHeight = magnificationSize;
1241         final int expectedWindowWidth = magnificationSize;
1242 
1243         mInstrumentation.runOnMainSync(
1244                 () ->
1245                         mWindowMagnificationController.enableWindowMagnificationInternal(
1246                                 Float.NaN, Float.NaN, Float.NaN));
1247 
1248         final AtomicInteger actualWindowHeight = new AtomicInteger();
1249         final AtomicInteger actualWindowWidth = new AtomicInteger();
1250         mInstrumentation.runOnMainSync(
1251                 () -> {
1252                     mWindowMagnificationController.changeMagnificationSize(
1253                             WindowMagnificationSettings.MagnificationSize.LARGE);
1254                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1255                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1256                 });
1257 
1258         assertEquals(expectedWindowHeight, actualWindowHeight.get());
1259         assertEquals(expectedWindowWidth, actualWindowWidth.get());
1260     }
1261 
1262     @Test
editModeOnDragCorner_resizesWindow()1263     public void editModeOnDragCorner_resizesWindow() {
1264         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1265 
1266         final int startingSize = (int) (bounds.width() / 2);
1267 
1268         mInstrumentation.runOnMainSync(
1269                 () ->
1270                         mWindowMagnificationController.enableWindowMagnificationInternal(
1271                                 Float.NaN, Float.NaN, Float.NaN));
1272 
1273         final AtomicInteger actualWindowHeight = new AtomicInteger();
1274         final AtomicInteger actualWindowWidth = new AtomicInteger();
1275 
1276         mInstrumentation.runOnMainSync(
1277                 () -> {
1278                     mWindowMagnificationController.setWindowSize(startingSize, startingSize);
1279                     mWindowMagnificationController.setEditMagnifierSizeMode(true);
1280                 });
1281 
1282         waitForIdleSync();
1283 
1284         mInstrumentation.runOnMainSync(
1285                 () -> {
1286                     mWindowMagnificationController
1287                             .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
1288                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1289                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1290                 });
1291 
1292         assertEquals(startingSize + 1, actualWindowHeight.get());
1293         assertEquals(startingSize + 2, actualWindowWidth.get());
1294     }
1295 
1296     @Test
editModeOnDragEdge_resizesWindowInOnlyOneDirection()1297     public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
1298         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1299 
1300         final int startingSize = (int) (bounds.width() / 2f);
1301 
1302         mInstrumentation.runOnMainSync(
1303                 () ->
1304                         mWindowMagnificationController.enableWindowMagnificationInternal(
1305                                 Float.NaN, Float.NaN, Float.NaN));
1306 
1307         final AtomicInteger actualWindowHeight = new AtomicInteger();
1308         final AtomicInteger actualWindowWidth = new AtomicInteger();
1309 
1310         mInstrumentation.runOnMainSync(
1311                 () -> {
1312                     mWindowMagnificationController.setWindowSize(startingSize, startingSize);
1313                     mWindowMagnificationController.setEditMagnifierSizeMode(true);
1314                     mWindowMagnificationController
1315                             .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
1316                     actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
1317                     actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
1318                 });
1319         assertEquals(startingSize + 1, actualWindowHeight.get());
1320         assertEquals(startingSize, actualWindowWidth.get());
1321     }
1322 
1323     @Test
setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen()1324     public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
1325 
1326         final int minimumWindowSize = mResources.getDimensionPixelSize(
1327                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
1328         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1329         mInstrumentation.runOnMainSync(
1330                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
1331                         Float.NaN, Float.NaN));
1332 
1333         final AtomicInteger magnificationCenterX = new AtomicInteger();
1334         final AtomicInteger magnificationCenterY = new AtomicInteger();
1335         mInstrumentation.runOnMainSync(() -> {
1336             mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
1337                     minimumWindowSize, bounds.right, bounds.bottom);
1338             magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
1339             magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
1340         });
1341 
1342         assertTrue(magnificationCenterX.get() < bounds.right);
1343         assertTrue(magnificationCenterY.get() < bounds.bottom);
1344     }
1345 
1346     @Test
1347     public void performSingleTap_DragHandle() {
1348         final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
1349         mInstrumentation.runOnMainSync(
1350                 () -> {
1351                     mWindowMagnificationController.enableWindowMagnificationInternal(
1352                             1.5f, bounds.centerX(), bounds.centerY());
1353                 });
1354         View dragButton = getInternalView(R.id.drag_handle);
1355 
1356         // Perform a single-tap
1357         final long downTime = SystemClock.uptimeMillis();
1358         dragButton.dispatchTouchEvent(
1359                 obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
1360         dragButton.dispatchTouchEvent(
1361                 obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
1362 
1363         verify(mWindowManager).addView(any(View.class), any());
1364     }
1365 
1366     private <T extends View> T getInternalView(@IdRes int idRes) {
1367         View mirrorView = mWindowManager.getAttachedView();
1368         T view = mirrorView.findViewById(idRes);
1369         assertNotNull(view);
1370         return view;
1371     }
1372 
1373     private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
1374                                           float y) {
1375         return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
1376     }
1377 
1378     private CharSequence getAccessibilityWindowTitle() {
1379         final View mirrorView = mWindowManager.getAttachedView();
1380         if (mirrorView == null) {
1381             return null;
1382         }
1383         WindowManager.LayoutParams layoutParams =
1384                 (WindowManager.LayoutParams) mirrorView.getLayoutParams();
1385         return layoutParams.accessibilityTitle;
1386     }
1387 
1388     private boolean hasMagnificationOverlapFlag() {
1389         return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
1390     }
1391 
1392     private void setSystemGestureInsets() {
1393         final WindowInsets testInsets = new WindowInsets.Builder()
1394                 .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
1395                 .build();
1396         mWindowManager.setWindowInsets(testInsets);
1397     }
1398 
1399     @Surface.Rotation
1400     private int simulateRotateTheDevice() {
1401         final Display display = Mockito.spy(mContext.getDisplay());
1402         final int currentRotation = display.getRotation();
1403         final int newRotation = (currentRotation + 1) % 4;
1404         when(display.getRotation()).thenReturn(newRotation);
1405         when(mContext.getDisplay()).thenReturn(display);
1406         return newRotation;
1407     }
1408 
1409 }
1410