From a3b03418c1d81394ae74cb98e72f95833b1200ef Mon Sep 17 00:00:00 2001 From: Matthew Ng Date: Fri, 15 Mar 2019 12:41:41 -0700 Subject: [PATCH] Moved assistant gesture to corners The corners will be separated with quick switch by detecting at the slop of the angle from touch down to that position. If over 30 deg then assistant will be tracked otherwise quick switch while swipe up will not be tracked at all. Test: manual Bug: 112934365 Change-Id: I6a3aeb1509d9706696a30ef1fba3ce7e3e5ec07c --- .../quickstep/AssistantTouchConsumer.java | 221 ++++++++++-------- .../quickstep/TouchInteractionService.java | 37 +-- quickstep/res/values/config.xml | 4 + quickstep/res/values/dimens.xml | 4 +- 4 files changed, 153 insertions(+), 113 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java index 109a4c5f79..54940529e0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java @@ -22,59 +22,70 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; +import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Resources; import android.graphics.PointF; -import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; -import android.view.Display; import android.view.MotionEvent; -import android.view.Surface; -import android.view.ViewConfiguration; -import android.view.WindowManager; +import com.android.launcher3.anim.Interpolators; +import com.android.quickstep.util.MotionPauseDetector; import com.android.systemui.shared.recents.ISystemUiProxy; -import com.android.systemui.shared.system.NavigationBarCompat; -import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.launcher3.R; +import com.android.systemui.shared.system.NavigationBarCompat; /** * Touch consumer for handling events to launch assistant from launcher */ public class AssistantTouchConsumer implements InputConsumer { 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 final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); + private final PointF mStartDragPos = new PointF(); + private int mActivePointerId = -1; - - private final int mDisplayRotation; - private final Rect mStableInsets = new Rect(); - - private final float mDragSlop; - private final float mTouchSlop; - private final float mThreshold; - - private float mStartDisplacement; - private boolean mPassedDragSlop; - private boolean mPassedTouchSlop; - private long mPassedTouchSlopTime; + private boolean mPassedSlop; private boolean mLaunchedAssistant; + private float mDistance; + private float mTimeFraction; + private long mDragTime; private float mLastProgress; + private int mState; + private final float mDistThreshold; + private final long mTimeThreshold; + private final int mAngleThreshold; + private final float mSlop; + private final MotionPauseDetector mMotionPauseDetector; private final ISystemUiProxy mSysUiProxy; + private final InputConsumer mConsumerDelegate; - public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy) { + public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy, + InputConsumer delegate) { + final Resources res = context.getResources(); mSysUiProxy = systemUiProxy; - - mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx(); - mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx(); - mThreshold = context.getResources().getDimension(R.dimen.gestures_assistant_threshold); - - Display display = context.getSystemService(WindowManager.class).getDefaultDisplay(); - mDisplayRotation = display.getRotation(); - WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + mConsumerDelegate = delegate; + mMotionPauseDetector = new MotionPauseDetector(context); + 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 = NavigationBarCompat.getQuickScrubTouchSlopPx(); + mState = STATE_INACTIVE; } @Override @@ -82,15 +93,29 @@ public class AssistantTouchConsumer implements InputConsumer { return TYPE_ASSISTANT; } + @Override + public boolean isActive() { + return mState != STATE_INACTIVE; + } + @Override public void onMotionEvent(MotionEvent ev) { // TODO add logging + switch (ev.getActionMasked()) { case ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); - mLastProgress = -1; + mTimeFraction = 0; + + // Detect when the gesture decelerates to start the assistant + mMotionPauseDetector.setOnMotionPauseListener(isPaused -> { + if (isPaused && mState == STATE_ASSISTANT_ACTIVE) { + mTimeFraction = 1; + updateAssistantProgress(); + } + }); break; } case ACTION_POINTER_UP: { @@ -107,94 +132,100 @@ public class AssistantTouchConsumer implements InputConsumer { break; } case ACTION_MOVE: { + if (mState == STATE_DELEGATE_ACTIVE) { + break; + } int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { break; } mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); - float displacement = getDisplacement(ev); - if (!mPassedDragSlop) { - // Normal gesture, ensure we pass the drag slop before we start tracking - // the gesture - if (Math.abs(displacement) > mDragSlop) { - mPassedDragSlop = true; - mStartDisplacement = displacement; - mPassedTouchSlopTime = SystemClock.uptimeMillis(); - } - } + if (!mPassedSlop) { + // 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) { + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + mDragTime = SystemClock.uptimeMillis(); - if (!mPassedTouchSlop) { - if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >= - mTouchSlop) { - mPassedTouchSlop = true; - if (!mPassedDragSlop) { - mPassedDragSlop = true; - mStartDisplacement = displacement; - mPassedTouchSlopTime = SystemClock.uptimeMillis(); + // Determine if angle is larger than threshold for assistant detection + float angle = (float) Math.toDegrees( + Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x)); + angle = angle > 90 ? 180 - angle : angle; + if (angle > mAngleThreshold) { + mState = STATE_ASSISTANT_ACTIVE; + + if (mConsumerDelegate != null) { + // Send cancel event + MotionEvent event = MotionEvent.obtain(ev); + event.setAction(MotionEvent.ACTION_CANCEL); + mConsumerDelegate.onMotionEvent(event); + } + } else { + mState = STATE_DELEGATE_ACTIVE; } } - } - - if (mPassedDragSlop) { - // Move - float distance = mStartDisplacement - displacement; - if (distance >= 0) { - onAssistantProgress(distance / mThreshold); + } else { + // Movement + mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x, + mLastPos.y - mStartDragPos.y); + mMotionPauseDetector.addPosition(mDistance, 0); + if (mDistance >= 0) { + final long diff = SystemClock.uptimeMillis() - mDragTime; + mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1); + updateAssistantProgress(); } } break; } case ACTION_CANCEL: - break; - case ACTION_UP: { - if (ev.getEventTime() - mPassedTouchSlopTime < ViewConfiguration.getTapTimeout()) { - onAssistantProgress(1); + case ACTION_UP: + if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) { + ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0) + .setDuration(RETRACT_ANIMATION_DURATION_MS); + animator.addUpdateListener(valueAnimator -> { + float progress = (float) valueAnimator.getAnimatedValue(); + try { + mSysUiProxy.onAssistantProgress(progress); + } catch (RemoteException e) { + Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + + progress, e); + } + }); + animator.setInterpolator(Interpolators.DEACCEL_2); + animator.start(); } - + mMotionPauseDetector.clear(); break; - } + } + + if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) { + mConsumerDelegate.onMotionEvent(ev); } } - private void onAssistantProgress(float progress) { - if (mLastProgress == progress) { - return; - } - try { - mSysUiProxy.onAssistantProgress(Math.max(0, Math.min(1, progress))); - if (progress >= 1 && !mLaunchedAssistant) { - mSysUiProxy.startAssistant(new Bundle()); - mLaunchedAssistant = true; - } + private void updateAssistantProgress() { + if (!mLaunchedAssistant) { + float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction; mLastProgress = progress; - } catch (RemoteException e) { - Log.w(TAG, "Failed to notify SysUI to start/send assistant progress: " + progress, e); + try { + mSysUiProxy.onAssistantProgress(progress); + + if (mDistance >= mDistThreshold && mTimeFraction >= 1) { + mSysUiProxy.startAssistant(new Bundle()); + mLaunchedAssistant = true; + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e); + } } } - private boolean isNavBarOnRight() { - return mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0; - } - - private boolean isNavBarOnLeft() { - return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0; - } - - private float getDisplacement(MotionEvent ev) { - float eventX = ev.getX(); - float eventY = ev.getY(); - float displacement = eventY - mDownPos.y; - if (isNavBarOnRight()) { - displacement = eventX - mDownPos.x; - } else if (isNavBarOnLeft()) { - displacement = mDownPos.x - eventX; - } - return displacement; - } - - static boolean withinTouchRegion(Context context, float x) { - return x > context.getResources().getDisplayMetrics().widthPixels - - context.getResources().getDimension(R.dimen.gestures_assistant_width); + static boolean withinTouchRegion(Context context, MotionEvent ev) { + final Resources res = context.getResources(); + final int width = res.getDisplayMetrics().widthPixels; + final int height = res.getDisplayMetrics().heightPixels; + final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size); + return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 6d608eef6e..f2786823f3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -313,31 +313,36 @@ public class TouchInteractionService extends Service { mSwipeSharedState.clearAllState(); } + final ActivityControlHelper activityControl = + mOverviewComponentObserver.getActivityControlHelper(); if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) { return InputConsumer.NO_OP; } else if (mAssistantAvailable && mOverviewInteractionState.isSwipeUpGestureEnabled() && FeatureFlags.ENABLE_ASSISTANT_GESTURE.get() - && AssistantTouchConsumer.withinTouchRegion(this, event.getX())) { - return new AssistantTouchConsumer(this, mRecentsModel.getSystemUiProxy()); - } else if (mSwipeSharedState.goingToLauncher || - mOverviewComponentObserver.getActivityControlHelper().isResumed()) { - return OverviewInputConsumer.newInstance( - mOverviewComponentObserver.getActivityControlHelper(), false); + && AssistantTouchConsumer.withinTouchRegion(this, event)) { + return new AssistantTouchConsumer(this, mISystemUiProxy, !activityControl.isResumed() + ? createOtherActivityInputConsumer(event, runningTaskInfo) : null); + } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) { + return OverviewInputConsumer.newInstance(activityControl, false); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && - mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) { - return OverviewInputConsumer.newInstance( - mOverviewComponentObserver.getActivityControlHelper(), false); + activityControl.isInLiveTileMode()) { + return OverviewInputConsumer.newInstance(activityControl, false); } else { - ActivityControlHelper activityControl = - mOverviewComponentObserver.getActivityControlHelper(); - boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event); - return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel, - mOverviewComponentObserver.getOverviewIntent(), activityControl, - shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer, - this::onConsumerInactive, mSwipeSharedState); + return createOtherActivityInputConsumer(event, runningTaskInfo); } } + private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event, + RunningTaskInfo runningTaskInfo) { + final ActivityControlHelper activityControl = + mOverviewComponentObserver.getActivityControlHelper(); + boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event); + return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel, + mOverviewComponentObserver.getOverviewIntent(), activityControl, + shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer, + this::onConsumerInactive, mSwipeSharedState); + } + /** * To be called by the consumer when it's no longer active. */ diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 3fbfcdd6c4..a96669832c 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -26,4 +26,8 @@ determines how many thumbnails will be fetched in the background. --> 3 12 + + + 200 + 30 diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index f5e5dd32cb..9c97c8c905 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -66,6 +66,6 @@ 24dp - 70dp - 200dp + 28dp + 70dp