diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 9561de09971be..0e04c426ce1e2 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -1108,6 +1108,11 @@ public class AppTransitionController { // the same transition. for (int i = rootTasks.size() - 1; i >= 0; i--) { final Task rootTask = rootTasks.valueAt(i); + if (rootTask == null) { + // It is possible that one activity may have been removed from the hierarchy. No + // need to check for this case. + continue; + } final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> { if (!taskFragment.isReadyToTransit()) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s", diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 5fa76bb2e25b3..82140f4d965ce 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -922,6 +922,40 @@ public class AppTransitionControllerTest extends WindowTestsBase { verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any()); } + @Test + public void testTransitionGoodToGoForTaskFragments_detachedApp() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = createTask(mDisplayContent); + final TaskFragment changeTaskFragment = + createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + changeTaskFragment.getTopMostActivity().allDrawn = true; + // To make sure that having a detached activity won't cause any issue. + final ActivityRecord detachedActivity = createActivityRecord(task); + detachedActivity.removeImmediately(); + assertNull(detachedActivity.getRootTask()); + spyOn(mDisplayContent.mAppTransition); + spyOn(emptyTaskFragment); + + prepareAndTriggerAppTransition( + null /* openingActivity */, detachedActivity, changeTaskFragment); + + // Transition not ready because there is an empty non-finishing TaskFragment. + verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any()); + + doReturn(true).when(emptyTaskFragment).hasChild(); + emptyTaskFragment.remove(false /* withTransition */, "test"); + + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + // Transition ready because the empty (no running activity) TaskFragment is requested to be + // removed. + verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any()); + } + /** Registers remote animation for the organizer. */ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, RemoteAnimationAdapter adapter) {