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
This commit is contained in:
Chris Li
2022-04-29 10:59:14 +08:00
parent 4cac1be2ee
commit 9ebaba818d
2 changed files with 50 additions and 16 deletions

View File

@@ -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;

View File

@@ -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}.