Merge "Fix transition wait when ActivityEmbedding enters PiP" into tm-dev
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user