Merge "modularize the gesture detection mechanism for magnification"
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.DisplayContext;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.os.Handler;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
/**
|
||||
* Detects single tap and drag gestures using the supplied {@link MotionEvent}s. The {@link
|
||||
* OnGestureListener} callback will notify users when a particular motion event has occurred. This
|
||||
* class should only be used with {@link MotionEvent}s reported via touch (don't use for trackball
|
||||
* events).
|
||||
*/
|
||||
class MagnificationGestureDetector {
|
||||
|
||||
interface OnGestureListener {
|
||||
/**
|
||||
* Called when a tap is completed within {@link ViewConfiguration#getLongPressTimeout()} and
|
||||
* the offset between {@link MotionEvent}s and the down event doesn't exceed {@link
|
||||
* ViewConfiguration#getScaledTouchSlop()}.
|
||||
*
|
||||
* @return {@code true} if this gesture is handled.
|
||||
*/
|
||||
boolean onSingleTap();
|
||||
|
||||
/**
|
||||
* Called when the user is performing dragging gesture. It is started after the offset
|
||||
* between the down location and the move event location exceed
|
||||
* {@link ViewConfiguration#getScaledTouchSlop()}.
|
||||
*
|
||||
* @param offsetX The X offset in screen coordinate.
|
||||
* @param offsetY The Y offset in screen coordinate.
|
||||
* @return {@code true} if this gesture is handled.
|
||||
*/
|
||||
boolean onDrag(float offsetX, float offsetY);
|
||||
|
||||
/**
|
||||
* Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will
|
||||
* be triggered immediately for every down event. All other events should be preceded by
|
||||
* this.
|
||||
*
|
||||
* @param x The X coordinate of the down event.
|
||||
* @param y The Y coordinate of the down event.
|
||||
* @return {@code true} if the down event is handled, otherwise the events won't be sent to
|
||||
* the view.
|
||||
*/
|
||||
boolean onStart(float x, float y);
|
||||
|
||||
/**
|
||||
* Called when the detection is finished. In other words, it is called when up/cancel {@link
|
||||
* MotionEvent} is received. It will be triggered after single-tap
|
||||
*
|
||||
* @param x The X coordinate on the screen of the up event or the cancel event.
|
||||
* @param y The Y coordinate on the screen of the up event or the cancel event.
|
||||
* @return {@code true} if the event is handled.
|
||||
*/
|
||||
boolean onFinish(float x, float y);
|
||||
}
|
||||
|
||||
private final PointF mPointerDown = new PointF();
|
||||
private final PointF mPointerLocation = new PointF(Float.NaN, Float.NaN);
|
||||
private final Handler mHandler;
|
||||
private final Runnable mCancelTapGestureRunnable;
|
||||
private final OnGestureListener mOnGestureListener;
|
||||
private int mTouchSlopSquare;
|
||||
// Assume the gesture default is a single-tap. Set it to false if the gesture couldn't be a
|
||||
// single-tap anymore.
|
||||
private boolean mDetectSingleTap = true;
|
||||
private boolean mDraggingDetected = false;
|
||||
|
||||
/**
|
||||
* @param context {@link Context} that is from {@link Context#createDisplayContext(Display)}.
|
||||
* @param handler The handler to post the runnable.
|
||||
* @param listener The listener invoked for all the callbacks.
|
||||
*/
|
||||
MagnificationGestureDetector(@DisplayContext Context context, @NonNull Handler handler,
|
||||
@NonNull OnGestureListener listener) {
|
||||
final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
mTouchSlopSquare = touchSlop * touchSlop;
|
||||
mHandler = handler;
|
||||
mOnGestureListener = listener;
|
||||
mCancelTapGestureRunnable = () -> mDetectSingleTap = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the given motion event and if applicable to trigger the appropriate callbacks on the
|
||||
* {@link OnGestureListener} supplied.
|
||||
*
|
||||
* @param event The current motion event.
|
||||
* @return {@code True} if the {@link OnGestureListener} consumes the event, else false.
|
||||
*/
|
||||
boolean onTouch(MotionEvent event) {
|
||||
final float rawX = event.getRawX();
|
||||
final float rawY = event.getRawY();
|
||||
boolean handled = false;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mPointerDown.set(rawX, rawY);
|
||||
mHandler.postAtTime(mCancelTapGestureRunnable,
|
||||
event.getDownTime() + ViewConfiguration.getLongPressTimeout());
|
||||
handled |= mOnGestureListener.onStart(rawX, rawY);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
stopSingleTapDetection();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
stopSingleTapDetectionIfNeeded(rawX, rawY);
|
||||
handled |= notifyDraggingGestureIfNeeded(rawX, rawY);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
stopSingleTapDetectionIfNeeded(rawX, rawY);
|
||||
if (mDetectSingleTap) {
|
||||
handled |= mOnGestureListener.onSingleTap();
|
||||
}
|
||||
// Fall through
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handled |= mOnGestureListener.onFinish(rawX, rawY);
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
private void stopSingleTapDetectionIfNeeded(float x, float y) {
|
||||
if (mDraggingDetected) {
|
||||
return;
|
||||
}
|
||||
if (!isLocationValid(mPointerDown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int deltaX = (int) (mPointerDown.x - x);
|
||||
final int deltaY = (int) (mPointerDown.y - y);
|
||||
final int distanceSquare = (deltaX * deltaX) + (deltaY * deltaY);
|
||||
if (distanceSquare > mTouchSlopSquare) {
|
||||
mDraggingDetected = true;
|
||||
stopSingleTapDetection();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopSingleTapDetection() {
|
||||
mHandler.removeCallbacks(mCancelTapGestureRunnable);
|
||||
mDetectSingleTap = false;
|
||||
}
|
||||
|
||||
private boolean notifyDraggingGestureIfNeeded(float x, float y) {
|
||||
if (!mDraggingDetected) {
|
||||
return false;
|
||||
}
|
||||
if (!isLocationValid(mPointerLocation)) {
|
||||
mPointerLocation.set(mPointerDown);
|
||||
}
|
||||
final float offsetX = x - mPointerLocation.x;
|
||||
final float offsetY = y - mPointerLocation.y;
|
||||
mPointerLocation.set(x, y);
|
||||
return mOnGestureListener.onDrag(offsetX, offsetY);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
resetPointF(mPointerDown);
|
||||
resetPointF(mPointerLocation);
|
||||
mHandler.removeCallbacks(mCancelTapGestureRunnable);
|
||||
mDetectSingleTap = true;
|
||||
mDraggingDetected = false;
|
||||
}
|
||||
|
||||
private static void resetPointF(PointF pointF) {
|
||||
pointF.x = Float.NaN;
|
||||
pointF.y = Float.NaN;
|
||||
}
|
||||
|
||||
private static boolean isLocationValid(PointF location) {
|
||||
return !Float.isNaN(location.x) && !Float.isNaN(location.y);
|
||||
}
|
||||
}
|
||||
@@ -22,16 +22,13 @@ import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.MathUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
@@ -46,11 +43,11 @@ import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of
|
||||
* {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled.
|
||||
* {@link Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled.
|
||||
* The button icon is movable by dragging. And the button UI would automatically be dismissed after
|
||||
* displaying for a period of time.
|
||||
*/
|
||||
class MagnificationModeSwitch {
|
||||
class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener {
|
||||
|
||||
@VisibleForTesting
|
||||
static final long FADING_ANIMATION_DURATION_MS = 300;
|
||||
@@ -66,13 +63,11 @@ class MagnificationModeSwitch {
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final WindowManager mWindowManager;
|
||||
private final ImageView mImageView;
|
||||
private final PointF mLastDown = new PointF();
|
||||
private final PointF mLastDrag = new PointF();
|
||||
private final int mTapTimeout = ViewConfiguration.getTapTimeout();
|
||||
private final int mTouchSlop;
|
||||
private int mMagnificationMode = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
|
||||
private final LayoutParams mParams;
|
||||
private boolean mIsVisible = false;
|
||||
private final MagnificationGestureDetector mGestureDetector;
|
||||
private boolean mSingleTapDetected = false;
|
||||
|
||||
MagnificationModeSwitch(Context context) {
|
||||
this(context, createView(context));
|
||||
@@ -86,7 +81,6 @@ class MagnificationModeSwitch {
|
||||
Context.WINDOW_SERVICE);
|
||||
mParams = createLayoutParams(context);
|
||||
mImageView = imageView;
|
||||
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
|
||||
applyResourcesValues();
|
||||
mImageView.setOnTouchListener(this::onTouch);
|
||||
mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
|
||||
@@ -127,6 +121,8 @@ class MagnificationModeSwitch {
|
||||
.start();
|
||||
mIsFadeOutAnimating = true;
|
||||
};
|
||||
mGestureDetector = new MagnificationGestureDetector(context,
|
||||
context.getMainThreadHandler(), this);
|
||||
}
|
||||
|
||||
private CharSequence formatStateDescription() {
|
||||
@@ -144,39 +140,38 @@ class MagnificationModeSwitch {
|
||||
}
|
||||
|
||||
private boolean onTouch(View v, MotionEvent event) {
|
||||
if (!mIsVisible || mImageView == null) {
|
||||
if (!mIsVisible) {
|
||||
return false;
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
stopFadeOutAnimation();
|
||||
mLastDown.set(event.getRawX(), event.getRawY());
|
||||
mLastDrag.set(event.getRawX(), event.getRawY());
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
// Move the button position.
|
||||
moveButton(event.getRawX() - mLastDrag.x,
|
||||
event.getRawY() - mLastDrag.y);
|
||||
mLastDrag.set(event.getRawX(), event.getRawY());
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
// Single tap to toggle magnification mode and the button position will be reset
|
||||
// after the action is performed.
|
||||
final float distance = MathUtils.dist(mLastDown.x, mLastDown.y,
|
||||
event.getRawX(), event.getRawY());
|
||||
if ((event.getEventTime() - event.getDownTime()) <= mTapTimeout
|
||||
&& distance <= mTouchSlop) {
|
||||
handleSingleTap();
|
||||
} else {
|
||||
showButton(mMagnificationMode);
|
||||
}
|
||||
return true;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
showButton(mMagnificationMode);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
return mGestureDetector.onTouch(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTap() {
|
||||
mSingleTapDetected = true;
|
||||
handleSingleTap();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDrag(float offsetX, float offsetY) {
|
||||
moveButton(offsetX, offsetY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStart(float x, float y) {
|
||||
stopFadeOutAnimation();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFinish(float xOffset, float yOffset) {
|
||||
if (!mSingleTapDetected) {
|
||||
showButton(mMagnificationMode);
|
||||
}
|
||||
mSingleTapDetected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void moveButton(float offsetX, float offsetY) {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
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.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class MagnificationGestureDetectorTest extends SysuiTestCase {
|
||||
|
||||
private static final float ACTION_DOWN_X = 100;
|
||||
private static final float ACTION_DOWN_Y = 200;
|
||||
private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
|
||||
private MagnificationGestureDetector mGestureDetector;
|
||||
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
|
||||
@Mock
|
||||
private MagnificationGestureDetector.OnGestureListener mListener;
|
||||
@Mock
|
||||
private Handler mHandler;
|
||||
private Runnable mCancelSingleTapRunnable;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doAnswer((invocation) -> {
|
||||
mCancelSingleTapRunnable = invocation.getArgument(0);
|
||||
return null;
|
||||
}).when(mHandler).postAtTime(any(Runnable.class), anyLong());
|
||||
mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mMotionEventHelper.recycleEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActionDown_invokeDownCallback() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
|
||||
mListener.onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performSingleTap_invokeCallbacksInOrder() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
mGestureDetector.onTouch(upEvent);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mListener);
|
||||
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
inOrder.verify(mListener).onSingleTap();
|
||||
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
verify(mListener, never()).onDrag(anyFloat(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performSingleTapWithActionCancel_notInvokeOnSingleTapCallback() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
final MotionEvent cancelEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_CANCEL, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
mGestureDetector.onTouch(cancelEvent);
|
||||
|
||||
verify(mListener, never()).onSingleTap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performSingleTapWithTwoPointers_notInvokeSingleTapCallback() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_POINTER_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
mGestureDetector.onTouch(upEvent);
|
||||
|
||||
verify(mListener, never()).onSingleTap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performLongPress_invokeCallbacksInOrder() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
// Execute the pending message for stopping single-tap detection.
|
||||
mCancelSingleTapRunnable.run();
|
||||
mGestureDetector.onTouch(upEvent);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mListener);
|
||||
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
verify(mListener, never()).onSingleTap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performDrag_invokeCallbacksInOrder() {
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
final float dragOffset = mTouchSlop + 10;
|
||||
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
final MotionEvent moveEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_MOVE, ACTION_DOWN_X + dragOffset, ACTION_DOWN_Y);
|
||||
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
|
||||
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
|
||||
mGestureDetector.onTouch(downEvent);
|
||||
mGestureDetector.onTouch(moveEvent);
|
||||
mGestureDetector.onTouch(upEvent);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(mListener);
|
||||
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
inOrder.verify(mListener).onDrag(dragOffset, 0);
|
||||
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
|
||||
verify(mListener, never()).onSingleTap();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@@ -91,8 +90,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
|
||||
private ViewPropertyAnimator mViewPropertyAnimator;
|
||||
private MagnificationModeSwitch mMagnificationModeSwitch;
|
||||
private View.OnTouchListener mTouchListener;
|
||||
private List<MotionEvent> mMotionEvents = new ArrayList<>();
|
||||
private Runnable mFadeOutAnimation;
|
||||
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -117,11 +116,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
for (MotionEvent event:mMotionEvents) {
|
||||
event.recycle();
|
||||
}
|
||||
mMotionEvents.clear();
|
||||
mFadeOutAnimation = null;
|
||||
mMotionEventHelper.recycleEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -436,9 +432,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
|
||||
|
||||
private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
|
||||
float y) {
|
||||
MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
|
||||
mMotionEvents.add(event);
|
||||
return event;
|
||||
return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
|
||||
}
|
||||
|
||||
private void executeFadeOutAnimation() {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.view.MotionEvent;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class MotionEventHelper {
|
||||
@GuardedBy("this")
|
||||
private final List<MotionEvent> mMotionEvents = new ArrayList<>();
|
||||
|
||||
void recycleEvents() {
|
||||
for (MotionEvent event:mMotionEvents) {
|
||||
event.recycle();
|
||||
}
|
||||
synchronized (this) {
|
||||
mMotionEvents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
|
||||
float y) {
|
||||
MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
|
||||
synchronized (this) {
|
||||
mMotionEvents.add(event);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user