Merge "Moved assistant gesture to corners" into ub-launcher3-master
This commit is contained in:
commit
e498e99fe2
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,31 +311,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.
|
||||
*/
|
||||
|
|
|
@ -26,4 +26,8 @@
|
|||
determines how many thumbnails will be fetched in the background. -->
|
||||
<integer name="recentsThumbnailCacheSize">3</integer>
|
||||
<integer name="recentsIconCacheSize">12</integer>
|
||||
|
||||
<!-- Assistant Gesture -->
|
||||
<integer name="assistant_gesture_min_time_threshold">200</integer>
|
||||
<integer name="assistant_gesture_corner_deg_threshold">30</integer>
|
||||
</resources>
|
||||
|
|
|
@ -66,6 +66,6 @@
|
|||
<dimen name="shelf_surface_offset">24dp</dimen>
|
||||
|
||||
<!-- Assistant Gestures -->
|
||||
<dimen name="gestures_assistant_width">70dp</dimen>
|
||||
<dimen name="gestures_assistant_threshold">200dp</dimen>
|
||||
<dimen name="gestures_assistant_size">28dp</dimen>
|
||||
<dimen name="gestures_assistant_drag_threshold">70dp</dimen>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue