Merge changes from topic "magnification_switch" into sc-dev am: b23e8bc3c3

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14060607

Change-Id: I560d52e9c7833c62400e0b49eb1883407479e136
This commit is contained in:
Minche Li
2021-05-20 04:47:53 +00:00
committed by Automerger Merge Worker
5 changed files with 354 additions and 142 deletions

View File

@@ -1293,7 +1293,7 @@
<dimen name="magnification_drag_view_size">36dp</dimen>
<dimen name="magnification_controls_size">90dp</dimen>
<dimen name="magnification_switch_button_size">48dp</dimen>
<dimen name="magnification_switch_button_padding">6dp</dimen>
<dimen name="magnification_switch_button_margin">16dp</dimen>
<dimen name="magnifier_left_right_controls_width">35dp</dimen>
<dimen name="magnifier_left_right_controls_height">45dp</dimen>
<dimen name="magnifier_up_down_controls_width">45dp</dimen>

View File

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

View File

@@ -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<WindowManager.LayoutParams> 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<WindowManager.LayoutParams> 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<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
final ArgumentCaptor<Runnable> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<Runnable> 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<WindowManager.LayoutParams> 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();
}
}
}