diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index e202c574ad..52a6dd5fe5 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -46,6 +46,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; +import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; @@ -82,7 +83,8 @@ public interface ActivityControlHelper { void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo, boolean activityVisible); - float getTranslationYForQuickScrub(T activity); + float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, + Context context); void executeOnWindowAvailable(T activity, Runnable action); @@ -151,10 +153,15 @@ public interface ActivityControlHelper { } @Override - public float getTranslationYForQuickScrub(Launcher activity) { - LauncherRecentsView recentsView = activity.getOverviewPanel(); - return recentsView.computeTranslationYForFactor( - FastOverviewState.OVERVIEW_TRANSLATION_FACTOR); + public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, + Context context) { + // The padding calculations are exactly same as that of RecentsView.setInsets + int topMargin = context.getResources() + .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); + int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top; + int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom; + + return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop); } @Override @@ -380,7 +387,8 @@ public interface ActivityControlHelper { } @Override - public float getTranslationYForQuickScrub(RecentsActivity activity) { + public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, + Context context) { return 0; } diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java index b92678a2ef..8e83bd0792 100644 --- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java @@ -63,6 +63,16 @@ public class DeferredTouchConsumer implements TouchConsumer { mTarget.onQuickScrubProgress(progress); } + @Override + public void onQuickStep(MotionEvent ev) { + mTarget.onQuickStep(ev); + } + + @Override + public void onCommand(int command) { + mTarget.onCommand(command); + } + @Override public void preProcessMotionEvent(MotionEvent ev) { mVelocityTracker.addMovement(ev); @@ -92,6 +102,11 @@ public class DeferredTouchConsumer implements TouchConsumer { return target == null ? true : target.deferNextEventToMainThread(); } + @Override + public void onShowOverviewFromAltTab() { + mTarget.onShowOverviewFromAltTab(); + } + public interface DeferredTouchProvider { TouchConsumer createTouchConsumer(VelocityTracker tracker); diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java index 15f5aa5247..f73be6cba9 100644 --- a/quickstep/src/com/android/quickstep/MotionEventQueue.java +++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java @@ -55,6 +55,8 @@ public class MotionEventQueue { ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT); private static final int ACTION_QUICK_STEP = ACTION_VIRTUAL | (7 << ACTION_POINTER_INDEX_SHIFT); + private static final int ACTION_COMMAND = + ACTION_VIRTUAL | (8 << ACTION_POINTER_INDEX_SHIFT); private final EventArray mEmptyArray = new EventArray(); private final Object mExecutionLock = new Object(); @@ -165,6 +167,9 @@ public class MotionEventQueue { case ACTION_QUICK_STEP: mConsumer.onQuickStep(event); break; + case ACTION_COMMAND: + mConsumer.onCommand(event.getSource()); + break; default: Log.e(TAG, "Invalid virtual event: " + event.getAction()); } @@ -222,6 +227,12 @@ public class MotionEventQueue { queueVirtualAction(ACTION_DEFER_INIT, 0); } + public void onCommand(int command) { + MotionEvent ev = MotionEvent.obtain(0, 0, ACTION_COMMAND, 0, 0, 0); + ev.setSource(command); + queueNoPreProcess(ev); + } + public TouchConsumer getConsumer() { return mConsumer; } diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 9ba3328319..23357ea597 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -37,6 +37,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Looper; import android.os.SystemClock; +import android.util.SparseArray; import android.view.Choreographer; import android.view.Display; import android.view.MotionEvent; @@ -69,6 +70,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150; + private final SparseArray mAnimationStates = new SparseArray<>(); private final RunningTaskInfo mRunningTask; private final RecentsModel mRecentsModel; private final Intent mHomeIntent; @@ -212,8 +214,9 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private void startTouchTrackingForWindowAnimation(long touchTimeMs) { // Create the shared handler + RecentsAnimationState animationState = new RecentsAnimationState(); final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler( - mRunningTask, this, touchTimeMs, mActivityControlHelper); + animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper); // Preload the plan mRecentsModel.loadTasks(mRunningTask.id, null); @@ -237,31 +240,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC public void onHandleAssistData(Bundle bundle) { mRecentsModel.preloadAssistData(mRunningTask.id, bundle); } - }, - new RecentsAnimationListener() { - public void onAnimationStart( - RecentsAnimationControllerCompat controller, - RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, - Rect minimizedHomeBounds) { - if (mInteractionHandler == handler) { - TraceHelper.partitionSection("RecentsController", "Received"); - handler.onRecentsAnimationStart(controller, - new RemoteAnimationTargetSet(apps, MODE_CLOSING), - homeContentInsets, minimizedHomeBounds); - } else { - TraceHelper.endSection("RecentsController", "Finishing no handler"); - controller.finish(false /* toHome */); - } - } - - public void onAnimationCanceled() { - TraceHelper.endSection("RecentsController", - "Cancelled: " + mInteractionHandler); - if (mInteractionHandler == handler) { - handler.onRecentsAnimationCanceled(); - } - } - }, null, null); + }, animationState, null, null); if (Looper.myLooper() != Looper.getMainLooper()) { startActivity.run(); @@ -277,6 +256,14 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC } } + @Override + public void onCommand(int command) { + RecentsAnimationState state = mAnimationStates.get(command); + if (state != null) { + state.execute(); + } + } + /** * Called when the gesture has ended. Does not correlate to the completion of the interaction as * the animation can still be running. @@ -398,4 +385,55 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC // TODO: Consider also check if the eventQueue is using mainThread of not. return mInteractionHandler != null; } + + private class RecentsAnimationState implements RecentsAnimationListener { + + private final int id; + + private RecentsAnimationControllerCompat mController; + private RemoteAnimationTargetSet mTargets; + private Rect mHomeContentInsets; + private Rect mMinimizedHomeBounds; + private boolean mCancelled; + + public RecentsAnimationState() { + id = mAnimationStates.size(); + mAnimationStates.put(id, this); + } + + @Override + public void onAnimationStart( + RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, + Rect minimizedHomeBounds) { + mController = controller; + mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING); + mHomeContentInsets = homeContentInsets; + mMinimizedHomeBounds = minimizedHomeBounds; + mEventQueue.onCommand(id); + } + + @Override + public void onAnimationCanceled() { + mCancelled = true; + mEventQueue.onCommand(id); + } + + public void execute() { + if (mInteractionHandler == null || mInteractionHandler.id != id) { + if (!mCancelled && mController != null) { + TraceHelper.endSection("RecentsController", "Finishing no handler"); + mController.finish(false /* toHome */); + } + } else if (mCancelled) { + TraceHelper.endSection("RecentsController", + "Cancelled: " + mInteractionHandler); + mInteractionHandler.onRecentsAnimationCanceled(); + } else { + TraceHelper.partitionSection("RecentsController", "Received"); + mInteractionHandler.onRecentsAnimationStart(mController, mTargets, + mHomeContentInsets, mMinimizedHomeBounds); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java index 30b10b0eab..34d42ac927 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -20,6 +20,8 @@ import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; + +import java.util.ArrayList; import java.util.concurrent.ExecutorService; /** @@ -27,6 +29,10 @@ import java.util.concurrent.ExecutorService; */ public class RecentsAnimationWrapper { + // A list of callbacks to run when we receive the recents animation target. There are different + // than the state callbacks as these run on the current worker thread. + private final ArrayList mCallbacks = new ArrayList<>(); + public RemoteAnimationTargetSet targetSet; private RecentsAnimationControllerCompat mController; @@ -46,6 +52,21 @@ public class RecentsAnimationWrapper { if (mInputConsumerEnabled) { enableInputConsumer(); } + + if (!mCallbacks.isEmpty()) { + for (Runnable action : new ArrayList<>(mCallbacks)) { + action.run(); + } + mCallbacks.clear(); + } + } + + public synchronized void runOnInit(Runnable action) { + if (targetSet == null) { + mCallbacks.add(action); + } else { + action.run(); + } } /** diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java index aa844d80df..4cecffa2be 100644 --- a/quickstep/src/com/android/quickstep/TouchConsumer.java +++ b/quickstep/src/com/android/quickstep/TouchConsumer.java @@ -48,6 +48,8 @@ public interface TouchConsumer extends Consumer { default void onQuickStep(MotionEvent ev) { } + default void onCommand(int command) { } + /** * Called on the binder thread to allow the consumer to process the motion event before it is * posted on a handler thread. diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index d171f69452..939811bd11 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -29,6 +29,7 @@ import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; +import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; @@ -36,6 +37,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.os.UserHandle; import android.support.annotation.AnyThread; import android.support.annotation.UiThread; import android.support.annotation.WorkerThread; @@ -51,6 +53,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.logging.UserEventDispatcher; @@ -65,11 +68,12 @@ import com.android.quickstep.ActivityControlHelper.AnimationFactory; import com.android.quickstep.ActivityControlHelper.LayoutListener; import com.android.quickstep.TouchConsumer.InteractionType; import com.android.quickstep.util.ClipAnimationHelper; -import com.android.quickstep.util.TransformedRect; import com.android.quickstep.util.RemoteAnimationTargetSet; +import com.android.quickstep.util.TransformedRect; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; @@ -167,6 +171,12 @@ public class WindowTransformSwipeHandler { private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); + // An increasing identifier per single instance of OtherActivityTouchConsumer. Generally one + // instance of OtherActivityTouchConsumer will only have one swipe handle, but sometimes we can + // end up with multiple handlers if we get recents command in the middle of a swipe gesture. + // This is used to match the corresponding activity manager callbacks in + // OtherActivityTouchConsumer + public final int id; private final Context mContext; private final ActivityControlHelper mActivityControlHelper; private final ActivityInitListener mActivityInitListener; @@ -199,6 +209,7 @@ public class WindowTransformSwipeHandler { InputConsumerController.getRecentsAnimationInputConsumer(); private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper(); + private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; @@ -207,8 +218,9 @@ public class WindowTransformSwipeHandler { private float mLongSwipeDisplacement = 0; private LongSwipeHelper mLongSwipeController; - WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, - ActivityControlHelper controller) { + WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context, + long touchTimeMs, ActivityControlHelper controller) { + this.id = id; mContext = context; mRunningTaskInfo = runningTaskInfo; mRunningTaskId = runningTaskInfo.id; @@ -453,6 +465,7 @@ public class WindowTransformSwipeHandler { "Can't change interaction type to " + interactionType); } mInteractionType = interactionType; + mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub); setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); @@ -460,6 +473,34 @@ public class WindowTransformSwipeHandler { animateToProgress(1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR); } + private void shiftAnimationDestinationForQuickscrub() { + TransformedRect tempRect = new TransformedRect(); + mActivityControlHelper + .getSwipeUpDestinationAndLength(mDp, mContext, mInteractionType, tempRect); + mClipAnimationHelper.updateTargetRect(tempRect); + + float offsetY = + mActivityControlHelper.getTranslationYForQuickScrub(tempRect, mDp, mContext); + float scale, offsetX; + Resources res = mContext.getResources(); + + if (ActivityManagerWrapper.getInstance().getRecentTasks(2, UserHandle.myUserId()).size() + < 2) { + // There are not enough tasks, we don't need to shift + offsetX = 0; + scale = 1; + } else { + offsetX = res.getDimensionPixelSize(R.dimen.recents_page_spacing) + + tempRect.rect.width(); + float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 + + res.getDimensionPixelSize(R.dimen.recents_page_spacing); + float interpolation = Math.min(1, offsetX / distanceToReachEdge); + scale = TaskView.getCurveScaleForInterpolation(interpolation); + } + mClipAnimationHelper.offsetTarget(scale, Utilities.isRtl(res) ? -offsetX : offsetX, offsetY, + QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR); + } + @WorkerThread public void updateDisplacement(float displacement) { // We are moving in the negative x/y direction @@ -658,7 +699,7 @@ public class WindowTransformSwipeHandler { : STATE_SCALED_CONTROLLER_APP); } }); - anim.start(); + mRecentsAnimationWrapper.runOnInit(anim::start); } @UiThread @@ -786,30 +827,6 @@ public class WindowTransformSwipeHandler { // Inform the last progress in case we skipped before. mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress); - - // Make sure the window follows the first task if it moves, e.g. during quick scrub. - TaskView firstTask = mRecentsView.getPageAt(0); - // The first task may be null if we are swiping up from a task that does not - // appear in the list (i.e. the assistant) - if (firstTask != null) { - int scrollForFirstTask = mRecentsView.getScrollForPage(0); - int scrollForSecondTask = mRecentsView.getChildCount() > 1 - ? mRecentsView.getScrollForPage(1) : scrollForFirstTask; - float offsetFromFirstTask = scrollForFirstTask - scrollForSecondTask; - - TransformedRect tempRect = new TransformedRect(); - mActivityControlHelper - .getSwipeUpDestinationAndLength(mDp, mContext, mInteractionType, tempRect); - float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 + - mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); - float interpolation = Math.min(1, - Math.abs(offsetFromFirstTask) / distanceToReachEdge); - - mClipAnimationHelper.offsetTarget( - firstTask.getCurveScaleForInterpolation(interpolation), offsetFromFirstTask, - mActivityControlHelper.getTranslationYForQuickScrub(mActivity), - QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR); - } } private void onFinishedTransitionToQuickScrub() { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 02cdd3a7c4..776509c855 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -507,6 +507,7 @@ public abstract class RecentsView extends PagedView impl DeviceProfile dp = mActivity.getDeviceProfile(); getTaskSize(dp, mTempRect); + // Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub. mTempRect.top -= mTaskTopMargin; setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, dp.availableWidthPx + mInsets.left - mTempRect.right, diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 5413a13199..91c0fa5be1 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -250,12 +250,12 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); } - public float getCurveScaleForInterpolation(float linearInterpolation) { + public static float getCurveScaleForInterpolation(float linearInterpolation) { float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation); return getCurveScaleForCurveInterpolation(curveInterpolation); } - private float getCurveScaleForCurveInterpolation(float curveInterpolation) { + private static float getCurveScaleForCurveInterpolation(float curveInterpolation) { return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR; } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 62b581f83e..de9cd986f2 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1450,6 +1450,10 @@ public abstract class PagedView extends ViewGrou protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate, TimeInterpolator interpolator) { + if (mFirstLayout) { + setCurrentPage(whichPage); + return false; + } if (FeatureFlags.IS_DOGFOOD_BUILD) { duration *= Settings.System.getFloat(getContext().getContentResolver(),