Loading services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +20 −1 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.Configuration; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Rect; Loading Loading @@ -497,6 +498,23 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return null; return null; } } private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task, @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. return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED && task.topRunningActivity() == null && lastInfo != null && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; } void dispatchPendingEvents() { void dispatchPendingEvents() { if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() || mPendingTaskFragmentEvents.isEmpty()) { || mPendingTaskFragmentEvents.isEmpty()) { Loading @@ -510,7 +528,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; if (task != null && (task.lastActiveTime <= event.mDeferTime if (task != null && (task.lastActiveTime <= event.mDeferTime || !isTaskVisible(task, visibleTasks, invisibleTasks))) { || !(isTaskVisible(task, visibleTasks, invisibleTasks) || shouldSendEventWhenTaskInvisible(task, event)))) { // Defer sending events to the TaskFragment until the host task is active again. // Defer sending events to the TaskFragment until the host task is active again. event.mDeferTime = task.lastActiveTime; event.mDeferTime = task.lastActiveTime; continue; continue; Loading services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +38 −0 Original line number Original line Diff line number Diff line Loading @@ -21,14 +21,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import android.content.Intent; import android.content.Intent; Loading Loading @@ -471,6 +474,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setParentTask(task) .setOrganizer(mOrganizer) .setOrganizer(mOrganizer) .setFragmentToken(mFragmentToken) .build(); .build(); // Mock the task to invisible // Mock the task to invisible Loading @@ -485,4 +489,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Verifies that event was not sent // Verifies that event was not sent verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); } } /** * Tests that a task fragment info changed event is still sent if the task is invisible only * when the info changed event is because of the last activity in a task finishing. */ @Test public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() { // Create a TaskFragment with an activity, all within a parent task final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setOrganizer(mOrganizer) .setFragmentToken(mFragmentToken) .setCreateParentTask() .createActivityCount(1) .build(); final Task parentTask = taskFragment.getTask(); final ActivityRecord activity = taskFragment.getTopNonFinishingActivity(); assertTrue(parentTask.shouldBeVisible(null)); // Dispatch pending info changed event from creating the activity mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); // Finish the activity and verify that the task is invisible activity.finishing = true; assertFalse(parentTask.shouldBeVisible(null)); // Verify the info changed callback still occurred despite the task being invisible reset(mOrganizer); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); verify(mOrganizer).onTaskFragmentInfoChanged(any()); } } } Loading
services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +20 −1 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.Configuration; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Rect; Loading Loading @@ -497,6 +498,23 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return null; return null; } } private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task, @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. return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED && task.topRunningActivity() == null && lastInfo != null && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; } void dispatchPendingEvents() { void dispatchPendingEvents() { if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred() || mPendingTaskFragmentEvents.isEmpty()) { || mPendingTaskFragmentEvents.isEmpty()) { Loading @@ -510,7 +528,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i); final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null; if (task != null && (task.lastActiveTime <= event.mDeferTime if (task != null && (task.lastActiveTime <= event.mDeferTime || !isTaskVisible(task, visibleTasks, invisibleTasks))) { || !(isTaskVisible(task, visibleTasks, invisibleTasks) || shouldSendEventWhenTaskInvisible(task, event)))) { // Defer sending events to the TaskFragment until the host task is active again. // Defer sending events to the TaskFragment until the host task is active again. event.mDeferTime = task.lastActiveTime; event.mDeferTime = task.lastActiveTime; continue; continue; Loading
services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +38 −0 Original line number Original line Diff line number Diff line Loading @@ -21,14 +21,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import android.content.Intent; import android.content.Intent; Loading Loading @@ -471,6 +474,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setParentTask(task) .setOrganizer(mOrganizer) .setOrganizer(mOrganizer) .setFragmentToken(mFragmentToken) .build(); .build(); // Mock the task to invisible // Mock the task to invisible Loading @@ -485,4 +489,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Verifies that event was not sent // Verifies that event was not sent verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); } } /** * Tests that a task fragment info changed event is still sent if the task is invisible only * when the info changed event is because of the last activity in a task finishing. */ @Test public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() { // Create a TaskFragment with an activity, all within a parent task final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setOrganizer(mOrganizer) .setFragmentToken(mFragmentToken) .setCreateParentTask() .createActivityCount(1) .build(); final Task parentTask = taskFragment.getTask(); final ActivityRecord activity = taskFragment.getTopNonFinishingActivity(); assertTrue(parentTask.shouldBeVisible(null)); // Dispatch pending info changed event from creating the activity mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); // Finish the activity and verify that the task is invisible activity.finishing = true; assertFalse(parentTask.shouldBeVisible(null)); // Verify the info changed callback still occurred despite the task being invisible reset(mOrganizer); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); verify(mOrganizer).onTaskFragmentInfoChanged(any()); } } }