Loading services/core/java/com/android/server/wm/ActivityStarter.java +2 −3 Original line number Diff line number Diff line Loading @@ -1884,9 +1884,8 @@ class ActivityStarter { false /* forceSend */, mStartActivity); final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded(); mTargetRootTask.startActivityLocked(mStartActivity, topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask, isTaskSwitch, mOptions, sourceRecord); mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch, mOptions, sourceRecord); if (mDoResume) { final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked(); if (!mTargetRootTask.isTopActivityFocusable() Loading services/core/java/com/android/server/wm/Task.java +42 −23 Original line number Diff line number Diff line Loading @@ -5031,9 +5031,9 @@ class Task extends TaskFragment { return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea()); } void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity, boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask); Task rTask = r.getTask(); final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); final boolean isOrhasTask = rTask == this || hasChild(rTask); Loading Loading @@ -5099,10 +5099,8 @@ class Task extends TaskFragment { // supporting picture-in-picture while pausing only if the starting activity // would not be considered an overlay on top of the current activity // (eg. not fullscreen, or the assistant) if (canEnterPipOnTaskSwitch(focusedTopActivity, null /* toFrontTask */, r, options)) { focusedTopActivity.supportsEnterPipOnTaskSwitch = true; } enableEnterPipOnTaskSwitch(pipCandidate, null /* toFrontTask */, r, options); transit = TRANSIT_OLD_TASK_OPEN; } } Loading Loading @@ -5159,20 +5157,44 @@ class Task extends TaskFragment { } } /** On Task switch, finds the top activity that supports PiP. */ @Nullable static ActivityRecord findEnterPipOnTaskSwitchCandidate(@Nullable Task topTask) { if (topTask == null) { return null; } final ActivityRecord[] candidate = new ActivityRecord[1]; topTask.forAllLeafTaskFragments(tf -> { // Find the top activity that may enter Pip while pausing. final ActivityRecord topActivity = tf.getTopNonFinishingActivity(); if (topActivity != null && topActivity.isState(RESUMED, PAUSING) && topActivity.supportsPictureInPicture()) { candidate[0] = topActivity; return true; } return false; }); return candidate[0]; } /** * @return Whether the switch to another task can trigger the currently running activity to * When switching to another Task, marks the currently PiP candidate activity as supporting to * enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or * {@param toFrontActivity} should be set. */ private boolean canEnterPipOnTaskSwitch(ActivityRecord pipCandidate, Task toFrontTask, ActivityRecord toFrontActivity, ActivityOptions opts) { private static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate, @Nullable Task toFrontTask, @Nullable ActivityRecord toFrontActivity, @Nullable ActivityOptions opts) { if (pipCandidate == null) { return; } if (opts != null && opts.disallowEnterPictureInPictureWhileLaunching()) { // Ensure the caller has requested not to trigger auto-enter PiP return false; return; } if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) { // Ensure that we do not trigger entering PiP an activity on the root pinned task return false; if (pipCandidate.inPinnedWindowingMode()) { // Ensure that we do not trigger entering PiP an activity on the root pinned task. return; } final boolean isTransient = opts != null && opts.getTransientLaunch(); final Task targetRootTask = toFrontTask != null Loading @@ -5181,9 +5203,10 @@ class Task extends TaskFragment { // Ensure the task/activity being brought forward is not the assistant and is not // transient. In the case of transient-launch, we want to wait until the end of the // transition and only allow switch if the transient launch was committed. return false; return; } return true; pipCandidate.supportsEnterPipOnTaskSwitch = true; } /** Loading Loading @@ -5492,9 +5515,8 @@ class Task extends TaskFragment { AppTimeTracker timeTracker, boolean deferResume, String reason) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); final Task topRootTask = getDisplayArea().getTopRootTask(); final ActivityRecord topActivity = topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null; final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate( getDisplayArea().getTopRootTask()); if (tr != this && !tr.isDescendantOf(this)) { // nothing to do! Loading Loading @@ -5549,10 +5571,7 @@ class Task extends TaskFragment { // picture-in-picture while paused only if the task would not be considered an oerlay // on top // of the current activity (eg. not fullscreen, or the assistant) if (canEnterPipOnTaskSwitch(topActivity, tr, null /* toFrontActivity */, options)) { topActivity.supportsEnterPipOnTaskSwitch = true; } enableEnterPipOnTaskSwitch(pipCandidate, tr, null /* toFrontActivity */, options); if (!deferResume) { mRootWindowContainer.resumeFocusedTasksTopActivities(); Loading services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +2 −2 Original line number Diff line number Diff line Loading @@ -2693,7 +2693,7 @@ public class ActivityRecordTests extends WindowTestsBase { .resumeFocusedTasksTopActivities(); // Make mVisibleSetFromTransferredStartingWindow true. final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build(); task.startActivityLocked(middle, null /* focusedTopActivity */, task.startActivityLocked(middle, null /* topTask */, false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); middle.makeFinishingLocked(); Loading @@ -2706,7 +2706,7 @@ public class ActivityRecordTests extends WindowTestsBase { // a visible activity. top.setVisible(false); // The finishing middle should be able to transfer starting window to top. task.startActivityLocked(top, null /* focusedTopActivity */, task.startActivityLocked(top, null /* topTask */, false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); Loading services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -21,9 +21,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; Loading Loading @@ -170,4 +172,34 @@ public class TaskFragmentTest extends WindowTestsBase { false /* preserveWindows */); assertEquals(true, activityBelow.isVisibleRequested()); } @Test public void testMoveTaskToFront_supportsEnterPipOnTaskSwitchForAdjacentTaskFragment() { final Task bottomTask = createTask(mDisplayContent); final ActivityRecord bottomActivity = createActivityRecord(bottomTask); final Task topTask = createTask(mDisplayContent); // First create primary TF, and then secondary TF, so that the secondary will be on the top. final TaskFragment primaryTf = createTaskFragmentWithParentTask( topTask, false /* createEmbeddedTask */); final TaskFragment secondaryTf = createTaskFragmentWithParentTask( topTask, false /* createEmbeddedTask */); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); doReturn(true).when(primaryActivity).supportsPictureInPicture(); doReturn(false).when(secondaryActivity).supportsPictureInPicture(); primaryTf.setAdjacentTaskFragment(secondaryTf, false /* moveAdjacentTogether */); primaryActivity.setState(RESUMED, "test"); secondaryActivity.setState(RESUMED, "test"); assertEquals(topTask, bottomTask.getDisplayArea().getTopRootTask()); // When moving Task to front, the resumed activity that supports PIP should support enter // PIP on Task switch even if it is not the topmost in the Task. bottomTask.moveTaskToFront(bottomTask, false /* noAnimation */, null /* options */, null /* timeTracker */, "test"); assertTrue(primaryActivity.supportsEnterPipOnTaskSwitch); assertFalse(secondaryActivity.supportsEnterPipOnTaskSwitch); } } Loading
services/core/java/com/android/server/wm/ActivityStarter.java +2 −3 Original line number Diff line number Diff line Loading @@ -1884,9 +1884,8 @@ class ActivityStarter { false /* forceSend */, mStartActivity); final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded(); mTargetRootTask.startActivityLocked(mStartActivity, topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask, isTaskSwitch, mOptions, sourceRecord); mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch, mOptions, sourceRecord); if (mDoResume) { final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked(); if (!mTargetRootTask.isTopActivityFocusable() Loading
services/core/java/com/android/server/wm/Task.java +42 −23 Original line number Diff line number Diff line Loading @@ -5031,9 +5031,9 @@ class Task extends TaskFragment { return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea()); } void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity, boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask); Task rTask = r.getTask(); final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); final boolean isOrhasTask = rTask == this || hasChild(rTask); Loading Loading @@ -5099,10 +5099,8 @@ class Task extends TaskFragment { // supporting picture-in-picture while pausing only if the starting activity // would not be considered an overlay on top of the current activity // (eg. not fullscreen, or the assistant) if (canEnterPipOnTaskSwitch(focusedTopActivity, null /* toFrontTask */, r, options)) { focusedTopActivity.supportsEnterPipOnTaskSwitch = true; } enableEnterPipOnTaskSwitch(pipCandidate, null /* toFrontTask */, r, options); transit = TRANSIT_OLD_TASK_OPEN; } } Loading Loading @@ -5159,20 +5157,44 @@ class Task extends TaskFragment { } } /** On Task switch, finds the top activity that supports PiP. */ @Nullable static ActivityRecord findEnterPipOnTaskSwitchCandidate(@Nullable Task topTask) { if (topTask == null) { return null; } final ActivityRecord[] candidate = new ActivityRecord[1]; topTask.forAllLeafTaskFragments(tf -> { // Find the top activity that may enter Pip while pausing. final ActivityRecord topActivity = tf.getTopNonFinishingActivity(); if (topActivity != null && topActivity.isState(RESUMED, PAUSING) && topActivity.supportsPictureInPicture()) { candidate[0] = topActivity; return true; } return false; }); return candidate[0]; } /** * @return Whether the switch to another task can trigger the currently running activity to * When switching to another Task, marks the currently PiP candidate activity as supporting to * enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or * {@param toFrontActivity} should be set. */ private boolean canEnterPipOnTaskSwitch(ActivityRecord pipCandidate, Task toFrontTask, ActivityRecord toFrontActivity, ActivityOptions opts) { private static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate, @Nullable Task toFrontTask, @Nullable ActivityRecord toFrontActivity, @Nullable ActivityOptions opts) { if (pipCandidate == null) { return; } if (opts != null && opts.disallowEnterPictureInPictureWhileLaunching()) { // Ensure the caller has requested not to trigger auto-enter PiP return false; return; } if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) { // Ensure that we do not trigger entering PiP an activity on the root pinned task return false; if (pipCandidate.inPinnedWindowingMode()) { // Ensure that we do not trigger entering PiP an activity on the root pinned task. return; } final boolean isTransient = opts != null && opts.getTransientLaunch(); final Task targetRootTask = toFrontTask != null Loading @@ -5181,9 +5203,10 @@ class Task extends TaskFragment { // Ensure the task/activity being brought forward is not the assistant and is not // transient. In the case of transient-launch, we want to wait until the end of the // transition and only allow switch if the transient launch was committed. return false; return; } return true; pipCandidate.supportsEnterPipOnTaskSwitch = true; } /** Loading Loading @@ -5492,9 +5515,8 @@ class Task extends TaskFragment { AppTimeTracker timeTracker, boolean deferResume, String reason) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); final Task topRootTask = getDisplayArea().getTopRootTask(); final ActivityRecord topActivity = topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null; final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate( getDisplayArea().getTopRootTask()); if (tr != this && !tr.isDescendantOf(this)) { // nothing to do! Loading Loading @@ -5549,10 +5571,7 @@ class Task extends TaskFragment { // picture-in-picture while paused only if the task would not be considered an oerlay // on top // of the current activity (eg. not fullscreen, or the assistant) if (canEnterPipOnTaskSwitch(topActivity, tr, null /* toFrontActivity */, options)) { topActivity.supportsEnterPipOnTaskSwitch = true; } enableEnterPipOnTaskSwitch(pipCandidate, tr, null /* toFrontActivity */, options); if (!deferResume) { mRootWindowContainer.resumeFocusedTasksTopActivities(); Loading
services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +2 −2 Original line number Diff line number Diff line Loading @@ -2693,7 +2693,7 @@ public class ActivityRecordTests extends WindowTestsBase { .resumeFocusedTasksTopActivities(); // Make mVisibleSetFromTransferredStartingWindow true. final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build(); task.startActivityLocked(middle, null /* focusedTopActivity */, task.startActivityLocked(middle, null /* topTask */, false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); middle.makeFinishingLocked(); Loading @@ -2706,7 +2706,7 @@ public class ActivityRecordTests extends WindowTestsBase { // a visible activity. top.setVisible(false); // The finishing middle should be able to transfer starting window to top. task.startActivityLocked(top, null /* focusedTopActivity */, task.startActivityLocked(top, null /* topTask */, false /* newTask */, false /* isTaskSwitch */, null /* options */, null /* sourceRecord */); Loading
services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -21,9 +21,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; Loading Loading @@ -170,4 +172,34 @@ public class TaskFragmentTest extends WindowTestsBase { false /* preserveWindows */); assertEquals(true, activityBelow.isVisibleRequested()); } @Test public void testMoveTaskToFront_supportsEnterPipOnTaskSwitchForAdjacentTaskFragment() { final Task bottomTask = createTask(mDisplayContent); final ActivityRecord bottomActivity = createActivityRecord(bottomTask); final Task topTask = createTask(mDisplayContent); // First create primary TF, and then secondary TF, so that the secondary will be on the top. final TaskFragment primaryTf = createTaskFragmentWithParentTask( topTask, false /* createEmbeddedTask */); final TaskFragment secondaryTf = createTaskFragmentWithParentTask( topTask, false /* createEmbeddedTask */); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); doReturn(true).when(primaryActivity).supportsPictureInPicture(); doReturn(false).when(secondaryActivity).supportsPictureInPicture(); primaryTf.setAdjacentTaskFragment(secondaryTf, false /* moveAdjacentTogether */); primaryActivity.setState(RESUMED, "test"); secondaryActivity.setState(RESUMED, "test"); assertEquals(topTask, bottomTask.getDisplayArea().getTopRootTask()); // When moving Task to front, the resumed activity that supports PIP should support enter // PIP on Task switch even if it is not the topmost in the Task. bottomTask.moveTaskToFront(bottomTask, false /* noAnimation */, null /* options */, null /* timeTracker */, "test"); assertTrue(primaryActivity.supportsEnterPipOnTaskSwitch); assertFalse(secondaryActivity.supportsEnterPipOnTaskSwitch); } }