summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java4
-rw-r--r--core/java/android/view/WindowManager.java8
-rw-r--r--core/java/android/view/WindowManagerImpl.java4
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java26
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java349
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java151
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java1502
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java31
12 files changed, 2048 insertions, 84 deletions
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 4840f003da3e..5249fd5253f2 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -538,8 +538,8 @@ public class SurfaceControlViewHost {
}
private void addWindowToken(WindowManager.LayoutParams attrs) {
- final WindowManagerImpl wm =
- (WindowManagerImpl) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
+ final WindowManager wm =
+ (WindowManager) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
attrs.token = wm.getDefaultToken();
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 427d053f754e..c78826116426 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -6109,4 +6109,12 @@ public interface WindowManager extends ViewManager {
throw new UnsupportedOperationException(
"getSurfaceControlInputClientToken is not implemented");
}
+
+ /**
+ * @hide
+ */
+ default @NonNull IBinder getDefaultToken() {
+ throw new UnsupportedOperationException(
+ "getDefaultToken is not implemented");
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 41d181c1b10c..5072ad755cba 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -458,7 +458,9 @@ public final class WindowManagerImpl implements WindowManager {
return null;
}
- IBinder getDefaultToken() {
+ @Override
+ @NonNull
+ public IBinder getDefaultToken() {
return mDefaultToken;
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 7782fd7e6c2a..ef1bf5a5c548 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -62,6 +62,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.view.IWindow;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityEvent.EventType;
@@ -2404,4 +2405,29 @@ public final class AccessibilityManager {
throw re.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+ * specified display.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ public void attachAccessibilityOverlayToDisplay(
+ int displayId, @NonNull SurfaceControl surfaceControl) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.attachAccessibilityOverlayToDisplay_enforcePermission(
+ displayId, surfaceControl);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 9c04c27d189a..1c5d29e0ff1e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -31,6 +31,7 @@ import android.view.accessibility.IMagnificationConnection;
import android.view.InputEvent;
import android.view.IWindow;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
/**
* Interface implemented by the AccessibilityManagerService called by
@@ -136,4 +137,7 @@ interface IAccessibilityManager {
MagnificationSpec magnificationSpec;
}
WindowTransformationSpec getWindowTransformationSpec(int windowId);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
+ void attachAccessibilityOverlayToDisplay_enforcePermission(int displayId, in SurfaceControl surfaceControl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 3ca95e11d789..5171a1f22791 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -19,6 +19,7 @@ package com.android.systemui.accessibility;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -28,10 +29,12 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Display;
import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IMagnificationConnection;
@@ -40,6 +43,7 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.CoreStartable;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
@@ -49,6 +53,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
+import java.util.function.Supplier;
import javax.inject.Inject;
@@ -101,19 +106,28 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@Override
protected WindowMagnificationController createInstance(Display display) {
final Context windowContext = mContext.createWindowContext(display,
- TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
+ Flags.createWindowlessWindowMagnifier()
+ ? TYPE_ACCESSIBILITY_OVERLAY
+ : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ /* options */ null);
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+
+ Supplier<SurfaceControlViewHost> scvhSupplier = () ->
+ Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
+ mContext.getDisplay(), new Binder(), TAG) : null;
+
return new WindowMagnificationController(
windowContext,
mHandler,
new WindowMagnificationAnimationController(windowContext),
- new SfVsyncFrameCallbackProvider(),
- null,
+ /* mirrorWindowControl= */ null,
new SurfaceControl.Transaction(),
mWindowMagnifierCallback,
mSysUiState,
- WindowManagerGlobal::getWindowSession,
- mSecureSettings);
+ mSecureSettings,
+ scvhSupplier,
+ new SfVsyncFrameCallbackProvider(),
+ WindowManagerGlobal::getWindowSession);
}
}
@@ -140,7 +154,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@Override
protected MagnificationSettingsController createInstance(Display display) {
final Context windowContext = mContext.createWindowContext(display,
- TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
+ TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new MagnificationSettingsController(
windowContext,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index dde9f48424ea..33728ac330dc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -63,23 +63,27 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.UiThread;
import androidx.core.math.MathUtils;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -158,6 +162,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private int mMagnificationFrameOffsetX = 0;
private int mMagnificationFrameOffsetY = 0;
+ @Nullable private Supplier<SurfaceControlViewHost> mScvhSupplier;
+
+ /**
+ * SurfaceControlViewHost is used to control the position of the window containing
+ * {@link #mMirrorView}. Using SurfaceControlViewHost instead of a regular window enables
+ * changing the window's position and setting {@link #mMirrorSurface}'s geometry atomically.
+ */
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+
// The root of the mirrored content
private SurfaceControl mMirrorSurface;
@@ -236,21 +249,21 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@UiContext Context context,
@NonNull Handler handler,
@NonNull WindowMagnificationAnimationController animationController,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl,
SurfaceControl.Transaction transaction,
@NonNull WindowMagnifierCallback callback,
SysUiState sysUiState,
- @NonNull Supplier<IWindowSession> globalWindowSessionSupplier,
- SecureSettings secureSettings) {
+ SecureSettings secureSettings,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
+ Supplier<IWindowSession> globalWindowSessionSupplier) {
mContext = context;
mHandler = handler;
mAnimationController = animationController;
- mGlobalWindowSessionSupplier = globalWindowSessionSupplier;
mAnimationController.setWindowMagnificationController(this);
- mSfVsyncFrameProvider = sfVsyncFrameProvider;
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
+ mScvhSupplier = scvhSupplier;
mConfiguration = new Configuration(context.getResources().getConfiguration());
mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
@@ -288,22 +301,78 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mTransaction = transaction;
mGestureDetector =
new MagnificationGestureDetector(mContext, handler, this);
+ mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+ mGlobalWindowSessionSupplier = globalWindowSessionSupplier;
+ mSfVsyncFrameProvider = sfVsyncFrameProvider;
// Initialize listeners.
- mMirrorViewRunnable = () -> {
- if (mMirrorView != null) {
- final Rect oldViewBounds = new Rect(mMirrorViewBounds);
- mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
- if (oldViewBounds.width() != mMirrorViewBounds.width()
- || oldViewBounds.height() != mMirrorViewBounds.height()) {
- mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
- new Rect(0, 0, mMirrorViewBounds.width(), mMirrorViewBounds.height())));
+ if (Flags.createWindowlessWindowMagnifier()) {
+ mMirrorViewRunnable = new Runnable() {
+ final Rect mPreviousBounds = new Rect();
+
+ @Override
+ public void run() {
+ if (mMirrorView != null) {
+ if (mPreviousBounds.width() != mMirrorViewBounds.width()
+ || mPreviousBounds.height() != mMirrorViewBounds.height()) {
+ mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
+ new Rect(0, 0, mMirrorViewBounds.width(),
+ mMirrorViewBounds.height())));
+ mPreviousBounds.set(mMirrorViewBounds);
+ }
+ updateSystemUIStateIfNeeded();
+ mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
+ mDisplayId, mMirrorViewBounds);
+ }
}
- updateSystemUIStateIfNeeded();
- mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
- mDisplayId, mMirrorViewBounds);
- }
- };
+ };
+
+ mMirrorSurfaceViewLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ mMirrorView.post(this::applyTapExcludeRegion);
+
+ mMirrorViewGeometryVsyncCallback = null;
+ } else {
+ mMirrorViewRunnable = () -> {
+ if (mMirrorView != null) {
+ final Rect oldViewBounds = new Rect(mMirrorViewBounds);
+ mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
+ if (oldViewBounds.width() != mMirrorViewBounds.width()
+ || oldViewBounds.height() != mMirrorViewBounds.height()) {
+ mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
+ new Rect(0, 0,
+ mMirrorViewBounds.width(), mMirrorViewBounds.height())));
+ }
+ updateSystemUIStateIfNeeded();
+ mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
+ mDisplayId, mMirrorViewBounds);
+ }
+ };
+
+ mMirrorSurfaceViewLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ mMirrorView.post(this::applyTapExcludeRegion);
+
+ mMirrorViewGeometryVsyncCallback =
+ l -> {
+ if (isActivated() && mMirrorSurface != null && calculateSourceBounds(
+ mMagnificationFrame, mScale)) {
+ // The final destination for the magnification surface should be at 0,0
+ // since the ViewRootImpl's position will change
+ mTmpRect.set(0, 0, mMagnificationFrame.width(),
+ mMagnificationFrame.height());
+ mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect,
+ Surface.ROTATION_0).apply();
+
+ // Notify source bounds change when the magnifier is not animating.
+ if (!mAnimationController.isAnimating()) {
+ mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId,
+ mSourceBounds);
+ }
+ }
+ };
+ }
+
mMirrorViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (!mHandler.hasCallbacks(mMirrorViewRunnable)) {
@@ -311,34 +380,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
};
- mMirrorSurfaceViewLayoutChangeListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
- mMirrorView.post(this::applyTapExcludeRegion);
-
- mMirrorViewGeometryVsyncCallback =
- l -> {
- if (isActivated() && mMirrorSurface != null && calculateSourceBounds(
- mMagnificationFrame, mScale)) {
- // The final destination for the magnification surface should be at 0,0
- // since the ViewRootImpl's position will change
- mTmpRect.set(0, 0, mMagnificationFrame.width(),
- mMagnificationFrame.height());
- mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect,
- Surface.ROTATION_0).apply();
-
- // Notify source bounds change when the magnifier is not animating.
- if (!mAnimationController.isAnimating()) {
- mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId,
- mSourceBounds);
- }
- }
- };
mUpdateStateDescriptionRunnable = () -> {
if (isActivated()) {
mMirrorView.setStateDescription(formatStateDescription(mScale));
}
};
- mWindowInsetChangeRunnable = this::onWindowInsetChanged;
}
private void setupMagnificationSizeScaleOptions() {
@@ -448,13 +494,21 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (mMirrorView != null) {
mHandler.removeCallbacks(mMirrorViewRunnable);
mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
- mWm.removeView(mMirrorView);
+ if (!Flags.createWindowlessWindowMagnifier()) {
+ mWm.removeView(mMirrorView);
+ }
mMirrorView = null;
}
if (mMirrorWindowControl != null) {
mMirrorWindowControl.destroyControl();
}
+
+ if (mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ mSurfaceControlViewHost = null;
+ }
+
mMirrorViewBounds.setEmpty();
mSourceBounds.setEmpty();
updateSystemUIStateIfNeeded();
@@ -551,7 +605,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (!isActivated()) return;
LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
params.accessibilityTitle = getAccessibilityWindowTitle();
- mWm.updateViewLayout(mMirrorView, params);
+ if (Flags.createWindowlessWindowMagnifier()) {
+ mSurfaceControlViewHost.relayout(params);
+ } else {
+ mWm.updateViewLayout(mMirrorView, params);
+ }
}
/**
@@ -602,6 +660,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private void createMirrorWindow() {
+ if (Flags.createWindowlessWindowMagnifier()) {
+ createWindowlessMirrorWindow();
+ return;
+ }
+
// The window should be the size the mirrored surface will be but also add room for the
// border and the drag handle.
int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
@@ -652,6 +715,68 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
addDragTouchListeners();
}
+ private void createWindowlessMirrorWindow() {
+ // The window should be the size the mirrored surface will be but also add room for the
+ // border and the drag handle.
+ int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
+ int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
+
+ // TODO delete TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, it shouldn't be needed anymore
+
+ LayoutParams params = new LayoutParams(
+ windowWidth, windowHeight,
+ LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+ LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.receiveInsetsIgnoringZOrder = true;
+ params.setTitle(mContext.getString(R.string.magnification_window_title));
+ params.accessibilityTitle = getAccessibilityWindowTitle();
+ params.setTrustedOverlay();
+
+ mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
+ mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+
+ mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
+
+ // Allow taps to go through to the mirror SurfaceView below.
+ mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
+
+ mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
+ mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
+ mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> {
+ if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) {
+ mHandler.post(mWindowInsetChangeRunnable);
+ }
+ return v.onApplyWindowInsets(insets);
+ });
+
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mMirrorView, params);
+ SurfaceControl surfaceControl = mSurfaceControlViewHost
+ .getSurfacePackage().getSurfaceControl();
+
+ int x = mMagnificationFrame.left - mMirrorSurfaceMargin;
+ int y = mMagnificationFrame.top - mMirrorSurfaceMargin;
+ mTransaction
+ .setCrop(surfaceControl, new Rect(0, 0, windowWidth, windowHeight))
+ .setPosition(surfaceControl, x, y)
+ .setLayer(surfaceControl, Integer.MAX_VALUE)
+ .show(surfaceControl)
+ .apply();
+
+ mMirrorViewBounds.set(x, y, x + windowWidth, y + windowHeight);
+
+ AccessibilityManager accessibilityManager = mContext
+ .getSystemService(AccessibilityManager.class);
+ accessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+
+ SurfaceHolder holder = mMirrorSurfaceView.getHolder();
+ holder.addCallback(this);
+ holder.setFormat(PixelFormat.RGBA_8888);
+ addDragTouchListeners();
+ }
+
private void onWindowInsetChanged() {
if (updateSystemGestureInsetsTop()) {
updateSystemUIStateIfNeeded();
@@ -659,6 +784,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private void applyTapExcludeRegion() {
+ if (Flags.createWindowlessWindowMagnifier()) {
+ applyTouchableRegion();
+ return;
+ }
+
// Sometimes this can get posted and run after deleteWindowMagnification() is called.
if (mMirrorView == null) return;
@@ -709,6 +839,51 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return regionInsideDragBorder;
}
+ private void applyTouchableRegion() {
+ // Sometimes this can get posted and run after deleteWindowMagnification() is called.
+ if (mMirrorView == null) return;
+
+ var surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl();
+ surfaceControl.setTouchableRegion(calculateTouchableRegion());
+ }
+
+ private Region calculateTouchableRegion() {
+ Region touchableRegion = new Region(0, 0, mMirrorView.getWidth(), mMirrorView.getHeight());
+
+ Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
+ mMirrorView.getWidth() - mBorderDragSize,
+ mMirrorView.getHeight() - mBorderDragSize);
+ touchableRegion.op(regionInsideDragBorder, Region.Op.DIFFERENCE);
+
+ Rect dragArea = new Rect();
+ mDragView.getHitRect(dragArea);
+
+ Rect topLeftArea = new Rect();
+ mTopLeftCornerView.getHitRect(topLeftArea);
+
+ Rect topRightArea = new Rect();
+ mTopRightCornerView.getHitRect(topRightArea);
+
+ Rect bottomLeftArea = new Rect();
+ mBottomLeftCornerView.getHitRect(bottomLeftArea);
+
+ Rect bottomRightArea = new Rect();
+ mBottomRightCornerView.getHitRect(bottomRightArea);
+
+ Rect closeArea = new Rect();
+ mCloseView.getHitRect(closeArea);
+
+ // Add touchable regions for drag and close
+ touchableRegion.op(dragArea, Region.Op.UNION);
+ touchableRegion.op(topLeftArea, Region.Op.UNION);
+ touchableRegion.op(topRightArea, Region.Op.UNION);
+ touchableRegion.op(bottomLeftArea, Region.Op.UNION);
+ touchableRegion.op(bottomRightArea, Region.Op.UNION);
+ touchableRegion.op(closeArea, Region.Op.UNION);
+
+ return touchableRegion;
+ }
+
private String getAccessibilityWindowTitle() {
return mResources.getString(com.android.internal.R.string.android_system_label);
}
@@ -852,8 +1027,84 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* {@link #mMagnificationFrame}.
*/
private void modifyWindowMagnification(boolean computeWindowSize) {
- mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback);
- updateMirrorViewLayout(computeWindowSize);
+ if (Flags.createWindowlessWindowMagnifier()) {
+ updateMirrorSurfaceGeometry();
+ updateWindowlessMirrorViewLayout(computeWindowSize);
+ } else {
+ mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback);
+ updateMirrorViewLayout(computeWindowSize);
+ }
+ }
+
+ /**
+ * Updates {@link #mMirrorSurface}'s geometry. This modifies {@link #mTransaction} but does not
+ * apply it.
+ */
+ @UiThread
+ private void updateMirrorSurfaceGeometry() {
+ if (isActivated() && mMirrorSurface != null
+ && calculateSourceBounds(mMagnificationFrame, mScale)) {
+ // The final destination for the magnification surface should be at 0,0
+ // since the ViewRootImpl's position will change
+ mTmpRect.set(0, 0, mMagnificationFrame.width(), mMagnificationFrame.height());
+ mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, Surface.ROTATION_0);
+
+ // Notify source bounds change when the magnifier is not animating.
+ if (!mAnimationController.isAnimating()) {
+ mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ }
+ }
+ }
+
+ /**
+ * Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based
+ * on the position and size of {@link #mMagnificationFrame}.
+ *
+ * @param computeWindowSize set to {@code true} to compute window size with
+ * {@link #mMagnificationFrame}.
+ */
+ @UiThread
+ private void updateWindowlessMirrorViewLayout(boolean computeWindowSize) {
+ if (!isActivated()) {
+ return;
+ }
+
+ final int width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
+ final int height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
+
+ final int minX = -mOuterBorderSize;
+ final int maxX = mWindowBounds.right - width + mOuterBorderSize;
+ final int x = MathUtils.clamp(mMagnificationFrame.left - mMirrorSurfaceMargin, minX, maxX);
+
+ final int minY = -mOuterBorderSize;
+ final int maxY = mWindowBounds.bottom - height + mOuterBorderSize;
+ final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY);
+
+ if (computeWindowSize) {
+ LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
+ params.width = width;
+ params.height = height;
+ mSurfaceControlViewHost.relayout(params);
+ mTransaction.setCrop(mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl(),
+ new Rect(0, 0, width, height));
+ }
+
+ mMirrorViewBounds.set(x, y, x + width, y + height);
+ mTransaction.setPosition(
+ mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl(), x, y);
+ if (computeWindowSize) {
+ mSurfaceControlViewHost.getRootSurfaceControl().applyTransactionOnDraw(mTransaction);
+ } else {
+ mTransaction.apply();
+ }
+
+ // If they are not dragging the handle, we can move the drag handle immediately without
+ // disruption. But if they are dragging it, we avoid moving until the end of the drag.
+ if (!mIsDragging) {
+ mMirrorView.post(this::maybeRepositionButton);
+ }
+
+ mMirrorViewRunnable.run();
}
/**
@@ -1094,7 +1345,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/**
* Enables window magnification with specified parameters. If the given scale is <strong>less
- * than or equal to 1.0f<strong>, then
+ * than or equal to 1.0f</strong>, then
* {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
* be consistent with the behavior of display magnification.
*
@@ -1110,7 +1361,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/**
* Enables window magnification with specified parameters. If the given scale is <strong>less
- * than 1.0f<strong>, then
+ * than 1.0f</strong>, then
* {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to
* be consistent with the behavior of display magnification.
*
@@ -1426,10 +1677,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
final FrameLayout.LayoutParams layoutParams =
(FrameLayout.LayoutParams) mDragView.getLayoutParams();
- mMirrorView.getBoundsOnScreen(mTmpRect);
-
final int newGravity;
- if (mTmpRect.right >= screenEdgeX) {
+ if (mMirrorViewBounds.right >= screenEdgeX) {
newGravity = Gravity.BOTTOM | Gravity.LEFT;
} else {
newGravity = Gravity.BOTTOM | Gravity.RIGHT;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index 44770fab2d30..b23dfdc68a87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -16,8 +16,10 @@
package com.android.systemui.accessibility;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.IBinder;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
@@ -89,6 +91,11 @@ public class TestableWindowManager implements WindowManager {
return mWindowManager.getMaximumWindowMetrics();
}
+ @Override
+ public @NonNull IBinder getDefaultToken() {
+ return mWindowManager.getDefaultToken();
+ }
+
public View getAttachedView() {
return mView;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index d86d12303140..8299acbc2d52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -21,8 +21,10 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -33,10 +35,16 @@ import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -46,6 +54,7 @@ import android.view.animation.AccelerateInterpolator;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.model.SysUiState;
@@ -63,7 +72,10 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
@LargeTest
@RunWith(AndroidTestingRunner.class)
@@ -71,6 +83,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Rule
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final float DEFAULT_SCALE = 4.0f;
private static final float DEFAULT_CENTER_X = 400.0f;
private static final float DEFAULT_CENTER_Y = 500.0f;
@@ -107,6 +121,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
private TestableWindowManager mWindowManager;
private ValueAnimator mValueAnimator;
+ // This list contains all SurfaceControlViewHosts created during a given test. If the
+ // magnification window is recreated during a test, the list will contain more than a single
+ // element.
+ private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
+ // The most recently created SurfaceControlViewHost.
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControl.Transaction mTransaction;
@Before
public void setUp() throws Exception {
@@ -123,10 +144,27 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mValueAnimator = newValueAnimator();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, mValueAnimator);
- mController = new SpyWindowMagnificationController(mContext, mHandler,
+
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
+ mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
+ mContext, mContext.getDisplay(), new Binder(), "WindowMagnification"));
+ mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
+ return mSurfaceControlViewHost;
+ };
+
+ mTransaction = spy(new SurfaceControl.Transaction());
+ mController = new SpyWindowMagnificationController(
+ mContext,
+ mHandler,
mWindowMagnificationAnimationController,
- mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
- mWindowMagnifierCallback, mSysUiState, mSecureSettings);
+ /* mirrorWindowControl= */ null,
+ mTransaction,
+ mWindowMagnifierCallback,
+ mSysUiState,
+ mSecureSettings,
+ scvhSupplier,
+ mSfVsyncFrameProvider);
+
mSpyController = mController.getSpyController();
}
@@ -235,8 +273,52 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
+ @RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
@Test
- public void enableWindowMagnificationWithScaleOne_enabled_AnimationAndInvokeCallback()
+ public void
+ enableWindowMagnificationScaleOne_enabledAndWindowlessFlagOn_AnimationAndCallbackTrue()
+ throws RemoteException {
+ enableWindowMagnificationWithoutAnimation();
+
+ // Wait for Rects updated.
+ waitForIdleSync();
+ View mirrorView = mSurfaceControlViewHost.getView();
+ final float targetScale = 1.0f;
+ // Move the magnifier to the top left corner, within the boundary
+ final float targetCenterX = mirrorView.getWidth() / 2.0f;
+ final float targetCenterY = mirrorView.getHeight() / 2.0f;
+
+ Mockito.reset(mSpyController);
+ getInstrumentation().runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
+ });
+
+ verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ mScaleCaptor.capture(),
+ mCenterXCaptor.capture(), mCenterYCaptor.capture(),
+ mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
+ verifyStartValue(mScaleCaptor, mCurrentScale.get());
+ verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
+ verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
+ verifyStartValue(mOffsetXCaptor, 0f);
+ verifyStartValue(mOffsetYCaptor, 0f);
+
+ verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
+
+ verify(mAnimationCallback).onResult(true);
+ assertEquals(WindowMagnificationAnimationController.STATE_ENABLED,
+ mWindowMagnificationAnimationController.getState());
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
+ @Test
+ public void
+ enableWindowMagnificationScaleOne_enabledAndWindowlessFlagOff_AnimationAndCallbackTrue()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
@@ -475,8 +557,46 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
verify(mAnimationCallback2).onResult(true);
}
+ @RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
+ @Test
+ public void enableWindowMagnificationWithOffset_windowlessFlagOn_expectedValues() {
+ final float offsetRatio = -0.1f;
+ final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+
+ Mockito.reset(mSpyController);
+ getInstrumentation().runOnMainSync(() -> {
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ windowBounds.exactCenterX(), windowBounds.exactCenterY(),
+ offsetRatio, offsetRatio, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ });
+ // Wait for Rects update
+ waitForIdleSync();
+
+ final int mirrorSurfaceMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ final int defaultMagnificationWindowSize =
+ mController.getMagnificationWindowSizeFromIndex(
+ WindowMagnificationSettings.MagnificationSize.MEDIUM);
+ final int defaultMagnificationFrameSize =
+ defaultMagnificationWindowSize - 2 * mirrorSurfaceMargin;
+ final int expectedOffset = (int) (defaultMagnificationFrameSize / 2 * offsetRatio);
+
+ final float expectedX = (int) (windowBounds.exactCenterX() + expectedOffset
+ - defaultMagnificationWindowSize / 2);
+ final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
+ - defaultMagnificationWindowSize / 2);
+
+ // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+ // created and we place the mirrored content as a child of the SurfaceView
+ // (3) the animation starts (4) the animation updates
+ verify(mTransaction, times(4))
+ .setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
@Test
- public void enableWindowMagnificationWithOffset_expectedValues() {
+ public void enableWindowMagnificationWithOffset_windowlessFlagOff_expectedValues() {
final float offsetRatio = -0.1f;
final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
@@ -876,23 +996,28 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
private static class SpyWindowMagnificationController extends WindowMagnificationController {
private WindowMagnificationController mSpyController;
- SpyWindowMagnificationController(Context context, Handler handler,
+ SpyWindowMagnificationController(Context context,
+ Handler handler,
WindowMagnificationAnimationController animationController,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
- MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
- WindowMagnifierCallback callback, SysUiState sysUiState,
- SecureSettings secureSettings) {
+ MirrorWindowControl mirrorWindowControl,
+ SurfaceControl.Transaction transaction,
+ WindowMagnifierCallback callback,
+ SysUiState sysUiState,
+ SecureSettings secureSettings,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
super(
context,
handler,
animationController,
- sfVsyncFrameProvider,
mirrorWindowControl,
transaction,
callback,
sysUiState,
- WindowManagerGlobal::getWindowSession,
- secureSettings);
+ secureSettings,
+ scvhSupplier,
+ sfVsyncFrameProvider,
+ WindowManagerGlobal::getWindowSession);
mSpyController = Mockito.mock(WindowMagnificationController.class);
}
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 04aef82cef43..2225ad6e49d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -68,6 +68,9 @@ import android.graphics.RegionIterator;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -90,6 +93,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -121,10 +125,13 @@ import java.util.concurrent.atomic.AtomicInteger;
@LargeTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
+@RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
public class WindowMagnificationControllerTest extends SysuiTestCase {
@Rule
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
@Mock
@@ -216,13 +223,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mContext,
mHandler,
mWindowMagnificationAnimationController,
- mSfVsyncFrameProvider,
mMirrorWindowControl,
mTransaction,
mWindowMagnifierCallback,
mSysUiState,
- () -> mWindowSessionSpy,
- mSecureSettings);
+ mSecureSettings,
+ /* scvhSupplier= */ () -> null,
+ mSfVsyncFrameProvider,
+ /* globalWindowSessionSupplier= */ () -> mWindowSessionSpy);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
@@ -270,7 +278,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(
() -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
- /* magnificationFrameOffsetRatioY= */ 0, null));
+ /* magnificationFrameOffsetRatioY= */ 0, null));
// Waits for the surface created
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
@@ -1415,7 +1423,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
- float y) {
+ float y) {
return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
}
@@ -1474,4 +1482,4 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
});
}
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
new file mode 100644
index 000000000000..66fb63b6c331
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -0,0 +1,1502 @@
+/*
+ * Copyright (C) 2024 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.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+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.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.returnsSecondArg;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+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;
+
+import android.animation.ValueAnimator;
+import android.annotation.IdRes;
+import android.annotation.Nullable;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.text.TextUtils;
+import android.util.Size;
+import android.view.AttachedSurfaceControl;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.widget.FrameLayout;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.util.leak.ReferenceTestUtils;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.utils.os.FakeHandler;
+
+import com.google.common.util.concurrent.AtomicDouble;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+@LargeTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+@RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
+public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
+
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+ @Mock
+ private MirrorWindowControl mMirrorWindowControl;
+ @Mock
+ private WindowMagnifierCallback mWindowMagnifierCallback;
+ @Mock
+ IRemoteMagnificationAnimationCallback mAnimationCallback;
+ @Mock
+ IRemoteMagnificationAnimationCallback mAnimationCallback2;
+
+ private SurfaceControl.Transaction mTransaction;
+ @Mock
+ private SecureSettings mSecureSettings;
+
+ private long mWaitAnimationDuration;
+ private long mWaitBounceEffectDuration;
+
+ private Handler mHandler;
+ private TestableWindowManager mWindowManager;
+ private SysUiState mSysUiState;
+ private Resources mResources;
+ private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+ private WindowMagnificationController mWindowMagnificationController;
+ private Instrumentation mInstrumentation;
+ private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+ private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+
+ private View mSpyView;
+ private View.OnTouchListener mTouchListener;
+
+ private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+ // This list contains all SurfaceControlViewHosts created during a given test. If the
+ // magnification window is recreated during a test, the list will contain more than a single
+ // element.
+ private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
+ // The most recently created SurfaceControlViewHost.
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private KosmosJavaAdapter mKosmos;
+
+ /**
+ * return whether window magnification is supported for current test context.
+ */
+ private boolean isWindowModeSupported() {
+ return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mKosmos = new KosmosJavaAdapter(this);
+ mContext = Mockito.spy(getContext());
+ mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = spy(new TestableWindowManager(wm));
+
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
+ mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
+ returnsSecondArg());
+ when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
+ returnsSecondArg());
+
+ mResources = getContext().getOrCreateTestableResources().getResources();
+ // prevent the config orientation from undefined, which may cause config.diff method
+ // neglecting the orientation update.
+ if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+ mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+ }
+
+ // Using the animation duration in WindowMagnificationAnimationController for testing.
+ mWaitAnimationDuration = mResources.getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+ // Using the bounce effect duration in WindowMagnificationController for testing.
+ mWaitBounceEffectDuration = mResources.getInteger(
+ com.android.internal.R.integer.config_shortAnimTime);
+
+ mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+ mContext, mValueAnimator);
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
+ mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
+ mContext, mContext.getDisplay(), new Binder(), "WindowMagnification"));
+ ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+ when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
+ mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
+ return mSurfaceControlViewHost;
+ };
+ mTransaction = spy(new SurfaceControl.Transaction());
+ mWindowMagnificationController =
+ new WindowMagnificationController(
+ mContext,
+ mHandler,
+ mWindowMagnificationAnimationController,
+ mMirrorWindowControl,
+ mTransaction,
+ mWindowMagnifierCallback,
+ mSysUiState,
+ mSecureSettings,
+ scvhSupplier,
+ /* sfVsyncFrameProvider= */ null,
+ /* globalWindowSessionSupplier= */ null);
+
+ verify(mMirrorWindowControl).setWindowDelegate(
+ any(MirrorWindowControl.MirrorWindowDelegate.class));
+ mSpyView = Mockito.spy(new View(mContext));
+ doAnswer((invocation) -> {
+ mTouchListener = invocation.getArgument(0);
+ return null;
+ }).when(mSpyView).setOnTouchListener(
+ any(View.OnTouchListener.class));
+
+ // skip test if window magnification is not supported to prevent fail results. (b/279820875)
+ Assume.assumeTrue(isWindowModeSupported());
+ }
+
+ @After
+ public void tearDown() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.deleteWindowMagnification());
+ mValueAnimator.cancel();
+ }
+
+ @Test
+ public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() {
+ verify(mSecureSettings).getIntForUser(
+ eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
+ /* def */ eq(1), /* userHandle= */ anyInt());
+ assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled());
+ }
+
+ @Test
+ public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ verify(mMirrorWindowControl).showControl();
+ verify(mWindowMagnifierCallback,
+ timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
+ eq(mContext.getDisplayId()), any(Rect.class));
+ }
+
+ @Test
+ public void enableWindowMagnification_notifySourceBoundsChanged() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
+ /* magnificationFrameOffsetRatioY= */ 0, null));
+
+ // Waits for the surface created
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
+ (eq(mContext.getDisplayId())), any());
+ }
+
+ @Test
+ public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
+ enableWindowMagnification_notifySourceBoundsChanged();
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.deleteWindowMagnification(null));
+ Mockito.reset(mWindowMagnifierCallback);
+
+ enableWindowMagnification_notifySourceBoundsChanged();
+ }
+
+ @Test
+ public void enableWindowMagnification_withAnimation_schedulesFrame() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
+ 10, /* magnificationFrameOffsetRatioX= */ 0,
+ /* magnificationFrameOffsetRatioY= */ 0,
+ Mockito.mock(IRemoteMagnificationAnimationCallback.class));
+ });
+ advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
+
+ verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+ eq(Surface.ROTATION_0));
+ }
+
+ @Test
+ public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifier(10, 10);
+ });
+
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
+ (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ }
+
+ @Test
+ public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ // Wait for Rects updated.
+ waitForIdleSync();
+
+ List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
+ assertFalse(rects.isEmpty());
+ }
+
+ @Ignore("The default window size should be constrained after fixing b/288056772")
+ @Test
+ public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
+ final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+ mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ final int halfScreenSize = screenSize / 2;
+ ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+ // The frame size should be the half of smaller value of window height/width unless it
+ //exceed the max frame size.
+ assertTrue(params.width < halfScreenSize);
+ assertTrue(params.height < halfScreenSize);
+ }
+
+ @Test
+ public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN,
+ Float.NaN));
+
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.deleteWindowMagnification());
+
+ verify(mMirrorWindowControl).destroyControl();
+ verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
+ }
+
+ @Test
+ public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ bounds.bottom);
+ });
+ ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.deleteWindowMagnification();
+ });
+
+ verify(mMirrorWindowControl).destroyControl();
+ assertFalse(hasMagnificationOverlapFlag());
+ }
+
+ @Test
+ public void deleteWindowMagnification_notifySourceBoundsChanged() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN,
+ Float.NaN));
+
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.deleteWindowMagnification());
+
+ // The first time is for notifying magnification enabled and the second time is for
+ // notifying magnification disabled.
+ verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
+ (eq(mContext.getDisplayId())), any());
+ }
+
+ @Test
+ public void moveMagnifier_schedulesFrame() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ waitForIdleSync();
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
+
+ verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+ eq(Surface.ROTATION_0));
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
+ throws RemoteException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
+ final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback);
+ });
+ advanceTimeBy(mWaitAnimationDuration);
+
+ verify(mAnimationCallback, times(1)).onResult(eq(true));
+ verify(mAnimationCallback, never()).onResult(eq(false));
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
+ throws RemoteException {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN, 0, 0, null);
+ });
+
+ final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
+ final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 10, centerY + 10, mAnimationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 20, centerY + 20, mAnimationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 30, centerY + 30, mAnimationCallback);
+ mWindowMagnificationController.moveWindowMagnifierToPosition(
+ centerX + 40, centerY + 40, mAnimationCallback2);
+ });
+ advanceTimeBy(mWaitAnimationDuration);
+
+ // only the last one callback will return true
+ verify(mAnimationCallback2).onResult(eq(true));
+ // the others will return false
+ verify(mAnimationCallback, times(3)).onResult(eq(false));
+ verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+ .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+ assertEquals(mWindowMagnificationController.getCenterX(),
+ sourceBoundsCaptor.getValue().exactCenterX(), 0);
+ assertEquals(mWindowMagnificationController.getCenterY(),
+ sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
+ assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+ }
+
+ @Test
+ public void setScale_enabled_expectedValueAndUpdateStateDescription() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
+ Float.NaN, Float.NaN));
+
+ mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
+
+ assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ assertNotNull(mirrorView);
+ assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
+ }
+
+ @Test
+ public void onConfigurationChanged_disabled_withoutException() {
+ Display display = Mockito.spy(mContext.getDisplay());
+ when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+ when(mContext.getDisplay()).thenReturn(display);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+ }
+
+ @Test
+ public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
+ final int newRotation = simulateRotateTheDevice();
+ final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+ final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
+ final float displayWidth = windowBounds.width();
+ final PointF magnifiedCenter = new PointF(center, center + 5f);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ magnifiedCenter.x, magnifiedCenter.y);
+ // Get the center again in case the center we set is out of screen.
+ magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
+ mWindowMagnificationController.getCenterY());
+ });
+ // Rotate the window clockwise 90 degree.
+ windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+ windowBounds.right);
+ mWindowManager.setWindowBounds(windowBounds);
+
+ mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_ORIENTATION));
+
+ assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ final PointF expectedCenter = new PointF(magnifiedCenter.y,
+ displayWidth - magnifiedCenter.x);
+ final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
+ mWindowMagnificationController.getCenterY());
+ assertEquals(expectedCenter, actualCenter);
+ }
+
+ @Test
+ public void onOrientationChanged_disabled_updateDisplayRotation() {
+ final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+ // Rotate the window clockwise 90 degree.
+ windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+ windowBounds.right);
+ mWindowManager.setWindowBounds(windowBounds);
+ final int newRotation = simulateRotateTheDevice();
+
+ mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_ORIENTATION));
+
+ assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ }
+
+ @Test
+ public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+ // The default position is at the center of the screen.
+ final float expectedRatio = 0.5f;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ // Screen size and density change
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+
+ // The ratio of center to window size should be the same.
+ assertEquals(expectedRatio,
+ mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
+ 0);
+ assertEquals(expectedRatio,
+ mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
+ 0);
+ }
+
+ @Test
+ public void onScreenChangedToSavedDensity_enabled_restoreSavedMagnifierWindow() {
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ int windowFrameSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ mWindowMagnificationController.mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+ new Size(windowFrameSize, windowFrameSize));
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+ assertTrue(params.width == windowFrameSize);
+ assertTrue(params.height == windowFrameSize);
+ }
+
+ @Test
+ public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+ // Screen size and density change
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+
+ final int defaultWindowSize =
+ mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
+ WindowMagnificationSettings.MagnificationSize.MEDIUM);
+ ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+
+ assertTrue(params.width == defaultWindowSize);
+ assertTrue(params.height == defaultWindowSize);
+ }
+
+ @Test
+ public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ Mockito.reset(mWindowManager);
+ Mockito.reset(mMirrorWindowControl);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+ });
+
+ verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+ verify(mSurfaceControlViewHosts.get(0)).release();
+ verify(mMirrorWindowControl).destroyControl();
+ verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
+ verify(mMirrorWindowControl).showControl();
+ }
+
+ @Test
+ public void onDensityChanged_disabled_updateDimensions() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+ });
+
+ verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+ }
+
+ @Test
+ public void initializeA11yNode_enabled_expectedValues() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ Float.NaN);
+ });
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ assertNotNull(mirrorView);
+ final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
+
+ mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
+
+ assertNotNull(nodeInfo.getContentDescription());
+ assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
+ assertThat(nodeInfo.getActionList(),
+ hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
+ new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
+ new AccessibilityAction(R.id.accessibility_action_move_right, null),
+ new AccessibilityAction(R.id.accessibility_action_move_left, null),
+ new AccessibilityAction(R.id.accessibility_action_move_down, null),
+ new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+ }
+
+ @Test
+ public void performA11yActions_visible_expectedResults() {
+ final int displayId = mContext.getDisplayId();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(1.5f, Float.NaN,
+ Float.NaN);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ assertTrue(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+ // Minimum scale is 1.0.
+ verify(mWindowMagnifierCallback).onPerformScaleAction(
+ eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
+
+ assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+ verify(mWindowMagnifierCallback).onPerformScaleAction(
+ eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
+
+ // TODO: Verify the final state when the mirror surface is visible.
+ assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
+ assertTrue(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
+ assertTrue(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
+ assertTrue(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+ verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
+
+ assertTrue(mirrorView.performAccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), null));
+ verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
+ }
+
+ @Test
+ public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
+ final int displayId = mContext.getDisplayId();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN,
+ Float.NaN);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
+
+ verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
+ }
+
+ @Test
+ public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ View closeButton = getInternalView(R.id.close_button);
+ View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+ View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+ View topRightCorner = getInternalView(R.id.top_right_corner);
+ View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+ assertEquals(View.VISIBLE, closeButton.getVisibility());
+ assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+ assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ mInstrumentation.runOnMainSync(() ->
+ mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+ null));
+
+ assertEquals(View.GONE, closeButton.getVisibility());
+ assertEquals(View.GONE, bottomRightCorner.getVisibility());
+ assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+ assertEquals(View.GONE, topRightCorner.getVisibility());
+ assertEquals(View.GONE, topLeftCorner.getVisibility());
+ }
+
+ @Test
+
+ public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = (int) (windowBounds.width() * 0.8);
+ final int startingHeight = (int) (windowBounds.height() * 0.8);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_increase_window_width, null);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window width includes the magnifier frame and the margin. Increasing the window size
+ // will be increasing the amount of the frame size only.
+ int newWindowWidth =
+ (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(newWindowWidth, actualWindowWidth.get());
+ assertEquals(startingHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = (int) (windowBounds.width() * 0.8);
+ final int startingHeight = (int) (windowBounds.height() * 0.8);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_increase_window_height, null);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window height includes the magnifier frame and the margin. Increasing the window size
+ // will be increasing the amount of the frame size only.
+ int newWindowHeight =
+ (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(startingWidth, actualWindowWidth.get());
+ assertEquals(newWindowHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = windowBounds.width();
+ final int startingHeight = windowBounds.height();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+ }
+
+ @Test
+ public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = windowBounds.width();
+ final int startingHeight = windowBounds.height();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+ }
+
+ @Test
+ public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = (int) (mMinWindowSize * 1.1);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_decrease_window_width, null);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window width includes the magnifier frame and the margin. Decreasing the window size
+ // will be decreasing the amount of the frame size only.
+ int newWindowWidth =
+ (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(newWindowWidth, actualWindowWidth.get());
+ assertEquals(startingSize, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = (int) (mMinWindowSize * 1.1);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_decrease_window_height, null);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window height includes the magnifier frame and the margin. Decreasing the window size
+ // will be decreasing the amount of the frame size only.
+ int newWindowHeight =
+ (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(startingSize, actualWindowWidth.get());
+ assertEquals(newWindowHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = mMinWindowSize;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+ }
+
+ @Test
+ public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = mMinWindowSize;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+ }
+
+ @Test
+ public void enableWindowMagnification_hasA11yWindowTitle() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ assertEquals(getContext().getResources().getString(
+ com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
+ }
+
+ @Test
+ public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN,
+ Float.NaN);
+ });
+
+ assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
+ }
+
+ @Test
+ public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
+ // the config orientation should not be undefined, since it would cause config.diff
+ // returning 0 and thus the orientation changed would not be detected
+ assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+
+ final Configuration config = mResources.getConfiguration();
+ config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
+ : ORIENTATION_LANDSCAPE;
+ final int newRotation = simulateRotateTheDevice();
+
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN));
+
+ assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ }
+
+ @Test
+ public void enableWindowMagnification_registerComponentCallback() {
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN,
+ Float.NaN));
+
+ verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
+ }
+
+ @Test
+ public void onLocaleChanged_enabled_updateA11yWindowTitle() {
+ final String newA11yWindowTitle = "new a11y window title";
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ final TestableResources testableResources = getContext().getOrCreateTestableResources();
+ testableResources.addOverride(com.android.internal.R.string.android_system_label,
+ newA11yWindowTitle);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+ });
+
+ assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
+ }
+
+ @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
+ @Test
+ public void onSingleTap_enabled_scaleAnimates() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onSingleTap(mSpyView);
+ });
+
+ final View mirrorView = mSurfaceControlViewHost.getView();
+
+ final AtomicDouble maxScaleX = new AtomicDouble();
+ advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
+ // For some reason the fancy way doesn't compile...
+ // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
+ final double oldMax = maxScaleX.get();
+ final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
+ assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+ });
+
+ assertTrue(maxScaleX.get() > 1.0);
+ }
+
+ @Test
+ public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
+ });
+
+ ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
+ }
+
+ @Test
+ public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
+ throws RemoteException {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN);
+ });
+ // Wait for Region updated.
+ waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
+ });
+ // Wait for Region updated.
+ waitForIdleSync();
+
+ AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+ // Verifying two times in: (1) enable window magnification (2) reposition drag handle
+ verify(viewRoot, times(2)).setTouchableRegion(any());
+
+ View dragButton = getInternalView(R.id.drag_handle);
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+ assertEquals(Gravity.BOTTOM | Gravity.LEFT, params.gravity);
+ }
+
+ @Test
+ public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
+ throws RemoteException {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN);
+ });
+ // Wait for Region updated.
+ waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
+ });
+ // Wait for Region updated.
+ waitForIdleSync();
+
+ AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+ // Verifying one times in: (1) enable window magnification
+ verify(viewRoot).setTouchableRegion(any());
+
+ View dragButton = getInternalView(R.id.drag_handle);
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+ assertEquals(Gravity.BOTTOM | Gravity.RIGHT, params.gravity);
+ }
+
+ @Test
+ public void setMinimumWindowSize_enabled_expectedWindowSize() {
+ final int minimumWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int expectedWindowHeight = minimumWindowSize;
+ final int expectedWindowWidth = minimumWindowSize;
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
+ actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+
+ });
+
+ assertEquals(expectedWindowHeight, actualWindowHeight.get());
+ assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ }
+
+ @Test
+ public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
+ final int minimumWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int expectedWindowHeight = minimumWindowSize;
+ final int expectedWindowWidth = minimumWindowSize;
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN);
+ actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ assertEquals(expectedWindowHeight, actualWindowHeight.get());
+ assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ }
+
+ @Test
+ public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
+ final int minimumWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
+ minimumWindowSize - 10);
+ actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ assertEquals(minimumWindowSize, actualWindowHeight.get());
+ assertEquals(minimumWindowSize, actualWindowWidth.get());
+ }
+
+ @Test
+ public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
+ actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ assertEquals(bounds.height(), actualWindowHeight.get());
+ assertEquals(bounds.width(), actualWindowWidth.get());
+ }
+
+ @Test
+ public void changeMagnificationSize_expectedWindowSize() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final float magnificationScaleLarge = 2.5f;
+ final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+ final int magnificationSize = (int) (initSize * magnificationScaleLarge)
+ - (int) (initSize * magnificationScaleLarge) % 2;
+
+ final int expectedWindowHeight = magnificationSize;
+ final int expectedWindowWidth = magnificationSize;
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.changeMagnificationSize(
+ WindowMagnificationSettings.MagnificationSize.LARGE);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ assertEquals(expectedWindowHeight, actualWindowHeight.get());
+ assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragCorner_resizesWindow() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize + 2, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2f);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+ actualWindowHeight.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().height);
+ actualWindowWidth.set(
+ mSurfaceControlViewHost.getView().getLayoutParams().width);
+ });
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize, actualWindowWidth.get());
+ }
+
+ @Test
+ public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
+
+ final int minimumWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mInstrumentation.runOnMainSync(
+ () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+ Float.NaN, Float.NaN));
+
+ final AtomicInteger magnificationCenterX = new AtomicInteger();
+ final AtomicInteger magnificationCenterY = new AtomicInteger();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
+ minimumWindowSize, bounds.right, bounds.bottom);
+ magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
+ magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
+ });
+
+ assertTrue(magnificationCenterX.get() < bounds.right);
+ assertTrue(magnificationCenterY.get() < bounds.bottom);
+ }
+
+ @Test
+ public void performSingleTap_DragHandle() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ 1.5f, bounds.centerX(), bounds.centerY());
+ });
+ View dragButton = getInternalView(R.id.drag_handle);
+
+ // Perform a single-tap
+ final long downTime = SystemClock.uptimeMillis();
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+ verify(mSurfaceControlViewHost).setView(any(View.class), any());
+ }
+
+ private <T extends View> T getInternalView(@IdRes int idRes) {
+ View mirrorView = mSurfaceControlViewHost.getView();
+ T view = mirrorView.findViewById(idRes);
+ assertNotNull(view);
+ return view;
+ }
+
+ private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+ float y) {
+ return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+ }
+
+ private CharSequence getAccessibilityWindowTitle() {
+ final View mirrorView = mSurfaceControlViewHost.getView();
+ if (mirrorView == null) {
+ return null;
+ }
+ WindowManager.LayoutParams layoutParams =
+ (WindowManager.LayoutParams) mirrorView.getLayoutParams();
+ return layoutParams.accessibilityTitle;
+ }
+
+ private boolean hasMagnificationOverlapFlag() {
+ return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
+ }
+
+ private void setSystemGestureInsets() {
+ final WindowInsets testInsets = new WindowInsets.Builder()
+ .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
+ .build();
+ mWindowManager.setWindowInsets(testInsets);
+ }
+
+ private int updateMirrorSurfaceMarginDimension() {
+ return mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ }
+
+ @Surface.Rotation
+ private int simulateRotateTheDevice() {
+ final Display display = Mockito.spy(mContext.getDisplay());
+ final int currentRotation = display.getRotation();
+ final int newRotation = (currentRotation + 1) % 4;
+ when(display.getRotation()).thenReturn(newRotation);
+ when(mContext.getDisplay()).thenReturn(display);
+ return newRotation;
+ }
+
+ // advance time based on the device frame refresh rate
+ private void advanceTimeBy(long timeDelta) {
+ advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
+ }
+
+ // advance time based on the device frame refresh rate, and trigger runnable on each refresh
+ private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
+ final float frameRate = mContext.getDisplay().getRefreshRate();
+ final int timeSlot = (int) (1000 / frameRate);
+ int round = (int) Math.ceil((double) timeDelta / timeSlot);
+ for (; round >= 0; round--) {
+ mInstrumentation.runOnMainSync(() -> {
+ mAnimatorTestRule.advanceTimeBy(timeSlot);
+ if (runnableOnEachRefresh != null) {
+ runnableOnEachRefresh.run();
+ }
+ });
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 57c05396992e..63784ba61150 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
@@ -5724,6 +5725,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
+ public void attachAccessibilityOverlayToDisplay_enforcePermission(
+ int displayId, SurfaceControl sc) {
+ mContext.enforceCallingPermission(
+ INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay_enforcePermission");
+ mMainHandler.sendMessage(
+ obtainMessage(
+ AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
+ this,
+ -1,
+ displayId,
+ sc,
+ null));
+ }
+
+ @Override
public void attachAccessibilityOverlayToDisplay(
int interactionId,
int displayId,
@@ -5759,12 +5775,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
t.close();
result = AccessibilityService.OVERLAY_RESULT_SUCCESS;
}
- // Send the result back to the service.
- try {
- callback.sendAttachOverlayResult(result, interactionId);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Exception while attaching overlay.", re);
- // the other side will time out
+
+ if (callback != null) {
+ // Send the result back to the service.
+ try {
+ callback.sendAttachOverlayResult(result, interactionId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Exception while attaching overlay.", re);
+ // the other side will time out
+ }
}
}
}