diff options
6 files changed, 680 insertions, 106 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java new file mode 100644 index 000000000000..e76a2093f3f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import static android.view.WindowManager.LayoutParams; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Log; +import android.util.MathUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +import com.android.systemui.R; + +/** + * Contains a movable control UI to manipulate mirrored window's position, size and scale. The + * window type of the UI is {@link LayoutParams#TYPE_APPLICATION_SUB_PANEL} and the window type + * of the window token should be {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} to + * ensure it is above all windows and won't be mirrored. It is not movable to the navigation bar. + */ +public abstract class MirrorWindowControl { + private static final String TAG = "MirrorWindowControl"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG) | false; + + /** + * A delegate handling a mirrored window's offset. + */ + public interface MirrorWindowDelegate { + /** + * Moves the window with specified offset. + * + * @param xOffset the amount in pixels to offset the window in the X coordinate, in current + * display pixels. + * @param yOffset the amount in pixels to offset the window in the Y coordinate, in current + * display pixels. + */ + void move(int xOffset, int yOffset); + } + + protected final Context mContext; + private final Rect mDraggableBound = new Rect(); + final Point mTmpPoint = new Point(); + + @Nullable + protected MirrorWindowDelegate mMirrorWindowDelegate; + protected View mControlsView; + /** + * The left top position of the control UI. Initialized when the control UI is visible. + * + * @see #setDefaultPosition(LayoutParams) + */ + private final Point mControlPosition = new Point(); + private final WindowManager mWindowManager; + + MirrorWindowControl(Context context) { + mContext = context; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } + + public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) { + mMirrorWindowDelegate = windowDelegate; + } + + /** + * Shows the control UI. + * + * @param binder the window token of the + * {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} window. + */ + public final void showControl(IBinder binder) { + if (mControlsView != null) { + Log.w(TAG, "control view is visible"); + return; + } + final Point viewSize = mTmpPoint; + mControlsView = onCreateView(LayoutInflater.from(mContext), viewSize); + + final LayoutParams lp = new LayoutParams(); + final int defaultSize = mContext.getResources().getDimensionPixelSize( + R.dimen.magnification_controls_size); + lp.width = viewSize.x <= 0 ? defaultSize : viewSize.x; + lp.height = viewSize.y <= 0 ? defaultSize : viewSize.y; + lp.token = binder; + setDefaultParams(lp); + setDefaultPosition(lp); + mWindowManager.addView(mControlsView, lp); + updateDraggableBound(lp.width, lp.height); + } + + private void setDefaultParams(LayoutParams lp) { + lp.gravity = Gravity.TOP | Gravity.LEFT; + lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL + | LayoutParams.FLAG_NOT_FOCUSABLE; + lp.type = LayoutParams.TYPE_APPLICATION_SUB_PANEL; + lp.format = PixelFormat.RGBA_8888; + lp.setTitle(getWindowTitle()); + } + + private void setDefaultPosition(LayoutParams layoutParams) { + final Point displaySize = mTmpPoint; + mContext.getDisplay().getSize(displaySize); + layoutParams.x = displaySize.x - layoutParams.width; + layoutParams.y = displaySize.y - layoutParams.height; + mControlPosition.set(layoutParams.x, layoutParams.y); + } + + /** + * Removes the UI from the scene. + */ + public final void destroyControl() { + if (mControlsView != null) { + mWindowManager.removeView(mControlsView); + mControlsView = null; + } + } + + /** + * Moves the control view with specified offset. + * + * @param xOffset the amount in pixels to offset the UI in the X coordinate, in current + * display pixels. + * @param yOffset the amount in pixels to offset the UI in the Y coordinate, in current + * display pixels. + */ + public void move(int xOffset, int yOffset) { + if (mControlsView == null) { + Log.w(TAG, "control view is not available yet or destroyed"); + return; + } + final Point nextPosition = mTmpPoint; + nextPosition.set(mControlPosition.x, mControlPosition.y); + mTmpPoint.offset(xOffset, yOffset); + setPosition(mTmpPoint); + } + + private void setPosition(Point point) { + constrainFrameToDraggableBound(point); + if (point.equals(mControlPosition)) { + return; + } + mControlPosition.set(point.x, point.y); + LayoutParams lp = (LayoutParams) mControlsView.getLayoutParams(); + lp.x = mControlPosition.x; + lp.y = mControlPosition.y; + mWindowManager.updateViewLayout(mControlsView, lp); + } + + private void constrainFrameToDraggableBound(Point point) { + point.x = MathUtils.constrain(point.x, mDraggableBound.left, mDraggableBound.right); + point.y = MathUtils.constrain(point.y, mDraggableBound.top, mDraggableBound.bottom); + } + + private void updateDraggableBound(int viewWidth, int viewHeight) { + final Point size = mTmpPoint; + mContext.getDisplay().getSize(size); + mDraggableBound.set(0, 0, size.x - viewWidth, size.y - viewHeight); + if (DBG) { + Log.d(TAG, "updateDraggableBound :" + mDraggableBound); + } + } + + abstract String getWindowTitle(); + + /** + * Called when the UI is going to show. + * + * @param inflater The LayoutInflater object used to inflate the view. + * @param viewSize The {@link Point} to specify view's width with {@link Point#x)} and height + * with {@link Point#y)} .The value should be greater than 0, otherwise will + * fall back to the default size. + * @return the View for the control's UI. + */ + @NonNull + abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull Point viewSize); +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java new file mode 100644 index 000000000000..2ba2bb6edc18 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.os.Handler; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; + +import com.android.systemui.R; + +/** + * A basic control to move the mirror window. + */ +class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnClickListener, + View.OnTouchListener, View.OnLongClickListener { + + private static final String TAG = "SimpleMirrorWindowControl"; + private static final int MOVE_FRAME_DURATION_MS = 100; + private final int mMoveFrameAmountShort; + private final int mMoveFrameAmountLong; + + private boolean mIsDragState; + private boolean mShouldSetTouchStart; + + @Nullable private MoveWindowTask mMoveWindowTask; + private PointF mLastDrag = new PointF(); + private final Handler mHandler; + + SimpleMirrorWindowControl(Context context, Handler handler) { + super(context); + mHandler = handler; + final Resources resource = context.getResources(); + mMoveFrameAmountShort = resource.getDimensionPixelSize( + R.dimen.magnification_frame_move_short); + mMoveFrameAmountLong = resource.getDimensionPixelSize( + R.dimen.magnification_frame_move_long); + } + + @Override + String getWindowTitle() { + return mContext.getString(R.string.magnification_controls_title); + } + + @Override + View onCreateView(LayoutInflater layoutInflater, Point viewSize) { + final View view = layoutInflater.inflate(R.layout.magnifier_controllers, null); + final View leftControl = view.findViewById(R.id.left_control); + final View upControl = view.findViewById(R.id.up_control); + final View rightControl = view.findViewById(R.id.right_control); + final View bottomControl = view.findViewById(R.id.down_control); + + leftControl.setOnClickListener(this); + upControl.setOnClickListener(this); + rightControl.setOnClickListener(this); + bottomControl.setOnClickListener(this); + + leftControl.setOnLongClickListener(this); + upControl.setOnLongClickListener(this); + rightControl.setOnLongClickListener(this); + bottomControl.setOnLongClickListener(this); + + leftControl.setOnTouchListener(this); + upControl.setOnTouchListener(this); + rightControl.setOnTouchListener(this); + bottomControl.setOnTouchListener(this); + + view.setOnTouchListener(this); + view.setOnLongClickListener(this::onViewRootLongClick); + return view; + } + + private Point findOffset(View v, int moveFrameAmount) { + final Point offset = mTmpPoint; + offset.set(0, 0); + if (v.getId() == R.id.left_control) { + mTmpPoint.x = -moveFrameAmount; + } else if (v.getId() == R.id.up_control) { + mTmpPoint.y = -moveFrameAmount; + } else if (v.getId() == R.id.right_control) { + mTmpPoint.x = moveFrameAmount; + } else if (v.getId() == R.id.down_control) { + mTmpPoint.y = moveFrameAmount; + } else { + Log.w(TAG, "findOffset move is zero "); + } + return mTmpPoint; + } + + @Override + public void onClick(View v) { + if (mMirrorWindowDelegate != null) { + Point offset = findOffset(v, mMoveFrameAmountShort); + mMirrorWindowDelegate.move(offset.x, offset.y); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (handleDragState(event)) { + return true; + } + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mMoveWindowTask != null) { + mMoveWindowTask.cancel(); + mMoveWindowTask = null; + } + break; + } + return false; + } + + @Override + public boolean onLongClick(View v) { + Point offset = findOffset(v, mMoveFrameAmountLong); + mMoveWindowTask = new MoveWindowTask(mMirrorWindowDelegate, mHandler, offset.x, offset.y, + MOVE_FRAME_DURATION_MS); + mMoveWindowTask.schedule(); + return true; + } + + private boolean onViewRootLongClick(View view) { + mIsDragState = true; + mShouldSetTouchStart = true; + return true; + } + + private boolean handleDragState(final MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + if (mIsDragState) { + if (mShouldSetTouchStart) { + mLastDrag.set(event.getRawX(), event.getRawY()); + mShouldSetTouchStart = false; + } + int xDiff = (int) (event.getRawX() - mLastDrag.x); + int yDiff = (int) (event.getRawY() - mLastDrag.y); + move(xDiff, yDiff); + mLastDrag.set(event.getRawX(), event.getRawY()); + return true; + } + return false; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mIsDragState) { + mIsDragState = false; + return true; + } + return false; + default: + return false; + } + } + + /** + * A timer task to move the mirror window periodically. + */ + static class MoveWindowTask implements Runnable { + private final MirrorWindowDelegate mMirrorWindowDelegate; + private final int mXOffset; + private final int mYOffset; + private final Handler mHandler; + /** Time in milliseconds between successive task executions.*/ + private long mPeriod; + private boolean mCancel; + + MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset, + int yOffset, long period) { + mMirrorWindowDelegate = windowDelegate; + mXOffset = xOffset; + mYOffset = yOffset; + mHandler = handler; + mPeriod = period; + } + + @Override + public void run() { + if (mCancel) { + return; + } + mMirrorWindowDelegate.move(mXOffset, mYOffset); + schedule(); + } + + /** + * Schedules the specified task periodically and immediately. + */ + void schedule() { + mHandler.postDelayed(this, mPeriod); + mCancel = false; + } + + void cancel() { + mHandler.removeCallbacks(this); + mCancel = true; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 898cd1363d2b..b3ce4a0fa6e8 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -86,7 +86,7 @@ public class WindowMagnification extends SystemUI { private void enableMagnification() { if (mWindowMagnificationController == null) { - mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler); + mWindowMagnificationController = new WindowMagnificationController(mContext, null); } mWindowMagnificationController.createWindowMagnification(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 581cf7a2fbef..7176490d775d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -18,6 +18,7 @@ package com.android.systemui.accessibility; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; @@ -25,7 +26,6 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.os.Binder; -import android.os.Handler; import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; @@ -44,16 +44,13 @@ import com.android.systemui.shared.system.WindowManagerWrapper; /** * Class to handle adding and removing a window magnification. */ -public class WindowMagnificationController implements View.OnClickListener, - View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback { +public class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback, + MirrorWindowControl.MirrorWindowDelegate { private final int mBorderSize; - private final int mMoveFrameAmountShort; - private final int mMoveFrameAmountLong; private final Context mContext; private final Point mDisplaySize = new Point(); private final int mDisplayId; - private final Handler mHandler; private final Rect mMagnificationFrame = new Rect(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @@ -66,13 +63,6 @@ public class WindowMagnificationController implements View.OnClickListener, // The root of the mirrored content private SurfaceControl mMirrorSurface; - private boolean mIsPressedDown; - - private View mLeftControl; - private View mUpControl; - private View mRightControl; - private View mBottomControl; - private View mDragView; private View mLeftDrag; private View mTopDrag; @@ -80,20 +70,18 @@ public class WindowMagnificationController implements View.OnClickListener, private View mBottomDrag; private final PointF mLastDrag = new PointF(); - private final Point mMoveWindowOffset = new Point(); private View mMirrorView; private SurfaceView mMirrorSurfaceView; - private View mControlsView; private View mOverlayView; // The boundary of magnification frame. private final Rect mMagnificationFrameBoundary = new Rect(); - private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable(); + @Nullable + private MirrorWindowControl mMirrorWindowControl; - WindowMagnificationController(Context context, Handler handler) { + WindowMagnificationController(Context context, MirrorWindowControl mirrorWindowControl) { mContext = context; - mHandler = handler; Display display = mContext.getDisplay(); display.getRealSize(mDisplaySize); mDisplayId = mContext.getDisplayId(); @@ -102,10 +90,12 @@ public class WindowMagnificationController implements View.OnClickListener, Resources r = context.getResources(); mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size); - mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short); - mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long); mScale = r.getInteger(R.integer.magnification_default_scale); + mMirrorWindowControl = mirrorWindowControl; + if (mMirrorWindowControl != null) { + mMirrorWindowControl.setWindowDelegate(this); + } } /** @@ -176,9 +166,8 @@ public class WindowMagnificationController implements View.OnClickListener, mMirrorView = null; } - if (mControlsView != null) { - mWm.removeView(mControlsView); - mControlsView = null; + if (mMirrorWindowControl != null) { + mMirrorWindowControl.destroyControl(); } } @@ -238,40 +227,9 @@ public class WindowMagnificationController implements View.OnClickListener, } private void createControls() { - int controlsSize = (int) mContext.getResources().getDimension( - R.dimen.magnification_controls_size); - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize, - WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.RGBA_8888); - lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; - lp.token = mOverlayView.getWindowToken(); - lp.setTitle(mContext.getString(R.string.magnification_controls_title)); - - mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null); - mWm.addView(mControlsView, lp); - - mLeftControl = mControlsView.findViewById(R.id.left_control); - mUpControl = mControlsView.findViewById(R.id.up_control); - mRightControl = mControlsView.findViewById(R.id.right_control); - mBottomControl = mControlsView.findViewById(R.id.down_control); - - mLeftControl.setOnClickListener(this); - mUpControl.setOnClickListener(this); - mRightControl.setOnClickListener(this); - mBottomControl.setOnClickListener(this); - - mLeftControl.setOnLongClickListener(this); - mUpControl.setOnLongClickListener(this); - mRightControl.setOnLongClickListener(this); - mBottomControl.setOnLongClickListener(this); - - mLeftControl.setOnTouchListener(this); - mUpControl.setOnTouchListener(this); - mRightControl.setOnTouchListener(this); - mBottomControl.setOnTouchListener(this); + if (mMirrorWindowControl != null) { + mMirrorWindowControl.showControl(mOverlayView.getWindowToken()); + } } private void setInitialStartBounds() { @@ -331,40 +289,14 @@ public class WindowMagnificationController implements View.OnClickListener, } @Override - public void onClick(View v) { - setMoveOffset(v, mMoveFrameAmountShort); - moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y); - } - - @Override - public boolean onLongClick(View v) { - mIsPressedDown = true; - setMoveOffset(v, mMoveFrameAmountLong); - mHandler.post(mMoveMirrorRunnable); - return true; - } - - @Override public boolean onTouch(View v, MotionEvent event) { - if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) { - return handleControlTouchEvent(event); - } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag + if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag || v == mBottomDrag) { return handleDragTouchEvent(event); } return false; } - private boolean handleControlTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mIsPressedDown = false; - break; - } - return false; - } - private boolean handleDragTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -380,20 +312,6 @@ public class WindowMagnificationController implements View.OnClickListener, return false; } - private void setMoveOffset(View v, int moveFrameAmount) { - mMoveWindowOffset.set(0, 0); - - if (v == mLeftControl) { - mMoveWindowOffset.x = -moveFrameAmount; - } else if (v == mUpControl) { - mMoveWindowOffset.y = -moveFrameAmount; - } else if (v == mRightControl) { - mMoveWindowOffset.x = moveFrameAmount; - } else if (v == mBottomControl) { - mMoveWindowOffset.y = moveFrameAmount; - } - } - private void moveMirrorWindow(int xOffset, int yOffset) { if (updateMagnificationFramePosition(xOffset, yOffset)) { modifyWindowMagnification(mTransaction); @@ -461,6 +379,7 @@ public class WindowMagnificationController implements View.OnClickListener, } return false; } + @Override public void surfaceCreated(SurfaceHolder holder) { createMirror(); @@ -474,13 +393,13 @@ public class WindowMagnificationController implements View.OnClickListener, public void surfaceDestroyed(SurfaceHolder holder) { } - class MoveMirrorRunnable implements Runnable { - @Override - public void run() { - if (mIsPressedDown) { - moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y); - mHandler.postDelayed(mMoveMirrorRunnable, 100); - } + @Override + public void move(int xOffset, int yOffset) { + if (mMirrorSurfaceView == null) { + return; } + mMagnificationFrame.offset(xOffset, yOffset); + modifyWindowMagnification(mTransaction); + mTransaction.apply(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java new file mode 100644 index 000000000000..fff7e4a04cb4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import static android.view.WindowManager.LayoutParams; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.graphics.Point; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class MirrorWindowControlTest extends SysuiTestCase { + + @Mock WindowManager mWindowManager; + @Mock IBinder mIBinder; + View mView; + int mViewWidth; + int mViewHeight; + + StubMirrorWindowControl mStubMirrorWindowControl; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mView = new View(getContext()); + mViewWidth = 10; + mViewHeight = 20; + getContext().addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + doAnswer(invocation -> { + View view = invocation.getArgument(0); + LayoutParams lp = invocation.getArgument(1); + view.setLayoutParams(lp); + return null; + }).when(mWindowManager).addView(any(View.class), any(LayoutParams.class)); + + mStubMirrorWindowControl = new StubMirrorWindowControl(getContext(), mView, mViewWidth, + mViewHeight); + } + + @Test + public void showControl_createViewAndAddView() { + mStubMirrorWindowControl.showControl(mIBinder); + + assertTrue(mStubMirrorWindowControl.mInvokeOnCreateView); + ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass( + ViewGroup.LayoutParams.class); + verify(mWindowManager).addView(any(), lpCaptor.capture()); + assertTrue(lpCaptor.getValue().width == mViewWidth); + assertTrue(lpCaptor.getValue().height == mViewHeight); + } + + @Test + public void destroyControl_removeView() { + mStubMirrorWindowControl.showControl(mIBinder); + ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class); + verify(mWindowManager).addView(captor.capture(), any(LayoutParams.class)); + + mStubMirrorWindowControl.destroyControl(); + + verify(mWindowManager).removeView(eq(captor.getValue())); + } + + @Test + public void move_offsetIsCorrect() { + ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass( + ViewGroup.LayoutParams.class); + mStubMirrorWindowControl.showControl(mIBinder); + verify(mWindowManager).addView(any(), lpCaptor.capture()); + LayoutParams lp = (LayoutParams) lpCaptor.getValue(); + Point startPosition = new Point(lp.x, lp.y); + + mStubMirrorWindowControl.move(-10, -20); + + verify(mWindowManager).updateViewLayout(eq(mView), lpCaptor.capture()); + assertTrue(lpCaptor.getAllValues().size() == 2); + lp = (LayoutParams) lpCaptor.getValue(); + Point currentPosition = new Point(lp.x, lp.y); + assertEquals(-10, currentPosition.x - startPosition.x); + assertEquals(-20, currentPosition.y - startPosition.y); + } + + private static class StubMirrorWindowControl extends MirrorWindowControl { + private final int mWidth; + private final int mHeight; + private final View mView; + + boolean mInvokeOnCreateView = false; + + StubMirrorWindowControl(Context context, View view, int width, int height) { + super(context); + mView = view; + mWidth = width; + mHeight = height; + } + + @Override + public String getWindowTitle() { + return "StubMirrorWindowControl"; + } + + @Override + View onCreateView(LayoutInflater inflater, Point viewSize) { + mInvokeOnCreateView = true; + viewSize.x = mWidth; + viewSize.y = mHeight; + return mView; + } + + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java new file mode 100644 index 000000000000..08a61723aba2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class WindowMagnificationControllerTest extends SysuiTestCase { + + @Mock + MirrorWindowControl mMirrorWindowControl; + private WindowMagnificationController mWindowMagnificationController; + private Instrumentation mInstrumentation; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mWindowMagnificationController = new WindowMagnificationController(getContext(), + mMirrorWindowControl); + verify(mMirrorWindowControl).setWindowDelegate( + any(MirrorWindowControl.MirrorWindowDelegate.class)); + } + + @After + public void tearDown() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.deleteWindowMagnification(); + }); + mInstrumentation.waitForIdleSync(); + } + + @Test + public void createWindowMagnification_showControl() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.createWindowMagnification(); + }); + mInstrumentation.waitForIdleSync(); + verify(mMirrorWindowControl).showControl(any(IBinder.class)); + } + + @Test + public void deleteWindowMagnification_destroyControl() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.createWindowMagnification(); + }); + mInstrumentation.waitForIdleSync(); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.deleteWindowMagnification(); + }); + mInstrumentation.waitForIdleSync(); + + verify(mMirrorWindowControl).destroyControl(); + } +} |