Loading services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +9 −16 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -424,6 +424,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); } Loading Loading @@ -654,26 +658,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() { Loading @@ -690,7 +683,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; Loading services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -778,6 +778,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}. Loading Loading
services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +9 −16 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -424,6 +424,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); } Loading Loading @@ -654,26 +658,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() { Loading @@ -690,7 +683,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; Loading
services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -778,6 +778,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}. Loading