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.view.MotionEvent.ACTION_CANCEL;
21 import static android.view.MotionEvent.ACTION_DOWN;
22 import static android.view.MotionEvent.ACTION_MOVE;
23 import static android.view.MotionEvent.ACTION_UP;
24 import static android.view.WindowInsets.Type.displayCutout;
25 import static android.view.WindowInsets.Type.systemBars;
26 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK;
27 
28 import static com.android.systemui.accessibility.MagnificationModeSwitch.DEFAULT_FADE_OUT_ANIMATION_DELAY_MS;
29 import static com.android.systemui.accessibility.MagnificationModeSwitch.FADING_ANIMATION_DURATION_MS;
30 import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;
31 
32 import static junit.framework.Assert.assertEquals;
33 import static junit.framework.Assert.assertNotNull;
34 
35 import static org.hamcrest.CoreMatchers.hasItems;
36 import static org.hamcrest.MatcherAssert.assertThat;
37 import static org.junit.Assert.assertNotEquals;
38 import static org.junit.Assert.assertNull;
39 import static org.mockito.ArgumentMatchers.any;
40 import static org.mockito.ArgumentMatchers.anyInt;
41 import static org.mockito.ArgumentMatchers.anyLong;
42 import static org.mockito.ArgumentMatchers.eq;
43 import static org.mockito.Mockito.doAnswer;
44 import static org.mockito.Mockito.doNothing;
45 import static org.mockito.Mockito.doReturn;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.never;
48 import static org.mockito.Mockito.spy;
49 import static org.mockito.Mockito.verify;
50 import static org.mockito.Mockito.when;
51 
52 import android.content.Context;
53 import android.content.pm.ActivityInfo;
54 import android.graphics.Insets;
55 import android.graphics.Rect;
56 import android.os.Handler;
57 import android.os.SystemClock;
58 import android.testing.AndroidTestingRunner;
59 import android.testing.TestableLooper;
60 import android.view.Choreographer;
61 import android.view.MotionEvent;
62 import android.view.View;
63 import android.view.ViewConfiguration;
64 import android.view.ViewPropertyAnimator;
65 import android.view.WindowInsets;
66 import android.view.WindowManager;
67 import android.view.accessibility.AccessibilityManager;
68 import android.view.accessibility.AccessibilityNodeInfo;
69 import android.widget.ImageView;
70 
71 import androidx.test.filters.SmallTest;
72 
73 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
74 import com.android.systemui.R;
75 import com.android.systemui.SysuiTestCase;
76 
77 import org.junit.After;
78 import org.junit.Before;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
81 import org.mockito.ArgumentCaptor;
82 import org.mockito.InOrder;
83 import org.mockito.Mock;
84 import org.mockito.Mockito;
85 import org.mockito.MockitoAnnotations;
86 
87 import java.util.List;
88 
89 @SmallTest
90 @RunWith(AndroidTestingRunner.class)
91 @TestableLooper.RunWithLooper(setAsMainLooper = true)
92 public class MagnificationModeSwitchTest extends SysuiTestCase {
93 
94     private static final float FADE_IN_ALPHA = 1f;
95     private static final float FADE_OUT_ALPHA = 0f;
96 
97     private ImageView mSpyImageView;
98     @Mock
99     private AccessibilityManager mAccessibilityManager;
100     @Mock
101     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
102     @Mock
103     private MagnificationModeSwitch.ClickListener mClickListener;
104     private TestableWindowManager mWindowManager;
105     private ViewPropertyAnimator mViewPropertyAnimator;
106     private MagnificationModeSwitch mMagnificationModeSwitch;
107     private View.OnTouchListener mTouchListener;
108     private Runnable mFadeOutAnimation;
109     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
110 
111     @Before
setUp()112     public void setUp() throws Exception {
113         MockitoAnnotations.initMocks(this);
114         mContext = Mockito.spy(getContext());
115         final WindowManager wm = mContext.getSystemService(WindowManager.class);
116         mWindowManager = spy(new TestableWindowManager(wm));
117         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
118         mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
119         mSpyImageView = Mockito.spy(new ImageView(mContext));
120         mViewPropertyAnimator = Mockito.spy(mSpyImageView.animate());
121         resetAndStubMockImageViewAndAnimator();
122         doAnswer((invocation) -> {
123             mTouchListener = invocation.getArgument(0);
124             return null;
125         }).when(mSpyImageView).setOnTouchListener(
126                 any(View.OnTouchListener.class));
127         doAnswer(invocation -> {
128             Choreographer.FrameCallback callback = invocation.getArgument(0);
129             callback.doFrame(0);
130             return null;
131         }).when(mSfVsyncFrameProvider).postFrameCallback(
132                 any(Choreographer.FrameCallback.class));
133         mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView,
134                 mSfVsyncFrameProvider, mClickListener);
135         assertNotNull(mTouchListener);
136     }
137 
138     @After
tearDown()139     public void tearDown() {
140         mFadeOutAnimation = null;
141         mMotionEventHelper.recycleEvents();
142         mMagnificationModeSwitch.removeButton();
143     }
144 
145     @Test
removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks()146     public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
147         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
148 
149         mMagnificationModeSwitch.removeButton();
150 
151         verify(mWindowManager).removeView(mSpyImageView);
152         verify(mViewPropertyAnimator).cancel();
153         verify(mContext).unregisterComponentCallbacks(mMagnificationModeSwitch);
154     }
155 
156     @Test
showFullscreenModeButton_addViewAndSetImageResource()157     public void showFullscreenModeButton_addViewAndSetImageResource() {
158         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
159 
160         verify(mSpyImageView).setImageResource(
161                 getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
162         assertEquals(mSpyImageView, mWindowManager.getAttachedView());
163         assertShowFadingAnimation(FADE_IN_ALPHA);
164         assertShowFadingAnimation(FADE_OUT_ALPHA);
165     }
166 
167     @Test
showButton_excludeSystemGestureArea()168     public void showButton_excludeSystemGestureArea() {
169         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
170 
171         verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
172     }
173 
174     @Test
showMagnificationButton_setA11yTimeout_postDelayedAnimationWithA11yTimeout()175     public void showMagnificationButton_setA11yTimeout_postDelayedAnimationWithA11yTimeout() {
176         final int a11yTimeout = 12345;
177         when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
178                 a11yTimeout);
179 
180         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
181 
182         verify(mAccessibilityManager).getRecommendedTimeoutMillis(
183                 DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
184                         | AccessibilityManager.FLAG_CONTENT_CONTROLS);
185         verify(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), eq((long) a11yTimeout));
186     }
187 
188     @Test
showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction()189     public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
190         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
191         executeFadeOutAnimation();
192 
193         // Verify the end action after fade-out.
194         final ArgumentCaptor<Runnable> endActionCaptor = ArgumentCaptor.forClass(Runnable.class);
195         verify(mViewPropertyAnimator).withEndAction(endActionCaptor.capture());
196 
197         endActionCaptor.getValue().run();
198 
199         verify(mViewPropertyAnimator).cancel();
200         verify(mWindowManager).removeView(mSpyImageView);
201     }
202 
203     @Test
onDensityChanged_buttonIsShowing_updateResourcesAndLayout()204     public void onDensityChanged_buttonIsShowing_updateResourcesAndLayout() {
205         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
206         resetAndStubMockImageViewAndAnimator();
207 
208         mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
209 
210         InOrder inOrder = Mockito.inOrder(mWindowManager);
211         inOrder.verify(mWindowManager).updateViewLayout(eq(mSpyImageView), any());
212         inOrder.verify(mWindowManager).removeView(eq(mSpyImageView));
213         inOrder.verify(mWindowManager).addView(eq(mSpyImageView), any());
214         verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
215     }
216 
217     @Test
onApplyWindowInsetsWithBoundsChange_buttonIsShowing_updateLayoutPosition()218     public void onApplyWindowInsetsWithBoundsChange_buttonIsShowing_updateLayoutPosition() {
219         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
220 
221         mMagnificationModeSwitch.mDraggableWindowBounds.inset(10, 10);
222         mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
223 
224         verify(mWindowManager).updateViewLayout(eq(mSpyImageView),
225                 any(WindowManager.LayoutParams.class));
226         assertLayoutPosition(/* toLeftScreenEdge= */ false);
227     }
228 
229     @Test
onSystemBarsInsetsChanged_buttonIsShowing_draggableBoundsChanged()230     public void onSystemBarsInsetsChanged_buttonIsShowing_draggableBoundsChanged() {
231         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
232         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
233 
234         mWindowManager.setWindowInsets(new WindowInsets.Builder()
235                 .setInsetsIgnoringVisibility(systemBars(), Insets.of(0, 20, 0, 20))
236                 .build());
237         mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
238 
239         assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds);
240     }
241 
242     @Test
onDisplayCutoutInsetsChanged_buttonIsShowing_draggableBoundsChanged()243     public void onDisplayCutoutInsetsChanged_buttonIsShowing_draggableBoundsChanged() {
244         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
245         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
246 
247         mWindowManager.setWindowInsets(new WindowInsets.Builder()
248                 .setInsetsIgnoringVisibility(displayCutout(), Insets.of(20, 30, 20, 30))
249                 .build());
250         mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
251 
252         assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds);
253     }
254 
255     @Test
onWindowBoundsChanged_buttonIsShowing_draggableBoundsChanged()256     public void onWindowBoundsChanged_buttonIsShowing_draggableBoundsChanged() {
257         mWindowManager.setWindowBounds(new Rect(0, 0, 800, 1000));
258         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
259         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
260 
261         mWindowManager.setWindowBounds(new Rect(0, 0, 1000, 800));
262         mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
263 
264         assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds);
265     }
266 
267     @Test
onDraggingGestureFinish_buttonIsShowing_stickToRightEdge()268     public void onDraggingGestureFinish_buttonIsShowing_stickToRightEdge() {
269         final int windowHalfWidth =
270                 mWindowManager.getCurrentWindowMetrics().getBounds().width() / 2;
271         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
272 
273         // Drag button to right side on screen
274         final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop() + 10;
275         final long downTime = SystemClock.uptimeMillis();
276         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
277                 downTime, 0, ACTION_DOWN, 100, 100));
278         mTouchListener.onTouch(mSpyImageView,
279                 obtainMotionEvent(downTime, downTime, ACTION_MOVE, windowHalfWidth - offset, 100));
280 
281         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
282                 downTime, downTime, ACTION_UP, windowHalfWidth - offset, 100));
283 
284         assertLayoutPosition(/* toLeftScreenEdge= */false);
285     }
286 
287     @Test
performSingleTap_fullscreenMode_callbackTriggered()288     public void performSingleTap_fullscreenMode_callbackTriggered() {
289         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
290         resetAndStubMockImageViewAndAnimator();
291 
292         // Perform a single-tap
293         final long downTime = SystemClock.uptimeMillis();
294         mTouchListener.onTouch(mSpyImageView,
295                 obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
296         resetAndStubMockImageViewAndAnimator();
297         mTouchListener.onTouch(mSpyImageView,
298                 obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
299 
300         verify(mClickListener).onClick(eq(mContext.getDisplayId()));
301     }
302 
303     @Test
sendDownEvent_fullscreenMode_fadeOutAnimationIsNull()304     public void sendDownEvent_fullscreenMode_fadeOutAnimationIsNull() {
305         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
306         resetAndStubMockImageViewAndAnimator();
307 
308         final long downTime = SystemClock.uptimeMillis();
309         mTouchListener.onTouch(mSpyImageView,
310                 obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
311 
312         assertNull(mFadeOutAnimation);
313     }
314 
315     @Test
sendDownEvent_fullscreenModeAndFadingOut_cancelAnimation()316     public void sendDownEvent_fullscreenModeAndFadingOut_cancelAnimation() {
317         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
318         executeFadeOutAnimation();
319         resetAndStubMockImageViewAndAnimator();
320 
321         final long downTime = SystemClock.uptimeMillis();
322         mTouchListener.onTouch(mSpyImageView,
323                 obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
324 
325         verify(mViewPropertyAnimator).cancel();
326     }
327 
328     @Test
performDragging_showMagnificationButton_updateViewLayout()329     public void performDragging_showMagnificationButton_updateViewLayout() {
330         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
331         resetAndStubMockImageViewAndAnimator();
332 
333         // Perform dragging
334         final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop() + 10;
335         final long downTime = SystemClock.uptimeMillis();
336         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
337                 downTime, 0, ACTION_DOWN, 100, 100));
338 
339         mTouchListener.onTouch(mSpyImageView,
340                 obtainMotionEvent(downTime, downTime, ACTION_MOVE, 100 + offset,
341                         100));
342         verify(mWindowManager).updateViewLayout(eq(mSpyImageView),
343                 any(WindowManager.LayoutParams.class));
344 
345         resetAndStubMockImageViewAndAnimator();
346         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
347                 downTime, downTime, ACTION_UP, 100 + offset, 100));
348 
349         verify(mClickListener, never()).onClick(anyInt());
350         assertShowFadingAnimation(FADE_OUT_ALPHA);
351     }
352 
353     @Test
performSingleTapActionCanceled_showButtonAnimation()354     public void performSingleTapActionCanceled_showButtonAnimation() {
355         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
356         resetAndStubMockImageViewAndAnimator();
357 
358         final long downTime = SystemClock.uptimeMillis();
359         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
360                 downTime, downTime, ACTION_DOWN, 100, 100));
361         resetAndStubMockImageViewAndAnimator();
362         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
363                 downTime, downTime, ACTION_CANCEL, 100, 100));
364 
365         verify(mClickListener, never()).onClick(anyInt());
366         assertShowFadingAnimation(FADE_OUT_ALPHA);
367     }
368 
369     @Test
performDraggingActionCanceled_showButtonAnimation()370     public void performDraggingActionCanceled_showButtonAnimation() {
371         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
372         resetAndStubMockImageViewAndAnimator();
373 
374         // Perform dragging
375         final long downTime = SystemClock.uptimeMillis();
376         final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop() + 10;
377         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
378                 0, 0, ACTION_DOWN, 100, 100));
379         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
380                 downTime, downTime, ACTION_MOVE, 100 + offset, 100));
381         resetAndStubMockImageViewAndAnimator();
382         mTouchListener.onTouch(mSpyImageView, obtainMotionEvent(
383                 downTime, downTime, ACTION_CANCEL, 100 + offset, 100));
384 
385         verify(mClickListener, never()).onClick(anyInt());
386         assertShowFadingAnimation(FADE_OUT_ALPHA);
387     }
388 
389     @Test
initializeA11yNode_showWindowModeButton_expectedValues()390     public void initializeA11yNode_showWindowModeButton_expectedValues() {
391         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
392         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
393 
394         mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
395 
396         assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
397                 nodeInfo.getContentDescription());
398         assertEquals(mContext.getString(R.string.magnification_mode_switch_state_full_screen),
399                 nodeInfo.getStateDescription().toString());
400         assertThat(nodeInfo.getActionList(),
401                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
402                         ACTION_CLICK.getId(), mContext.getResources().getString(
403                         R.string.magnification_open_settings_click_label))));
404         assertThat(nodeInfo.getActionList(),
405                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
406                         R.id.accessibility_action_move_up, mContext.getResources().getString(
407                         R.string.accessibility_control_move_up))));
408         assertThat(nodeInfo.getActionList(),
409                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
410                         R.id.accessibility_action_move_down, mContext.getResources().getString(
411                         R.string.accessibility_control_move_down))));
412         assertThat(nodeInfo.getActionList(),
413                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
414                         R.id.accessibility_action_move_left, mContext.getResources().getString(
415                         R.string.accessibility_control_move_left))));
416         assertThat(nodeInfo.getActionList(),
417                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
418                         R.id.accessibility_action_move_right, mContext.getResources().getString(
419                         R.string.accessibility_control_move_right))));
420     }
421 
422     @Test
performClickA11yActions_showWindowModeButton_callbackTriggered()423     public void performClickA11yActions_showWindowModeButton_callbackTriggered() {
424         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
425         resetAndStubMockImageViewAndAnimator();
426 
427         mSpyImageView.performAccessibilityAction(
428                 ACTION_CLICK.getId(), null);
429 
430         verify(mClickListener).onClick(mContext.getDisplayId());
431     }
432 
433     @Test
performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge()434     public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
435         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
436 
437         mSpyImageView.performAccessibilityAction(
438                 R.id.accessibility_action_move_left, null);
439 
440         assertLayoutPosition(/* toLeftScreenEdge= */true);
441     }
442 
443     @Test
showButton_showFadeOutAnimation_fadeOutAnimationCanceled()444     public void showButton_showFadeOutAnimation_fadeOutAnimationCanceled() {
445         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
446         assertShowFadingAnimation(FADE_OUT_ALPHA);
447         resetAndStubMockImageViewAndAnimator();
448 
449         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
450 
451         verify(mViewPropertyAnimator).cancel();
452         assertEquals(1f, mSpyImageView.getAlpha());
453         assertShowFadingAnimation(FADE_OUT_ALPHA);
454     }
455 
456     @Test
showButton_hasAccessibilityWindowTitle()457     public void showButton_hasAccessibilityWindowTitle() {
458         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
459 
460         final WindowManager.LayoutParams layoutPrams =
461                 mWindowManager.getLayoutParamsFromAttachedView();
462         assertNotNull(layoutPrams);
463         assertEquals(getContext().getResources().getString(
464                 com.android.internal.R.string.android_system_label),
465                 layoutPrams.accessibilityTitle);
466     }
467 
468     @Test
showButton_registerComponentCallbacks()469     public void showButton_registerComponentCallbacks() {
470         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
471 
472         verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
473     }
474 
475     @Test
onLocaleChanged_buttonIsShowing_updateA11yWindowTitle()476     public void onLocaleChanged_buttonIsShowing_updateA11yWindowTitle() {
477         final String newA11yWindowTitle = "new a11y window title";
478         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
479 
480         getContext().getOrCreateTestableResources().addOverride(
481                 com.android.internal.R.string.android_system_label, newA11yWindowTitle);
482         mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
483 
484         final WindowManager.LayoutParams layoutParams =
485                 mWindowManager.getLayoutParamsFromAttachedView();
486         assertNotNull(layoutParams);
487         assertEquals(newA11yWindowTitle, layoutParams.accessibilityTitle);
488     }
489 
490     @Test
onRotationChanged_buttonIsShowing_expectedYPosition()491     public void onRotationChanged_buttonIsShowing_expectedYPosition() {
492         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
493         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
494         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
495         final float windowHeightFraction =
496                 (float) (mWindowManager.getLayoutParamsFromAttachedView().y
497                         - oldDraggableBounds.top) / oldDraggableBounds.height();
498 
499         // The window bounds and the draggable bounds are changed due to the rotation change.
500         final Rect newWindowBounds = new Rect(0, 0, windowBounds.height(), windowBounds.width());
501         mWindowManager.setWindowBounds(newWindowBounds);
502         mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
503 
504         int expectedY = (int) (windowHeightFraction
505                 * mMagnificationModeSwitch.mDraggableWindowBounds.height())
506                 + mMagnificationModeSwitch.mDraggableWindowBounds.top;
507         assertEquals(
508                 "The Y position does not keep the same height ratio after the rotation changed.",
509                 expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
510     }
511 
512     @Test
onScreenSizeChanged_buttonIsShowingOnTheRightSide_expectedPosition()513     public void onScreenSizeChanged_buttonIsShowingOnTheRightSide_expectedPosition() {
514         final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
515         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
516         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
517         final float windowHeightFraction =
518                 (float) (mWindowManager.getLayoutParamsFromAttachedView().y
519                         - oldDraggableBounds.top) / oldDraggableBounds.height();
520 
521         // The window bounds and the draggable bounds are changed due to the screen size change.
522         final Rect tmpRect = new Rect(windowBounds);
523         tmpRect.scale(2);
524         final Rect newWindowBounds = new Rect(tmpRect);
525         mWindowManager.setWindowBounds(newWindowBounds);
526         mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
527 
528         final int expectedX = mMagnificationModeSwitch.mDraggableWindowBounds.right;
529         final int expectedY = (int) (windowHeightFraction
530                 * mMagnificationModeSwitch.mDraggableWindowBounds.height())
531                 + mMagnificationModeSwitch.mDraggableWindowBounds.top;
532         assertEquals(expectedX, mWindowManager.getLayoutParamsFromAttachedView().x);
533         assertEquals(expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
534     }
535 
assertShowFadingAnimation(float alpha)536     private void assertShowFadingAnimation(float alpha) {
537         final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
538         if (alpha == FADE_IN_ALPHA) { // Fade-in
539             verify(mSpyImageView).postOnAnimation(runnableCaptor.capture());
540         } else { // Fade-out
541             verify(mSpyImageView).postOnAnimationDelayed(runnableCaptor.capture(), anyLong());
542         }
543         resetAndStubMockAnimator();
544 
545         runnableCaptor.getValue().run();
546 
547         verify(mViewPropertyAnimator).setDuration(eq(FADING_ANIMATION_DURATION_MS));
548         verify(mViewPropertyAnimator).alpha(alpha);
549         verify(mViewPropertyAnimator).start();
550     }
551 
resetAndStubMockImageViewAndAnimator()552     private void resetAndStubMockImageViewAndAnimator() {
553         resetAndStubMockAnimator();
554         Mockito.reset(mSpyImageView);
555         final Handler handler = mock(Handler.class);
556         when(mSpyImageView.getHandler()).thenReturn(handler);
557         doAnswer(invocation -> {
558             final Runnable runnable = invocation.getArgument(0);
559             runnable.run();
560             return null;
561         }).when(handler).post(any(Runnable.class));
562         doAnswer(invocation -> {
563             final Runnable runnable = invocation.getArgument(0);
564             runnable.run();
565             return null;
566         }).when(mSpyImageView).post(any(Runnable.class));
567         doReturn(mViewPropertyAnimator).when(mSpyImageView).animate();
568         doAnswer((invocation) -> {
569             mFadeOutAnimation = invocation.getArgument(0);
570             return null;
571         }).when(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), anyLong());
572         doAnswer((invocation) -> {
573             if (mFadeOutAnimation == invocation.getArgument(0)) {
574                 mFadeOutAnimation = null;
575             }
576             return null;
577         }).when(mSpyImageView).removeCallbacks(any(Runnable.class));
578     }
579 
resetAndStubMockAnimator()580     private void resetAndStubMockAnimator() {
581         Mockito.reset(mViewPropertyAnimator);
582         doNothing().when(mViewPropertyAnimator).start();
583     }
584 
obtainMotionEvent(long downTime, long eventTime, int action, float x, float y)585     private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
586             float y) {
587         return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
588     }
589 
executeFadeOutAnimation()590     private void executeFadeOutAnimation() {
591         assertNotNull(mFadeOutAnimation);
592         mFadeOutAnimation.run();
593         mFadeOutAnimation = null;
594     }
595 
assertLayoutPosition(boolean toLeftScreenEdge)596     private void assertLayoutPosition(boolean toLeftScreenEdge) {
597         final int expectedX =
598                 toLeftScreenEdge ? mMagnificationModeSwitch.mDraggableWindowBounds.left
599                         : mMagnificationModeSwitch.mDraggableWindowBounds.right;
600         final int expectedY = mMagnificationModeSwitch.mDraggableWindowBounds.bottom;
601         final WindowManager.LayoutParams layoutParams =
602                 mWindowManager.getLayoutParamsFromAttachedView();
603         assertNotNull(layoutParams);
604         assertEquals(expectedX, layoutParams.x);
605         assertEquals(expectedY, layoutParams.y);
606     }
607 }
608