Loading quickstep/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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(); } } quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +15 −7 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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(); Loading Loading @@ -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. */ Loading quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +202 −6 Original line number Diff line number Diff line Loading @@ -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; private boolean mThresholdCrossed = false; private SwipeAnimationTargetSet mTargetSet; public DeviceLockedInputConsumer(Context context) { 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 Loading @@ -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) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mTouchDown.set(x, y); } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { 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; } } Loading
quickstep/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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>
quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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(); } }
quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +15 −7 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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(); Loading Loading @@ -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. */ Loading
quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +202 −6 Original line number Diff line number Diff line Loading @@ -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; private boolean mThresholdCrossed = false; private SwipeAnimationTargetSet mTargetSet; public DeviceLockedInputConsumer(Context context) { 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 Loading @@ -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) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mTouchDown.set(x, y); } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { 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; } }