diff options
7 files changed, 712 insertions, 25 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c41f4007aa59..80e9703e0e62 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -120,6 +120,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; +import com.android.server.accessibility.magnification.MagnificationTransitionController; import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -258,6 +259,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; + private MagnificationTransitionController mMagnificationTransitionController; private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); @@ -302,6 +304,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager = a11yWindowManager; mA11yDisplayListener = a11yDisplayListener; mWindowMagnificationMgr = windowMagnificationMgr; + mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); init(); } @@ -321,6 +324,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); + mMagnificationTransitionController = new MagnificationTransitionController(this, mLock); init(); } @@ -1550,9 +1554,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (fallBackMagnificationModeSettingsLocked(userState)) { return; } - mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::notifyRefreshMagnificationModeToInputFilter, - this)); + mMagnificationTransitionController.transitionMagnificationModeLocked( + Display.DEFAULT_DISPLAY, userState.getMagnificationModeLocked(), + this::onMagnificationTransitionEndedLocked); + } + + /** + * Called when the magnification mode transition is completed. + */ + void onMagnificationTransitionEndedLocked(boolean success) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + final int previousMode = userState.getMagnificationModeLocked() + ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + if (!success && previousMode != 0) { + userState.setMagnificationModeLocked(previousMode); + persistMagnificationModeSettingLocked(previousMode); + } else { + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::notifyRefreshMagnificationModeToInputFilter, + this)); + } } private void notifyRefreshMagnificationModeToInputFilter() { @@ -2962,7 +2983,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getWindowMagnificationMgr().setConnection(connection); } - WindowMagnificationManager getWindowMagnificationMgr() { + /** + * Getter of {@link WindowMagnificationManager}. + * + * @return WindowMagnificationManager + */ + public WindowMagnificationManager getWindowMagnificationMgr() { synchronized (mLock) { if (mWindowMagnificationMgr == null) { mWindowMagnificationMgr = new WindowMagnificationManager(mContext, mCurrentUserId); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java new file mode 100644 index 000000000000..af4b34f9613a --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.provider.Settings; +import android.util.Slog; +import android.util.SparseArray; +import android.view.accessibility.MagnificationAnimationCallback; + +import com.android.server.accessibility.AccessibilityManagerService; + +/** + * Handles magnification mode transition. + */ +public class MagnificationTransitionController { + + private static final boolean DEBUG = false; + private static final String TAG = "MagnificationController"; + private final AccessibilityManagerService mAms; + private final PointF mTempPoint = new PointF(); + private final Object mLock; + private final SparseArray<DisableMagnificationCallback> + mMagnificationEndRunnableSparseArray = new SparseArray(); + + + /** + * A callback to inform the magnification transition result. + */ + public interface TransitionCallBack { + /** + * Invoked when the transition ends. + * @param success {@code true} if the transition success. + */ + void onResult(boolean success); + } + + public MagnificationTransitionController(AccessibilityManagerService ams, Object lock) { + mAms = ams; + mLock = lock; + } + + /** + * Transitions to the target Magnification mode with current center of the magnification mode + * if it is available. + * + * @param displayId The logical display + * @param targetMode The target magnification mode + * @param transitionCallBack The callback invoked when the transition is finished. + */ + public void transitionMagnificationModeLocked(int displayId, int targetMode, + @NonNull TransitionCallBack transitionCallBack) { + final PointF magnificationCenter = getCurrentMagnificationBoundsCenterLocked(displayId, + targetMode); + final DisableMagnificationCallback animationCallback = + getDisableMagnificationEndRunnableLocked(displayId); + if (magnificationCenter == null && animationCallback == null) { + transitionCallBack.onResult(true); + return; + } + + if (animationCallback != null) { + if (animationCallback.mCurrentMode == targetMode) { + animationCallback.restoreToCurrentMagnificationMode(); + return; + } + Slog.w(TAG, "request during transition, abandon current:" + + animationCallback.mTargetMode); + animationCallback.setExpiredAndRemoveFromListLocked(); + } + + if (magnificationCenter == null) { + Slog.w(TAG, "Invalid center, ignore it"); + transitionCallBack.onResult(true); + return; + } + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationManager(); + final float scale = windowMagnificationMgr.getPersistedScale(); + final DisableMagnificationCallback animationEndCallback = + new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, + scale, magnificationCenter); + if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + screenMagnificationController.reset(displayId, animationEndCallback); + } else { + windowMagnificationMgr.disableWindowMagnification(displayId, false, + animationEndCallback); + } + setDisableMagnificationCallbackLocked(displayId, animationEndCallback); + } + + private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( + int displayId) { + return mMagnificationEndRunnableSparseArray.get(displayId); + } + + private void setDisableMagnificationCallbackLocked(int displayId, + @Nullable DisableMagnificationCallback callback) { + mMagnificationEndRunnableSparseArray.put(displayId, callback); + if (DEBUG) { + Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId + + ", callback = " + callback); + } + } + + private FullScreenMagnificationController getFullScreenMagnificationController() { + return mAms.getFullScreenMagnificationController(); + } + + private WindowMagnificationManager getWindowMagnificationManager() { + return mAms.getWindowMagnificationMgr(); + } + + private @Nullable + PointF getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode) { + if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { + final WindowMagnificationManager magnificationManager = getWindowMagnificationManager(); + if (!magnificationManager.isWindowMagnifierEnabled(displayId)) { + return null; + } + mTempPoint.set(magnificationManager.getCenterX(displayId), + magnificationManager.getCenterY(displayId)); + } else { + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + if (!screenMagnificationController.isMagnifying(displayId)) { + return null; + } + mTempPoint.set(screenMagnificationController.getCenterX(displayId), + screenMagnificationController.getCenterY(displayId)); + } + return mTempPoint; + } + + private final class DisableMagnificationCallback implements + MagnificationAnimationCallback { + private final TransitionCallBack mTransitionCallBack; + private boolean mExpired = false; + private final int mDisplayId; + private final int mTargetMode; + private final int mCurrentMode; + private final float mCurrentScale; + private final PointF mCurrentCenter = new PointF(); + + DisableMagnificationCallback(TransitionCallBack transitionCallBack, + int displayId, int targetMode, float scale, PointF currentCenter) { + mTransitionCallBack = transitionCallBack; + mDisplayId = displayId; + mTargetMode = targetMode; + mCurrentMode = mTargetMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + mCurrentScale = scale; + mCurrentCenter.set(currentCenter); + } + + @Override + public void onResult(boolean success) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onResult success = " + success); + } + if (mExpired) { + return; + } + setExpiredAndRemoveFromListLocked(); + if (success) { + adjustCurrentCenterIfNeededLocked(); + applyMagnificationModeLocked(mTargetMode); + } + mTransitionCallBack.onResult(success); + } + } + + private void adjustCurrentCenterIfNeededLocked() { + if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + return; + } + final Region outRegion = new Region(); + getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion); + if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) { + return; + } + final Rect bounds = outRegion.getBounds(); + mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY()); + } + + void restoreToCurrentMagnificationMode() { + synchronized (mLock) { + if (mExpired) { + return; + } + setExpiredAndRemoveFromListLocked(); + applyMagnificationModeLocked(mCurrentMode); + mTransitionCallBack.onResult(true); + } + } + + void setExpiredAndRemoveFromListLocked() { + mExpired = true; + setDisableMagnificationCallbackLocked(mDisplayId, null); + } + + private void applyMagnificationModeLocked(int mode) { + if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { + getFullScreenMagnificationController().setScaleAndCenter(mDisplayId, + mCurrentScale, mCurrentCenter.x, + mCurrentCenter.y, true, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } else { + getWindowMagnificationManager().enableWindowMagnification(mDisplayId, + mCurrentScale, mCurrentCenter.x, + mCurrentCenter.y); + } + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index d78d99e0a74b..54618dc6fdce 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -90,7 +90,7 @@ public class WindowMagnificationManager implements */ public void setConnection(@Nullable IWindowMagnificationConnection connection) { synchronized (mLock) { - //Reset connectionWrapper. + // Reset connectionWrapper. if (mConnectionWrapper != null) { mConnectionWrapper.setConnectionCallback(null); if (mConnectionCallback != null) { @@ -302,7 +302,8 @@ public class WindowMagnificationManager implements * @param displayId The logical display id. * @return {@code true} if the window magnification is enabled. */ - boolean isWindowMagnifierEnabled(int displayId) { + @VisibleForTesting + public boolean isWindowMagnifierEnabled(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -398,6 +399,38 @@ public class WindowMagnificationManager implements } /** + * Returns the screen-relative X coordinate of the center of the magnified bounds. + * + * @param displayId The logical display id + * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled. + */ + float getCenterX(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return Float.NaN; + } + return magnifier.getCenterX(); + } + } + + /** + * Returns the screen-relative Y coordinate of the center of the magnified bounds. + * + * @param displayId The logical display id + * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled. + */ + float getCenterY(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return Float.NaN; + } + return magnifier.getCenterY(); + } + } + + /** * Creates the windowMagnifier based on the specified display and stores it. * * @param displayId logical display id. @@ -436,11 +469,13 @@ public class WindowMagnificationManager implements @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { - WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { - magnifier = createWindowMagnifier(displayId); + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + magnifier = createWindowMagnifier(displayId); + } + magnifier.onSourceBoundsChanged(sourceBounds); } - magnifier.onSourceBoundsChanged(sourceBounds); } @Override @@ -469,9 +504,9 @@ public class WindowMagnificationManager implements private boolean mEnabled; private final WindowMagnificationManager mWindowMagnificationManager; - //Records the bounds of window magnification. + // Records the bounds of window magnification. private final Rect mBounds = new Rect(); - //The magnified bounds on the screen. + // The magnified bounds on the screen. private final Rect mSourceBounds = new Rect(); WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { @@ -553,9 +588,20 @@ public class WindowMagnificationManager implements mEnabled = false; } + @GuardedBy("mLock") public void onSourceBoundsChanged(Rect sourceBounds) { mSourceBounds.set(sourceBounds); } + + @GuardedBy("mLock") + float getCenterX() { + return mEnabled ? mSourceBounds.exactCenterX() : Float.NaN; + } + + @GuardedBy("mLock") + float getCenterY() { + return mEnabled ? mSourceBounds.exactCenterY() : Float.NaN; + } } private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 23a37c116c6d..b7355ce92c28 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -230,4 +230,19 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { verify(mMockWindowMagnificationMgr, never()).showMagnificationButton(anyInt(), anyInt()); } + + @SmallTest + public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + userState.setMagnificationCapabilitiesLocked( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + mA11yms.onMagnificationTransitionEndedLocked(false); + + assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, + userState.getMagnificationModeLocked()); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java new file mode 100644 index 000000000000..cd8e39cfd2e7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.magnification; + +import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.test.mock.MockContentResolver; +import android.view.Display; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; +import android.view.accessibility.MagnificationAnimationCallback; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.accessibility.AccessibilityManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for MagnificationController. + */ +@RunWith(AndroidJUnit4.class) +public class MagnificationTransitionControllerTest { + + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; + private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600); + private static final float MAGNIFIED_CENTER_X = 100; + private static final float MAGNIFIED_CENTER_Y = 200; + private static final float DEFAULT_SCALE = 3f; + private static final int CURRENT_USER_ID = UserHandle.USER_CURRENT; + private static final int MODE_WINDOW = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + private static final int MODE_FULLSCREEN = + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + + @Mock private AccessibilityManagerService mService; + @Mock private MagnificationTransitionController.TransitionCallBack mTransitionCallBack; + @Mock private Context mContext; + @Mock private FullScreenMagnificationController mScreenMagnificationController; + @Captor private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor; + + private MockWindowMagnificationConnection mMockConnection; + private WindowMagnificationManager mWindowMagnificationManager; + private MockContentResolver mMockResolver; + private MagnificationTransitionController mMagnificationTransitionController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + FakeSettingsProvider.clearSettingsProvider(); + mMockResolver = new MockContentResolver(); + mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mMockResolver); + Settings.Secure.putFloatForUser(mMockResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE, + CURRENT_USER_ID); + mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID); + when(mService.getFullScreenMagnificationController()).thenReturn( + mScreenMagnificationController); + when(mService.getWindowMagnificationMgr()).thenReturn(mWindowMagnificationManager); + mMockConnection = new MockWindowMagnificationConnection(true); + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mMagnificationTransitionController = new MagnificationTransitionController(mService, + new Object()); + } + + @After + public void tearDown() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Test + public void transitionToWindowMode_notMagnifying_doNothing() throws RemoteException { + setMagnificationModeSettings(MODE_FULLSCREEN); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + verify(mTransitionCallBack).onResult(true); + verify(mScreenMagnificationController, never()).reset(anyInt(), + any(MagnificationAnimationCallback.class)); + verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(), + anyFloat(), anyFloat(), anyFloat(), + nullable(IRemoteMagnificationAnimationCallback.class)); + } + + @Test + public void transitionToWindowMode_fullScreenMagnifying_disableFullScreenAndEnableWindow() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + mCallbackArgumentCaptor.capture()); + mCallbackArgumentCaptor.getValue().onResult(true); + mMockConnection.invokeCallbacks(); + verify(mTransitionCallBack).onResult(true); + assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + } + + @Test + public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + mMockConnection.invokeCallbacks(); + verify(mTransitionCallBack).onResult(true); + assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + } + + @Test + public void transitionToFullScreenMode_windowMagnifying_disableWindowAndEnableFullScreen() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + mMockConnection.invokeCallbacks(); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + @Test + public void transitionToFullScreen_centerNotInTheBounds_magnifyTheCenterOfMagnificationBounds() + throws RemoteException { + final Rect magnificationBounds = MAGNIFICATION_REGION.getBounds(); + final PointF magnifiedCenter = new PointF(magnificationBounds.right + 100, + magnificationBounds.bottom + 100); + setMagnificationEnabled(MODE_WINDOW, magnifiedCenter.x, magnifiedCenter.y); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + mMockConnection.invokeCallbacks(); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, + magnificationBounds.exactCenterX(), magnificationBounds.exactCenterY(), true, + MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + @Test + public void transitionToFullScreenMode_disablingFullScreen_enableFullScreenWithFormerCenter() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, + mTransitionCallBack); + + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(true); + } + + + @Test + public void interruptDuringTransitionToFullScreenMode_windowMagnifying_notifyTransitionFailed() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_FULLSCREEN, + mTransitionCallBack); + + // Enable window magnification while animating. + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, + Float.NaN, Float.NaN, null); + mMockConnection.invokeCallbacks(); + + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, MAGNIFICATION_GESTURE_HANDLER_ID); + verify(mTransitionCallBack).onResult(false); + } + + private void setMagnificationEnabled(int mode) throws RemoteException { + setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + } + + private void setMagnificationEnabled(int mode, float centerX, float centerY) + throws RemoteException { + setMagnificationModeSettings(mode); + Mockito.reset(mScreenMagnificationController); + doAnswer(invocation -> { + final Region outRegion = invocation.getArgument(1); + outRegion.set(MAGNIFICATION_REGION); + return null; + }).when(mScreenMagnificationController).getMagnificationRegion(anyInt(), any(Region.class)); + + final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled( + TEST_DISPLAY); + if (windowMagnifying) { + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + mMockConnection.invokeCallbacks(); + } + if (mode == MODE_FULLSCREEN) { + when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true); + when(mScreenMagnificationController.getPersistedScale()).thenReturn(DEFAULT_SCALE); + when(mScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(DEFAULT_SCALE); + when(mScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn( + centerX); + when(mScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn( + centerY); + } else { + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, + centerX, centerY, null); + mMockConnection.invokeCallbacks(); + } + } + + private void setMagnificationModeSettings(int mode) { + Settings.Secure.putIntForUser(mMockResolver, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, mode, CURRENT_USER_ID); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java index 10322e7ceff0..2a5350454f67 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -43,11 +44,23 @@ class MockWindowMagnificationConnection { public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; private final IWindowMagnificationConnection mConnection; private final Binder mBinder; + private final boolean mSuspendCallback; + private boolean mHasPendingCallback = false; + private boolean mWindowMagnificationEnabled = false; private IBinder.DeathRecipient mDeathRecipient; private IWindowMagnificationConnectionCallback mIMirrorWindowCallback; + private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500); + private float mScale = 2.0f; + private Rect mSourceBounds = new Rect(); + private IRemoteMagnificationAnimationCallback mAnimationCallback; MockWindowMagnificationConnection() throws RemoteException { + this(false); + } + + MockWindowMagnificationConnection(boolean suspendCallback) throws RemoteException { + mSuspendCallback = suspendCallback; mConnection = mock(IWindowMagnificationConnection.class); mBinder = mock(Binder.class); when(mConnection.asBinder()).thenReturn(mBinder); @@ -66,38 +79,57 @@ class MockWindowMagnificationConnection { } private void stubConnection() throws RemoteException { + stubEnableWindowMagnification(); + stubDisableWindowMagnification(); + } + + private void stubEnableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); if (displayId != TEST_DISPLAY) { throw new IllegalArgumentException("only support default display :" + displayId); } + mWindowMagnificationEnabled = true; + final float scale = invocation.getArgument(1); + mScale = Float.isNaN(scale) ? mScale : scale; computeMirrorWindowFrame(invocation.getArgument(2), invocation.getArgument(3)); - final IRemoteMagnificationAnimationCallback callback = invocation.getArgument(4); - if (callback != null) { - callback.onResult(true); - } - if (mIMirrorWindowCallback != null) { - mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, - mMirrorWindowFrame); + setAnimationCallback(invocation.getArgument(4)); + computeSourceBounds(); + mHasPendingCallback = true; + if (!mSuspendCallback) { + invokeCallbacksInternal(true); } return null; }).when(mConnection).enableWindowMagnification(anyInt(), anyFloat(), anyFloat(), anyFloat(), nullable(IRemoteMagnificationAnimationCallback.class)); + } + private void stubDisableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); if (displayId != TEST_DISPLAY) { throw new IllegalArgumentException("only support default display :" + displayId); } - final IRemoteMagnificationAnimationCallback callback = invocation.getArgument(1); - if (callback != null) { - callback.onResult(true); + setAnimationCallback(invocation.getArgument(1)); + mHasPendingCallback = true; + if (!mSuspendCallback) { + invokeCallbacksInternal(true); } return null; }).when(mConnection).disableWindowMagnification(anyInt(), nullable(IRemoteMagnificationAnimationCallback.class)); } + private void computeSourceBounds() { + final int halfWidth = mMirrorWindowFrame.width() / 2; + final int halfHeight = mMirrorWindowFrame.height() / 2; + final int left = mMirrorWindowFrame.left + (halfWidth - (int) (halfWidth / mScale)); + final int right = mMirrorWindowFrame.right - (halfWidth - (int) (halfWidth / mScale)); + final int top = mMirrorWindowFrame.top + (halfHeight - (int) (halfHeight / mScale)); + final int bottom = mMirrorWindowFrame.bottom - (halfHeight - (int) (halfHeight / mScale)); + mSourceBounds.set(left, top, right, bottom); + } + private void computeMirrorWindowFrame(float centerX, float centerY) { final float offsetX = Float.isNaN(centerX) ? 0 : centerX - mMirrorWindowFrame.exactCenterX(); @@ -106,6 +138,13 @@ class MockWindowMagnificationConnection { mMirrorWindowFrame.offset((int) offsetX, (int) offsetY); } + private void sendAnimationEndCallbackIfNeeded(boolean success) throws RemoteException { + if (mAnimationCallback != null) { + mAnimationCallback.onResult(success); + mAnimationCallback = null; + } + } + IWindowMagnificationConnection getConnection() { return mConnection; } @@ -122,8 +161,38 @@ class MockWindowMagnificationConnection { return mIMirrorWindowCallback; } - public Rect getMirrorWindowFrame() { + Rect getMirrorWindowFrame() { return new Rect(mMirrorWindowFrame); } + + void invokeCallbacks() throws RemoteException { + if (!mSuspendCallback) { + throw new IllegalStateException("Invoke callbacks automatically"); + } + invokeCallbacksInternal(true); + } + + private void invokeCallbacksInternal(boolean success) throws RemoteException { + if (!mHasPendingCallback) { + throw new IllegalStateException("There is no any pending callbacks"); + } + if (mWindowMagnificationEnabled && mIMirrorWindowCallback != null) { + mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, + mMirrorWindowFrame); + mIMirrorWindowCallback.onSourceBoundsChanged(TEST_DISPLAY, + mSourceBounds); + } + sendAnimationEndCallbackIfNeeded(success); + mHasPendingCallback = false; + } + + private void setAnimationCallback( + @Nullable IRemoteMagnificationAnimationCallback animationCallback) + throws RemoteException { + if (mAnimationCallback != null) { + invokeCallbacksInternal(false); + } + mAnimationCallback = animationCallback; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 9366e9535ab9..89b0a03a25bb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -47,6 +47,7 @@ import android.test.mock.MockContentResolver; import android.view.Display; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; @@ -180,6 +181,8 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f, mAnimationCallback); + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), + eq(200f), eq(300f), any(IRemoteMagnificationAnimationCallback.class)); verify(mAnimationCallback).onResult(true); } @@ -195,13 +198,16 @@ public class WindowMagnificationManagerTest { } @Test - public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification() { + public void disableWithCallback_hasConnectionAndEnabled_disableWindowMagnification() + throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); - mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false, + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false, mAnimationCallback); + verify(mMockConnection.getConnection()).disableWindowMagnification(eq(TEST_DISPLAY), + any(IRemoteMagnificationAnimationCallback.class)); verify(mAnimationCallback).onResult(true); } @@ -354,6 +360,15 @@ public class WindowMagnificationManagerTest { assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } + @Test + public void centerGetter_enabledOnTestDisplay_expectedValues() { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); + + assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + } + private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) { final int len = pointersLocation.length; |