Merge "Try to detect and handle delayed quickswitch task launch failure" into udc-dev

This commit is contained in:
Winson Chung
2023-05-06 07:12:18 +00:00
committed by Android (Google) Code Review
9 changed files with 154 additions and 13 deletions
@@ -207,6 +207,10 @@ public class TaskbarLauncherStateController {
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());
}
};
@@ -145,6 +145,9 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* @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)) {
@@ -79,7 +79,7 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
}
@Override
public void startHome() {
public void startHome(boolean animated) {
mActivity.startHome();
AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
}
@@ -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();
}
}
}
@@ -321,7 +321,7 @@ public class DesktopTaskView extends TaskView {
}
@Override
public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
launchTasks();
callback.accept(true);
}
@@ -237,9 +237,9 @@ public class GroupedTaskView extends TaskView {
}
@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());
}
@@ -39,6 +39,7 @@ import com.android.launcher3.LauncherState;
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 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
}
@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
@@ -2285,7 +2285,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
}
public abstract void startHome();
public void startHome() {
startHome(mActivity.isStarted());
}
public abstract void startHome(boolean animated);
public void reset() {
setCurrentTask(-1);
@@ -102,6 +102,7 @@ import com.android.quickstep.util.CancellableTask;
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 @@ public class TaskView extends FrameLayout implements Reusable {
* 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());