summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2019-05-28 01:26:19 -0700
committerSunny Goyal <sunnygoyal@google.com>2019-05-28 15:55:07 -0700
commit221895d06b4e8c0cbe8c52b63115d673f1061a5d (patch)
tree92b2e3b5e8c2f4683c2ecb4eb76c190b6a7f6feb
parentebd5e88164ef3a59cfe5f75a9eaeb95222ded7fa (diff)
downloadandroid_packages_apps_Trebuchet-221895d06b4e8c0cbe8c52b63115d673f1061a5d.tar.gz
android_packages_apps_Trebuchet-221895d06b4e8c0cbe8c52b63115d673f1061a5d.tar.bz2
android_packages_apps_Trebuchet-221895d06b4e8c0cbe8c52b63115d673f1061a5d.zip
Improving swipe up interaction when device is locked
When device is locked, only scale down the top task as a response to the user interaction. When user flings or lifts his finger, the task is dismissed to go to the lock screen LockScreenRecentsActivity is an empty activity which starts on top of lock screen and finishes immediately. This allows us to start a recents transition with just the top activity as the animation target. This target is then used for swipe up interaction Bug: 133167096 Change-Id: I466ed142ea33d626c78cb9cc5f6311bad26b8d98
-rw-r--r--quickstep/AndroidManifest.xml5
-rw-r--r--quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java31
-rw-r--r--quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java22
-rw-r--r--quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java208
4 files changed, 253 insertions, 13 deletions
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index a38979d41..332e0fa36 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -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>
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
new file mode 100644
index 000000000..65f323c7d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
@@ -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();
+ }
+}
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 6ba1bf5c5..14bdec562 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -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.
*/
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index d01b5ec19..db2af59ac 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -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 SwipeSharedState mSwipeSharedState;
+ private final InputMonitorCompat mInputMonitorCompat;
+
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;
- public DeviceLockedInputConsumer(Context context) {
+ 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;
}
}