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();