Merge "Fix transition wait when ActivityEmbedding enters PiP" into tm-dev

This commit is contained in:
Chris Li
2022-04-25 02:46:53 +00:00
committed by Android (Google) Code Review
5 changed files with 95 additions and 16 deletions

View File

@@ -562,9 +562,6 @@ public class AppTransitionController {
leafTask = null; leafTask = null;
break; break;
} }
// The activity may be a child of embedded Task, but we want to find the owner Task.
// As a result, find the organized TaskFragment first.
final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
// There are also cases where the Task contains non-embedded activity, such as launching // There are also cases where the Task contains non-embedded activity, such as launching
// split TaskFragments from a non-embedded activity. // split TaskFragments from a non-embedded activity.
// The hierarchy may looks like this: // The hierarchy may looks like this:
@@ -575,10 +572,9 @@ public class AppTransitionController {
// - TaskFragment // - TaskFragment
// - Activity // - Activity
// We also want to have the organizer handle the transition for such case. // We also want to have the organizer handle the transition for such case.
final Task task = organizedTaskFragment != null final Task task = r.getTask();
? organizedTaskFragment.getTask() // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
: r.getTask(); if (task == null || task.inPinnedWindowingMode()) {
if (task == null) {
leafTask = null; leafTask = null;
break; break;
} }

View File

@@ -2097,11 +2097,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
r.setWindowingMode(intermediateWindowingMode); r.setWindowingMode(intermediateWindowingMode);
r.mWaitForEnteringPinnedMode = true; r.mWaitForEnteringPinnedMode = true;
rootTask.forAllTaskFragments(tf -> { rootTask.forAllTaskFragments(tf -> {
// When the Task is entering picture-in-picture, we should clear all override from if (!tf.isOrganizedTaskFragment()) {
// the client organizer, so the PIP activity can get the correct config from the return;
// Task, and prevent conflict with the PipTaskOrganizer. }
if (tf.isOrganizedTaskFragment()) { tf.resetAdjacentTaskFragment();
tf.resetAdjacentTaskFragment(); if (tf.getTopNonFinishingActivity() != null) {
// When the Task is entering picture-in-picture, we should clear all override
// from the client organizer, so the PIP activity can get the correct config
// from the Task, and prevent conflict with the PipTaskOrganizer.
tf.updateRequestedOverrideConfiguration(EMPTY); tf.updateRequestedOverrideConfiguration(EMPTY);
} }
}); });
@@ -2116,7 +2119,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// to the root pinned task // to the root pinned task
r.supportsEnterPipOnTaskSwitch = false; r.supportsEnterPipOnTaskSwitch = false;
if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) { if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip
&& organizedTf.isTaskVisibleRequested()) {
// Dispatch the pending info to TaskFragmentOrganizer before PIP animation. // Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
// Otherwise, it will keep waiting for the empty TaskFragment to be non-empty. // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(

View File

@@ -2302,11 +2302,32 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return mTaskFragmentOrganizer != null; return mTaskFragmentOrganizer != null;
} }
/** Whether the Task should be visible. */
boolean isTaskVisibleRequested() {
final Task task = getTask();
return task != null && task.isVisibleRequested();
}
boolean isReadyToTransit() { boolean isReadyToTransit() {
// We only wait when this is organized to give the organizer a chance to update.
if (!isOrganizedTaskFragment()) {
return true;
}
// We don't want to start the transition if the organized TaskFragment is empty, unless // We don't want to start the transition if the organized TaskFragment is empty, unless
// it is requested to be removed. // it is requested to be removed.
return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
|| mIsRemovalRequested; return true;
}
// Organizer shouldn't change embedded TaskFragment in PiP.
if (isEmbeddedTaskFragmentInPip()) {
return true;
}
// The TaskFragment becomes empty because the last running activity enters PiP when the Task
// is minimized.
if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
return true;
}
return false;
} }
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
@@ -2424,8 +2445,19 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mIsRemovalRequested = false; mIsRemovalRequested = false;
resetAdjacentTaskFragment(); resetAdjacentTaskFragment();
cleanUp(); cleanUp();
final boolean shouldExecuteAppTransition =
mClearedTaskFragmentForPip && isTaskVisibleRequested();
super.removeImmediately(); super.removeImmediately();
sendTaskFragmentVanished(); sendTaskFragmentVanished();
if (shouldExecuteAppTransition && mDisplayContent != null) {
// When the Task is still visible, and the TaskFragment is removed because the last
// running activity is reparenting to PiP, it is possible that no activity is getting
// paused or resumed (having an embedded activity in split), thus we need to relayout
// and execute it explicitly.
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
mDisplayContent.executeAppTransition();
}
} }
/** Called on remove to cleanup. */ /** Called on remove to cleanup. */

View File

@@ -518,8 +518,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
// longer has activities. As a result, the organizer will never get this info changed event // 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 // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
// has running activities. // 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.
return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
&& task.topRunningActivity() == null && lastInfo != null && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip())
&& lastInfo != null
&& lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
} }

View File

@@ -19,8 +19,10 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 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.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -30,6 +32,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import android.content.res.Configuration; import android.content.res.Configuration;
@@ -257,6 +260,9 @@ public class TaskFragmentTest extends WindowTestsBase {
.createActivityCount(1) .createActivityCount(1)
.build(); .build();
final ActivityRecord activity0 = taskFragment0.getTopMostActivity(); final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
spyOn(mAtm.mTaskFragmentOrganizerController); spyOn(mAtm.mTaskFragmentOrganizerController);
// Move activity to pinned. // Move activity to pinned.
@@ -269,10 +275,46 @@ public class TaskFragmentTest extends WindowTestsBase {
final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo(); final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
assertTrue(info.isTaskFragmentClearedForPip()); assertTrue(info.isTaskFragmentClearedForPip());
assertTrue(info.isEmpty()); assertTrue(info.isEmpty());
// Notify organizer because the Task is still visible.
assertTrue(task.isVisibleRequested());
verify(mAtm.mTaskFragmentOrganizerController) verify(mAtm.mTaskFragmentOrganizerController)
.dispatchPendingInfoChangedEvent(taskFragment0); .dispatchPendingInfoChangedEvent(taskFragment0);
} }
@Test
public void testIsReadyToTransit() {
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()
.setOrganizer(mOrganizer)
.setFragmentToken(new Binder())
.build();
final Task task = taskFragment.getTask();
// Not ready when it is empty.
assertFalse(taskFragment.isReadyToTransit());
// Ready when it is not empty.
final ActivityRecord activity = createActivityRecord(mDisplayContent);
doNothing().when(activity).setDropInputMode(anyInt());
activity.reparent(taskFragment, WindowContainer.POSITION_TOP);
assertTrue(taskFragment.isReadyToTransit());
// Ready when the Task is in PiP.
taskFragment.removeChild(activity);
task.setWindowingMode(WINDOWING_MODE_PINNED);
assertTrue(taskFragment.isReadyToTransit());
// Ready when the TaskFragment is empty because of PiP, and the Task is invisible.
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskFragment.mClearedTaskFragmentForPip = true;
assertTrue(taskFragment.isReadyToTransit());
// Not ready if the task is still visible when the TaskFragment becomes empty.
doReturn(true).when(task).isVisibleRequested();
assertFalse(taskFragment.isReadyToTransit());
}
@Test @Test
public void testActivityHasOverlayOverUntrustedModeEmbedded() { public void testActivityHasOverlayOverUntrustedModeEmbedded() {
final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,