Merge "Adding support for accessibiliy gesture" into ub-launcher3-qt-dev

This commit is contained in:
TreeHugger Robot
2019-04-29 21:54:41 +00:00
committed by Android (Google) Code Review
9 changed files with 306 additions and 89 deletions
@@ -21,4 +21,6 @@
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
<!-- Minimum distance to swipe to trigger accessibility gesture -->
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
</resources>
@@ -0,0 +1,156 @@
/*
* 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.quickstep;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.launcher3.R;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Touch consumer for two finger swipe actions for accessibility actions
*/
public class AccessibilityInputConsumer extends DelegateInputConsumer {
private static final String TAG = "A11yInputConsumer";
private final ISystemUiProxy mSystemUiProxy;
private final VelocityTracker mVelocityTracker;
private final MotionPauseDetector mMotionPauseDetector;
private final boolean mAllowLongClick;
private final float mMinGestureDistance;
private final float mMinFlingVelocity;
private int mActivePointerId = -1;
private float mDownY;
private float mTotalY;
public AccessibilityInputConsumer(Context context, ISystemUiProxy systemUiProxy,
boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
mSystemUiProxy = systemUiProxy;
mVelocityTracker = VelocityTracker.obtain();
mMinGestureDistance = context.getResources()
.getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
mMotionPauseDetector = new MotionPauseDetector(context);
mAllowLongClick = allowLongClick;
}
@Override
public int getType() {
return TYPE_ACCESSIBILITY | mDelegate.getType();
}
@Override
public void onMotionEvent(MotionEvent ev) {
if (mState != STATE_DELEGATE_ACTIVE) {
mVelocityTracker.addMovement(ev);
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
break;
}
case ACTION_POINTER_UP: {
if (mState == STATE_ACTIVE) {
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIdx = pointerIndex == 0 ? 1 : 0;
mTotalY += (ev.getY(pointerIndex) - mDownY);
mDownY = ev.getY(newPointerIdx);
mActivePointerId = ev.getPointerId(newPointerIdx);
}
}
break;
}
case ACTION_POINTER_DOWN: {
if (mState == STATE_INACTIVE) {
if (mDelegate.allowInterceptByParent()) {
setActive(ev);
int pointerIndex = ev.getActionIndex();
mActivePointerId = ev.getPointerId(pointerIndex);
mDownY = ev.getY(pointerIndex);
} else {
mState = STATE_DELEGATE_ACTIVE;
}
}
break;
}
case ACTION_MOVE: {
if (mState == STATE_ACTIVE && mAllowLongClick) {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mMotionPauseDetector.addPosition(ev.getY(pointerIndex) - mDownY,
ev.getEventTime());
}
break;
}
case ACTION_UP:
if (mState == STATE_ACTIVE) {
try {
if (mAllowLongClick && mMotionPauseDetector.isPaused()) {
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
} else {
mTotalY += (ev.getY() - mDownY);
mVelocityTracker.computeCurrentVelocity(1000);
if ((-mTotalY) > mMinGestureDistance
|| (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
mSystemUiProxy.notifyAccessibilityButtonClicked(
Display.DEFAULT_DISPLAY);
}
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to notify accessibility event", e);
}
}
// Follow through
case ACTION_CANCEL: {
mVelocityTracker.recycle();
mMotionPauseDetector.clear();
break;
}
}
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
}
}
@@ -50,20 +50,10 @@ import com.android.systemui.shared.system.QuickStepContract;
/**
* Touch consumer for handling events to launch assistant from launcher
*/
public class AssistantTouchConsumer implements InputConsumer {
public class AssistantTouchConsumer extends DelegateInputConsumer {
private static final String TAG = "AssistantTouchConsumer";
private static final long RETRACT_ANIMATION_DURATION_MS = 300;
/* The assistant touch consume competes with quick switch InputConsumer gesture. The delegate
* can be chosen to run if the angle passing the slop is lower than the threshold angle. When
* this occurs, the state changes to {@link #STATE_DELEGATE_ACTIVE} where the next incoming
* motion events are handled by the delegate instead of the assistant touch consumer. If the
* angle is higher than the threshold, the state will change to {@link #STATE_ASSISTANT_ACTIVE}.
*/
private static final int STATE_INACTIVE = 0;
private static final int STATE_ASSISTANT_ACTIVE = 1;
private static final int STATE_DELEGATE_ACTIVE = 2;
private static final String INVOCATION_TYPE_KEY = "invocation_type";
private static final int INVOCATION_TYPE_GESTURE = 1;
@@ -78,7 +68,6 @@ public class AssistantTouchConsumer implements InputConsumer {
private float mTimeFraction;
private long mDragTime;
private float mLastProgress;
private int mState;
private int mDirection;
private ActivityControlHelper mActivityControlHelper;
@@ -87,46 +76,25 @@ public class AssistantTouchConsumer implements InputConsumer {
private final int mAngleThreshold;
private final float mSlop;
private final ISystemUiProxy mSysUiProxy;
private final InputConsumer mConsumerDelegate;
private final Context mContext;
private final InputMonitorCompat mInputMonitorCompat;
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
InputConsumer delegate, InputMonitorCompat inputMonitorCompat,
ActivityControlHelper activityControlHelper) {
ActivityControlHelper activityControlHelper, InputConsumer delegate,
InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
final Resources res = context.getResources();
mContext = context;
mSysUiProxy = systemUiProxy;
mConsumerDelegate = delegate;
mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
mSlop = QuickStepContract.getQuickStepDragSlopPx();
mInputMonitorCompat = inputMonitorCompat;
mActivityControlHelper = activityControlHelper;
mState = STATE_INACTIVE;
}
@Override
public int getType() {
return TYPE_ASSISTANT;
}
@Override
public boolean useSharedSwipeState() {
if (mConsumerDelegate != null) {
return mConsumerDelegate.useSharedSwipeState();
}
return false;
}
@Override
public void onConsumerAboutToBeSwitched() {
if (mConsumerDelegate != null) {
mConsumerDelegate.onConsumerAboutToBeSwitched();
}
return TYPE_ASSISTANT | mDelegate.getType();
}
@Override
@@ -158,6 +126,10 @@ public class AssistantTouchConsumer implements InputConsumer {
if (mState == STATE_DELEGATE_ACTIVE) {
break;
}
if (!mDelegate.allowInterceptByParent()) {
mState = STATE_DELEGATE_ACTIVE;
break;
}
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
@@ -168,9 +140,6 @@ public class AssistantTouchConsumer implements InputConsumer {
// Normal gesture, ensure we pass the slop before we start tracking the gesture
if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
// Cancel touches to other windows (intercept)
mInputMonitorCompat.pilferPointers();
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
mDragTime = SystemClock.uptimeMillis();
@@ -182,15 +151,7 @@ public class AssistantTouchConsumer implements InputConsumer {
angle = angle > 90 ? 180 - angle : angle;
if (angle > mAngleThreshold && angle < 90) {
mState = STATE_ASSISTANT_ACTIVE;
if (mConsumerDelegate != null) {
// Send cancel event
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mConsumerDelegate.onMotionEvent(event);
event.recycle();
}
setActive(ev);
} else {
mState = STATE_DELEGATE_ACTIVE;
}
@@ -232,8 +193,8 @@ public class AssistantTouchConsumer implements InputConsumer {
break;
}
if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) {
mConsumerDelegate.onMotionEvent(ev);
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
}
@@ -249,7 +210,8 @@ public class AssistantTouchConsumer implements InputConsumer {
Bundle args = new Bundle();
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
BaseDraggingActivity launcherActivity = mActivityControlHelper.getCreatedActivity();
BaseDraggingActivity launcherActivity =
mActivityControlHelper.getCreatedActivity();
if (launcherActivity != null) {
launcherActivity.getRootView().
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
@@ -0,0 +1,49 @@
package com.android.quickstep;
import android.view.MotionEvent;
import com.android.systemui.shared.system.InputMonitorCompat;
public abstract class DelegateInputConsumer implements InputConsumer {
protected static final int STATE_INACTIVE = 0;
protected static final int STATE_ACTIVE = 1;
protected static final int STATE_DELEGATE_ACTIVE = 2;
protected final InputConsumer mDelegate;
protected final InputMonitorCompat mInputMonitor;
protected int mState;
public DelegateInputConsumer(InputConsumer delegate, InputMonitorCompat inputMonitor) {
mDelegate = delegate;
mInputMonitor = inputMonitor;
mState = STATE_INACTIVE;
}
@Override
public boolean useSharedSwipeState() {
return mDelegate.useSharedSwipeState();
}
@Override
public boolean allowInterceptByParent() {
return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
}
@Override
public void onConsumerAboutToBeSwitched() {
mDelegate.onConsumerAboutToBeSwitched();
}
protected void setActive(MotionEvent ev) {
mState = STATE_ACTIVE;
mInputMonitor.pilferPointers();
// Send cancel event
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
mDelegate.onMotionEvent(event);
event.recycle();
}
}
@@ -24,11 +24,12 @@ import android.view.MotionEvent;
@TargetApi(Build.VERSION_CODES.O)
public interface InputConsumer {
int TYPE_NO_OP = 0;
int TYPE_OVERVIEW = 1;
int TYPE_OTHER_ACTIVITY = 2;
int TYPE_ASSISTANT = 3;
int TYPE_DEVICE_LOCKED = 4;
int TYPE_NO_OP = 1 << 0;
int TYPE_OVERVIEW = 1 << 1;
int TYPE_OTHER_ACTIVITY = 1 << 2;
int TYPE_ASSISTANT = 1 << 3;
int TYPE_DEVICE_LOCKED = 1 << 4;
int TYPE_ACCESSIBILITY = 1 << 5;
InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -38,6 +39,13 @@ public interface InputConsumer {
return false;
}
/**
* Returns true if the user has crossed the threshold for it to be an explicit action.
*/
default boolean allowInterceptByParent() {
return true;
}
/**
* Called by the event queue when the consumer is about to be switched to a new consumer.
*/
@@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
@@ -345,18 +346,21 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
*/
private void finishTouchTracking(MotionEvent ev) {
if (mPassedDragSlop && mInteractionHandler != null) {
if (ev.getActionMasked() == ACTION_CANCEL) {
mInteractionHandler.onGestureCancelled();
} else {
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = isNavBarOnRight() ? velocityX
: isNavBarOnLeft() ? -velocityX
: velocityY;
mVelocityTracker.computeCurrentVelocity(1000,
ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
float velocity = isNavBarOnRight() ? velocityX
: isNavBarOnLeft() ? -velocityX
: velocityY;
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
mDownPos);
mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
mDownPos);
}
} else {
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, just cleanup immediately.
@@ -387,7 +391,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
if (mSwipeSharedState.canGestureBeContinued) {
mInteractionHandler.cancel();
mInteractionHandler.cancelCurrentAnimation();
} else {
mInteractionHandler.reset();
}
@@ -425,4 +429,9 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
public boolean useSharedSwipeState() {
return mInteractionHandler != null;
}
@Override
public boolean allowInterceptByParent() {
return !mPassedTouchSlop;
}
}
@@ -64,6 +64,11 @@ public class OverviewInputConsumer<T extends BaseDraggingActivity>
return TYPE_OVERVIEW;
}
@Override
public boolean allowInterceptByParent() {
return !mTargetHandledTouch;
}
@Override
public void onMotionEvent(MotionEvent ev) {
if (!mProxyTouch) {
@@ -20,6 +20,8 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -67,9 +69,8 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -456,25 +457,32 @@ public class TouchInteractionService extends Service implements
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
InputConsumer base;
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
return InputConsumer.NO_OP;
} else if (mAssistantAvailable
&& SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
&& AssistantTouchConsumer.withinTouchRegion(this, event)) {
boolean addDelegate = !activityControl.isResumed();
return new AssistantTouchConsumer(this, mISystemUiProxy, addDelegate ?
createOtherActivityInputConsumer(event, runningTaskInfo) : null,
mInputMonitorCompat, activityControl);
base = InputConsumer.NO_OP;
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
activityControl.isInLiveTileMode()) {
return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
} else {
return createOtherActivityInputConsumer(event, runningTaskInfo);
base = createOtherActivityInputConsumer(event, runningTaskInfo);
}
if (mMode == Mode.NO_BUTTON) {
if (mAssistantAvailable && AssistantTouchConsumer.withinTouchRegion(this, event)) {
base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
mInputMonitorCompat);
}
if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
base = new AccessibilityInputConsumer(this, mISystemUiProxy,
(mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
mInputMonitorCompat);
}
}
return base;
}
private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
@@ -769,6 +769,17 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
}
/**
* Called as a result on ACTION_CANCEL to return the UI to the start state.
*/
@UiThread
public void onGestureCancelled() {
updateDisplacement(0);
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = Touch.SWIPE_NOOP;
handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
}
/**
* @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
* @param velocity The x and y components of the velocity when the gesture ends.
@@ -789,7 +800,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
}
mDownPos = downPos;
handleNormalGestureEnd(endVelocity, isFling, velocity);
handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
}
@UiThread
@@ -807,7 +818,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
@UiThread
private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
boolean isCancel) {
PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
@@ -825,7 +837,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
if (!isFling) {
if (mMode == Mode.NO_BUTTON) {
if (isCancel) {
endTarget = LAST_TASK;
} else if (mMode == Mode.NO_BUTTON) {
if (mIsShelfPeeking) {
endTarget = RECENTS;
} else if (goingToNewTask) {
@@ -908,7 +922,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void doLogGesture(GestureEndTarget endTarget) {
DeviceProfile dp = mDp;
if (dp == null) {
if (dp == null || mDownPos == null) {
// We probably never received an animation controller, skip logging.
return;
}
@@ -1106,7 +1120,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
public void cancel() {
/**
* Cancels any running animation so that the active target can be overriden by a new swipe
* handle (in case of quick switch).
*/
public void cancelCurrentAnimation() {
mCurrentShift.cancelAnimation();
if (mLauncherTransitionController != null && mLauncherTransitionController
.getAnimationPlayer().isStarted()) {