Merge "Try to detect and handle delayed quickswitch task launch failure" into udc-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index ed78e2d..75cfd05 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -207,6 +207,10 @@
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
mControllers, finalState.disallowTaskbarGlobalDrag(),
disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
+ // LauncherTaskbarUIController depends on the state when checking whether
+ // to handle resume, so it should also be poked if current state changes
+ mLauncher.getTaskbarUIController().onLauncherResumedOrPaused(
+ mLauncher.hasBeenResumed());
}
};
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 913f08f..d798e62 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -145,6 +145,9 @@
* @param filter Returns true if GroupTask should be in the list of considerations
*/
public void isTaskRemoved(int taskId, Consumer<Boolean> callback, Predicate<GroupTask> filter) {
+ // Invalidate the existing list before checking to ensure this reflects the current state in
+ // the system
+ mTaskList.onRecentTasksChanged();
mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
for (GroupTask group : taskGroups) {
if (group.containsTask(taskId)) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 4b1dd43..074aedd 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -79,7 +79,7 @@
}
@Override
- public void startHome() {
+ public void startHome(boolean animated) {
mActivity.startHome();
AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
new file mode 100644
index 0000000..c22e0bc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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 static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
+import com.android.quickstep.RecentsModel;
+
+/**
+ * This class tracks the failure of a task launch through the TaskView.launchTask() call, in an
+ * edge case in which starting a new task may initially succeed (startActivity returns true), but
+ * the launch ultimately fails if the activity finishes while it is resuming.
+ *
+ * There are two signals this class checks, the launcher lifecycle and the transition completion.
+ * If we hit either of those signals and the task is no longer valid, then the registered failure
+ * callback will be notified.
+ */
+public class TaskRemovedDuringLaunchListener implements ActivityLifecycleCallbacksAdapter {
+
+ private Activity mActivity;
+ private int mLaunchedTaskId = INVALID_TASK_ID;
+ private Runnable mTaskLaunchFailedCallback = null;
+
+ /**
+ * Registers a failure listener callback if it detects a scenario in which an app launch
+ * failed before the transition finished.
+ */
+ public void register(Activity activity, int launchedTaskId,
+ @NonNull Runnable taskLaunchFailedCallback) {
+ activity.registerActivityLifecycleCallbacks(this);
+ mActivity = activity;
+ mLaunchedTaskId = launchedTaskId;
+ mTaskLaunchFailedCallback = taskLaunchFailedCallback;
+ }
+
+ /**
+ * Unregisters the failure listener.
+ */
+ private void unregister() {
+ mActivity.unregisterActivityLifecycleCallbacks(this);
+ mActivity = null;
+ mLaunchedTaskId = INVALID_TASK_ID;
+ mTaskLaunchFailedCallback = null;
+ }
+
+ /**
+ * Called when the transition finishes.
+ */
+ public void onTransitionFinished() {
+ // The transition finished and Launcher was not stopped, check if the launch failed
+ checkTaskLaunchFailed();
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ // The normal task launch case, Launcher stops and updates its state correctly
+ unregister();
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ // The transition hasn't finished but Launcher was resumed, check if the launch failed
+ checkTaskLaunchFailed();
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ // If we somehow don't get any of the above signals, then just unregister this listener
+ unregister();
+ }
+
+ private void checkTaskLaunchFailed() {
+ if (mLaunchedTaskId != INVALID_TASK_ID) {
+ final int launchedTaskId = mLaunchedTaskId;
+ final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback;
+ RecentsModel.INSTANCE.getNoCreate().isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
+ if (taskRemoved) {
+ ActiveGestureLog.INSTANCE.addLog("Launch failed, task (id=" + launchedTaskId
+ + ") finished mid transition");
+ taskLaunchFailedCallback.run();
+ }
+ }, (task) -> true /* filter */);
+ unregister();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 379722b..1cfaf14 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -321,7 +321,7 @@
}
@Override
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
launchTasks();
callback.accept(true);
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 5bfd035..c6c84bd 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -237,9 +237,9 @@
}
@Override
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id,
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback, isQuickswitch,
getSplitRatio());
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 8ff0e9b..2008129 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.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.PendingSplitSelectInfo;
@@ -79,9 +80,11 @@
}
@Override
- public void startHome() {
- mActivity.getStateManager().goToState(NORMAL);
- AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
+ public void startHome(boolean animated) {
+ StateManager stateManager = mActivity.getStateManager();
+ animated &= stateManager.shouldAnimateStateChange();
+ stateManager.goToState(NORMAL, animated);
+ AbstractFloatingView.closeAllOpenViews(mActivity, animated);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f0afa69..ff5af28 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2285,7 +2285,11 @@
}
}
- public abstract void startHome();
+ public void startHome() {
+ startHome(mActivity.isStarted());
+ }
+
+ public abstract void startHome(boolean animated);
public void reset() {
setCurrentTask(-1);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 53660b5..796cd62 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -102,6 +102,7 @@
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.util.TaskRemovedDuringLaunchListener;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -815,23 +816,44 @@
* Starts the task associated with this view without any animation
*/
public void launchTask(@NonNull Consumer<Boolean> callback) {
- launchTask(callback, false /* freezeTaskList */);
+ launchTask(callback, false /* isQuickswitch */);
}
/**
* Starts the task associated with this view without any animation
*/
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
if (mTask != null) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
+ final TaskRemovedDuringLaunchListener
+ failureListener = new TaskRemovedDuringLaunchListener();
+ if (isQuickswitch) {
+ // We only listen for failures to launch in quickswitch because the during this
+ // gesture launcher is in the background state, vs other launches which are in
+ // the actual overview state
+ failureListener.register(mActivity, mTask.key.id, () -> {
+ notifyTaskLaunchFailed(TAG);
+ // Disable animations for now, as it is an edge case and the app usually covers
+ // launcher and also any state transition animation also gets clobbered by
+ // QuickstepTransitionManager.createWallpaperOpenAnimations when launcher
+ // shows again
+ getRecentsView().startHome(false /* animated */);
+ });
+ }
// Indicate success once the system has indicated that the transition has started
- ActivityOptions opts = makeCustomAnimation(getContext(), 0, 0,
- () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
+ ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(getContext(), 0, 0,
+ MAIN_EXECUTOR.getHandler(),
+ elapsedRealTime -> {
+ callback.accept(true);
+ },
+ elapsedRealTime -> {
+ failureListener.onTransitionFinished();
+ });
opts.setLaunchDisplayId(
getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
- if (freezeTaskList) {
+ if (isQuickswitch) {
opts.setFreezeRecentTasksReordering();
}
opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView());