From 9ebaba818d4f0b07d27575b2ef900c09e07b1cae Mon Sep 17 00:00:00 2001 From: Chris Li Date: Fri, 29 Apr 2022 10:59:14 +0800 Subject: [PATCH] Make sure empty TaskFragment notify organizer Before, we only notify organizer for empty TaskFragment when either the Task is empty or the last activity in the TaskFragment is reparented to PIP task. However, there can also be cases where the last activity in a TaskFragment is finished in background. We also want to give it a chance to remove the TaskFragment. Bug: 225371112 Test: atest WmTests:TaskFragmentOrganizerControllerTest Change-Id: Id679524a48a0693f15334f98058aaaf4caac52d2 --- .../wm/TaskFragmentOrganizerController.java | 25 ++++------- .../TaskFragmentOrganizerControllerTest.java | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index eaf25260405a6..b68605b37bd53 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -181,7 +181,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName()); try { - mOrganizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo()); + mOrganizer.onTaskFragmentInfoChanged(info); mLastSentTaskFragmentInfos.put(tf, info); } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e); @@ -422,6 +422,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } // Remove and add for re-ordering. mPendingTaskFragmentEvents.remove(pendingEvent); + // Reset the defer time when TaskFragment is changed, so that it can check again if + // the event should be sent to the organizer, for example the TaskFragment may become + // empty. + pendingEvent.mDeferTime = 0; } mPendingTaskFragmentEvents.add(pendingEvent); } @@ -652,26 +656,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return null; } - private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task, - @NonNull PendingTaskFragmentEvent event) { + private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) { final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder()); final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment); final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo(); // Send an info changed callback if this event is for the last activities to finish in a - // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise, - // the Task may be removed before it becomes visible again to send this event because it no - // longer has activities. As a result, the organizer will never get this info changed event - // and will not delete the TaskFragment because the organizer thinks the TaskFragment still - // has running activities. - // Another case is when an organized TaskFragment became empty because the last running - // activity is reparented to a new Task due to enter PiP. We also want to notify the - // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment - // without causing the extra delay. + // TaskFragment so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED - && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip()) - && lastInfo != null - && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; + && lastInfo != null && lastInfo.hasRunningActivity() && info.isEmpty(); } void dispatchPendingEvents() { @@ -688,7 +681,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; if (task != null && (task.lastActiveTime <= event.mDeferTime || !(isTaskVisible(task, visibleTasks, invisibleTasks) - || shouldSendEventWhenTaskInvisible(task, event)))) { + || shouldSendEventWhenTaskInvisible(event)))) { // Defer sending events to the TaskFragment until the host task is active again. event.mDeferTime = task.lastActiveTime; continue; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 7a704742fba2f..1efec51d30de3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -770,6 +770,47 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer).onTaskFragmentInfoChanged(any()); } + /** + * Tests that a task fragment info changed event is sent if the TaskFragment becomes empty + * even if the Task is invisible. + */ + @Test + public void testPendingTaskFragmentInfoChangedEvent_emptyTaskFragment() { + // Create a TaskFragment with an activity, all within a parent task + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .createActivityCount(1) + .build(); + final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity(); + // Add another activity in the Task so that it always contains a non-finishing activitiy. + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + assertTrue(task.shouldBeVisible(null)); + + // Dispatch pending info changed event from creating the activity + mController.registerOrganizer(mIOrganizer); + taskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentInfoChanged(any()); + + // Verify the info changed callback is not called when the task is invisible + reset(mOrganizer); + doReturn(false).when(task).shouldBeVisible(any()); + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + + // Finish the embedded activity, and verify the info changed callback is called because the + // TaskFragment is becoming empty. + embeddedActivity.finishing = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentInfoChanged(any()); + } + /** * When an embedded {@link TaskFragment} is removed, we should clean up the reference in the * {@link WindowOrganizerController}.