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