Merge "Improving swipe up interaction when device is locked" into ub-launcher3-qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e34c91765f
@@ -84,6 +84,11 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name="com.android.quickstep.LockScreenRecentsActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:showOnLockScreen="true"
|
||||
android:directBootAware="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Empty activity to start a recents transition
|
||||
*/
|
||||
public class LockScreenRecentsActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
+15
-7
@@ -479,7 +479,7 @@ public class TouchInteractionService extends Service implements
|
||||
if (isInValidSystemUiState) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
|
||||
// launched while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
return createDeviceLockedInputConsumer(mAM.getRunningTask(0));
|
||||
} else {
|
||||
return InputConsumer.NO_OP;
|
||||
}
|
||||
@@ -512,16 +512,15 @@ public class TouchInteractionService extends Service implements
|
||||
}
|
||||
|
||||
private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
|
||||
if (mKM.isDeviceLocked()) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
|
||||
// while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
}
|
||||
|
||||
final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
if (!useSharedState) {
|
||||
mSwipeSharedState.clearAllState();
|
||||
}
|
||||
if (mKM.isDeviceLocked()) {
|
||||
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
|
||||
// while device is locked even after exiting direct boot mode (e.g. camera).
|
||||
return createDeviceLockedInputConsumer(runningTaskInfo);
|
||||
}
|
||||
|
||||
final ActivityControlHelper activityControl =
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
@@ -559,6 +558,15 @@ public class TouchInteractionService extends Service implements
|
||||
mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion);
|
||||
}
|
||||
|
||||
private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
|
||||
if (mMode == Mode.NO_BUTTON && taskInfo != null) {
|
||||
return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat,
|
||||
mSwipeTouchRegion, taskInfo.taskId);
|
||||
} else {
|
||||
return InputConsumer.NO_OP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by the consumer when it's no longer active.
|
||||
*/
|
||||
|
||||
+203
-7
@@ -15,26 +15,102 @@
|
||||
*/
|
||||
package com.android.quickstep.inputconsumers;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.Utilities.squaredTouchSlop;
|
||||
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
|
||||
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.quickstep.LockScreenRecentsActivity;
|
||||
import com.android.quickstep.MultiStateCallback;
|
||||
import com.android.quickstep.SwipeSharedState;
|
||||
import com.android.quickstep.util.ClipAnimationHelper;
|
||||
import com.android.quickstep.util.RecentsAnimationListenerSet;
|
||||
import com.android.quickstep.util.SwipeAnimationTargetSet;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.BackgroundExecutor;
|
||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
/**
|
||||
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
|
||||
*/
|
||||
public class DeviceLockedInputConsumer implements InputConsumer {
|
||||
public class DeviceLockedInputConsumer implements InputConsumer,
|
||||
SwipeAnimationTargetSet.SwipeAnimationListener {
|
||||
|
||||
private static final float SCALE_DOWN = 0.75f;
|
||||
|
||||
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
|
||||
private static int getFlagForIndex(int index, String name) {
|
||||
if (DEBUG_STATES) {
|
||||
STATE_NAMES[index] = name;
|
||||
}
|
||||
return 1 << index;
|
||||
}
|
||||
|
||||
private static final int STATE_TARGET_RECEIVED =
|
||||
getFlagForIndex(0, "STATE_TARGET_RECEIVED");
|
||||
private static final int STATE_HANDLER_INVALIDATED =
|
||||
getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
|
||||
|
||||
private final Context mContext;
|
||||
private final float mTouchSlopSquared;
|
||||
private final PointF mTouchDown = new PointF();
|
||||
private final SwipeSharedState mSwipeSharedState;
|
||||
private final InputMonitorCompat mInputMonitorCompat;
|
||||
|
||||
public DeviceLockedInputConsumer(Context context) {
|
||||
private final PointF mTouchDown = new PointF();
|
||||
private final ClipAnimationHelper mClipAnimationHelper;
|
||||
private final ClipAnimationHelper.TransformParams mTransformParams;
|
||||
private final Point mDisplaySize;
|
||||
private final MultiStateCallback mStateCallback;
|
||||
private final RectF mSwipeTouchRegion;
|
||||
public final int mRunningTaskId;
|
||||
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private float mProgress;
|
||||
|
||||
private boolean mThresholdCrossed = false;
|
||||
|
||||
private SwipeAnimationTargetSet mTargetSet;
|
||||
|
||||
public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
|
||||
InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) {
|
||||
mContext = context;
|
||||
mTouchSlopSquared = squaredTouchSlop(context);
|
||||
mSwipeSharedState = swipeSharedState;
|
||||
mClipAnimationHelper = new ClipAnimationHelper(context);
|
||||
mTransformParams = new ClipAnimationHelper.TransformParams();
|
||||
mInputMonitorCompat = inputMonitorCompat;
|
||||
mSwipeTouchRegion = swipeTouchRegion;
|
||||
mRunningTaskId = runningTaskId;
|
||||
|
||||
// Do not use DeviceProfile as the user data might be locked
|
||||
mDisplaySize = new Point();
|
||||
context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize);
|
||||
|
||||
// Init states
|
||||
mStateCallback = new MultiStateCallback(STATE_NAMES);
|
||||
mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
|
||||
this::endRemoteAnimation);
|
||||
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,17 +120,137 @@ public class DeviceLockedInputConsumer implements InputConsumer {
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent ev) {
|
||||
if (mVelocityTracker == null) {
|
||||
return;
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
float x = ev.getX();
|
||||
float y = ev.getY();
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchDown.set(x, y);
|
||||
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTouchDown.set(x, y);
|
||||
break;
|
||||
case ACTION_POINTER_DOWN: {
|
||||
if (!mThresholdCrossed) {
|
||||
// Cancel interaction in case of multi-touch interaction
|
||||
int ptrIdx = ev.getActionIndex();
|
||||
if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
|
||||
int action = ev.getAction();
|
||||
ev.setAction(ACTION_CANCEL);
|
||||
finishTouchTracking(ev);
|
||||
ev.setAction(action);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (!mThresholdCrossed) {
|
||||
if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) {
|
||||
startRecentsTransition();
|
||||
}
|
||||
} else {
|
||||
float dy = Math.max(mTouchDown.y - y, 0);
|
||||
mProgress = dy / mDisplaySize.y;
|
||||
mTransformParams.setProgress(mProgress);
|
||||
if (mTargetSet != null) {
|
||||
mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
finishTouchTracking(ev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the gesture has ended. Does not correlate to the completion of the interaction as
|
||||
* the animation can still be running.
|
||||
*/
|
||||
private void finishTouchTracking(MotionEvent ev) {
|
||||
mStateCallback.setState(STATE_HANDLER_INVALIDATED);
|
||||
if (mThresholdCrossed && ev.getAction() == ACTION_UP) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000,
|
||||
ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
|
||||
|
||||
float velocityY = mVelocityTracker.getYVelocity();
|
||||
float flingThreshold = mContext.getResources()
|
||||
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
|
||||
|
||||
boolean dismissTask;
|
||||
if (Math.abs(velocityY) > flingThreshold) {
|
||||
// Is fling
|
||||
dismissTask = velocityY < 0;
|
||||
} else {
|
||||
dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW);
|
||||
}
|
||||
if (dismissTask) {
|
||||
// For now, just start the home intent so user is prompted to unlock the device.
|
||||
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
}
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
private void startRecentsTransition() {
|
||||
mThresholdCrossed = true;
|
||||
RecentsAnimationListenerSet newListenerSet =
|
||||
mSwipeSharedState.newRecentsAnimationListenerSet();
|
||||
newListenerSet.addListener(this);
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
.setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
|
||||
mInputMonitorCompat.pilferPointers();
|
||||
BackgroundExecutor.get().submit(
|
||||
() -> ActivityManagerWrapper.getInstance().startRecentsActivity(
|
||||
intent, null, newListenerSet, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
|
||||
mTargetSet = targetSet;
|
||||
|
||||
Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
|
||||
RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId);
|
||||
if (targetCompat != null) {
|
||||
mClipAnimationHelper.updateSource(displaySize, targetCompat);
|
||||
}
|
||||
|
||||
Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
|
||||
displaySize.offsetTo(displaySize.left, 0);
|
||||
mClipAnimationHelper.updateTargetRect(displaySize);
|
||||
mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
|
||||
|
||||
mStateCallback.setState(STATE_TARGET_RECEIVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled() {
|
||||
mTargetSet = null;
|
||||
}
|
||||
|
||||
private void endRemoteAnimation() {
|
||||
if (mTargetSet != null) {
|
||||
mTargetSet.finishController(
|
||||
false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConsumerAboutToBeSwitched() {
|
||||
mStateCallback.setState(STATE_HANDLER_INVALIDATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowInterceptByParent() {
|
||||
return !mThresholdCrossed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user