From 5a763a25c9255b5326324f08b3e43b7e3712cd65 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 29 Oct 2018 16:08:29 -0400 Subject: [PATCH] Support live tile in Overview (Pt2) - Complete functionality - Punch a hole (by erasing part of launcher where current task is rendered) to reveal app surface drawn underneath using surface transform. We use LauncherLayoutListener before reaching OVERVIEW threshold, and TaskView after threshold due to layering constraint (it's above Overview but below All Apps) - Render live tile following user-trigger scrolling (horizontal and vertical) by tracking the task view rect. - When user launches the current running app (through the live tile or icon in the app drawer), finish recents animation to app. - When user launches another app (through Overview or other entry points where user opens an app), take a screenshot of the current running app, switch to screenshot mode and launch the other app. - Refactor ClipAnimationController#ApplyTransform to consolidate transforming by progress and by getting the current rect of the app on the screen. Bug: 111697218 Test: manual test Change-Id: I0ad764399e872f181a9d65dc453f0175f2b58dd1 --- .../launcher3/uioverrides/AllAppsState.java | 7 +- .../uioverrides/TaskViewTouchController.java | 14 ++ .../quickstep/ActivityControlHelper.java | 14 ++ .../quickstep/QuickScrubController.java | 8 + .../com/android/quickstep/RecentsModel.java | 1 - .../quickstep/TouchInteractionService.java | 27 ++- .../WindowTransformSwipeHandler.java | 116 ++++++---- .../quickstep/util/ClipAnimationHelper.java | 81 ++++--- .../quickstep/util/TaskViewDrawable.java | 6 + .../views/LauncherLayoutListener.java | 5 +- .../quickstep/views/LauncherRecentsView.java | 57 ++++- .../android/quickstep/views/RecentsView.java | 203 +++++++++++++++--- .../android/quickstep/views/TaskMenuView.java | 9 +- .../quickstep/views/TaskThumbnailView.java | 22 ++ .../com/android/quickstep/views/TaskView.java | 61 +++++- .../android/launcher3/config/BaseFlags.java | 3 + 16 files changed, 512 insertions(+), 122 deletions(-) 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) {