Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8345a1f authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Move the ActivityEmbedding Task visibility filter to the client" into tm-qpr-dev

parents 37840b8a 5dafb54f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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)
+20 −38
Original line number Diff line number Diff line
@@ -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) {
            return;
        }
        if (!allActivitiesCreated()) {
    void updateCallbackIfNecessary() {
        if (mEmbeddingCallback == null || !readyToReportToClient()) {
            return;
        }
        List<SplitInfo> currentSplitStates = getActiveSplitStates();
        if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
        final List<SplitInfo> currentSplitStates = getActiveSplitStates();
        if (mLastReportedSplitStates.equals(currentSplitStates)) {
            return;
        }
        mLastReportedSplitStates.clear();
@@ -1661,50 +1664,29 @@ 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()) {
            if (mTaskContainers.valueAt(i).isInIntermediateState()) {
                // If any Task is in an intermediate state, wait for the server update.
                return false;
            }
        }
        }
        return true;
    }

+18 −0
Original line number Diff line number Diff line
@@ -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}
+26 −8
Original line number Diff line number Diff line
@@ -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;
        }
        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;
        }
        return mPendingAppearedActivities.isEmpty()
                && mInfo.getActivities().size() == collectNonFinishingActivities().size();
        // 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
+93 −3
Original line number Diff line number Diff line
@@ -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);
Loading