Merge "Improving swipe up interaction when device is locked" into ub-launcher3-qt-dev

am: e34c91765f

Change-Id: If49078e2b15375eb08c23aa002709f67c692e1e6
This commit is contained in:
Sunny Goyal
2019-05-28 16:13:01 -07:00
committed by android-build-merger
4 changed files with 254 additions and 14 deletions
+5
View File
@@ -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>
@@ -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();
}
}
@@ -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.
*/
@@ -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;
}
}