From d560a73ca9e38ffbf8a7115d1f09814f0c070b2f Mon Sep 17 00:00:00 2001 From: mincheli Date: Fri, 28 Jan 2022 14:20:57 +0800 Subject: [PATCH] Notifies magnification change for the transitioning target mode When the magnification settings mode or config mode transitions, MagnificationController would 1. Disables the current magnification mode. Then 2. Starts to enable the target magnification mode. To prevent the magnification change callback send twice during the mode transition, MagnificationController should only notify the change of the transitioning target mode and ignore the notifcation change for the disabling magnification mode. And when WindowMagnification resets or disables by WindowMagnificationController#deleteWindowMagnification(), it should notify source bounds empty when magnification is deleted. And when FullscreenMagnification resets and the animator is animating, FullScreenMagnificationController should notify magnification change. Bug: 209701404 Bug: 210636326 Test: atest AccessibilityMagnificationTest, atest WindowMagnificationControllerTest, atest com.android.server.accessibility.magnification Change-Id: Ief718614d80177a4a076eddc708a7ecfc4dfb73f --- .../WindowMagnificationController.java | 2 + .../WindowMagnificationControllerTest.java | 16 ++ .../FullScreenMagnificationController.java | 17 ++- .../MagnificationController.java | 142 +++++++++++++++--- .../MagnificationControllerTest.java | 120 ++++++++++++++- 5 files changed, 264 insertions(+), 33 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 807ff21bf47a8..a1428f3120c8e 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -353,6 +353,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mSourceBounds.setEmpty(); updateSystemUIStateIfNeeded(); mContext.unregisterComponentCallbacks(this); + // Notify source bounds empty when magnification is deleted. + mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect()); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index ee150ca9db1b8..18ba7dc020e23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -292,6 +292,22 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { assertFalse(hasMagnificationOverlapFlag()); } + @Test + public void deleteWindowMagnification_notifySourceBoundsChanged() { + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + Float.NaN, + Float.NaN)); + + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.deleteWindowMagnification()); + + // The first time is for notifying magnification enabled and the second time is for + // notifying magnification disabled. + verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged( + (eq(mContext.getDisplayId())), any()); + } + @Test public void moveMagnifier_schedulesFrame() { mInstrumentation.runOnMainSync(() -> { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index ecc45eb743c60..6cfbfb8888fb5 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -356,13 +356,6 @@ public class FullScreenMagnificationController implements mSpecAnimationBridge, spec, animationCallback); mControllerCtx.getHandler().sendMessage(m); } - - final boolean lastMagnificationActivated = mMagnificationActivated; - mMagnificationActivated = spec.scale > 1.0f; - if (mMagnificationActivated != lastMagnificationActivated) { - mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState( - mDisplayId, mMagnificationActivated); - } } /** @@ -376,9 +369,17 @@ public class FullScreenMagnificationController implements @GuardedBy("mLock") void onMagnificationChangedLocked() { + final float scale = getScale(); + final boolean lastMagnificationActivated = mMagnificationActivated; + mMagnificationActivated = scale > 1.0f; + if (mMagnificationActivated != lastMagnificationActivated) { + mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState( + mDisplayId, mMagnificationActivated); + } + final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_FULLSCREEN) - .setScale(getScale()) + .setScale(scale) .setCenterX(getCenterX()) .setCenterY(getCenterY()).build(); mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId, diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index b263fb377e825..bb286e61815dc 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -16,6 +16,7 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW; import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; @@ -111,6 +112,15 @@ public class MagnificationController implements WindowMagnificationManager.Callb @GuardedBy("mLock") private final SparseLongArray mFullScreenModeEnabledTimeArray = new SparseLongArray(); + /** + * The transitioning magnification modes on the displays. The controller notifies + * magnification change depending on the target config mode. + * If the target mode is null, it means the config mode of the display is not + * transitioning. + */ + @GuardedBy("mLock") + private final SparseArray mTransitionModes = new SparseArray(); + @GuardedBy("mLock") private final SparseArray mAccessibilityCallbacksDelegateArray = @@ -213,6 +223,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode); final DisableMagnificationCallback animationCallback = getDisableMagnificationEndRunnableLocked(displayId); + if (currentCenter == null && animationCallback == null) { transitionCallBack.onResult(displayId, true); return; @@ -233,6 +244,9 @@ public class MagnificationController implements WindowMagnificationManager.Callb transitionCallBack.onResult(displayId, true); return; } + + setTransitionState(displayId, targetMode); + final FullScreenMagnificationController screenMagnificationController = getFullScreenMagnificationController(); final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); @@ -286,26 +300,51 @@ public class MagnificationController implements WindowMagnificationManager.Callb Slog.w(TAG, "Discard previous animation request"); animationCallback.setExpiredAndRemoveFromListLocked(); } - final FullScreenMagnificationController screenMagnificationController = getFullScreenMagnificationController(); final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); final float targetScale = Float.isNaN(config.getScale()) ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode) : config.getScale(); - if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { - screenMagnificationController.reset(displayId, false); - windowMagnificationMgr.enableWindowMagnification(displayId, - targetScale, magnificationCenter.x, magnificationCenter.y, - animate ? STUB_ANIMATION_CALLBACK : null, id); - } else if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { - windowMagnificationMgr.disableWindowMagnification(displayId, false, null); - if (!screenMagnificationController.isRegistered(displayId)) { - screenMagnificationController.register(displayId); + try { + setTransitionState(displayId, targetMode); + + if (targetMode == MAGNIFICATION_MODE_WINDOW) { + screenMagnificationController.reset(displayId, false); + windowMagnificationMgr.enableWindowMagnification(displayId, + targetScale, magnificationCenter.x, magnificationCenter.y, + animate ? STUB_ANIMATION_CALLBACK : null, id); + } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) { + windowMagnificationMgr.disableWindowMagnification(displayId, false, null); + if (!screenMagnificationController.isRegistered(displayId)) { + screenMagnificationController.register(displayId); + } + screenMagnificationController.setScaleAndCenter(displayId, targetScale, + magnificationCenter.x, magnificationCenter.y, animate, + id); } - screenMagnificationController.setScaleAndCenter(displayId, targetScale, - magnificationCenter.x, magnificationCenter.y, animate, - id); + } finally { + // Reset transition state after enabling target mode. + setTransitionState(displayId, null); + } + } + } + + /** + * Sets magnification config mode transition state. Called when the mode transition starts and + * ends. If the targetMode and the display id are null, it resets all + * the transition state. + * + * @param displayId The logical display id + * @param targetMode The transition target mode. It is not transitioning, if the target mode + * is set null + */ + private void setTransitionState(Integer displayId, Integer targetMode) { + synchronized (mLock) { + if (targetMode == null && displayId == null) { + mTransitionModes.clear(); + } else { + mTransitionModes.put(displayId, targetMode); } } } @@ -413,18 +452,57 @@ public class MagnificationController implements WindowMagnificationManager.Callb @Override public void onSourceBoundsChanged(int displayId, Rect bounds) { - final MagnificationConfig config = new MagnificationConfig.Builder() - .setMode(MAGNIFICATION_MODE_WINDOW) - .setScale(getWindowMagnificationMgr().getScale(displayId)) - .setCenterX(bounds.exactCenterX()) - .setCenterY(bounds.exactCenterY()).build(); - mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); + if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(MAGNIFICATION_MODE_WINDOW) + .setScale(getWindowMagnificationMgr().getScale(displayId)) + .setCenterX(bounds.exactCenterX()) + .setCenterY(bounds.exactCenterY()).build(); + mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); + } } @Override public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config) { - mAms.notifyMagnificationChanged(displayId, region, config); + if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) { + mAms.notifyMagnificationChanged(displayId, region, config); + } + } + + /** + * Should notify magnification change for the given display under the conditions below + * + *
    + *
  1. 1. No mode transitioning and the change mode is active.
  2. + *
  3. 2. No mode transitioning and all the modes are inactive.
  4. + *
  5. 3. It is mode transitioning and the change mode is the transition mode.
  6. + *
