Merge "Try to detect and handle delayed quickswitch task launch failure" into udc-dev
This commit is contained in:
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user