Merge "Fix hard dragging the window magnification from the bottom of the screen" into sc-dev

This commit is contained in:
Ryan Lin
2021-05-12 03:00:13 +00:00
committed by Android (Google) Code Review
10 changed files with 393 additions and 133 deletions

View File

@@ -105,6 +105,8 @@ public class QuickStepContract {
public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17;
// The IME is showing
public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -125,7 +127,8 @@ public class QuickStepContract {
SYSUI_STATE_GLOBAL_ACTIONS_SHOWING,
SYSUI_STATE_ONE_HANDED_ACTIVE,
SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
SYSUI_STATE_IME_SHOWING
SYSUI_STATE_IME_SHOWING,
SYSUI_STATE_MAGNIFICATION_OVERLAP
})
public @interface SystemUiStateFlags {}
@@ -153,6 +156,7 @@ public class QuickStepContract {
str.add((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
? "allow_gesture" : "");
str.add((flags & SYSUI_STATE_IME_SHOWING) != 0 ? "ime_visible" : "");
str.add((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0 ? "magnification_overlap" : "");
return str.toString();
}

View File

@@ -63,17 +63,15 @@ abstract class DisplayIdIndexSupplier<T> {
}
/**
* Gets the object by the element index.
* Returns the object with the given display id.
*
* <p> If the index is bigger than the array size, an {@link ArrayIndexOutOfBoundsException} is
* thrown for apps targeting {@link android.os.Build.VERSION_CODES#Q} and later </p>
*
* @param index the element index
* @param displayId the logical display Id
* @return T
* @see SparseArray#valueAt(int)
*/
public T valueAt(int index) {
return mSparseArray.valueAt(index);
@Nullable
public T valueAt(int displayId) {
return mSparseArray.get(displayId);
}
@NonNull

View File

@@ -113,7 +113,7 @@ class MagnificationGestureDetector {
final float rawX = event.getRawX();
final float rawY = event.getRawY();
boolean handled = false;
switch (event.getAction()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mPointerDown.set(rawX, rawY);
mHandler.postAtTime(mCancelTapGestureRunnable,

View File

@@ -18,10 +18,11 @@ package com.android.systemui.accessibility;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -37,9 +38,13 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import javax.inject.Inject;
/**
@@ -52,34 +57,33 @@ import javax.inject.Inject;
public class WindowMagnification extends SystemUI implements WindowMagnifierCallback,
CommandQueue.Callbacks {
private static final String TAG = "WindowMagnification";
private static final int CONFIG_MASK =
ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION
| ActivityInfo.CONFIG_LOCALE;
private final ModeSwitchesController mModeSwitchesController;
private final Handler mHandler;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
private Configuration mLastConfiguration;
private SysUiState mSysUiState;
private static class AnimationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationAnimationController> {
private final Context mContext;
private final Handler mHandler;
private final NavigationModeController mNavigationModeController;
private final WindowMagnifierCallback mWindowMagnifierCallback;
private final SysUiState mSysUiState;
AnimationControllerSupplier(Context context, Handler handler,
NavigationModeController navigationModeController,
WindowMagnifierCallback windowMagnifierCallback, DisplayManager displayManager) {
WindowMagnifierCallback windowMagnifierCallback,
DisplayManager displayManager, SysUiState sysUiState) {
super(displayManager);
mContext = context;
mHandler = handler;
mNavigationModeController = navigationModeController;
mWindowMagnifierCallback = windowMagnifierCallback;
mSysUiState = sysUiState;
}
@Override
@@ -89,10 +93,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
final WindowMagnificationController controller = new WindowMagnificationController(
mContext,
mHandler, new SfVsyncFrameCallbackProvider(), null,
new SurfaceControl.Transaction(), mWindowMagnifierCallback);
final int navBarMode = mNavigationModeController.addListener(
controller::onNavigationModeChanged);
controller.onNavigationModeChanged(navBarMode);
new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState);
return new WindowMagnificationAnimationController(windowContext, controller);
}
}
@@ -103,24 +104,22 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
@Inject
public WindowMagnification(Context context, @Main Handler mainHandler,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
NavigationModeController navigationModeController) {
SysUiState sysUiState, OverviewProxyService overviewProxyService) {
super(context);
mHandler = mainHandler;
mLastConfiguration = new Configuration(context.getResources().getConfiguration());
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
mSysUiState = sysUiState;
mOverviewProxyService = overviewProxyService;
mAnimationControllerSupplier = new AnimationControllerSupplier(context,
mHandler, navigationModeController, this,
context.getSystemService(DisplayManager.class));
mHandler, this, context.getSystemService(DisplayManager.class), sysUiState);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
final int configDiff = newConfig.diff(mLastConfiguration);
if ((configDiff & CONFIG_MASK) == 0) {
return;
}
mLastConfiguration.setTo(newConfig);
mAnimationControllerSupplier.forEach(
animationController -> animationController.onConfigurationChanged(configDiff));
@@ -132,6 +131,28 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
@Override
public void start() {
mCommandQueue.addCallback(this);
mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
@Override
public void onConnectionChanged(boolean isConnected) {
if (isConnected) {
updateSysUiStateFlag();
}
}
});
}
private void updateSysUiStateFlag() {
//TODO(b/187510533): support multi-display once SysuiState supports it.
final WindowMagnificationAnimationController controller =
mAnimationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY);
if (controller != null) {
controller.updateSysUiStateFlag();
} else {
// The instance is initialized when there is an IPC request. Considering
// self-crash cases, we need to reset the flag in such situation.
mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
.commitUpdate(Display.DEFAULT_DISPLAY);
}
}
@MainThread
@@ -210,6 +231,13 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG);
mAnimationControllerSupplier.forEach(
animationController -> animationController.dump(pw));
}
private void setWindowMagnificationConnection() {
if (mWindowMagnificationConnectionImpl == null) {
mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this,

View File

@@ -31,6 +31,7 @@ import android.view.animation.AccelerateInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -269,6 +270,14 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
mController.enableWindowMagnification(sentScale, centerX, centerY);
}
public void updateSysUiStateFlag() {
mController.updateSysUIStateFlag();
}
void dump(PrintWriter pw) {
mController.dump(pw);
}
private static ValueAnimator newValueAnimator(Resources resources) {
final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(

View File

@@ -16,9 +16,10 @@
package com.android.systemui.accessibility;
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.WindowManager.LayoutParams;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -28,6 +29,7 @@ import android.annotation.UiContext;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -52,14 +54,17 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Locale;
@@ -111,6 +116,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener;
private final Runnable mMirrorViewRunnable;
private final Runnable mUpdateStateDescriptionRunnable;
private final Runnable mWindowInsetChangeRunnable;
private View mMirrorView;
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
@@ -119,9 +125,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private int mOuterBorderSize;
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
private int mNavBarMode;
private int mNavGestureHeight;
// The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
private int mSystemGestureTop = -1;
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private final MagnificationGestureDetector mGestureDetector;
@@ -130,6 +135,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private Locale mLocale;
private NumberFormat mPercentFormat;
private float mBounceEffectAnimationScale;
private SysUiState mSysUiState;
// Set it to true when the view is overlapped with the gesture insets at the bottom.
private boolean mOverlapWithGestureInsets;
@Nullable
private MirrorWindowControl mMirrorWindowControl;
@@ -137,11 +145,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
WindowMagnificationController(@UiContext Context context, @NonNull Handler handler,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
@NonNull WindowMagnifierCallback callback) {
@NonNull WindowMagnifierCallback callback, SysUiState sysUiState) {
mContext = context;
mHandler = handler;
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
final Display display = mContext.getDisplay();
mDisplayId = mContext.getDisplayId();
@@ -170,13 +179,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mMirrorViewRunnable = () -> {
if (mMirrorView != null) {
mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
updateSystemUIStateIfNeeded();
mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
mDisplayId, mMirrorViewBounds);
}
};
mMirrorViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (!mHandler.hasCallbacks(mMirrorViewRunnable)) {
mHandler.post(mMirrorViewRunnable);
}
};
mMirrorSurfaceViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-> applyTapExcludeRegion();
@@ -199,6 +213,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mMirrorView.setStateDescription(formatStateDescription(mScale));
}
};
mWindowInsetChangeRunnable = this::onWindowInsetChanged;
}
private void updateDimensions() {
@@ -210,7 +225,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
R.dimen.magnification_drag_view_size);
mOuterBorderSize = mResources.getDimensionPixelSize(
R.dimen.magnification_outer_border_margin);
updateNavigationBarDimensions();
}
private void computeBounceAnimationScale() {
@@ -220,16 +234,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mBounceEffectAnimationScale = Math.min(animationScaleMax, ANIMATION_BOUNCE_EFFECT_SCALE);
}
private void updateNavigationBarDimensions() {
if (!supportsSwipeUpGesture()) {
mNavGestureHeight = 0;
return;
private boolean updateSystemGestureInsetsTop() {
final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics();
final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures());
final int gestureTop =
insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1;
if (gestureTop != mSystemGestureTop) {
mSystemGestureTop = gestureTop;
return true;
}
mNavGestureHeight = (mWindowBounds.width() > mWindowBounds.height())
? mResources.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
: mResources.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
return false;
}
/**
@@ -255,6 +269,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (mMirrorWindowControl != null) {
mMirrorWindowControl.destroyControl();
}
updateSystemUIStateIfNeeded();
}
/**
@@ -277,6 +292,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
private void updateSystemUIStateIfNeeded() {
updateSysUIState(false);
}
private void updateAccessibilityWindowTitleIfNeeded() {
if (!isWindowVisible()) return;
LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
@@ -284,13 +303,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWm.updateViewLayout(mMirrorView, params);
}
/** Handles MirrorWindow position when the navigation bar mode changed. */
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
updateNavigationBarDimensions();
updateMirrorViewLayout();
}
/** Handles MirrorWindow position when the device rotation changed. */
private void onRotate() {
final Display display = mContext.getDisplay();
@@ -299,7 +311,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
setMagnificationFrameBoundary();
mRotation = display.getRotation();
updateNavigationBarDimensions();
if (!isWindowVisible()) {
return;
@@ -351,6 +362,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
params.receiveInsetsIgnoringZOrder = true;
params.setTitle(mContext.getString(R.string.magnification_window_title));
params.accessibilityTitle = getAccessibilityWindowTitle();
@@ -368,6 +380,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> {
if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) {
mHandler.post(mWindowInsetChangeRunnable);
}
return v.onApplyWindowInsets(insets);
});
mWm.addView(mMirrorView, params);
@@ -377,6 +395,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
addDragTouchListeners();
}
private void onWindowInsetChanged() {
if (updateSystemGestureInsetsTop()) {
updateSystemUIStateIfNeeded();
}
}
private void applyTapExcludeRegion() {
final Region tapExcludeRegion = calculateTapExclude();
final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken());
@@ -464,17 +488,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return;
}
final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth();
final int maxMirrorViewY =
mWindowBounds.height() - mMirrorView.getHeight() - mNavGestureHeight;
final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight();
LayoutParams params =
(LayoutParams) mMirrorView.getLayoutParams();
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
// If nav bar mode supports swipe-up gesture, the Y position of mirror view should not
// overlap nav bar window to prevent window-dragging obscured.
if (supportsSwipeUpGesture()) {
params.y = Math.min(params.y, maxMirrorViewY);
}
// Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
// able to move close to the screen edges.
@@ -508,6 +527,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return false;
}
public void updateSysUIStateFlag() {
updateSysUIState(true);
}
/**
* Calculates the desired source bounds. This will be the area under from the center of the
* displayFrame, factoring in scale.
@@ -569,6 +592,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return false;
}
private void updateSysUIState(boolean force) {
final boolean overlap = isWindowVisible() && mSystemGestureTop > 0
&& mMirrorViewBounds.bottom > mSystemGestureTop;
if (force || overlap != mOverlapWithGestureInsets) {
mOverlapWithGestureInsets = overlap;
mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, mOverlapWithGestureInsets)
.commitUpdate(mDisplayId);
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
createMirror();
@@ -676,10 +709,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return mMirrorView != null;
}
private boolean supportsSwipeUpGesture() {
return mNavBarMode == NAV_BAR_MODE_2BUTTON || mNavBarMode == NAV_BAR_MODE_GESTURAL;
}
private CharSequence formatStateDescription(float scale) {
// Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed
// non-null, so the first time this is called we will always get the appropriate
@@ -722,6 +751,14 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
scaleAnimator.start();
}
public void dump(PrintWriter pw) {
pw.println("WindowMagnificationController (displayId=" + mDisplayId + "):");
pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets);
pw.println(" mScale:" + mScale);
pw.println(" mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty"));
pw.println(" mSystemGestureTop:" + mSystemGestureTop);
}
private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
@Override

View File

@@ -37,7 +37,8 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
@@ -67,9 +68,12 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
@Mock
private ModeSwitchesController mModeSwitchesController;
@Mock
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
@Mock
private IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock
private OverviewProxyService mOverviewProxyService;
private IWindowMagnificationConnection mIWindowMagnificationConnection;
private WindowMagnification mWindowMagnification;
@@ -83,8 +87,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
}).when(mAccessibilityManager).setWindowMagnificationConnection(
any(IWindowMagnificationConnection.class));
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mNavigationModeController);
getContext().getMainThreadHandler(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService);
mWindowMagnification.mAnimationControllerSupplier = new FakeAnimationControllerSupplier(
mContext.getSystemService(DisplayManager.class));

View File

@@ -41,6 +41,7 @@ import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import org.junit.After;
import org.junit.Before;
@@ -83,6 +84,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock
IRemoteMagnificationAnimationCallback mAnimationCallback2;
@Mock
SysUiState mSysUiState;
private SpyWindowMagnificationController mController;
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
@@ -98,7 +101,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
mController = new SpyWindowMagnificationController(mContext, mHandler,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
mWindowMagnifierCallback);
mWindowMagnifierCallback, mSysUiState);
mSpyController = mController.getSpyController();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, mController, newValueAnimator());
@@ -394,6 +397,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
verify(mSpyController).onConfigurationChanged(100);
}
@Test
public void updateSysUiStateFlag_passThrough() {
mWindowMagnificationAnimationController.updateSysUiStateFlag();
verify(mSpyController).updateSysUIStateFlag();
}
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -440,9 +450,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
SpyWindowMagnificationController(Context context, Handler handler,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
WindowMagnifierCallback callback) {
WindowMagnifierCallback callback, SysUiState sysUiState) {
super(context, handler, sfVsyncFrameProvider, mirrorWindowControl, transaction,
callback);
callback, sysUiState);
mSpyController = Mockito.mock(WindowMagnificationController.class);
}

View File

@@ -17,12 +17,15 @@
package com.android.systemui.accessibility;
import static android.view.Choreographer.FrameCallback;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
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;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -33,6 +36,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -40,49 +46,60 @@ 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;
import android.testing.TestableResources;
import android.text.TextUtils;
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;
import androidx.test.filters.SmallTest;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.util.leak.ReferenceTestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@LargeTest
@RunWith(AndroidTestingRunner.class)
public class WindowMagnificationControllerTest extends SysuiTestCase {
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
@Mock
Handler mHandler;
private Handler mHandler;
@Mock
SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
MirrorWindowControl mMirrorWindowControl;
@Mock
WindowMagnifierCallback mWindowMagnifierCallback;
@Mock
SurfaceControl.Transaction mTransaction;
private MirrorWindowControl mMirrorWindowControl;
@Mock
private WindowMagnifierCallback mWindowMagnifierCallback;
@Mock (answer = Answers.RETURNS_DEEP_STUBS)
private SurfaceControl.Transaction mTransaction;
private WindowManager mWindowManager;
private SysUiState mSysUiState = new SysUiState();
private Resources mResources;
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
@@ -93,37 +110,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mContext = Mockito.spy(getContext());
mInstrumentation = InstrumentationRegistry.getInstrumentation();
WindowManager wm = mContext.getSystemService(WindowManager.class);
doAnswer(invocation ->
wm.getMaximumWindowMetrics()
).when(mWindowManager).getMaximumWindowMetrics();
doAnswer(invocation ->
wm.getCurrentWindowMetrics()
).when(mWindowManager).getCurrentWindowMetrics();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
doAnswer(invocation -> {
mMirrorView = invocation.getArgument(0);
WindowManager.LayoutParams lp = invocation.getArgument(1);
mMirrorView.setLayoutParams(lp);
return null;
}).when(mWindowManager).addView(any(View.class), any(WindowManager.LayoutParams.class));
doAnswer(invocation -> {
mMirrorView = null;
return null;
}).when(mWindowManager).removeView(any(View.class));
doAnswer(invocation -> {
FrameCallback callback = invocation.getArgument(0);
callback.doFrame(0);
return null;
}).when(mSfVsyncFrameProvider).postFrameCallback(
any(FrameCallback.class));
when(mTransaction.remove(any())).thenReturn(mTransaction);
when(mTransaction.setGeometry(any(), any(), any(),
anyInt())).thenReturn(mTransaction);
doAnswer(invocation -> {
final Runnable runnable = invocation.getArgument(0);
runnable.run();
return null;
}).when(mHandler).post(
any(Runnable.class));
mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
mResources = getContext().getOrCreateTestableResources().getResources();
mWindowMagnificationController = new WindowMagnificationController(mContext,
mHandler, mSfVsyncFrameProvider,
mMirrorWindowControl, mTransaction, mWindowMagnifierCallback);
mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
}
@@ -135,12 +145,21 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
public void enableWindowMagnification_showControl() {
public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN);
});
verify(mMirrorWindowControl).showControl();
ArgumentCaptor<Rect> boundsCaptor = ArgumentCaptor.forClass(Rect.class);
verify(mWindowMagnifierCallback,
timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onWindowMagnifierBoundsChanged(
eq(mContext.getDisplayId()), boundsCaptor.capture());
final Rect actualBounds = new Rect();
mMirrorView.getBoundsOnScreen(actualBounds);
assertEquals(actualBounds, boundsCaptor.getValue());
}
@Test
@@ -157,6 +176,25 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mMirrorWindowControl).destroyControl();
}
@Test
public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
bounds.bottom);
});
ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.deleteWindowMagnification();
});
verify(mMirrorWindowControl).destroyControl();
assertFalse(hasMagnificationOverlapFlag());
}
@Test
public void moveMagnifier_schedulesFrame() {
mInstrumentation.runOnMainSync(() -> {
@@ -210,7 +248,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
});
assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
verify(mWindowManager).updateViewLayout(any(), any());
// The first invocation is called when the surface is created.
verify(mWindowManager, times(2)).updateViewLayout(any(), any());
}
@Test
@@ -317,17 +356,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
}
@Test
public void onNavigationModeChanged_updateMirrorViewLayout() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN);
mWindowMagnificationController.onNavigationModeChanged(NAV_BAR_MODE_GESTURAL);
});
verify(mWindowManager).updateViewLayout(eq(mMirrorView), any());
}
@Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
@@ -353,16 +381,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final TestableResources testableResources = getContext().getOrCreateTestableResources();
testableResources.addOverride(com.android.internal.R.string.android_system_label,
newA11yWindowTitle);
when(mContext.getResources()).thenReturn(testableResources.getResources());
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
});
ArgumentCaptor<WindowManager.LayoutParams> paramsArgumentCaptor = ArgumentCaptor.forClass(
WindowManager.LayoutParams.class);
verify(mWindowManager).updateViewLayout(eq(mMirrorView), paramsArgumentCaptor.capture());
assertEquals(newA11yWindowTitle, paramsArgumentCaptor.getValue().accessibilityTitle);
assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
}
@Test
@@ -386,4 +410,95 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
fail("mMirrorView scale is not changed");
}
@Test
public void moveWindowMagnificationToTheBottom_enabled_overlapFlagIsTrue() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN);
});
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
});
ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
}
private CharSequence getAccessibilityWindowTitle() {
if (mMirrorView == null) {
return null;
}
WindowManager.LayoutParams layoutParams =
(WindowManager.LayoutParams) mMirrorView.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();
}
}
}

View File

@@ -16,15 +16,21 @@
package com.android.systemui.accessibility;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -36,12 +42,14 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,17 +58,21 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
public class WindowMagnificationTest extends SysuiTestCase {
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock
private ModeSwitchesController mModeSwitchesController;
@Mock
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
@Mock
private IWindowMagnificationConnectionCallback mConnectionCallback;
@Mock
private OverviewProxyService mOverviewProxyService;
private CommandQueue mCommandQueue;
private WindowMagnification mWindowMagnification;
private OverviewProxyListener mOverviewProxyListener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -72,11 +84,18 @@ public class WindowMagnificationTest extends SysuiTestCase {
}).when(mAccessibilityManager).setWindowMagnificationConnection(
any(IWindowMagnificationConnection.class));
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mCommandQueue = new CommandQueue(getContext());
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mNavigationModeController);
mSysUiState, mOverviewProxyService);
mWindowMagnification.start();
final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
ArgumentCaptor.forClass(OverviewProxyListener.class);
verify(mOverviewProxyService).addCallback(listenerArgumentCaptor.capture());
mOverviewProxyListener = listenerArgumentCaptor.getValue();
}
@Test
@@ -99,10 +118,9 @@ public class WindowMagnificationTest extends SysuiTestCase {
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
mWindowMagnification.onWindowMagnifierBoundsChanged(Display.DEFAULT_DISPLAY, testBounds);
mWindowMagnification.onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
verify(mConnectionCallback).onWindowMagnifierBoundsChanged(Display.DEFAULT_DISPLAY,
testBounds);
verify(mConnectionCallback).onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
}
@Test
@@ -111,10 +129,9 @@ public class WindowMagnificationTest extends SysuiTestCase {
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
mWindowMagnification.onPerformScaleAction(Display.DEFAULT_DISPLAY, newScale);
mWindowMagnification.onPerformScaleAction(TEST_DISPLAY, newScale);
verify(mConnectionCallback).onPerformScaleAction(eq(Display.DEFAULT_DISPLAY),
eq(newScale));
verify(mConnectionCallback).onPerformScaleAction(TEST_DISPLAY, newScale);
}
@Test
@@ -122,9 +139,9 @@ public class WindowMagnificationTest extends SysuiTestCase {
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
mWindowMagnification.onAccessibilityActionPerformed(Display.DEFAULT_DISPLAY);
mWindowMagnification.onAccessibilityActionPerformed(TEST_DISPLAY);
verify(mConnectionCallback).onAccessibilityActionPerformed(eq(Display.DEFAULT_DISPLAY));
verify(mConnectionCallback).onAccessibilityActionPerformed(TEST_DISPLAY);
}
@Test
@@ -135,4 +152,42 @@ public class WindowMagnificationTest extends SysuiTestCase {
verify(mModeSwitchesController).onConfigurationChanged(anyInt());
}
@Test
public void overviewProxyIsConnected_noController_resetFlag() {
mOverviewProxyListener.onConnectionChanged(true);
verify(mSysUiState).setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false);
verify(mSysUiState).commitUpdate(mContext.getDisplayId());
}
@Test
public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
final WindowMagnificationAnimationController mController = mock(
WindowMagnificationAnimationController.class);
mWindowMagnification.mAnimationControllerSupplier = new FakeAnimationControllerSupplier(
mContext.getSystemService(DisplayManager.class), mController);
mWindowMagnification.mAnimationControllerSupplier.get(TEST_DISPLAY);
mOverviewProxyListener.onConnectionChanged(true);
verify(mController).updateSysUiStateFlag();
}
private static class FakeAnimationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationAnimationController> {
private final WindowMagnificationAnimationController mController;
FakeAnimationControllerSupplier(DisplayManager displayManager,
WindowMagnificationAnimationController controller) {
super(displayManager);
mController = controller;
}
@Override
protected WindowMagnificationAnimationController createInstance(Display display) {
return mController;
}
}
}