+ * + * @param displayId The logical display id + * @param changeMode The mode that has magnification spec change + */ + private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) { + synchronized (mLock) { + final boolean fullScreenMagnifying = mFullScreenMagnificationController != null + && mFullScreenMagnificationController.isMagnifying(displayId); + final boolean windowEnabled = mWindowMagnificationMgr != null + && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); + final Integer transitionMode = mTransitionModes.get(displayId); + if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenMagnifying) + || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled)) + && (transitionMode == null)) { + return true; + } + if ((!fullScreenMagnifying && !windowEnabled) + && (transitionMode == null)) { + return true; + } + if (transitionMode != null && changeMode == transitionMode) { + return true; + } + } + return false; } private void disableFullScreenMagnificationIfNeeded(int displayId) { @@ -740,9 +818,32 @@ public class MagnificationController implements WindowMagnificationManager.Callb return; } setExpiredAndRemoveFromListLocked(); + setTransitionState(mDisplayId, null); + if (success) { adjustCurrentCenterIfNeededLocked(); applyMagnificationModeLocked(mTargetMode); + } else { + // Notify magnification change if magnification is inactive when the + // transition is failed. This is for the failed transition from + // full-screen to window mode. Disable magnification callback helps to send + // magnification inactive change since FullScreenMagnificationController + // would not notify magnification change if the spec is not changed. + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + && !screenMagnificationController.isMagnifying(mDisplayId)) { + MagnificationConfig.Builder configBuilder = + new MagnificationConfig.Builder(); + Region region = new Region(); + configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN) + .setScale(screenMagnificationController.getScale(mDisplayId)) + .setCenterX(screenMagnificationController.getCenterX(mDisplayId)) + .setCenterY(screenMagnificationController.getCenterY(mDisplayId)); + screenMagnificationController.getMagnificationRegion(mDisplayId, + region); + mAms.notifyMagnificationChanged(mDisplayId, region, configBuilder.build()); + } } updateMagnificationButton(mDisplayId, mTargetMode); if (mTransitionCallBack != null) { @@ -770,6 +871,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb return; } setExpiredAndRemoveFromListLocked(); + setTransitionState(mDisplayId, null); applyMagnificationModeLocked(mCurrentMode); updateMagnificationButton(mDisplayId, mCurrentMode); if (mTransitionCallBack != null) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index ca22f80a7189d..eac86715e4c73 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -197,6 +197,31 @@ public class MagnificationControllerTest { assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); } + @Test + public void transitionToWindowModeFailedByReset_fullScreenMagnifying_notifyTransitionFailed() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + mCallbackArgumentCaptor.capture()); + // The transition is interrupted and failed by calling reset. + mCallbackArgumentCaptor.getValue().onResult(false); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, false); + final ArgumentCaptor configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + // The first time is for notifying full-screen enabled and the second time is for notifying + // the target mode transitions failed. + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(MODE_FULLSCREEN, actualConfig.getMode(), 0); + assertEquals(1.0f, actualConfig.getScale(), 0); + } + @Test public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter() throws RemoteException { @@ -478,6 +503,92 @@ public class MagnificationControllerTest { assertEquals(config.getScale(), actualConfig.getScale(), 0); } + @Test + public void transitionMagnificationMode_windowEnabled_notifyTargetMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, mTransitionCallBack); + mMockConnection.invokeCallbacks(); + + final ArgumentCaptor configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + // The first time is for notifying window enabled and the second time is for notifying + // the target mode transitions. + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(MODE_FULLSCREEN, actualConfig.getMode(), 0); + } + + @Test + public void transitionConfigMode_windowEnabled_notifyTargetMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN); + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + config, true, TEST_SERVICE_ID); + mMockConnection.invokeCallbacks(); + + final ArgumentCaptor configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + // The first time is for notifying window enabled and the second time is for notifying + // the target mode transitions. + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0); + assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0); + assertEquals(config.getScale(), actualConfig.getScale(), 0); + } + + @Test + public void transitionMagnificationMode_fullScreenEnabled_notifyTargetMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, mTransitionCallBack); + verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + mCallbackArgumentCaptor.capture()); + mCallbackArgumentCaptor.getValue().onResult(true); + mMockConnection.invokeCallbacks(); + + final ArgumentCaptor configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + // The first time is for notifying full-screen enabled and the second time is for notifying + // the target mode transitions. + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(MODE_WINDOW, actualConfig.getMode(), 0); + } + + @Test + public void transitionConfigMode_fullScreenEnabled_notifyTargetMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + final MagnificationConfig config = obtainMagnificationConfig(MODE_WINDOW); + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + config, true, TEST_SERVICE_ID); + mMockConnection.invokeCallbacks(); + + final ArgumentCaptor configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + // The first time is for notifying full-screen enabled and the second time is for notifying + // the target mode transitions. + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0); + assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0); + assertEquals(config.getScale(), actualConfig.getScale(), 0); + } + + @Test public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton() throws RemoteException { @@ -743,7 +854,7 @@ public class MagnificationControllerTest { } @Test - public void disableWindowMode_windowModeInActive_removeMagnificationButton() + public void disableWindowMode_windowEnabled_removeMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -753,7 +864,7 @@ public class MagnificationControllerTest { } @Test - public void onFullScreenDeactivated_fullscreenModeInActive_removeMagnificationButton() + public void onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, @@ -766,7 +877,7 @@ public class MagnificationControllerTest { } @Test - public void transitionToFullScreenMode_fullscreenModeInActive_showMagnificationButton() + public void transitionToFullScreenMode_windowEnabled_showMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -779,7 +890,7 @@ public class MagnificationControllerTest { } @Test - public void transitionToWindow_fullscreenModeInActive_showMagnificationButton() + public void transitionToWindow_fullScreenEnabled_showMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); @@ -1018,7 +1129,6 @@ public class MagnificationControllerTest { reset(); } - final MagnificationConfig config = new MagnificationConfig.Builder().setMode( MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY( mCenterY).build();