Merge "Move the ActivityEmbedding Task visibility filter to the client" into tm-qpr-dev
This commit is contained in:
@@ -109,6 +109,12 @@ class SplitContainer {
|
||||
return (mSplitRule instanceof SplitPlaceholderRule);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
SplitInfo toSplitInfo() {
|
||||
return new SplitInfo(mPrimaryContainer.toActivityStack(),
|
||||
mSecondaryContainer.toActivityStack(), mSplitAttributes);
|
||||
}
|
||||
|
||||
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
|
||||
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
|
||||
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
|
||||
|
||||
@@ -1422,6 +1422,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
@GuardedBy("mLock")
|
||||
void updateContainer(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentContainer container) {
|
||||
if (!container.getTaskContainer().isVisible()) {
|
||||
// Wait until the Task is visible to avoid unnecessary update when the Task is still in
|
||||
// background.
|
||||
return;
|
||||
}
|
||||
if (launchPlaceholderIfNecessary(wct, container)) {
|
||||
// Placeholder was launched, the positions will be updated when the activity is added
|
||||
// to the secondary container.
|
||||
@@ -1643,16 +1648,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
/**
|
||||
* Notifies listeners about changes to split states if necessary.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
private void updateCallbackIfNecessary() {
|
||||
if (mEmbeddingCallback == null) {
|
||||
void updateCallbackIfNecessary() {
|
||||
if (mEmbeddingCallback == null || !readyToReportToClient()) {
|
||||
return;
|
||||
}
|
||||
if (!allActivitiesCreated()) {
|
||||
return;
|
||||
}
|
||||
List<SplitInfo> currentSplitStates = getActiveSplitStates();
|
||||
if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
|
||||
final List<SplitInfo> currentSplitStates = getActiveSplitStates();
|
||||
if (mLastReportedSplitStates.equals(currentSplitStates)) {
|
||||
return;
|
||||
}
|
||||
mLastReportedSplitStates.clear();
|
||||
@@ -1661,48 +1664,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of descriptors for currently active split states. If the value returned is
|
||||
* null, that indicates that the active split states are in an intermediate state and should
|
||||
* not be reported.
|
||||
* Returns a list of descriptors for currently active split states.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
@NonNull
|
||||
private List<SplitInfo> getActiveSplitStates() {
|
||||
List<SplitInfo> splitStates = new ArrayList<>();
|
||||
final List<SplitInfo> splitStates = new ArrayList<>();
|
||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||
final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
|
||||
.mSplitContainers;
|
||||
for (SplitContainer container : splitContainers) {
|
||||
if (container.getPrimaryContainer().isEmpty()
|
||||
|| container.getSecondaryContainer().isEmpty()) {
|
||||
// We are in an intermediate state because either the split container is about
|
||||
// to be removed or the primary or secondary container are about to receive an
|
||||
// activity.
|
||||
return null;
|
||||
}
|
||||
final ActivityStack primaryContainer = container.getPrimaryContainer()
|
||||
.toActivityStack();
|
||||
final ActivityStack secondaryContainer = container.getSecondaryContainer()
|
||||
.toActivityStack();
|
||||
final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
|
||||
container.getSplitAttributes());
|
||||
splitStates.add(splitState);
|
||||
}
|
||||
mTaskContainers.valueAt(i).getSplitStates(splitStates);
|
||||
}
|
||||
return splitStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all activities that are registered with the containers have already appeared in
|
||||
* the client.
|
||||
* Whether we can now report the split states to the client.
|
||||
*/
|
||||
private boolean allActivitiesCreated() {
|
||||
@GuardedBy("mLock")
|
||||
private boolean readyToReportToClient() {
|
||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|
||||
for (TaskFragmentContainer container : containers) {
|
||||
if (!container.taskInfoActivityCountMatchesCreated()) {
|
||||
return false;
|
||||
}
|
||||
if (mTaskContainers.valueAt(i).isInIntermediateState()) {
|
||||
// If any Task is in an intermediate state, wait for the server update.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -221,6 +221,24 @@ class TaskContainer {
|
||||
return mContainers.indexOf(child);
|
||||
}
|
||||
|
||||
/** Whether the Task is in an intermediate state waiting for the server update.*/
|
||||
boolean isInIntermediateState() {
|
||||
for (TaskFragmentContainer container : mContainers) {
|
||||
if (container.isInIntermediateState()) {
|
||||
// We are in an intermediate state to wait for server update on this TaskFragment.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
|
||||
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
|
||||
for (SplitContainer container : mSplitContainers) {
|
||||
outSplitStates.add(container.toSplitInfo());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class which contains the display ID and {@link Configuration} of a
|
||||
* {@link TaskContainer}
|
||||
|
||||
@@ -166,16 +166,34 @@ class TaskFragmentContainer {
|
||||
return allActivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the count of activities from the same process in task fragment info corresponds to
|
||||
* the ones created and available on the client side.
|
||||
*/
|
||||
boolean taskInfoActivityCountMatchesCreated() {
|
||||
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
|
||||
boolean isInIntermediateState() {
|
||||
if (mInfo == null) {
|
||||
return false;
|
||||
// Haven't received onTaskFragmentAppeared event.
|
||||
return true;
|
||||
}
|
||||
return mPendingAppearedActivities.isEmpty()
|
||||
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
|
||||
if (mInfo.isEmpty()) {
|
||||
// Empty TaskFragment will be removed or will have activity launched into it soon.
|
||||
return true;
|
||||
}
|
||||
if (!mPendingAppearedActivities.isEmpty()) {
|
||||
// Reparented activity hasn't appeared.
|
||||
return true;
|
||||
}
|
||||
// Check if there is any reported activity that is no longer alive.
|
||||
for (IBinder token : mInfo.getActivities()) {
|
||||
final Activity activity = mController.getActivity(token);
|
||||
if (activity == null && !mTaskContainer.isVisible()) {
|
||||
// Activity can be null if the activity is not attached to process yet. That can
|
||||
// happen when the activity is started in background.
|
||||
continue;
|
||||
}
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
// One of the reported activity is no longer alive, wait for the server update.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -102,6 +102,7 @@ import org.mockito.MockitoAnnotations;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Test class for {@link SplitController}.
|
||||
@@ -132,6 +133,8 @@ public class SplitControllerTest {
|
||||
|
||||
private SplitController mSplitController;
|
||||
private SplitPresenter mSplitPresenter;
|
||||
private Consumer<List<SplitInfo>> mEmbeddingCallback;
|
||||
private List<SplitInfo> mSplitInfos;
|
||||
private TransactionManager mTransactionManager;
|
||||
|
||||
@Before
|
||||
@@ -141,9 +144,16 @@ public class SplitControllerTest {
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
mSplitController = new SplitController(mWindowLayoutComponent);
|
||||
mSplitPresenter = mSplitController.mPresenter;
|
||||
mSplitInfos = new ArrayList<>();
|
||||
mEmbeddingCallback = splitInfos -> {
|
||||
mSplitInfos.clear();
|
||||
mSplitInfos.addAll(splitInfos);
|
||||
};
|
||||
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
|
||||
mTransactionManager = mSplitController.mTransactionManager;
|
||||
spyOn(mSplitController);
|
||||
spyOn(mSplitPresenter);
|
||||
spyOn(mEmbeddingCallback);
|
||||
spyOn(mTransactionManager);
|
||||
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
|
||||
final Configuration activityConfig = new Configuration();
|
||||
@@ -328,6 +338,30 @@ public class SplitControllerTest {
|
||||
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateContainer_skipIfTaskIsInvisible() {
|
||||
final Activity r0 = createMockActivity();
|
||||
final Activity r1 = createMockActivity();
|
||||
addSplitTaskFragments(r0, r1);
|
||||
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
|
||||
final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
|
||||
spyOn(taskContainer);
|
||||
|
||||
// No update when the Task is invisible.
|
||||
clearInvocations(mSplitPresenter);
|
||||
doReturn(false).when(taskContainer).isVisible();
|
||||
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
|
||||
|
||||
verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
|
||||
|
||||
// Update the split when the Task is visible.
|
||||
doReturn(true).when(taskContainer).isVisible();
|
||||
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
|
||||
|
||||
verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
|
||||
taskFragmentContainer, mTransaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartActivityResultError() {
|
||||
final Intent intent = new Intent();
|
||||
@@ -1162,14 +1196,69 @@ public class SplitControllerTest {
|
||||
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitInfoCallback_reportSplit() {
|
||||
final Activity r0 = createMockActivity();
|
||||
final Activity r1 = createMockActivity();
|
||||
addSplitTaskFragments(r0, r1);
|
||||
|
||||
mSplitController.updateCallbackIfNecessary();
|
||||
assertEquals(1, mSplitInfos.size());
|
||||
final SplitInfo splitInfo = mSplitInfos.get(0);
|
||||
assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
|
||||
assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
|
||||
assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0));
|
||||
assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitInfoCallback_reportSplitInMultipleTasks() {
|
||||
final int taskId0 = 1;
|
||||
final int taskId1 = 2;
|
||||
final Activity r0 = createMockActivity(taskId0);
|
||||
final Activity r1 = createMockActivity(taskId0);
|
||||
final Activity r2 = createMockActivity(taskId1);
|
||||
final Activity r3 = createMockActivity(taskId1);
|
||||
addSplitTaskFragments(r0, r1);
|
||||
addSplitTaskFragments(r2, r3);
|
||||
|
||||
mSplitController.updateCallbackIfNecessary();
|
||||
assertEquals(2, mSplitInfos.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitInfoCallback_doNotReportIfInIntermediateState() {
|
||||
final Activity r0 = createMockActivity();
|
||||
final Activity r1 = createMockActivity();
|
||||
addSplitTaskFragments(r0, r1);
|
||||
final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0);
|
||||
final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1);
|
||||
spyOn(tf0);
|
||||
spyOn(tf1);
|
||||
|
||||
// Do not report if activity has not appeared in the TaskFragmentContainer in split.
|
||||
doReturn(true).when(tf0).isInIntermediateState();
|
||||
mSplitController.updateCallbackIfNecessary();
|
||||
verify(mEmbeddingCallback, never()).accept(any());
|
||||
|
||||
doReturn(false).when(tf0).isInIntermediateState();
|
||||
mSplitController.updateCallbackIfNecessary();
|
||||
verify(mEmbeddingCallback).accept(any());
|
||||
}
|
||||
|
||||
/** Creates a mock activity in the organizer process. */
|
||||
private Activity createMockActivity() {
|
||||
return createMockActivity(TASK_ID);
|
||||
}
|
||||
|
||||
/** Creates a mock activity in the organizer process. */
|
||||
private Activity createMockActivity(int taskId) {
|
||||
final Activity activity = mock(Activity.class);
|
||||
doReturn(mActivityResources).when(activity).getResources();
|
||||
final IBinder activityToken = new Binder();
|
||||
doReturn(activityToken).when(activity).getActivityToken();
|
||||
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
||||
doReturn(TASK_ID).when(activity).getTaskId();
|
||||
doReturn(taskId).when(activity).getTaskId();
|
||||
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
|
||||
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
|
||||
return activity;
|
||||
@@ -1177,7 +1266,8 @@ public class SplitControllerTest {
|
||||
|
||||
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
||||
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
|
||||
final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
|
||||
final TaskFragmentContainer container = mSplitController.newContainer(activity,
|
||||
activity.getTaskId());
|
||||
setupTaskFragmentInfo(container, activity);
|
||||
return container;
|
||||
}
|
||||
@@ -1268,7 +1358,7 @@ public class SplitControllerTest {
|
||||
|
||||
// We need to set those in case we are not respecting clear top.
|
||||
// TODO(b/231845476) we should always respect clearTop.
|
||||
final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
|
||||
final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
|
||||
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
|
||||
primaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||
|
||||
@@ -334,6 +334,70 @@ public class TaskFragmentContainerTest {
|
||||
assertFalse(container.hasActivity(mActivity.getActivityToken()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsInIntermediateState() {
|
||||
// True if no info set.
|
||||
final TaskContainer taskContainer = createTestTaskContainer();
|
||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||
mIntent, taskContainer, mController);
|
||||
spyOn(taskContainer);
|
||||
doReturn(true).when(taskContainer).isVisible();
|
||||
|
||||
assertTrue(container.isInIntermediateState());
|
||||
assertTrue(taskContainer.isInIntermediateState());
|
||||
|
||||
// True if empty info set.
|
||||
final List<IBinder> activities = new ArrayList<>();
|
||||
doReturn(activities).when(mInfo).getActivities();
|
||||
doReturn(true).when(mInfo).isEmpty();
|
||||
container.setInfo(mTransaction, mInfo);
|
||||
|
||||
assertTrue(container.isInIntermediateState());
|
||||
assertTrue(taskContainer.isInIntermediateState());
|
||||
|
||||
// False if info is not empty.
|
||||
doReturn(false).when(mInfo).isEmpty();
|
||||
container.setInfo(mTransaction, mInfo);
|
||||
|
||||
assertFalse(container.isInIntermediateState());
|
||||
assertFalse(taskContainer.isInIntermediateState());
|
||||
|
||||
// True if there is pending appeared activity.
|
||||
container.addPendingAppearedActivity(mActivity);
|
||||
|
||||
assertTrue(container.isInIntermediateState());
|
||||
assertTrue(taskContainer.isInIntermediateState());
|
||||
|
||||
// True if the activity is finishing.
|
||||
activities.add(mActivity.getActivityToken());
|
||||
doReturn(true).when(mActivity).isFinishing();
|
||||
container.setInfo(mTransaction, mInfo);
|
||||
|
||||
assertTrue(container.isInIntermediateState());
|
||||
assertTrue(taskContainer.isInIntermediateState());
|
||||
|
||||
// False if the activity is not finishing.
|
||||
doReturn(false).when(mActivity).isFinishing();
|
||||
container.setInfo(mTransaction, mInfo);
|
||||
|
||||
assertFalse(container.isInIntermediateState());
|
||||
assertFalse(taskContainer.isInIntermediateState());
|
||||
|
||||
// True if there is a token that can't find associated activity.
|
||||
activities.clear();
|
||||
activities.add(new Binder());
|
||||
container.setInfo(mTransaction, mInfo);
|
||||
|
||||
assertTrue(container.isInIntermediateState());
|
||||
assertTrue(taskContainer.isInIntermediateState());
|
||||
|
||||
// False if there is a token that can't find associated activity when the Task is invisible.
|
||||
doReturn(false).when(taskContainer).isVisible();
|
||||
|
||||
assertFalse(container.isInIntermediateState());
|
||||
assertFalse(taskContainer.isInIntermediateState());
|
||||
}
|
||||
|
||||
/** Creates a mock activity in the organizer process. */
|
||||
private Activity createMockActivity() {
|
||||
final Activity activity = mock(Activity.class);
|
||||
|
||||
@@ -607,6 +607,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
||||
int opType, @NonNull Throwable exception) {
|
||||
validateAndGetState(organizer);
|
||||
Slog.w(TAG, "onTaskFragmentError ", exception);
|
||||
final PendingTaskFragmentEvent vanishedEvent = taskFragment != null
|
||||
? getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_VANISHED)
|
||||
: null;
|
||||
if (vanishedEvent != null) {
|
||||
// No need to notify if the TaskFragment has been removed.
|
||||
return;
|
||||
}
|
||||
addPendingEvent(new PendingTaskFragmentEvent.Builder(
|
||||
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
|
||||
.setErrorCallbackToken(errorCallbackToken)
|
||||
@@ -878,23 +885,6 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
|
||||
if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
|
||||
// Always send parent info changed to update task visibility
|
||||
|| event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
// TaskFragment so that the {@link TaskFragmentOrganizer} can delete this TaskFragment.
|
||||
return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
|
||||
&& lastInfo != null && lastInfo.hasRunningActivity() && info.isEmpty();
|
||||
}
|
||||
|
||||
void dispatchPendingEvents() {
|
||||
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|
||||
|| mPendingTaskFragmentEvents.isEmpty()) {
|
||||
@@ -908,37 +898,19 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
|
||||
private void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
|
||||
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
|
||||
if (pendingEvents.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<Task> visibleTasks = new ArrayList<>();
|
||||
final ArrayList<Task> invisibleTasks = new ArrayList<>();
|
||||
final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
|
||||
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
|
||||
final PendingTaskFragmentEvent event = pendingEvents.get(i);
|
||||
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
|
||||
// TODO(b/251132298): move visibility check to the client side.
|
||||
if (task != null && (task.lastActiveTime <= event.mDeferTime
|
||||
|| !(isTaskVisible(task, visibleTasks, invisibleTasks)
|
||||
|| shouldSendEventWhenTaskInvisible(event)))) {
|
||||
// Defer sending events to the TaskFragment until the host task is active again.
|
||||
event.mDeferTime = task.lastActiveTime;
|
||||
continue;
|
||||
}
|
||||
candidateEvents.add(event);
|
||||
}
|
||||
final int numEvents = candidateEvents.size();
|
||||
if (numEvents == 0) {
|
||||
if (shouldDeferPendingEvents(state, pendingEvents)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTmpTaskSet.clear();
|
||||
final int numEvents = pendingEvents.size();
|
||||
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
|
||||
for (int i = 0; i < numEvents; i++) {
|
||||
final PendingTaskFragmentEvent event = candidateEvents.get(i);
|
||||
final PendingTaskFragmentEvent event = pendingEvents.get(i);
|
||||
if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED
|
||||
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
|
||||
final Task task = event.mTaskFragment.getTask();
|
||||
@@ -954,7 +926,47 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
||||
}
|
||||
mTmpTaskSet.clear();
|
||||
state.dispatchTransaction(transaction);
|
||||
pendingEvents.removeAll(candidateEvents);
|
||||
pendingEvents.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to defer sending the events to the organizer to avoid waking the app process
|
||||
* when it is in background. We want to either send all events or none to avoid inconsistency.
|
||||
*/
|
||||
private boolean shouldDeferPendingEvents(@NonNull TaskFragmentOrganizerState state,
|
||||
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
|
||||
final ArrayList<Task> visibleTasks = new ArrayList<>();
|
||||
final ArrayList<Task> invisibleTasks = new ArrayList<>();
|
||||
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
|
||||
final PendingTaskFragmentEvent event = pendingEvents.get(i);
|
||||
if (event.mEventType != PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED
|
||||
&& event.mEventType != PendingTaskFragmentEvent.EVENT_INFO_CHANGED
|
||||
&& event.mEventType != PendingTaskFragmentEvent.EVENT_APPEARED) {
|
||||
// Send events for any other types.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we should send the event given the Task visibility and events.
|
||||
final Task task;
|
||||
if (event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
|
||||
task = event.mTask;
|
||||
} else {
|
||||
task = event.mTaskFragment.getTask();
|
||||
}
|
||||
if (task.lastActiveTime > event.mDeferTime
|
||||
&& isTaskVisible(task, visibleTasks, invisibleTasks)) {
|
||||
// Send events when the app has at least one visible Task.
|
||||
return false;
|
||||
} else if (shouldSendEventWhenTaskInvisible(task, state, event)) {
|
||||
// Sent events even if the Task is invisible.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Defer sending events to the organizer until the host task is active (visible) again.
|
||||
event.mDeferTime = task.lastActiveTime;
|
||||
}
|
||||
// Defer for invisible Task.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isTaskVisible(@NonNull Task task,
|
||||
@@ -975,6 +987,28 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
|
||||
@NonNull TaskFragmentOrganizerState state,
|
||||
@NonNull PendingTaskFragmentEvent event) {
|
||||
final TaskFragmentParentInfo lastParentInfo = state.mLastSentTaskFragmentParentInfos
|
||||
.get(task.mTaskId);
|
||||
if (lastParentInfo == null || lastParentInfo.isVisible()) {
|
||||
// When the Task was visible, or when there was no Task info changed sent (in which case
|
||||
// the organizer will consider it as visible by default), always send the event to
|
||||
// update the Task visibility.
|
||||
return true;
|
||||
}
|
||||
if (event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
|
||||
// Send info changed if the TaskFragment is becoming empty/non-empty so the
|
||||
// organizer can choose whether or not to remove the TaskFragment.
|
||||
final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos
|
||||
.get(event.mTaskFragment);
|
||||
final boolean isEmpty = event.mTaskFragment.getNonFinishingActivityCount() == 0;
|
||||
return lastInfo == null || lastInfo.isEmpty() != isEmpty;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
|
||||
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
|
||||
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
|
||||
|
||||
@@ -874,29 +874,87 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
|
||||
@Test
|
||||
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
|
||||
// Task - TaskFragment - Activity.
|
||||
final Task task = createTask(mDisplayContent);
|
||||
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(task)
|
||||
.setOrganizer(mOrganizer)
|
||||
.setFragmentToken(mFragmentToken)
|
||||
.build();
|
||||
|
||||
// Mock the task to invisible
|
||||
doReturn(false).when(task).shouldBeVisible(any());
|
||||
|
||||
// Sending events
|
||||
taskFragment.mTaskFragmentAppearedSent = true;
|
||||
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
|
||||
// Verify that events were not sent when the Task is in background.
|
||||
clearInvocations(mOrganizer);
|
||||
final Rect bounds = new Rect(0, 0, 500, 1000);
|
||||
task.setBoundsUnchecked(bounds);
|
||||
mController.onTaskFragmentParentInfoChanged(mIOrganizer, task);
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// Verifies that event was not sent
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Verify that the events were sent when the Task becomes visible.
|
||||
doReturn(true).when(task).shouldBeVisible(any());
|
||||
task.lastActiveTime++;
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAllPendingTaskFragmentEventsWhenAnyTaskIsVisible() {
|
||||
// Invisible Task.
|
||||
final Task invisibleTask = createTask(mDisplayContent);
|
||||
final TaskFragment invisibleTaskFragment = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(invisibleTask)
|
||||
.setOrganizer(mOrganizer)
|
||||
.setFragmentToken(mFragmentToken)
|
||||
.build();
|
||||
doReturn(false).when(invisibleTask).shouldBeVisible(any());
|
||||
|
||||
// Visible Task.
|
||||
final IBinder fragmentToken = new Binder();
|
||||
final Task visibleTask = createTask(mDisplayContent);
|
||||
final TaskFragment visibleTaskFragment = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(visibleTask)
|
||||
.setOrganizer(mOrganizer)
|
||||
.setFragmentToken(fragmentToken)
|
||||
.build();
|
||||
doReturn(true).when(invisibleTask).shouldBeVisible(any());
|
||||
|
||||
// Sending events
|
||||
invisibleTaskFragment.mTaskFragmentAppearedSent = true;
|
||||
visibleTaskFragment.mTaskFragmentAppearedSent = true;
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, invisibleTaskFragment);
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, visibleTaskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// Verify that both events are sent.
|
||||
verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
|
||||
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
|
||||
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
|
||||
|
||||
// There should be two Task info changed with two TaskFragment info changed.
|
||||
assertEquals(4, changes.size());
|
||||
// Invisible Task info changed
|
||||
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(0).getType());
|
||||
assertEquals(invisibleTask.mTaskId, changes.get(0).getTaskId());
|
||||
// Invisible TaskFragment info changed
|
||||
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(1).getType());
|
||||
assertEquals(invisibleTaskFragment.getFragmentToken(),
|
||||
changes.get(1).getTaskFragmentToken());
|
||||
// Visible Task info changed
|
||||
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, changes.get(2).getType());
|
||||
assertEquals(visibleTask.mTaskId, changes.get(2).getTaskId());
|
||||
// Visible TaskFragment info changed
|
||||
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, changes.get(3).getType());
|
||||
assertEquals(visibleTaskFragment.getFragmentToken(), changes.get(3).getTaskFragmentToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
|
||||
// Task - TaskFragment - Activity.
|
||||
final Task task = createTask(mDisplayContent);
|
||||
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
||||
.setParentTask(task)
|
||||
@@ -905,24 +963,26 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
.createActivityCount(1)
|
||||
.build();
|
||||
final ActivityRecord activity = taskFragment.getTopMostActivity();
|
||||
|
||||
// Mock the task to invisible
|
||||
doReturn(false).when(task).shouldBeVisible(any());
|
||||
taskFragment.setResumedActivity(null, "test");
|
||||
|
||||
// Sending events
|
||||
taskFragment.mTaskFragmentAppearedSent = true;
|
||||
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
|
||||
// Verify the info changed event is not sent because the Task is invisible
|
||||
clearInvocations(mOrganizer);
|
||||
final Rect bounds = new Rect(0, 0, 500, 1000);
|
||||
task.setBoundsUnchecked(bounds);
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
|
||||
// Verifies that event was not sent
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Mock the task becomes visible, and activity resumed
|
||||
// Mock the task becomes visible, and activity resumed. Verify the info changed event is
|
||||
// sent.
|
||||
doReturn(true).when(task).shouldBeVisible(any());
|
||||
taskFragment.setResumedActivity(activity, "test");
|
||||
|
||||
// Verifies that event is sent.
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
}
|
||||
@@ -977,25 +1037,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
||||
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
|
||||
// Add another activity in the Task so that it always contains a non-finishing activity.
|
||||
createActivityRecord(task);
|
||||
assertTrue(task.shouldBeVisible(null));
|
||||
doReturn(false).when(task).shouldBeVisible(any());
|
||||
|
||||
// Dispatch pending info changed event from creating the activity
|
||||
taskFragment.mTaskFragmentAppearedSent = true;
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
|
||||
// Verify the info changed callback is not called when the task is invisible
|
||||
// Verify the info changed event is not sent because the Task is invisible
|
||||
clearInvocations(mOrganizer);
|
||||
doReturn(false).when(task).shouldBeVisible(any());
|
||||
final Rect bounds = new Rect(0, 0, 500, 1000);
|
||||
task.setBoundsUnchecked(bounds);
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer, never()).onTransactionReady(any());
|
||||
|
||||
// Finish the embedded activity, and verify the info changed callback is called because the
|
||||
// Finish the embedded activity, and verify the info changed event is sent because the
|
||||
// TaskFragment is becoming empty.
|
||||
embeddedActivity.finishing = true;
|
||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||
mController.dispatchPendingEvents();
|
||||
verify(mOrganizer).onTransactionReady(any());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user