1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.quickstep.util; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 21 import android.app.Activity; 22 23 import androidx.annotation.NonNull; 24 25 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter; 26 import com.android.quickstep.RecentsModel; 27 28 /** 29 * This class tracks the failure of a task launch through the TaskView.launchTask() call, in an 30 * edge case in which starting a new task may initially succeed (startActivity returns true), but 31 * the launch ultimately fails if the activity finishes while it is resuming. 32 * 33 * There are two signals this class checks, the launcher lifecycle and the transition completion. 34 * If we hit either of those signals and the task is no longer valid, then the registered failure 35 * callback will be notified. 36 */ 37 public class TaskRemovedDuringLaunchListener implements ActivityLifecycleCallbacksAdapter { 38 39 private Activity mActivity; 40 private int mLaunchedTaskId = INVALID_TASK_ID; 41 private Runnable mTaskLaunchFailedCallback = null; 42 43 /** 44 * Registers a failure listener callback if it detects a scenario in which an app launch 45 * failed before the transition finished. 46 */ register(Activity activity, int launchedTaskId, @NonNull Runnable taskLaunchFailedCallback)47 public void register(Activity activity, int launchedTaskId, 48 @NonNull Runnable taskLaunchFailedCallback) { 49 activity.registerActivityLifecycleCallbacks(this); 50 mActivity = activity; 51 mLaunchedTaskId = launchedTaskId; 52 mTaskLaunchFailedCallback = taskLaunchFailedCallback; 53 } 54 55 /** 56 * Unregisters the failure listener. 57 */ unregister()58 private void unregister() { 59 mActivity.unregisterActivityLifecycleCallbacks(this); 60 mActivity = null; 61 mLaunchedTaskId = INVALID_TASK_ID; 62 mTaskLaunchFailedCallback = null; 63 } 64 65 /** 66 * Called when the transition finishes. 67 */ onTransitionFinished()68 public void onTransitionFinished() { 69 // The transition finished and Launcher was not stopped, check if the launch failed 70 checkTaskLaunchFailed(); 71 } 72 73 @Override onActivityStopped(Activity activity)74 public void onActivityStopped(Activity activity) { 75 // The normal task launch case, Launcher stops and updates its state correctly 76 unregister(); 77 } 78 79 @Override onActivityResumed(Activity activity)80 public void onActivityResumed(Activity activity) { 81 // The transition hasn't finished but Launcher was resumed, check if the launch failed 82 checkTaskLaunchFailed(); 83 } 84 85 @Override onActivityDestroyed(Activity activity)86 public void onActivityDestroyed(Activity activity) { 87 // If we somehow don't get any of the above signals, then just unregister this listener 88 unregister(); 89 } 90 checkTaskLaunchFailed()91 private void checkTaskLaunchFailed() { 92 if (mLaunchedTaskId != INVALID_TASK_ID) { 93 final int launchedTaskId = mLaunchedTaskId; 94 final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback; 95 RecentsModel.INSTANCE.getNoCreate().isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> { 96 if (taskRemoved) { 97 ActiveGestureLog.INSTANCE.addLog("Launch failed, task (id=" + launchedTaskId 98 + ") finished mid transition"); 99 taskLaunchFailedCallback.run(); 100 } 101 }, (task) -> true /* filter */); 102 unregister(); 103 } 104 } 105 } 106