diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 95d6dd02d5..e21dcbadf2 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -20,6 +20,8 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NO_OFFSET; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; @@ -70,6 +72,7 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.NavigationMode; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ObjectWrapper; +import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.UiThreadHelper; @@ -91,10 +94,10 @@ import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.unfold.UnfoldTransitionFactory; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; +import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig; import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider; import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider; -import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -130,9 +133,19 @@ public abstract class BaseQuickstepLauncher extends Launcher { private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider; private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController; + /** + * If Launcher restarted while in the middle of an Overview split select, it needs this data to + * recover. In all other cases this will remain null. + */ + private PendingSplitSelectInfo mPendingSplitSelectInfo = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mPendingSplitSelectInfo = ObjectWrapper.unwrap( + savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO)); + } addMultiWindowModeChangedListener(mDepthController); initUnfoldTransitionProgressProvider(); } @@ -643,4 +656,53 @@ public abstract class BaseQuickstepLauncher extends Launcher { mDepthController.dump(prefix, writer); } } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + // If Launcher shuts downs during split select, we save some extra data in the recovery + // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't + // work in this case because restoring straight to OverviewSplitSelect without staging data, + // or before the tasks themselves have loaded into Overview, causes a crash. So we tell + // Launcher to first restore into Overview state, wait for the relevant tasks and icons to + // load in, and then proceed to OverviewSplitSelect. + if (isInState(OVERVIEW_SPLIT_SELECT)) { + SplitSelectStateController splitSelectStateController = + ((RecentsView) getOverviewPanel()).getSplitPlaceholder(); + // Launcher will restart in Overview and then transition to OverviewSplitSelect. + outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap( + new PendingSplitSelectInfo( + splitSelectStateController.getInitialTaskId(), + splitSelectStateController.getActiveSplitStagePosition() + ) + )); + outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal); + } + } + + /** + * When Launcher restarts, it sometimes needs to recover to a split selection state. + * This function checks if such a recovery is needed. + * @return a boolean representing whether the launcher is waiting to recover to + * OverviewSplitSelect state. + */ + public boolean hasPendingSplitSelectInfo() { + return mPendingSplitSelectInfo != null; + } + + /** + * See {@link #hasPendingSplitSelectInfo()} + */ + public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() { + return mPendingSplitSelectInfo; + } + + /** + * When the launcher has successfully recovered to OverviewSplitSelect state, this function + * deletes the recovery data, returning it to a null state. + */ + public void finishSplitSelectRecovery() { + mPendingSplitSelectInfo = null; + } } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 6b82b32faf..3074dbb0b2 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -36,6 +36,7 @@ import com.android.launcher3.icons.IconProvider.IconChangeListener; import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -54,7 +55,8 @@ import java.util.function.Consumer; * Singleton class to load and manage recents model. */ @TargetApi(Build.VERSION_CODES.O) -public class RecentsModel implements IconChangeListener, TaskStackChangeListener { +public class RecentsModel implements IconChangeListener, TaskStackChangeListener, + TaskVisualsChangeListener { // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = @@ -77,6 +79,7 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener IconProvider iconProvider = new IconProvider(context); mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider); + mIconCache.registerTaskVisualsChangeListener(this); mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR); TaskStackChangeListeners.getInstance().registerTaskStackListener(this); @@ -203,6 +206,13 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener } } + @Override + public void onTaskIconChanged(int taskId) { + for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) { + listener.onTaskIconChanged(taskId); + } + } + @Override public void onSystemIconStateChanged(String iconState) { mIconCache.clearCache(); @@ -227,22 +237,6 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener mTaskList.dump(" ", writer); } - /** - * Listener for receiving various task properties changes - */ - public interface TaskVisualsChangeListener { - - /** - * Called whn the task thumbnail changes - */ - Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); - - /** - * Called when the icon for a task changes - */ - void onTaskIconChanged(String pkg, UserHandle user); - } - /** * Registers a listener for running tasks */ diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 3803f0350b..9ca5fc5fb7 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.content.Context; @@ -46,6 +47,7 @@ import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.TaskKeyLruCache; +import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.PackageManagerWrapper; @@ -70,6 +72,9 @@ public class TaskIconCache implements DisplayInfoChangeListener { private BaseIconFactory mIconFactory; + @Nullable + public TaskVisualsChangeListener mTaskVisualsChangeListener = null; + public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) { mContext = context; mBgExecutor = bgExecutor; @@ -116,6 +121,7 @@ public class TaskIconCache implements DisplayInfoChangeListener { task.icon = result.icon; task.titleDescription = result.contentDescription; callback.accept(task); + dispatchIconUpdate(task.key.id); } }; mBgExecutor.execute(request); @@ -272,4 +278,18 @@ public class TaskIconCache implements DisplayInfoChangeListener { public Drawable icon; public String contentDescription = ""; } + + void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) { + mTaskVisualsChangeListener = newListener; + } + + void removeTaskVisualsChangeListener() { + mTaskVisualsChangeListener = null; + } + + void dispatchIconUpdate(int taskId) { + if (mTaskVisualsChangeListener != null) { + mTaskVisualsChangeListener.onTaskIconChanged(taskId); + } + } } diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 250235925e..f1189c9146 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -330,4 +330,8 @@ public class SplitSelectStateController { private boolean isInitialTaskIntentSet() { return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null); } + + public int getInitialTaskId() { + return mInitialTaskId; + } } diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java new file mode 100644 index 0000000000..66bff730bf --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.util; + +import android.os.UserHandle; + +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; + +/** + * Listener for receiving various task properties changes + */ +public interface TaskVisualsChangeListener { + + /** + * Called when the task thumbnail changes + */ + default Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + return null; + } + + /** + * Called when the icon for a task changes + */ + default void onTaskIconChanged(String pkg, UserHandle user) {} + + /** + * Called when the icon for a task changes + */ + default void onTaskIconChanged(int taskId) {} +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 306ebd73c8..a736583471 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -39,6 +39,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.popup.QuickstepSystemShortcut; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.util.PendingSplitSelectInfo; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.util.SplitSelectStateController; @@ -88,6 +89,21 @@ public class LauncherRecentsView extends RecentsView protected static final int REQUEST_LAST = 100; // Type: int - private static final String RUNTIME_STATE = "launcher.state"; + protected static final String RUNTIME_STATE = "launcher.state"; // Type: PendingRequestArgs private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args"; // Type: int @@ -278,6 +277,9 @@ public class Launcher extends StatefulActivity // Type int[] private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids"; + // Type PendingSplitSelectInfo + protected static final String PENDING_SPLIT_SELECT_INFO = "launcher.pending_split_select_info"; + public static final String ON_CREATE_EVT = "Launcher.onCreate"; public static final String ON_START_EVT = "Launcher.onStart"; public static final String ON_RESUME_EVT = "Launcher.onResume"; diff --git a/src/com/android/launcher3/util/PendingSplitSelectInfo.java b/src/com/android/launcher3/util/PendingSplitSelectInfo.java new file mode 100644 index 0000000000..ed024655e1 --- /dev/null +++ b/src/com/android/launcher3/util/PendingSplitSelectInfo.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; + +/** + * Utility class to store information regarding a split select request. This includes the taskId of + * the originating task, plus the stage position. + * This information is intended to be saved across launcher instances, e.g. when Launcher needs to + * recover straight into a split select state. + */ +public class PendingSplitSelectInfo { + + private final int mStagedTaskId; + private final int mStagePosition; + + public PendingSplitSelectInfo(int stagedTaskId, int stagePosition) { + this.mStagedTaskId = stagedTaskId; + this.mStagePosition = stagePosition; + } + + public int getStagedTaskId() { + return mStagedTaskId; + } + + public @StagePosition int getStagePosition() { + return mStagePosition; + } +}