diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index e4a8f36497..307345a30a 100644 --- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -106,6 +106,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag private DeviceProfile mDeviceProfile; private View mFloatingView; + private RemoteAnimationRunnerCompat mRemoteAnimationOverride; + private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -175,6 +177,10 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag return getDefaultActivityLaunchOptions(launcher, v); } + public void setRemoteAnimationOverride(RemoteAnimationRunnerCompat remoteAnimationOverride) { + mRemoteAnimationOverride = remoteAnimationOverride; + } + /** * Try to find a TaskView that corresponds with the component of the launched view. * @@ -635,6 +641,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag * Registers remote animations used when closing apps to home screen. */ private void registerRemoteAnimations() { + // Unregister this if (hasControlRemoteAppTransitionPermission()) { try { RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); @@ -669,12 +676,36 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag private RemoteAnimationRunnerCompat getWallpaperOpenRunner() { return new LauncherAnimationRunner(mHandler) { @Override - public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) { - if (mLauncher.getStateManager().getState().overviewUi) { - // We use a separate transition for Overview mode. - return null; + public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, + Runnable runnable) { + if (mLauncher.getStateManager().getState().overviewUi + && mRemoteAnimationOverride != null) { + // This transition is only used for the fallback activity and should not be + // managed here (but necessary to implement here since the defined remote + // animation currently takes precendence over the one defined in the activity + // options). + mRemoteAnimationOverride.onAnimationStart(targetCompats, runnable); + return; } + super.onAnimationStart(targetCompats, runnable); + } + @Override + public void onAnimationCancelled() { + if (mLauncher.getStateManager().getState().overviewUi + && mRemoteAnimationOverride != null) { + // This transition is only used for the fallback activity and should not be + // managed here (but necessary to implement here since the defined remote + // animation currently takes precendence over the one defined in the activity + // options). + mRemoteAnimationOverride.onAnimationCancelled(); + return; + } + super.onAnimationCancelled(); + } + + @Override + public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) { AnimatorSet anim = new AnimatorSet(); anim.play(getClosingWindowAnimators(targetCompats)); diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 4e3528c84d..3e96c4452e 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -36,8 +36,10 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppTransitionManagerImpl; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; +import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.util.ViewOnDrawExecutor; @@ -80,7 +82,10 @@ public interface ActivityControlHelper { ActivityInitListener createActivityInitListener(BiPredicate onInitListener); - void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver, + void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver, + final RecentsAnimationListener remoteAnimationListener); + + void startRecentsFromButton(Context context, Intent intent, RecentsAnimationListener remoteAnimationListener); @UiThread @@ -203,21 +208,44 @@ public interface ActivityControlHelper { } @Override - public void startRecents(Context context, Intent intent, - AssistDataReceiver assistDataReceiver, - RecentsAnimationListener remoteAnimationListener) { + public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver, + final RecentsAnimationListener remoteAnimationListener) { ActivityManagerWrapper.getInstance().startRecentsActivity( intent, assistDataReceiver, remoteAnimationListener, null, null); } + @Override + public void startRecentsFromButton(Context context, Intent intent, + RecentsAnimationListener remoteAnimationListener) { + // We should use the remove animation for the fallback activity recents button case, + // it works better with PiP. In Launcher, we have already registered the remote + // animation definition, which takes priority over explicitly defined remote + // animations in the provided activity options when starting the activity, so we + // just register a remote animation factory to get a callback to handle this. + LauncherAppTransitionManagerImpl appTransitionManager = + (LauncherAppTransitionManagerImpl) getLauncher().getAppTransitionManager(); + appTransitionManager.setRemoteAnimationOverride(new RecentsAnimationActivityOptions( + remoteAnimationListener, () -> { + // Once the controller is finished, also reset the remote animation override + appTransitionManager.setRemoteAnimationOverride(null); + })); + context.startActivity(intent); + } + + @Nullable + @UiThread + private Launcher getLauncher() { + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app == null) { + return null; + } + return (Launcher) app.getModel().getCallback(); + } + @Nullable @UiThread private Launcher getVisibleLaucher() { - LauncherAppState app = LauncherAppState.getInstanceNoCreate(); - if (app == null) { - return null; - } - Launcher launcher = (Launcher) app.getModel().getCallback(); + Launcher launcher = getLauncher(); return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ? launcher : null; } @@ -325,12 +353,23 @@ public interface ActivityControlHelper { } @Override - public void startRecents(Context context, Intent intent, - AssistDataReceiver assistDataReceiver, + public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver, final RecentsAnimationListener remoteAnimationListener) { - ActivityOptions options = - ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat( - new FallbackActivityOptions(remoteAnimationListener), 10000, 10000)); + // We can use the normal recents animation for swipe up + ActivityManagerWrapper.getInstance().startRecentsActivity( + intent, assistDataReceiver, remoteAnimationListener, null, null); + } + + @Override + public void startRecentsFromButton(Context context, Intent intent, + RecentsAnimationListener remoteAnimationListener) { + // We should use the remove animation for the fallback activity recents button case, + // it works better with PiP. For the fallback activity, we should not have registered + // the launcher app transition manager, so we should just start the remote animation here. + ActivityOptions options = ActivityOptionsCompat.makeRemoteAnimation( + new RemoteAnimationAdapterCompat( + new RecentsAnimationActivityOptions(remoteAnimationListener, null), + 10000, 10000)); context.startActivity(intent, options.toBundle()); } diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 0ab2df7e4c..4d695de907 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -218,7 +218,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC handler.initWhenReady(); TraceHelper.beginSection("RecentsController"); - Runnable startActivity = () -> mActivityControlHelper.startRecents(this, mHomeIntent, + Runnable startActivity = () -> mActivityControlHelper.startRecentsFromSwipe(mHomeIntent, new AssistDataReceiver() { @Override public void onHandleAssistData(Bundle bundle) { diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 985a36490e..8e59578a23 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -53,6 +53,7 @@ import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHel import com.android.quickstep.util.SysuiEventLogger; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; import com.android.systemui.shared.recents.view.RecentsTransition; @@ -77,7 +78,7 @@ public class OverviewCommandHelper extends InternalStateHandler { private static final int RID_CANCEL_CONTROLLER = 1; private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2; - private static final long RECENTS_LAUNCH_DURATION = 150; + private static final long RECENTS_LAUNCH_DURATION = 200; private static final String TAG = "OverviewCommandHelper"; private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false; @@ -151,12 +152,13 @@ public class OverviewCommandHelper extends InternalStateHandler { private void initSwipeHandler(ActivityControlHelper helper, long time, Consumer onAnimationInitCallback) { final int commandId = mCurrentCommandId; - RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask(); + final RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask(); + final int runningTaskId = runningTask.id; final WindowTransformSwipeHandler handler = - new WindowTransformSwipeHandler(taskInfo, mContext, time, helper); + new WindowTransformSwipeHandler(runningTask, mContext, time, helper); // Preload the plan - mRecentsModel.loadTasks(taskInfo.id, null); + mRecentsModel.loadTasks(runningTaskId, null); mWindowTransformSwipeHandler = handler; mTempTaskTargetRect.setEmpty(); @@ -172,13 +174,8 @@ public class OverviewCommandHelper extends InternalStateHandler { addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset); TraceHelper.beginSection(TAG); - Runnable startActivity = () -> helper.startRecents(mContext, homeIntent, - new AssistDataReceiver() { - @Override - public void onHandleAssistData(Bundle bundle) { - mRecentsModel.preloadAssistData(taskInfo.id, bundle); - } - }, + Runnable startActivity = () -> helper.startRecentsFromButton(mContext, + addToIntent(homeIntent), new RecentsAnimationListener() { public void onAnimationStart( RecentsAnimationControllerCompat controller, @@ -190,11 +187,17 @@ public class OverviewCommandHelper extends InternalStateHandler { minimizedHomeBounds); mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize)); + ThumbnailData thumbnail = mAM.getTaskThumbnail(runningTaskId, + true /* reducedResolution */); mMainThreadExecutor.execute(() -> { addFinishCommand(commandId, RID_CANCEL_CONTROLLER, () -> controller.finish(true)); if (commandId == mCurrentCommandId) { onAnimationInitCallback.accept(handler); + + // The animation has started, which means the other activity + // should be paused, lets update the thumbnail + handler.switchToScreenshotImmediate(thumbnail); } }); } else { @@ -230,7 +233,7 @@ public class OverviewCommandHelper extends InternalStateHandler { }); handler.onGestureStarted(); anim.setDuration(RECENTS_LAUNCH_DURATION); - anim.setInterpolator(Interpolators.AGGRESSIVE_EASE); + anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); anim.start(); addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel); } @@ -241,6 +244,7 @@ public class OverviewCommandHelper extends InternalStateHandler { return; } + ActivityManagerWrapper.getInstance().closeSystemWindows("recentapps"); long time = SystemClock.elapsedRealtime(); mMainThreadExecutor.execute(() -> { long elapsedTime = time - mLastToggleTime; @@ -248,40 +252,37 @@ public class OverviewCommandHelper extends InternalStateHandler { mCurrentCommandId++; mTempTaskTargetRect.round(mTaskTargetRect); - boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); int runnableCount = mCurrentCommandFinishRunnables.size(); if (runnableCount > 0) { for (int i = 0; i < runnableCount; i++) { mCurrentCommandFinishRunnables.valueAt(i).run(); } mCurrentCommandFinishRunnables.clear(); - isQuickTap = true; } + // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows + // the menu activity which takes window focus, prevening the right condition from + // being run below ActivityControlHelper helper = getActivityControlHelper(); RecentsView recents = helper.getVisibleRecentsView(); if (recents != null) { - int childCount = recents.getChildCount(); - if (childCount != 0) { - ((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true); + // Launch the next task + recents.showNextTask(); + } else { + if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) { + // The user tried to launch back into overview too quickly, either after + // launching an app, or before overview has actually shown, just ignore for now + return; } - // There are not enough tasks. Skip - return; + // Start overview + if (helper.switchToRecentsIfVisible()) { + SysuiEventLogger.writeDummyRecentsTransition(0); + // Do nothing + } else { + initSwipeHandler(helper, time, this::startZoomOutAnim); + } } - - if (isQuickTap) { - // Focus last task. Start is on background thread so that all ActivityManager calls - // are serialized - BackgroundExecutor.get().submit(this::startLastTask); - return; - } - if (helper.switchToRecentsIfVisible()) { - SysuiEventLogger.writeDummyRecentsTransition(0); - return; - } - - initSwipeHandler(helper, time, this::startZoomOutAnim); }); } @@ -362,10 +363,6 @@ public class OverviewCommandHelper extends InternalStateHandler { return false; } - public boolean isUsingFallbackActivity() { - return DEBUG_START_FALLBACK_ACTIVITY; - } - public ActivityControlHelper getActivityControlHelper() { if (DEBUG_START_FALLBACK_ACTIVITY) { return new FallbackActivityControllerHelper(); diff --git a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java similarity index 71% rename from quickstep/src/com/android/quickstep/FallbackActivityOptions.java rename to quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java index 04352c390b..a25e192dc2 100644 --- a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java @@ -24,24 +24,33 @@ import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.TransactionCompat; import com.android.systemui.shared.system.WindowManagerWrapper; +import java.util.function.Consumer; /** - * Temporary class to create activity options to emulate recents transition for fallback activtiy. + * Class to create activity options to emulate recents transition. */ -public class FallbackActivityOptions implements RemoteAnimationRunnerCompat { +public class RecentsAnimationActivityOptions implements RemoteAnimationRunnerCompat { private final RecentsAnimationListener mListener; + private final Runnable mFinishCallback; - public FallbackActivityOptions(RecentsAnimationListener listener) { + public RecentsAnimationActivityOptions(RecentsAnimationListener listener, + Runnable finishCallback) { mListener = listener; + mFinishCallback = finishCallback; } @Override public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) { showOpeningTarget(targetCompats); - DummyRecentsAnimationControllerCompat dummyRecentsAnim = - new DummyRecentsAnimationControllerCompat(runnable); + RemoteRecentsAnimationControllerCompat dummyRecentsAnim = + new RemoteRecentsAnimationControllerCompat(() -> { + runnable.run(); + if (mFinishCallback != null) { + mFinishCallback.run(); + } + }); Rect insets = new Rect(); WindowManagerWrapper.getInstance().getStableInsets(insets); @@ -54,23 +63,22 @@ public class FallbackActivityOptions implements RemoteAnimationRunnerCompat { } private void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) { + TransactionCompat t = new TransactionCompat(); for (RemoteAnimationTargetCompat target : targetCompats) { - TransactionCompat t = new TransactionCompat(); int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING ? Integer.MAX_VALUE : target.prefixOrderIndex; t.setLayer(target.leash, layer); t.show(target.leash); - t.apply(); } + t.apply(); } - private static class DummyRecentsAnimationControllerCompat - extends RecentsAnimationControllerCompat { + private class RemoteRecentsAnimationControllerCompat extends RecentsAnimationControllerCompat { final Runnable mFinishCallback; - public DummyRecentsAnimationControllerCompat(Runnable finishCallback) { + public RemoteRecentsAnimationControllerCompat(Runnable finishCallback) { mFinishCallback = finishCallback; } @@ -87,7 +95,8 @@ public class FallbackActivityOptions implements RemoteAnimationRunnerCompat { @Override public void finish(boolean toHome) { - if (toHome) { + // This should never be called with toHome == false + if (mFinishCallback != null) { mFinishCallback.run(); } } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 1e43202a1f..4652f2d656 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -158,6 +158,16 @@ public class RecentsModel extends TaskStackChangeListener { return requestId; } + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + mTaskChangeId++; + } + + @Override + public void onActivityUnpinned() { + mTaskChangeId++; + } + @Override public void onTaskStackChanged() { mTaskChangeId++; diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 36a9d56496..975c62ba49 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -196,6 +196,10 @@ public class WindowTransformSwipeHandler { private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; + // Only used with the recents activity, when the screenshot should be fetched at the beginning + // of the animation and not at the end when the activity is already paused + private boolean mSkipScreenshotAtEndOfTransition; + WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, ActivityControlHelper controller) { mContext = context; @@ -582,7 +586,6 @@ public class WindowTransformSwipeHandler { mSourceStackBounds.set(target.sourceContainerBounds); initTransitionEndpoints(dp); - break; } } } @@ -725,8 +728,9 @@ public class WindowTransformSwipeHandler { () -> setStateOnUiThread(STATE_SWITCH_TO_SCREENSHOT_COMPLETE)); } }; + synchronized (mRecentsAnimationWrapper) { - if (mRecentsAnimationWrapper.controller != null) { + if (mRecentsAnimationWrapper.controller != null && !mSkipScreenshotAtEndOfTransition) { TransactionCompat transaction = new TransactionCompat(); for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) { if (app.mode == MODE_CLOSING) { @@ -753,6 +757,12 @@ public class WindowTransformSwipeHandler { doLogGesture(true /* toLauncher */); } + @UiThread + public void switchToScreenshotImmediate(ThumbnailData thumbnail) { + mRecentsView.updateThumbnail(mRunningTaskId, thumbnail); + mSkipScreenshotAtEndOfTransition = true; + } + private void setupLauncherUiAfterSwipeUpAnimation() { if (mLauncherTransitionController != null) { mLauncherTransitionController.getAnimationPlayer().end(); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index a791861938..d95619c055 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -138,6 +138,26 @@ public abstract class RecentsView } } } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + // Check this is for the right user + if (!checkCurrentUserId(userId, false /* debug */)) { + return; + } + + // Remove the task immediately from the task list + TaskView taskView = getTaskView(taskId); + if (taskView != null) { + removeView(taskView); + } + } + + @Override + public void onActivityUnpinned() { + // TODO: Re-enable layout transitions for addition of the unpinned task + reloadIfNeeded(); + } }; private int mLoadPlanId = -1; @@ -595,6 +615,23 @@ public abstract class RecentsView } } + public void showNextTask() { + TaskView runningTaskView = getTaskView(mRunningTaskId); + if (runningTaskView == null) { + // Launch the first task + if (getChildCount() > 0) { + ((TaskView) getChildAt(0)).launchTask(true /* animate */); + } + } else { + // Get the next launch task + int runningTaskIndex = indexOfChild(runningTaskView); + int nextTaskIndex = Math.max(0, Math.min(getChildCount() - 1, runningTaskIndex + 1)); + if (nextTaskIndex < getChildCount()) { + ((TaskView) getChildAt(nextTaskIndex)).launchTask(true /* animate */); + } + } + } + public QuickScrubController getQuickScrubController() { return mQuickScrubController; }