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);
|
return (mSplitRule instanceof SplitPlaceholderRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
SplitInfo toSplitInfo() {
|
||||||
|
return new SplitInfo(mPrimaryContainer.toActivityStack(),
|
||||||
|
mSecondaryContainer.toActivityStack(), mSplitAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
|
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
|
||||||
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
|
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
|
||||||
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
|
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
|
||||||
|
|||||||
@@ -1422,6 +1422,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
void updateContainer(@NonNull WindowContainerTransaction wct,
|
void updateContainer(@NonNull WindowContainerTransaction wct,
|
||||||
@NonNull TaskFragmentContainer container) {
|
@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)) {
|
if (launchPlaceholderIfNecessary(wct, container)) {
|
||||||
// Placeholder was launched, the positions will be updated when the activity is added
|
// Placeholder was launched, the positions will be updated when the activity is added
|
||||||
// to the secondary container.
|
// to the secondary container.
|
||||||
@@ -1643,16 +1648,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
/**
|
/**
|
||||||
* Notifies listeners about changes to split states if necessary.
|
* Notifies listeners about changes to split states if necessary.
|
||||||
*/
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private void updateCallbackIfNecessary() {
|
void updateCallbackIfNecessary() {
|
||||||
if (mEmbeddingCallback == null) {
|
if (mEmbeddingCallback == null || !readyToReportToClient()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!allActivitiesCreated()) {
|
final List<SplitInfo> currentSplitStates = getActiveSplitStates();
|
||||||
return;
|
if (mLastReportedSplitStates.equals(currentSplitStates)) {
|
||||||
}
|
|
||||||
List<SplitInfo> currentSplitStates = getActiveSplitStates();
|
|
||||||
if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mLastReportedSplitStates.clear();
|
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
|
* Returns a list of descriptors for currently active split states.
|
||||||
* null, that indicates that the active split states are in an intermediate state and should
|
|
||||||
* not be reported.
|
|
||||||
*/
|
*/
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@Nullable
|
@NonNull
|
||||||
private List<SplitInfo> getActiveSplitStates() {
|
private List<SplitInfo> getActiveSplitStates() {
|
||||||
List<SplitInfo> splitStates = new ArrayList<>();
|
final List<SplitInfo> splitStates = new ArrayList<>();
|
||||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||||
final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
|
mTaskContainers.valueAt(i).getSplitStates(splitStates);
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return splitStates;
|
return splitStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all activities that are registered with the containers have already appeared in
|
* Whether we can now report the split states to the client.
|
||||||
* the client.
|
|
||||||
*/
|
*/
|
||||||
private boolean allActivitiesCreated() {
|
@GuardedBy("mLock")
|
||||||
|
private boolean readyToReportToClient() {
|
||||||
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
|
||||||
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|
if (mTaskContainers.valueAt(i).isInIntermediateState()) {
|
||||||
for (TaskFragmentContainer container : containers) {
|
// If any Task is in an intermediate state, wait for the server update.
|
||||||
if (!container.taskInfoActivityCountMatchesCreated()) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -221,6 +221,24 @@ class TaskContainer {
|
|||||||
return mContainers.indexOf(child);
|
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
|
* A wrapper class which contains the display ID and {@link Configuration} of a
|
||||||
* {@link TaskContainer}
|
* {@link TaskContainer}
|
||||||
|
|||||||
@@ -166,16 +166,34 @@ class TaskFragmentContainer {
|
|||||||
return allActivities;
|
return allActivities;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
|
||||||
* Checks if the count of activities from the same process in task fragment info corresponds to
|
boolean isInIntermediateState() {
|
||||||
* the ones created and available on the client side.
|
|
||||||
*/
|
|
||||||
boolean taskInfoActivityCountMatchesCreated() {
|
|
||||||
if (mInfo == null) {
|
if (mInfo == null) {
|
||||||
return false;
|
// Haven't received onTaskFragmentAppeared event.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return mPendingAppearedActivities.isEmpty()
|
if (mInfo.isEmpty()) {
|
||||||
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
|
// 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
|
@NonNull
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ import org.mockito.MockitoAnnotations;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for {@link SplitController}.
|
* Test class for {@link SplitController}.
|
||||||
@@ -132,6 +133,8 @@ public class SplitControllerTest {
|
|||||||
|
|
||||||
private SplitController mSplitController;
|
private SplitController mSplitController;
|
||||||
private SplitPresenter mSplitPresenter;
|
private SplitPresenter mSplitPresenter;
|
||||||
|
private Consumer<List<SplitInfo>> mEmbeddingCallback;
|
||||||
|
private List<SplitInfo> mSplitInfos;
|
||||||
private TransactionManager mTransactionManager;
|
private TransactionManager mTransactionManager;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -141,9 +144,16 @@ public class SplitControllerTest {
|
|||||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||||
mSplitController = new SplitController(mWindowLayoutComponent);
|
mSplitController = new SplitController(mWindowLayoutComponent);
|
||||||
mSplitPresenter = mSplitController.mPresenter;
|
mSplitPresenter = mSplitController.mPresenter;
|
||||||
|
mSplitInfos = new ArrayList<>();
|
||||||
|
mEmbeddingCallback = splitInfos -> {
|
||||||
|
mSplitInfos.clear();
|
||||||
|
mSplitInfos.addAll(splitInfos);
|
||||||
|
};
|
||||||
|
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
|
||||||
mTransactionManager = mSplitController.mTransactionManager;
|
mTransactionManager = mSplitController.mTransactionManager;
|
||||||
spyOn(mSplitController);
|
spyOn(mSplitController);
|
||||||
spyOn(mSplitPresenter);
|
spyOn(mSplitPresenter);
|
||||||
|
spyOn(mEmbeddingCallback);
|
||||||
spyOn(mTransactionManager);
|
spyOn(mTransactionManager);
|
||||||
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
|
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
|
||||||
final Configuration activityConfig = new Configuration();
|
final Configuration activityConfig = new Configuration();
|
||||||
@@ -328,6 +338,30 @@ public class SplitControllerTest {
|
|||||||
verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
|
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
|
@Test
|
||||||
public void testOnStartActivityResultError() {
|
public void testOnStartActivityResultError() {
|
||||||
final Intent intent = new Intent();
|
final Intent intent = new Intent();
|
||||||
@@ -1162,14 +1196,69 @@ public class SplitControllerTest {
|
|||||||
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
|
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. */
|
/** Creates a mock activity in the organizer process. */
|
||||||
private Activity createMockActivity() {
|
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);
|
final Activity activity = mock(Activity.class);
|
||||||
doReturn(mActivityResources).when(activity).getResources();
|
doReturn(mActivityResources).when(activity).getResources();
|
||||||
final IBinder activityToken = new Binder();
|
final IBinder activityToken = new Binder();
|
||||||
doReturn(activityToken).when(activity).getActivityToken();
|
doReturn(activityToken).when(activity).getActivityToken();
|
||||||
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
||||||
doReturn(TASK_ID).when(activity).getTaskId();
|
doReturn(taskId).when(activity).getTaskId();
|
||||||
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
|
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
|
||||||
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
|
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
|
||||||
return activity;
|
return activity;
|
||||||
@@ -1177,7 +1266,8 @@ public class SplitControllerTest {
|
|||||||
|
|
||||||
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
|
||||||
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
|
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);
|
setupTaskFragmentInfo(container, activity);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -1268,7 +1358,7 @@ public class SplitControllerTest {
|
|||||||
|
|
||||||
// We need to set those in case we are not respecting clear top.
|
// We need to set those in case we are not respecting clear top.
|
||||||
// TODO(b/231845476) we should always respect clearTop.
|
// 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);
|
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
|
||||||
primaryContainer.setLastRequestedWindowingMode(windowingMode);
|
primaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||||
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
|
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
|
||||||
|
|||||||
@@ -334,6 +334,70 @@ public class TaskFragmentContainerTest {
|
|||||||
assertFalse(container.hasActivity(mActivity.getActivityToken()));
|
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. */
|
/** Creates a mock activity in the organizer process. */
|
||||||
private Activity createMockActivity() {
|
private Activity createMockActivity() {
|
||||||
final Activity activity = mock(Activity.class);
|
final Activity activity = mock(Activity.class);
|
||||||
|
|||||||
@@ -607,6 +607,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
|||||||
int opType, @NonNull Throwable exception) {
|
int opType, @NonNull Throwable exception) {
|
||||||
validateAndGetState(organizer);
|
validateAndGetState(organizer);
|
||||||
Slog.w(TAG, "onTaskFragmentError ", exception);
|
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(
|
addPendingEvent(new PendingTaskFragmentEvent.Builder(
|
||||||
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
|
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
|
||||||
.setErrorCallbackToken(errorCallbackToken)
|
.setErrorCallbackToken(errorCallbackToken)
|
||||||
@@ -878,23 +885,6 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
|||||||
return null;
|
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() {
|
void dispatchPendingEvents() {
|
||||||
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|
||||||
|| mPendingTaskFragmentEvents.isEmpty()) {
|
|| 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) {
|
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
|
||||||
if (pendingEvents.isEmpty()) {
|
if (pendingEvents.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (shouldDeferPendingEvents(state, pendingEvents)) {
|
||||||
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTmpTaskSet.clear();
|
mTmpTaskSet.clear();
|
||||||
|
final int numEvents = pendingEvents.size();
|
||||||
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
|
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
|
||||||
for (int i = 0; i < numEvents; i++) {
|
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
|
if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED
|
||||||
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
|
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
|
||||||
final Task task = event.mTaskFragment.getTask();
|
final Task task = event.mTaskFragment.getTask();
|
||||||
@@ -954,7 +926,47 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
|
|||||||
}
|
}
|
||||||
mTmpTaskSet.clear();
|
mTmpTaskSet.clear();
|
||||||
state.dispatchTransaction(transaction);
|
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,
|
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) {
|
void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
|
||||||
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
|
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
|
||||||
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
|
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
|
||||||
|
|||||||
@@ -874,29 +874,87 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
|
public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
|
||||||
// Task - TaskFragment - Activity.
|
|
||||||
final Task task = createTask(mDisplayContent);
|
final Task task = createTask(mDisplayContent);
|
||||||
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
||||||
.setParentTask(task)
|
.setParentTask(task)
|
||||||
.setOrganizer(mOrganizer)
|
.setOrganizer(mOrganizer)
|
||||||
.setFragmentToken(mFragmentToken)
|
.setFragmentToken(mFragmentToken)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Mock the task to invisible
|
|
||||||
doReturn(false).when(task).shouldBeVisible(any());
|
doReturn(false).when(task).shouldBeVisible(any());
|
||||||
|
|
||||||
// Sending events
|
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||||
taskFragment.mTaskFragmentAppearedSent = true;
|
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.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
|
|
||||||
// Verifies that event was not sent
|
|
||||||
verify(mOrganizer, never()).onTransactionReady(any());
|
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
|
@Test
|
||||||
public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
|
public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
|
||||||
// Task - TaskFragment - Activity.
|
|
||||||
final Task task = createTask(mDisplayContent);
|
final Task task = createTask(mDisplayContent);
|
||||||
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
|
||||||
.setParentTask(task)
|
.setParentTask(task)
|
||||||
@@ -905,24 +963,26 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
|||||||
.createActivityCount(1)
|
.createActivityCount(1)
|
||||||
.build();
|
.build();
|
||||||
final ActivityRecord activity = taskFragment.getTopMostActivity();
|
final ActivityRecord activity = taskFragment.getTopMostActivity();
|
||||||
|
|
||||||
// Mock the task to invisible
|
|
||||||
doReturn(false).when(task).shouldBeVisible(any());
|
doReturn(false).when(task).shouldBeVisible(any());
|
||||||
taskFragment.setResumedActivity(null, "test");
|
taskFragment.setResumedActivity(null, "test");
|
||||||
|
|
||||||
// Sending events
|
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||||
taskFragment.mTaskFragmentAppearedSent = true;
|
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.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
|
|
||||||
// Verifies that event was not sent
|
|
||||||
verify(mOrganizer, never()).onTransactionReady(any());
|
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());
|
doReturn(true).when(task).shouldBeVisible(any());
|
||||||
taskFragment.setResumedActivity(activity, "test");
|
taskFragment.setResumedActivity(activity, "test");
|
||||||
|
|
||||||
// Verifies that event is sent.
|
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
verify(mOrganizer).onTransactionReady(any());
|
verify(mOrganizer).onTransactionReady(any());
|
||||||
}
|
}
|
||||||
@@ -977,25 +1037,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
|||||||
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
|
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
|
||||||
// Add another activity in the Task so that it always contains a non-finishing activity.
|
// Add another activity in the Task so that it always contains a non-finishing activity.
|
||||||
createActivityRecord(task);
|
createActivityRecord(task);
|
||||||
assertTrue(task.shouldBeVisible(null));
|
doReturn(false).when(task).shouldBeVisible(any());
|
||||||
|
|
||||||
// Dispatch pending info changed event from creating the activity
|
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
|
||||||
taskFragment.mTaskFragmentAppearedSent = true;
|
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
|
||||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
verify(mOrganizer).onTransactionReady(any());
|
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);
|
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.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
verify(mOrganizer, never()).onTransactionReady(any());
|
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.
|
// TaskFragment is becoming empty.
|
||||||
embeddedActivity.finishing = true;
|
embeddedActivity.finishing = true;
|
||||||
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
|
|
||||||
mController.dispatchPendingEvents();
|
mController.dispatchPendingEvents();
|
||||||
verify(mOrganizer).onTransactionReady(any());
|
verify(mOrganizer).onTransactionReady(any());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user