diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java index 1906286030..25e0af29a1 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java @@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW; import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; @@ -45,7 +46,11 @@ public class AllAppsState extends LauncherState { @Override public void onStateEnabled(Launcher launcher) { - AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + AbstractFloatingView.closeAllOpenViews(launcher); + } else { + AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW); + } dispatchWindowStateChanged(launcher); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java index bef3e54435..50af4a1d61 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java @@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; import static com.android.launcher3.Utilities.SINGLE_FRAME_MS; import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -231,6 +232,12 @@ public abstract class TaskViewTouchController mFlingBlockCheck.onEvent(); } mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier); + + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (mRecentsView.getCurrentPage() != 0 || isGoingUp) { + mRecentsView.redrawLiveTile(true); + } + } return true; } @@ -267,6 +274,13 @@ public abstract class TaskViewTouchController anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f); anim.setDuration(animationDuration); anim.setInterpolator(scrollInterpolatorForVelocity(velocity)); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + anim.addUpdateListener(valueAnimator -> { + if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) { + mRecentsView.redrawLiveTile(true); + } + }); + } anim.start(); } diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 861277f3b0..20aabae405 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -148,6 +148,8 @@ public interface ActivityControlHelper { */ int getContainerType(); + boolean isInLiveTileMode(); + class LauncherActivityControllerHelper implements ActivityControlHelper { @Override @@ -440,6 +442,13 @@ public interface ActivityControlHelper { return launcher != null ? launcher.getStateManager().getState().containerType : LauncherLogProto.ContainerType.APP; } + + @Override + public boolean isInLiveTileMode() { + Launcher launcher = getCreatedActivity(); + return launcher != null && launcher.getStateManager().getState() == OVERVIEW && + launcher.isStarted(); + } } class FallbackActivityControllerHelper implements ActivityControlHelper { @@ -625,6 +634,11 @@ public interface ActivityControlHelper { public int getContainerType() { return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER; } + + @Override + public boolean isInLiveTileMode() { + return false; + } } interface LayoutListener { diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java index da5c4fa9d1..db0150ea69 100644 --- a/quickstep/src/com/android/quickstep/QuickScrubController.java +++ b/quickstep/src/com/android/quickstep/QuickScrubController.java @@ -22,6 +22,7 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -124,6 +125,12 @@ public class QuickScrubController implements OnAlarmListener { mActivityControlHelper = controlHelper; mTouchInteractionLog = touchInteractionLog; + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (mRecentsView.getRunningTaskView() != null) { + mRecentsView.getRunningTaskView().setShowScreenshot(false); + } + } + if (mIsQuickSwitch) { mShouldSwitchToNext = true; mPrevProgressDelta = 0; @@ -342,6 +349,7 @@ public class QuickScrubController implements OnAlarmListener { if (action != null) { action.run(); } + mRecentsView.setEnableDrawingLiveTile(true); } public void onTaskRemoved(int taskId) { diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 75ccba6183..442b106fac 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -67,7 +67,6 @@ public class RecentsModel extends TaskStackChangeListener { private float mWindowCornerRadius = -1; - private RecentsModel(Context context) { mContext = context; diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index a42ee094f4..7a6b135cfa 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT; import static android.view.MotionEvent.ACTION_UP; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.systemui.shared.system.ActivityManagerWrapper .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; @@ -252,6 +253,11 @@ public class TouchInteractionService extends Service { mOverviewCommandHelper.getActivityControlHelper().isResumed()) { return OverviewTouchConsumer.newInstance( mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog); + } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && + mOverviewCommandHelper.getActivityControlHelper().isInLiveTileMode()) { + return OverviewTouchConsumer.newInstance( + mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog, + false /* waitForWindowAvailable */); } else { if (tracker == null) { tracker = VelocityTracker.obtain(); @@ -298,9 +304,11 @@ public class TouchInteractionService extends Service { private float mLastProgress = 0; private boolean mStartPending = false; private boolean mEndPending = false; + private boolean mWaitForWindowAvailable; OverviewTouchConsumer(ActivityControlHelper activityHelper, T activity, - boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog, + boolean waitForWindowAvailable) { mActivityHelper = activityHelper; mActivity = activity; mTarget = activity.getDragLayer(); @@ -311,6 +319,8 @@ public class TouchInteractionService extends Service { .getQuickScrubController(); mTouchInteractionLog = touchInteractionLog; mTouchInteractionLog.setTouchConsumer(this); + + mWaitForWindowAvailable = waitForWindowAvailable; } @Override @@ -433,7 +443,11 @@ public class TouchInteractionService extends Service { } }; - mActivityHelper.executeOnWindowAvailable(mActivity, action); + if (mWaitForWindowAvailable) { + mActivityHelper.executeOnWindowAvailable(mActivity, action); + } else { + action.run(); + } } @Override @@ -461,12 +475,19 @@ public class TouchInteractionService extends Service { public static TouchConsumer newInstance(ActivityControlHelper activityHelper, boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { + return newInstance(activityHelper, startingInActivityBounds, touchInteractionLog, + true /* waitForWindowAvailable */); + } + + public static TouchConsumer newInstance(ActivityControlHelper activityHelper, + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog, + boolean waitForWindowAvailable) { BaseDraggingActivity activity = activityHelper.getCreatedActivity(); if (activity == null) { return TouchConsumer.NO_OP; } return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds, - touchInteractionLog); + touchInteractionLog, waitForWindowAvailable); } } } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 0b1e6271ac..610ef0416f 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -22,6 +22,7 @@ import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION; import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION; @@ -347,9 +348,11 @@ public class WindowTransformSwipeHandler { mStateCallback.addCallback(LONG_SWIPE_ENTER_STATE, this::checkLongSwipeCanEnter); mStateCallback.addCallback(LONG_SWIPE_START_STATE, this::checkLongSwipeCanStart); - mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT - | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, - (b) -> mRecentsView.setRunningTaskHidden(!b)); + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT + | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, + (b) -> mRecentsView.setRunningTaskHidden(!b)); + } } private void executeOnUiThread(Runnable action) { @@ -421,6 +424,8 @@ public class WindowTransformSwipeHandler { mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { updateFinalShift(); }); + mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper); + mRecentsView.setClipAnimationHelper(mClipAnimationHelper); mQuickScrubController = mRecentsView.getQuickScrubController(); mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity); @@ -473,6 +478,7 @@ public class WindowTransformSwipeHandler { } private void setupRecentsViewUi() { + mRecentsView.setEnableDrawingLiveTile(false); mRecentsView.showTask(mRunningTaskId); mRecentsView.setRunningTaskHidden(true); mRecentsView.setRunningTaskIconScaledDown(true); @@ -653,6 +659,9 @@ public class WindowTransformSwipeHandler { private void updateFinalShiftUi() { if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mLayoutListener.open(); + } mLayoutListener.update(mCurrentShift.value > 1, mUiLongSwipeMode, mClipAnimationHelper.getCurrentRectWithInsets(), mClipAnimationHelper.getCurrentCornerRadius()); @@ -787,8 +796,10 @@ public class WindowTransformSwipeHandler { if (mLauncherTransitionController != null) { mLauncherTransitionController.getAnimationPlayer().end(); } - // Hide the task view, if not already hidden - setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { + // Hide the task view, if not already hidden + setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + } return OverviewTouchConsumer.newInstance(mActivityControlHelper, true, mTouchInteractionLog); @@ -1018,57 +1029,66 @@ public class WindowTransformSwipeHandler { } public void layoutListenerClosed() { + mRecentsView.setRunningTaskHidden(false); if (mWasLauncherAlreadyVisible && mLauncherTransitionController != null) { mLauncherTransitionController.setPlayFraction(1); } - mRecentsView.setRunningTaskHidden(false); + mRecentsView.setEnableDrawingLiveTile(true); } private void switchToScreenshot() { - boolean finishTransitionPosted = false; - RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController(); - if (controller != null) { - // Update the screenshot of the task - if (mTaskSnapshot == null) { - mTaskSnapshot = controller.screenshotTask(mRunningTaskId); - } - TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); - if (taskView != null) { - // Defer finishing the animation until the next launcher frame with the - // new thumbnail - finishTransitionPosted = new WindowCallbacksCompat(taskView) { - - // The number of frames to defer until we actually finish the animation - private int mDeferFrameCount = 2; - - @Override - public void onPostDraw(Canvas canvas) { - if (mDeferFrameCount > 0) { - mDeferFrameCount--; - // Workaround, detach and reattach to invalidate the root node for - // another draw - detach(); - attach(); - taskView.invalidate(); - return; - } - - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); - detach(); - } - }.attach(); - } - } - if (!finishTransitionPosted) { - // If we haven't posted a draw callback, set the state immediately. + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + } else { + boolean finishTransitionPosted = false; + RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController(); + if (controller != null) { + // Update the screenshot of the task + if (mTaskSnapshot == null) { + mTaskSnapshot = controller.screenshotTask(mRunningTaskId); + } + TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); + if (taskView != null) { + // Defer finishing the animation until the next launcher frame with the + // new thumbnail + finishTransitionPosted = new WindowCallbacksCompat(taskView) { + + // The number of frames to defer until we actually finish the animation + private int mDeferFrameCount = 2; + + @Override + public void onPostDraw(Canvas canvas) { + if (mDeferFrameCount > 0) { + mDeferFrameCount--; + // Workaround, detach and reattach to invalidate the root node for + // another draw + detach(); + attach(); + taskView.invalidate(); + return; + } + + setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + detach(); + } + }.attach(); + } + } + if (!finishTransitionPosted) { + // If we haven't posted a draw callback, set the state immediately. + setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + } } } private void finishCurrentTransitionToHome() { - synchronized (mRecentsAnimationWrapper) { - mRecentsAnimationWrapper.finish(true /* toHome */, - () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else { + synchronized (mRecentsAnimationWrapper) { + mRecentsAnimationWrapper.finish(true /* toHome */, + () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); + } } mTouchInteractionLog.finishRecentsAnimation(true); } @@ -1118,7 +1138,6 @@ public class WindowTransformSwipeHandler { mLauncherTransitionController.getAnimationPlayer().end(); mLauncherTransitionController = null; } - mLayoutListener.finish(); mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false, mTouchInteractionLog); @@ -1131,6 +1150,7 @@ public class WindowTransformSwipeHandler { if (mQuickScrubBlocked) { return; } + mLayoutListener.finish(); mQuickScrubController.onFinishedTransitionToQuickScrub(); mRecentsView.animateUpRunningTaskIconScale(); @@ -1255,7 +1275,9 @@ public class WindowTransformSwipeHandler { mLongSwipeController = mActivityControlHelper.getLongSwipeController( mActivity, mRunningTaskId); onLongSwipeDisplacementUpdated(); - setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { + setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + } } private void onLongSwipeGestureFinishUi(float velocity, boolean isFling, float velocityX) { diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java index 6a8482b59b..ea73e2c774 100644 --- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -73,6 +73,9 @@ public class ClipAnimationHelper { // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In // app window coordinates. private final RectF mSourceWindowClipInsets = new RectF(); + // The insets to be used for clipping the app window. For live tile, we don't transform the clip + // relative to the target rect. + private final RectF mSourceWindowClipInsetsForLiveTile = new RectF(); // The bounds of launcher (not including insets) in device coordinates public final Rect mHomeStackBounds = new Rect(); @@ -144,6 +147,7 @@ public class ClipAnimationHelper { Math.max(scaledTargetRect.top, 0), Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0), Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0)); + mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets); mSourceRect.set(scaledTargetRect); } @@ -152,27 +156,32 @@ public class ClipAnimationHelper { } public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) { - RectF currentRect; - mTmpRectF.set(mTargetRect); - Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale * params.offsetScale); - float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress); - float progress = mInterpolator.getInterpolation(params.progress); - currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF); - currentRect.offset(params.offsetX, 0); + if (params.currentRect == null) { + RectF currentRect; + mTmpRectF.set(mTargetRect); + Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale * params.offsetScale); + float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress); + float progress = mInterpolator.getInterpolation(params.progress); + currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF); + currentRect.offset(params.offsetX, 0); - synchronized (mTargetOffset) { - // Stay lined up with the center of the target, since it moves for quick scrub. - currentRect.offset(mTargetOffset.x * mOffsetScale * progress, - mTargetOffset.y * offsetYProgress); + synchronized (mTargetOffset) { + // Stay lined up with the center of the target, since it moves for quick scrub. + currentRect.offset(mTargetOffset.x * mOffsetScale * progress, + mTargetOffset.y * offsetYProgress); + } + + final RectF sourceWindowClipInsets = params.forLiveTile + ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets; + mClipRectF.left = sourceWindowClipInsets.left * progress; + mClipRectF.top = sourceWindowClipInsets.top * progress; + mClipRectF.right = + mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress); + mClipRectF.bottom = + mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress); + params.setCurrentRectAndTargetAlpha(currentRect, 1); } - mClipRectF.left = mSourceWindowClipInsets.left * progress; - mClipRectF.top = mSourceWindowClipInsets.top * progress; - mClipRectF.right = - mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress); - mClipRectF.bottom = - mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress); - SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length]; for (int i = 0; i < targetSet.unfilteredApps.length; i++) { RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i]; @@ -181,23 +190,17 @@ public class ClipAnimationHelper { float alpha = 1f; int layer; float cornerRadius = 0f; - float scale = currentRect.width() / crop.width(); + float scale = params.currentRect.width() / crop.width(); if (app.mode == targetSet.targetMode) { if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { - mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL); + mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL); mTmpMatrix.postTranslate(app.position.x, app.position.y); mClipRectF.roundOut(crop); - cornerRadius = Utilities.mapRange(progress, mWindowCornerRadius, + cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius, mTaskCornerRadius); mCurrentCornerRadius = cornerRadius; } - - if (app.isNotInRecents - || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { - alpha = 1 - progress; - } - - alpha = mTaskAlphaCallback.apply(app, alpha); + alpha = mTaskAlphaCallback.apply(app, params.targetAlpha); layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers); } else { crop = null; @@ -210,7 +213,7 @@ public class ClipAnimationHelper { cornerRadius / scale); } applySurfaceParams(params.syncTransactionApplier, surfaceParams); - return currentRect; + return params.currentRect; } public RectF getCurrentRectWithInsets() { @@ -356,16 +359,31 @@ public class ClipAnimationHelper { float progress; float offsetX; float offsetScale; + @Nullable RectF currentRect; + float targetAlpha; + boolean forLiveTile; + SyncRtSurfaceTransactionApplierCompat syncTransactionApplier; public TransformParams() { progress = 0; offsetX = 0; offsetScale = 1; + currentRect = null; + targetAlpha = 0; + forLiveTile = false; } public TransformParams setProgress(float progress) { this.progress = progress; + this.currentRect = null; + return this; + } + + public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) { + this.currentRect = currentRect; + this.targetAlpha = targetAlpha; + this.progress = 1; return this; } @@ -379,6 +397,11 @@ public class ClipAnimationHelper { return this; } + public TransformParams setForLiveTile(boolean forLiveTile) { + this.forLiveTile = forLiveTile; + return this; + } + public TransformParams setSyncTransactionApplier( SyncRtSurfaceTransactionApplierCompat applier) { this.syncTransactionApplier = applier; diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java index 6ee305f802..10283bfc52 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java @@ -53,6 +53,7 @@ public class TaskViewDrawable extends Drawable { private final RecentsView mParent; private final View mIconView; private final int[] mIconPos; + private final TaskView mTaskView; private final TaskThumbnailView mThumbnailView; @@ -65,6 +66,7 @@ public class TaskViewDrawable extends Drawable { public TaskViewDrawable(TaskView tv, RecentsView parent) { mParent = parent; + mTaskView = tv; mIconView = tv.getIconView(); mIconPos = new int[2]; mIconScale = mIconView.getScaleX(); @@ -139,4 +141,8 @@ public class TaskViewDrawable extends Drawable { public int getOpacity() { return PixelFormat.TRANSLUCENT; } + + public TaskView getTaskView() { + return mTaskView; + } } diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java index 8ec5361bb4..bcd3aee904 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java +++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; import android.graphics.Canvas; @@ -54,8 +55,8 @@ public class LauncherLayoutListener extends AbstractFloatingView @Override public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect, - float cornerRadius) { - if (shouldFinish) { + float cornerRadius) { + if (!ENABLE_QUICKSTEP_LIVE_TILE.get() && shouldFinish) { finish(); return; } diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 697bb4f1ca..6396d1a09b 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -15,10 +15,12 @@ */ package com.android.quickstep.views; +import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW; import static com.android.launcher3.LauncherAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -32,6 +34,7 @@ import android.util.FloatProperty; import android.view.View; import android.view.ViewDebug; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; @@ -40,6 +43,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.views.ScrimView; import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.util.ClipAnimationHelper; +import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.LayoutUtils; /** @@ -65,6 +69,8 @@ public class LauncherRecentsView extends RecentsView { @ViewDebug.ExportedProperty(category = "launcher") private float mTranslationYFactor; + private final TransformParams mTransformParams = new TransformParams(); + public LauncherRecentsView(Context context) { this(context, null); } @@ -80,7 +86,12 @@ public class LauncherRecentsView extends RecentsView { @Override protected void startHome() { - mActivity.getStateManager().goToState(NORMAL); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + takeScreenshotAndFinishRecentsAnimation(true, + () -> mActivity.getStateManager().goToState(NORMAL)); + } else { + mActivity.getStateManager().goToState(NORMAL); + } } @Override @@ -92,6 +103,9 @@ public class LauncherRecentsView extends RecentsView { public void setTranslationYFactor(float translationFactor) { mTranslationYFactor = translationFactor; setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + redrawLiveTile(false); + } } public float computeTranslationYForFactor(float translationYFactor) { @@ -168,4 +182,45 @@ public class LauncherRecentsView extends RecentsView { public boolean shouldUseMultiWindowTaskSizeStrategy() { return mActivity.isInMultiWindowModeCompat(); } + + @Override + public void scrollTo(int x, int y) { + super.scrollTo(x, y); + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) { + redrawLiveTile(true); + } + } + + @Override + public void redrawLiveTile(boolean mightNeedToRefill) { + AbstractFloatingView layoutListener = AbstractFloatingView.getTopOpenViewWithType( + mActivity, TYPE_QUICKSTEP_PREVIEW); + if (layoutListener != null && layoutListener.isOpen()) { + return; + } + if (mRecentsAnimationWrapper == null || mClipAnimationHelper == null) { + return; + } + TaskView taskView = getRunningTaskView(); + if (taskView != null) { + taskView.getThumbnail().getGlobalVisibleRect(mTempRect); + int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX() + - mTempRect.width()); + int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY() + - mTempRect.height()); + if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) { + mTempRect.right += offsetX; + } + if (mightNeedToRefill && offsetY > 0) { + mTempRect.top -= offsetY; + } + mTempRectF.set(mTempRect); + mTransformParams.setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha()) + .setSyncTransactionApplier(mSyncTransactionApplier); + if (mRecentsAnimationWrapper.targetSet != null) { + mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, + mTransformParams); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index e2a4dda18e..e2e02dda2c 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -21,12 +21,12 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; +import static com.android.quickstep.util.ClipAnimationHelper.TransformParams; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; - import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR; - import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -42,6 +42,7 @@ import android.content.Intent; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; @@ -80,6 +81,7 @@ import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.Themes; import com.android.quickstep.OverviewCallbacks; import com.android.quickstep.QuickScrubController; +import com.android.quickstep.RecentsAnimationWrapper; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; @@ -90,7 +92,10 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.BackgroundExecutor; import com.android.systemui.shared.system.PackageManagerWrapper; +import com.android.systemui.shared.system.RecentsAnimationControllerCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.WindowCallbacksCompat; import java.util.ArrayList; import java.util.function.Consumer; @@ -117,7 +122,14 @@ public abstract class RecentsView extends PagedView impl } }; - private final Rect mTempRect = new Rect(); + protected RecentsAnimationWrapper mRecentsAnimationWrapper; + protected ClipAnimationHelper mClipAnimationHelper; + protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier; + protected int mTaskWidth; + protected int mTaskHeight; + protected boolean mEnableDrawingLiveTile = false; + protected final Rect mTempRect = new Rect(); + protected final RectF mTempRectF = new RectF(); private static final int DISMISS_TASK_DURATION = 300; private static final int ADDITION_TASK_DURATION = 200; @@ -329,6 +341,7 @@ public abstract class RecentsView extends PagedView impl mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); + mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); } @Override @@ -338,6 +351,7 @@ public abstract class RecentsView extends PagedView impl mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); + mSyncTransactionApplier = null; } @Override @@ -552,6 +566,8 @@ public abstract class RecentsView extends PagedView impl mInsets.set(insets); DeviceProfile dp = mActivity.getDeviceProfile(); getTaskSize(dp, mTempRect); + mTaskWidth = mTempRect.width(); + mTaskHeight = mTempRect.height(); // Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub. mTempRect.top -= mTaskTopMargin; @@ -685,11 +701,15 @@ public abstract class RecentsView extends PagedView impl protected abstract void startHome(); public void reset() { + setRunningTaskViewShowScreenshot(false); mRunningTaskId = -1; mRunningTaskTileHidden = false; mIgnoreResetTaskId = -1; mTaskListChangeId = -1; + mRecentsAnimationWrapper = null; + mClipAnimationHelper = null; + unloadVisibleTaskData(); setCurrentPage(0); @@ -761,7 +781,9 @@ public abstract class RecentsView extends PagedView impl setRunningTaskIconScaledDown(false); setRunningTaskHidden(false); + setRunningTaskViewShowScreenshot(true); mRunningTaskId = runningTaskId; + setRunningTaskViewShowScreenshot(false); setRunningTaskIconScaledDown(runningTaskIconScaledDown); setRunningTaskHidden(runningTaskTileHidden); @@ -771,6 +793,15 @@ public abstract class RecentsView extends PagedView impl mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); } + private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + TaskView runningTaskView = getRunningTaskView(); + if (runningTaskView != null) { + runningTaskView.setShowScreenshot(showScreenshot); + } + } + } + public void showNextTask() { TaskView runningTaskView = getRunningTaskView(); if (runningTaskView == null) { @@ -978,26 +1009,40 @@ public abstract class RecentsView extends PagedView impl } mPendingAnimation = pendingAnimation; - mPendingAnimation.addEndListener((onEndListener) -> { - if (onEndListener.isSuccess) { - if (shouldRemoveTask) { - removeTask(taskView.getTask(), draggedIndex, onEndListener, true); - } - int pageToSnapTo = mCurrentPage; - if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount() - 1)) { - pageToSnapTo -= 1; - } - removeView(taskView); + mPendingAnimation.addEndListener(new Consumer() { + @Override + public void accept(PendingAnimation.OnEndListener onEndListener) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && + taskView.isRunningTask() && onEndListener.isSuccess) { + finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener)); + } else { + onEnd(onEndListener); + } + } - if (getTaskViewCount() == 0) { - removeView(mClearAllButton); - startHome(); - } else { - snapToPageImmediately(pageToSnapTo); - } - } - resetTaskVisuals(); - mPendingAnimation = null; + private void onEnd(PendingAnimation.OnEndListener onEndListener) { + if (onEndListener.isSuccess) { + if (shouldRemoveTask) { + removeTask(taskView.getTask(), draggedIndex, onEndListener, true); + } + + int pageToSnapTo = mCurrentPage; + if (draggedIndex < pageToSnapTo || + pageToSnapTo == (getTaskViewCount() - 1)) { + pageToSnapTo -= 1; + } + removeView(taskView); + + if (getTaskViewCount() == 0) { + removeView(mClearAllButton); + startHome(); + } else { + snapToPageImmediately(pageToSnapTo); + } + } + resetTaskVisuals(); + mPendingAnimation = null; + } }); return pendingAnimation; } @@ -1345,20 +1390,38 @@ public abstract class RecentsView extends PagedView impl ObjectAnimator drawableAnim = ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0); drawableAnim.setInterpolator(LINEAR); - drawableAnim.addUpdateListener((animator) -> { - // Once we pass a certain threshold, update the sysui flags to match the target tasks' - // flags - mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, - animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD - ? targetSysUiFlags - : 0); + drawableAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + TransformParams mParams = new TransformParams(); - // Passing the threshold from taskview to fullscreen app will vibrate - final boolean passed = animator.getAnimatedFraction() >= SUCCESS_TRANSITION_PROGRESS; - if (passed != passedOverviewThreshold[0]) { - passedOverviewThreshold[0] = passed; - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + @Override + public void onAnimationUpdate(ValueAnimator animator) { + // Once we pass a certain threshold, update the sysui flags to match the target + // tasks' flags + mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, + animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD + ? targetSysUiFlags + : 0); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (mRecentsAnimationWrapper.targetSet != null + && drawable.getTaskView().isRunningTask()) { + mParams.setProgress(1 - animator.getAnimatedFraction()) + .setSyncTransactionApplier(mSyncTransactionApplier) + .setForLiveTile(true); + drawable.getClipAnimationHelper().applyTransform( + mRecentsAnimationWrapper.targetSet, mParams); + } else { + redrawLiveTile(true); + } + } + + // Passing the threshold from taskview to fullscreen app will vibrate + final boolean passed = animator.getAnimatedFraction() >= + SUCCESS_TRANSITION_PROGRESS; + if (passed != passedOverviewThreshold[0]) { + passedOverviewThreshold[0] = passed; + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } } }); @@ -1457,4 +1520,74 @@ public abstract class RecentsView extends PagedView impl protected boolean isPageOrderFlipped() { return true; } + + public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { + mEnableDrawingLiveTile = enableDrawingLiveTile; + } + + public void redrawLiveTile(boolean mightNeedToRefill) { } + + public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) { + mRecentsAnimationWrapper = recentsAnimationWrapper; + } + + public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) { + mClipAnimationHelper = clipAnimationHelper; + } + + public void finishRecentsAnimation(boolean toHome, Runnable onFinishComplete) { + if (mRecentsAnimationWrapper == null) { + if (onFinishComplete != null) { + onFinishComplete.run(); + } + return; + } + + mRecentsAnimationWrapper.finish(toHome, onFinishComplete); + } + + public void takeScreenshotAndFinishRecentsAnimation(boolean toHome, Runnable onFinishComplete) { + if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) { + if (onFinishComplete != null) { + onFinishComplete.run(); + } + return; + } + + RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController(); + if (controller != null) { + // Update the screenshot of the task + ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId); + TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot); + if (taskView != null) { + taskView.setShowScreenshot(true); + // Defer finishing the animation until the next launcher frame with the + // new thumbnail + new WindowCallbacksCompat(taskView) { + + // The number of frames to defer until we actually finish the animation + private int mDeferFrameCount = 2; + + @Override + public void onPostDraw(Canvas canvas) { + if (mDeferFrameCount > 0) { + mDeferFrameCount--; + // Workaround, detach and reattach to invalidate the root node for + // another draw + detach(); + attach(); + taskView.invalidate(); + return; + } + + detach(); + mRecentsAnimationWrapper.finish(toHome, () -> { + onFinishComplete.run(); + mRunningTaskId = -1; + }); + } + }.attach(); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index 667165bf2e..bea646a556 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; import android.animation.Animator; @@ -206,7 +207,13 @@ public class TaskMenuView extends AbstractFloatingView { R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - menuOptionView.setOnClickListener(onClickListener); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + menuOptionView.setOnClickListener( + view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, + () -> onClickListener.onClick(view))); + } else { + menuOptionView.setOnClickListener(onClickListener); + } mOptionLayout.addView(menuOptionView); } diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index c2403a363c..e5bed543a1 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; import android.content.Context; @@ -29,6 +30,8 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; @@ -76,6 +79,8 @@ public class TaskThumbnailView extends View { private final boolean mIsDarkTextTheme; private final Paint mPaint = new Paint(); private final Paint mBackgroundPaint = new Paint(); + private final Paint mClearPaint = new Paint(); + private final Paint mDimmingPaintAfterClearing = new Paint(); private final Matrix mMatrix = new Matrix(); @@ -105,6 +110,8 @@ public class TaskThumbnailView extends View { mOverlay = TaskOverlayFactory.get(context).createOverlay(this); mPaint.setFilterBitmap(true); mBackgroundPaint.setColor(Color.WHITE); + mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + mDimmingPaintAfterClearing.setColor(Color.BLACK); mActivity = BaseActivity.fromContext(context); mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText); } @@ -213,6 +220,15 @@ public class TaskThumbnailView extends View { public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) { + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint); + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, + mDimmingPaintAfterClearing); + return; + } + } + // Draw the background in all cases, except when the thumbnail data is opaque final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null || mThumbnailData == null; @@ -233,8 +249,13 @@ public class TaskThumbnailView extends View { } } + protected TaskView getTaskView() { + return (TaskView) getParent(); + } + private void updateThumbnailPaintFilter() { int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255); + mDimmingPaintAfterClearing.setAlpha(255 - mul); if (mBitmapShader != null) { ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation); mPaint.setColorFilter(filter); @@ -242,6 +263,7 @@ public class TaskThumbnailView extends View { } else { mPaint.setColorFilter(null); mPaint.setColor(Color.argb(255, mul, mul, mul)); + mBackgroundPaint.setColorFilter(null); } invalidate(); } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index e1e596b301..456a022640 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -21,6 +21,7 @@ import static android.widget.Toast.LENGTH_SHORT; import static com.android.launcher3.BaseActivity.fromContext; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -46,9 +47,11 @@ import android.widget.Toast; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; +import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.launcher3.util.PendingAnimation; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; @@ -88,6 +91,7 @@ public class TaskView extends FrameLayout implements PageCallbacks { public static final long SCALE_ICON_DURATION = 120; private static final long DIM_ANIM_DURATION = 700; + private static final long TASK_LAUNCH_ANIM_DURATION = 200; public static final Property ZOOM_SCALE = new FloatProperty("zoomScale") { @@ -158,6 +162,8 @@ public class TaskView extends FrameLayout implements PageCallbacks { private Animator mIconAndDimAnimator; private float mFocusTransitionProgress = 1; + private boolean mShowScreenshot; + // The current background requests to load the task thumbnail and icon private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest; private TaskIconCache.IconLoadRequest mIconLoadRequest; @@ -176,7 +182,15 @@ public class TaskView extends FrameLayout implements PageCallbacks { if (getTask() == null) { return; } - launchTask(true /* animate */); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (isRunningTask()) { + createLaunchAnimationForRunningTask().start(); + } else { + launchTask(true /* animate */); + } + } else { + launchTask(true /* animate */); + } fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss( Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this), @@ -219,6 +233,19 @@ public class TaskView extends FrameLayout implements PageCallbacks { return mSnapshotView.getTaskOverlay(); } + public AnimatorPlaybackController createLaunchAnimationForRunningTask() { + final PendingAnimation pendingAnimation = + getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION); + pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN); + AnimatorPlaybackController currentAnimation = AnimatorPlaybackController + .wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null); + currentAnimation.setEndAction(() -> { + pendingAnimation.finish(true, Touch.SWIPE); + launchTask(false); + }); + return currentAnimation; + } + public void launchTask(boolean animate) { launchTask(animate, (result) -> { if (!result) { @@ -229,6 +256,21 @@ public class TaskView extends FrameLayout implements PageCallbacks { public void launchTask(boolean animate, Consumer resultCallback, Handler resultCallbackHandler) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + if (isRunningTask()) { + getRecentsView().finishRecentsAnimation(false, + () -> resultCallbackHandler.post(() -> resultCallback.accept(true))); + } else { + getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, + () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler)); + } + } else { + launchTaskInternal(animate, resultCallback, resultCallbackHandler); + } + } + + private void launchTaskInternal(boolean animate, Consumer resultCallback, + Handler resultCallbackHandler) { if (mTask != null) { final ActivityOptions opts; if (animate) { @@ -511,7 +553,7 @@ public class TaskView extends FrameLayout implements PageCallbacks { return super.performAccessibilityAction(action, arguments); } - private RecentsView getRecentsView() { + public RecentsView getRecentsView() { return (RecentsView) getParent(); } @@ -544,4 +586,19 @@ public class TaskView extends FrameLayout implements PageCallbacks { public float getFullscreenProgress() { return mFullscreenProgress; } + + public boolean isRunningTask() { + return this == getRecentsView().getRunningTaskView(); + } + + public void setShowScreenshot(boolean showScreenshot) { + mShowScreenshot = showScreenshot; + } + + public boolean showScreenshot() { + if (!isRunningTask()) { + return true; + } + return mShowScreenshot; + } } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 449cde76f5..1b1b15220b 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -101,6 +101,9 @@ abstract class BaseFlags { public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS", false, "Enable springs for quickstep animations"); + public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag( + "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) {