diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 915b62791f585..b292d7e291711 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1293,7 +1293,7 @@ 36dp 90dp 48dp - 6dp + 16dp 35dp 45dp 45dp diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 9f77b7d26c96c..ee7eb4b50bcb8 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -22,14 +22,17 @@ import android.annotation.NonNull; import android.annotation.UiContext; import android.content.Context; import android.content.pm.ActivityInfo; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import android.util.MathUtils; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityManager; @@ -38,6 +41,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.ImageView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import java.util.Collections; @@ -45,8 +49,8 @@ import java.util.Collections; /** * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of * {@link Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled. - * The button icon is movable by dragging. And the button UI would automatically be dismissed after - * displaying for a period of time. + * The button icon is movable by dragging and it would not overlap navigation bar window. + * And the button UI would automatically be dismissed after displaying for a period of time. */ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener { @@ -64,25 +68,30 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private final AccessibilityManager mAccessibilityManager; private final WindowManager mWindowManager; private final ImageView mImageView; + private final Runnable mWindowInsetChangeRunnable; + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private int mMagnificationMode = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; private final LayoutParams mParams; + @VisibleForTesting + final Rect mDraggableWindowBounds = new Rect(); private boolean mIsVisible = false; private final MagnificationGestureDetector mGestureDetector; private boolean mSingleTapDetected = false; + private boolean mToLeftScreenEdge = false; MagnificationModeSwitch(@UiContext Context context) { - this(context, createView(context)); + this(context, createView(context), new SfVsyncFrameCallbackProvider()); } @VisibleForTesting - MagnificationModeSwitch(Context context, @NonNull ImageView imageView) { + MagnificationModeSwitch(Context context, @NonNull ImageView imageView, + SfVsyncFrameCallbackProvider sfVsyncFrameProvider) { mContext = context; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); - mWindowManager = (WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE); + mWindowManager = mContext.getSystemService(WindowManager.class); + mSfVsyncFrameProvider = sfVsyncFrameProvider; mParams = createLayoutParams(context); mImageView = imageView; - applyResourcesValues(); mImageView.setOnTouchListener(this::onTouch); mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() { @Override @@ -107,6 +116,15 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL return super.performAccessibilityAction(host, action, args); } }); + mWindowInsetChangeRunnable = this::onWindowInsetChanged; + mImageView.setOnApplyWindowInsetsListener((v, insets) -> { + // Adds a pending post check to avoiding redundant calculation because this callback + // is sent frequently when the switch icon window dragged by the users. + if (!mImageView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) { + mImageView.getHandler().post(mWindowInsetChangeRunnable); + } + return v.onApplyWindowInsets(insets); + }); mFadeInAnimationTask = () -> { mImageView.animate() @@ -133,11 +151,17 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL return mContext.getResources().getString(stringId); } - private void applyResourcesValues() { - final int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_switch_button_padding); - mImageView.setPadding(padding, padding, padding, padding); - mImageView.setImageResource(getIconResId(mMagnificationMode)); + private void applyResourcesValuesWithDensityChanged() { + final int size = mContext.getResources().getDimensionPixelSize( + R.dimen.magnification_switch_button_size); + mParams.height = size; + mParams.width = size; + if (mIsVisible) { + stickToScreenEdge(mToLeftScreenEdge); + // Reset button to make its window layer always above the mirror window. + removeButton(); + showButton(mMagnificationMode, /* resetPosition= */false); + } } private boolean onTouch(View v, MotionEvent event) { @@ -168,6 +192,12 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL @Override public boolean onFinish(float xOffset, float yOffset) { + if (mIsVisible) { + final int windowWidth = mWindowManager.getCurrentWindowMetrics().getBounds().width(); + final int halfWindowWidth = windowWidth / 2; + mToLeftScreenEdge = (mParams.x < halfWindowWidth); + stickToScreenEdge(mToLeftScreenEdge); + } if (!mSingleTapDetected) { showButton(mMagnificationMode); } @@ -175,10 +205,18 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL return true; } + private void stickToScreenEdge(boolean toLeftScreenEdge) { + mParams.x = toLeftScreenEdge + ? mDraggableWindowBounds.left : mDraggableWindowBounds.right; + updateButtonViewLayoutIfNeeded(); + } + private void moveButton(float offsetX, float offsetY) { - mParams.x -= offsetX; - mParams.y -= offsetY; - mWindowManager.updateViewLayout(mImageView, mParams); + mSfVsyncFrameProvider.postFrameCallback(l -> { + mParams.x += offsetX; + mParams.y += offsetY; + updateButtonViewLayoutIfNeeded(); + }); } void removeButton() { @@ -193,16 +231,32 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mImageView.setAlpha(0f); mWindowManager.removeView(mImageView); mIsVisible = false; - mParams.x = 0; - mParams.y = 0; } void showButton(int mode) { + showButton(mode, true); + } + + /** + * Shows magnification switch button for the specified magnification mode. + * When the button is going to be visible by calling this method, the layout position can be + * reset depending on the flag. + * + * @param mode The magnification mode + * @param resetPosition if the button position needs be reset + */ + private void showButton(int mode, boolean resetPosition) { if (mMagnificationMode != mode) { mMagnificationMode = mode; mImageView.setImageResource(getIconResId(mode)); } if (!mIsVisible) { + if (resetPosition) { + mDraggableWindowBounds.set(getDraggableWindowBounds()); + mParams.x = mDraggableWindowBounds.right; + mParams.y = mDraggableWindowBounds.bottom; + mToLeftScreenEdge = false; + } mWindowManager.addView(mImageView, mParams); // Exclude magnification switch button from system gesture area. setSystemGestureExclusion(); @@ -229,12 +283,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL void onConfigurationChanged(int configDiff) { if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) { - applyResourcesValues(); - if (mIsVisible) { - mWindowManager.updateViewLayout(mImageView, mParams); - // Exclude magnification switch button from system gesture area. - setSystemGestureExclusion(); - } + applyResourcesValuesWithDensityChanged(); return; } if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { @@ -243,6 +292,25 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL } } + private void onWindowInsetChanged() { + final Rect newBounds = getDraggableWindowBounds(); + if (mDraggableWindowBounds.equals(newBounds)) { + return; + } + mDraggableWindowBounds.set(newBounds); + stickToScreenEdge(mToLeftScreenEdge); + } + + private void updateButtonViewLayoutIfNeeded() { + if (mIsVisible) { + mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left, + mDraggableWindowBounds.right); + mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top, + mDraggableWindowBounds.bottom); + mWindowManager.updateViewLayout(mImageView, mParams); + } + } + private void updateAccessibilityWindowTitle() { mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext); if (mIsVisible) { @@ -283,17 +351,34 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL } private static LayoutParams createLayoutParams(Context context) { + final int size = context.getResources().getDimensionPixelSize( + R.dimen.magnification_switch_button_size); final LayoutParams params = new LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT, + size, + size, LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); - params.gravity = Gravity.BOTTOM | Gravity.RIGHT; + params.gravity = Gravity.TOP | Gravity.LEFT; params.accessibilityTitle = getAccessibilityWindowTitle(context); return params; } + private Rect getDraggableWindowBounds() { + final int layoutMargin = mContext.getResources().getDimensionPixelSize( + R.dimen.magnification_switch_button_margin); + final Rect boundRect = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); + final Insets systemBars = + mWindowManager.getCurrentWindowMetrics().getWindowInsets() + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + final Rect insets = new Rect(layoutMargin, + systemBars.top + layoutMargin, + mParams.width + layoutMargin, + mParams.height + layoutMargin + systemBars.bottom); + boundRect.inset(insets); + return boundRect; + } + private static String getAccessibilityWindowTitle(Context context) { return context.getString(com.android.internal.R.string.android_system_label); } @@ -305,5 +390,4 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL new Rect(0, 0, mImageView.getWidth(), mImageView.getHeight()))); }); } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 3d504fbefefe7..d9a240f4273e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowInsets.Type.systemBars; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; import static com.android.systemui.accessibility.MagnificationModeSwitch.DEFAULT_FADE_OUT_ANIMATION_DELAY_MS; @@ -33,6 +34,7 @@ import static junit.framework.Assert.assertNotNull; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -41,19 +43,27 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ActivityInfo; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Choreographer; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewPropertyAnimator; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -61,6 +71,7 @@ import android.widget.ImageView; import androidx.test.filters.SmallTest; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -69,6 +80,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -77,6 +89,7 @@ import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class MagnificationModeSwitchTest extends SysuiTestCase { private static final float FADE_IN_ALPHA = 1f; @@ -86,7 +99,10 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock - private WindowManager mWindowManager; + private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; + @Mock + private Handler mHandler; + private TestableWindowManager mWindowManager; private ViewPropertyAnimator mViewPropertyAnimator; private MagnificationModeSwitch mMagnificationModeSwitch; private View.OnTouchListener mTouchListener; @@ -96,10 +112,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - WindowManager wm = mContext.getSystemService(WindowManager.class); - doAnswer(invocation -> - wm.getMaximumWindowMetrics() - ).when(mWindowManager).getMaximumWindowMetrics(); + final WindowManager wm = mContext.getSystemService(WindowManager.class); + mWindowManager = spy(new TestableWindowManager(wm)); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); mSpyImageView = Mockito.spy(new ImageView(mContext)); @@ -110,7 +124,14 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { return null; }).when(mSpyImageView).setOnTouchListener( any(View.OnTouchListener.class)); - mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView); + doAnswer(invocation -> { + Choreographer.FrameCallback callback = invocation.getArgument(0); + callback.doFrame(0); + return null; + }).when(mSfVsyncFrameProvider).postFrameCallback( + any(Choreographer.FrameCallback.class)); + mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView, + mSfVsyncFrameProvider); assertNotNull(mTouchListener); } @@ -178,19 +199,64 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test - public void onConfigurationChanged_buttonIsShowing_updateResourcesAndLayout() { + public void onDensityChanged_buttonIsShowing_updateResourcesAndLayout() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); resetAndStubMockImageViewAndAnimator(); mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - verify(mSpyImageView).setPadding(anyInt(), anyInt(), anyInt(), anyInt()); - verify(mSpyImageView).setImageResource( - getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)); - verify(mWindowManager).updateViewLayout(eq(mSpyImageView), any()); + InOrder inOrder = Mockito.inOrder(mWindowManager); + inOrder.verify(mWindowManager).updateViewLayout(eq(mSpyImageView), any()); + inOrder.verify(mWindowManager).removeView(eq(mSpyImageView)); + inOrder.verify(mWindowManager).addView(eq(mSpyImageView), any()); verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class)); } + @Test + public void onApplyWindowInsetsWithBoundsChange_buttonIsShowing_updateLayoutPosition() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + mMagnificationModeSwitch.mDraggableWindowBounds.inset(10, 10); + mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED); + + verify(mWindowManager).updateViewLayout(eq(mSpyImageView), + any(WindowManager.LayoutParams.class)); + assertLayoutPosition(/* toLeftScreenEdge= */ false); + } + + @Test + public void onApplyWindowInsetsWithWindowInsetsChange_buttonIsShowing_draggableBoundsChanged() { + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds); + + mWindowManager.setWindowInsets(new WindowInsets.Builder() + .setInsetsIgnoringVisibility(systemBars(), Insets.of(0, 20, 0, 20)) + .build()); + mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED); + + assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds); + } + + @Test + public void onDraggingGestureFinish_buttonIsShowing_stickToRightEdge() { + final int windowHalfWidth = + mWindowManager.getCurrentWindowMetrics().getBounds().width() / 2; + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + // Drag button to right side on screen + final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop() + 10; + final long downTime = SystemClock.uptimeMillis(); + mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( + downTime, 0, ACTION_DOWN, 100, 100)); + mTouchListener.onTouch(mSpyImageView, + obtainMotionEvent(downTime, downTime, ACTION_MOVE, windowHalfWidth - offset, 100)); + + mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( + downTime, downTime, ACTION_UP, windowHalfWidth - offset, 100)); + + assertLayoutPosition(/* toLeftScreenEdge= */false); + } + @Test public void performSingleTap_fullscreenMode_removeViewAndChangeSettingsValue() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); @@ -344,12 +410,12 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void showButton_hasAccessibilityWindowTitle() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - ArgumentCaptor paramsArgumentCaptor = ArgumentCaptor.forClass( - WindowManager.LayoutParams.class); - verify(mWindowManager).addView(eq(mSpyImageView), paramsArgumentCaptor.capture()); + final WindowManager.LayoutParams layoutPrams = + mWindowManager.getLayoutParamsFromAttachedView(); + assertNotNull(layoutPrams); assertEquals(getContext().getResources().getString( com.android.internal.R.string.android_system_label), - paramsArgumentCaptor.getValue().accessibilityTitle); + layoutPrams.accessibilityTitle); } @Test @@ -361,10 +427,10 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { com.android.internal.R.string.android_system_label, newA11yWindowTitle); mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); - ArgumentCaptor paramsArgumentCaptor = ArgumentCaptor.forClass( - WindowManager.LayoutParams.class); - verify(mWindowManager).updateViewLayout(eq(mSpyImageView), paramsArgumentCaptor.capture()); - assertEquals(newA11yWindowTitle, paramsArgumentCaptor.getValue().accessibilityTitle); + final WindowManager.LayoutParams layoutParams = + mWindowManager.getLayoutParamsFromAttachedView(); + assertNotNull(layoutParams); + assertEquals(newA11yWindowTitle, layoutParams.accessibilityTitle); } private void assertModeUnchanged(int expectedMode) { @@ -374,7 +440,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } private void assertShowFadingAnimation(float alpha) { - ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + final ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); if (alpha == FADE_IN_ALPHA) { // Fade-in verify(mSpyImageView).postOnAnimation(runnableCaptor.capture()); } else { // Fade-out @@ -392,6 +458,13 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { private void resetAndStubMockImageViewAndAnimator() { resetAndStubMockAnimator(); Mockito.reset(mSpyImageView); + final Handler handler = mock(Handler.class); + when(mSpyImageView.getHandler()).thenReturn(handler); + doAnswer(invocation -> { + final Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }).when(handler).post(any(Runnable.class)); doAnswer(invocation -> { final Runnable runnable = invocation.getArgument(0); runnable.run(); @@ -440,4 +513,16 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { mFadeOutAnimation.run(); mFadeOutAnimation = null; } + + private void assertLayoutPosition(boolean toLeftScreenEdge) { + final int expectedX = + toLeftScreenEdge ? mMagnificationModeSwitch.mDraggableWindowBounds.left + : mMagnificationModeSwitch.mDraggableWindowBounds.right; + final int expectedY = mMagnificationModeSwitch.mDraggableWindowBounds.bottom; + final WindowManager.LayoutParams layoutParams = + mWindowManager.getLayoutParamsFromAttachedView(); + assertNotNull(layoutParams); + assertEquals(expectedX, layoutParams.x); + assertEquals(expectedY, layoutParams.y); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java new file mode 100644 index 0000000000000..4f095acd6e6bc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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.systemui.accessibility; + +import static android.view.WindowInsets.Type.systemGestures; + +import android.graphics.Insets; +import android.graphics.Region; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +public class TestableWindowManager implements WindowManager { + + private final WindowManager mWindowManager; + private View mView; + private WindowInsets mWindowInsets = null; + + TestableWindowManager(WindowManager windowManager) { + mWindowManager = windowManager; + } + + @Override + public Display getDefaultDisplay() { + return mWindowManager.getDefaultDisplay(); + } + + @Override + public void removeViewImmediate(View view) { + mWindowManager.removeViewImmediate(view); + } + + @Override + public void requestAppKeyboardShortcuts(WindowManager.KeyboardShortcutsReceiver receiver, + int deviceId) { + mWindowManager.requestAppKeyboardShortcuts(receiver, deviceId); + } + + @Override + public Region getCurrentImeTouchRegion() { + return mWindowManager.getCurrentImeTouchRegion(); + } + + @Override + public void addView(View view, ViewGroup.LayoutParams params) { + mView = view; + mWindowManager.addView(view, params); + } + + @Override + public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + mWindowManager.updateViewLayout(view, params); + } + + @Override + public void removeView(View view) { + mView = null; + mWindowManager.removeView(view); + } + + @Override + public WindowMetrics getCurrentWindowMetrics() { + final Insets systemGesturesInsets = Insets.of(0, 0, 0, 10); + final WindowInsets insets = new WindowInsets.Builder() + .setInsets(systemGestures(), systemGesturesInsets) + .build(); + final WindowMetrics windowMetrics = new WindowMetrics( + mWindowManager.getCurrentWindowMetrics().getBounds(), + mWindowInsets == null ? insets : mWindowInsets); + return windowMetrics; + } + + @Override + public WindowMetrics getMaximumWindowMetrics() { + return mWindowManager.getMaximumWindowMetrics(); + } + + public View getAttachedView() { + return mView; + } + + public WindowManager.LayoutParams getLayoutParamsFromAttachedView() { + if (mView == null) { + return null; + } + return (WindowManager.LayoutParams) mView.getLayoutParams(); + } + + public void setWindowInsets(WindowInsets insets) { + mWindowInsets = insets; + } +} 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 b8734df678d60..5fa63bc3d0619 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -17,7 +17,6 @@ package com.android.systemui.accessibility; import static android.view.Choreographer.FrameCallback; -import static android.view.WindowInsets.Type.systemGestures; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; @@ -46,9 +45,7 @@ import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; -import android.graphics.Insets; import android.graphics.Rect; -import android.graphics.Region; import android.os.Handler; import android.os.SystemClock; import android.testing.AndroidTestingRunner; @@ -58,10 +55,7 @@ import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowMetrics; import android.view.accessibility.AccessibilityNodeInfo; import androidx.test.InstrumentationRegistry; @@ -96,14 +90,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private MirrorWindowControl mMirrorWindowControl; @Mock private WindowMagnifierCallback mWindowMagnifierCallback; - @Mock (answer = Answers.RETURNS_DEEP_STUBS) + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction; - private WindowManager mWindowManager; + private TestableWindowManager mWindowManager; private SysUiState mSysUiState = new SysUiState(); private Resources mResources; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; - private View mMirrorView; @Before public void setUp() { @@ -157,7 +150,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onWindowMagnifierBoundsChanged( eq(mContext.getDisplayId()), boundsCaptor.capture()); final Rect actualBounds = new Rect(); - mMirrorView.getBoundsOnScreen(actualBounds); + final View mirrorView = mWindowManager.getAttachedView(); + assertNotNull(mirrorView); + mirrorView.getBoundsOnScreen(actualBounds); assertEquals(actualBounds, boundsCaptor.getValue()); } @@ -218,7 +213,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mHandler).postDelayed(runnableArgumentCaptor.capture(), anyLong()); runnableArgumentCaptor.getValue().run(); - assertThat(mMirrorView.getStateDescription().toString(), containsString("300")); + final View mirrorView = mWindowManager.getAttachedView(); + assertNotNull(mirrorView); + assertThat(mirrorView.getStateDescription().toString(), containsString("300")); } @Test @@ -300,10 +297,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, Float.NaN); }); - assertNotNull(mMirrorView); + final View mirrorView = mWindowManager.getAttachedView(); + assertNotNull(mirrorView); final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); - mMirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); + mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); assertNotNull(nodeInfo.getContentDescription()); assertThat(nodeInfo.getStateDescription().toString(), containsString("250")); @@ -323,24 +321,24 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, Float.NaN); }); - assertNotNull(mMirrorView); + final View mirrorView = mWindowManager.getAttachedView(); assertTrue( - mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)); + mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)); // Minimum scale is 2.0. verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(2.0f)); - assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)); + assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)); verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(3.5f)); // TODO: Verify the final state when the mirror surface is visible. - assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)); + assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)); assertTrue( - mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)); + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)); assertTrue( - mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)); + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)); assertTrue( - mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)); + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)); } @Test @@ -351,7 +349,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); + final View mirrorView = mWindowManager.getAttachedView(); + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId)); } @@ -363,12 +362,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - ArgumentCaptor paramsArgumentCaptor = ArgumentCaptor.forClass( - WindowManager.LayoutParams.class); - verify(mWindowManager).addView(eq(mMirrorView), paramsArgumentCaptor.capture()); assertEquals(getContext().getResources().getString( - com.android.internal.R.string.android_system_label), - paramsArgumentCaptor.getValue().accessibilityTitle); + com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle()); } @Test @@ -400,15 +395,15 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.onSingleTap(); }); + final View mirrorView = mWindowManager.getAttachedView(); final long timeout = SystemClock.uptimeMillis() + 1000; while (SystemClock.uptimeMillis() < timeout) { SystemClock.sleep(10); - - if (Float.compare(1.0f, mMirrorView.getScaleX()) < 0) { + if (Float.compare(1.0f, mirrorView.getScaleX()) < 0) { return; } } - fail("mMirrorView scale is not changed"); + fail("MirrorView scale is not changed"); } @Test @@ -428,77 +423,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } private CharSequence getAccessibilityWindowTitle() { - if (mMirrorView == null) { - return null; + final View mirrorView = mWindowManager.getAttachedView(); + if (mirrorView == null) { + return null; } WindowManager.LayoutParams layoutParams = - (WindowManager.LayoutParams) mMirrorView.getLayoutParams(); + (WindowManager.LayoutParams) mirrorView.getLayoutParams(); return layoutParams.accessibilityTitle; } private boolean hasMagnificationOverlapFlag() { return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0; } - - private class TestableWindowManager implements WindowManager { - - private final WindowManager mWindowManager; - - TestableWindowManager(WindowManager windowManager) { - mWindowManager = windowManager; - } - - @Override - public Display getDefaultDisplay() { - return mWindowManager.getDefaultDisplay(); - } - - @Override - public void removeViewImmediate(View view) { - mWindowManager.removeViewImmediate(view); - } - - @Override - public void requestAppKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) { - mWindowManager.requestAppKeyboardShortcuts(receiver, deviceId); - } - - @Override - public Region getCurrentImeTouchRegion() { - return mWindowManager.getCurrentImeTouchRegion(); - } - - @Override - public void addView(View view, ViewGroup.LayoutParams params) { - mMirrorView = view; - mWindowManager.addView(view, params); - } - - @Override - public void updateViewLayout(View view, ViewGroup.LayoutParams params) { - mWindowManager.updateViewLayout(view, params); - } - - @Override - public void removeView(View view) { - mMirrorView = null; - mWindowManager.removeView(view); - } - - @Override - public WindowMetrics getCurrentWindowMetrics() { - final Insets systemGesturesInsets = Insets.of(0, 0, 0, 10); - final WindowInsets insets = new WindowInsets.Builder() - .setInsets(systemGestures(), systemGesturesInsets) - .build(); - final WindowMetrics windowMetrics = new WindowMetrics( - mWindowManager.getCurrentWindowMetrics().getBounds(), insets); - return windowMetrics; - } - - @Override - public WindowMetrics getMaximumWindowMetrics() { - return mWindowManager.getMaximumWindowMetrics(); - } - } }