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

Commit c924a21b authored by Chris Li's avatar Chris Li Committed by Automerger Merge Worker
Browse files

Merge "Don't launch placeholder for bottom TaskFragment when waiting for top"...

Merge "Don't launch placeholder for bottom TaskFragment when waiting for top" into tm-dev am: d4011e0e am: 38dbe45b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17983784



Change-Id: I705d957210730642bd4705986949f0c2c391586a
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 90c3766e 38dbe45b
Loading
Loading
Loading
Loading
+28 −6
Original line number Diff line number Diff line
@@ -81,9 +81,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @NonNull
    private Consumer<List<SplitInfo>> mEmbeddingCallback;
    private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
    private final Handler mHandler;

    public SplitController() {
        mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
        final MainThreadExecutor executor = new MainThreadExecutor();
        mHandler = executor.mHandler;
        mPresenter = new SplitPresenter(executor, this);
        ActivityThread activityThread = ActivityThread.currentActivityThread();
        // Register a callback to be notified about activities being created.
        activityThread.getApplication().registerActivityLifecycleCallbacks(
@@ -167,11 +170,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                // to fullscreen.
                cleanupForEnterPip(wct, container);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
            } else {
            } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                // Do not finish the dependents if this TaskFragment was cleared due to launching
                // activity in the Task.
                final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse();
                mPresenter.cleanupContainer(container, shouldFinishDependent, wct);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
            } else if (!container.isWaitingActivityAppear()) {
                // Do not finish the container before the expected activity appear until timeout.
                mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
            }
        } else if (wasInPip && isInPip) {
            // No update until exit PIP.
@@ -417,6 +422,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        launchPlaceholderIfNecessary(activity);
    }

    /**
     * Called when we have been waiting too long for the TaskFragment to become non-empty after
     * creation.
     */
    void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
        mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
    }

    /**
     * Returns a container that this activity is registered with. An activity can only belong to one
     * container, or no container at all.
@@ -452,7 +465,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (activityInTask == null) {
            throw new IllegalArgumentException("activityInTask must not be null,");
        }
        final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
        final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId, this);
        if (!mTaskContainers.contains(taskId)) {
            mTaskContainers.put(taskId, new TaskContainer(taskId));
        }
@@ -590,7 +603,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
            final TaskFragmentContainer container = taskContainer.mContainers.get(i);
            if (!container.isFinished() && container.getRunningActivityCount() > 0) {
            if (!container.isFinished() && (container.getRunningActivityCount() > 0
                    // We may be waiting for the top TaskFragment to become non-empty after
                    // creation. In that case, we don't want to treat the TaskFragment below it as
                    // top active, otherwise it may incorrectly launch placeholder on top of the
                    // pending TaskFragment.
                    || container.isWaitingActivityAppear())) {
                return container;
            }
        }
@@ -920,6 +938,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return mTaskContainers.get(taskId);
    }

    Handler getHandler() {
        return mHandler;
    }

    /**
     * Returns {@code true} if an Activity with the provided component name should always be
     * expanded to occupy full task bounds. Such activity must not be put in a split.
+42 −2
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import android.os.IBinder;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -39,6 +41,11 @@ import java.util.List;
 * on the server side.
 */
class TaskFragmentContainer {
    private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;

    @NonNull
    private final SplitController mController;

    /**
     * Client-created token that uniquely identifies the task fragment container instance.
     */
@@ -51,7 +58,8 @@ class TaskFragmentContainer {
    /**
     * Server-provided task fragment information.
     */
    private TaskFragmentInfo mInfo;
    @VisibleForTesting
    TaskFragmentInfo mInfo;

    /**
     * Activities that are being reparented or being started to this container, but haven't been
@@ -80,11 +88,21 @@ class TaskFragmentContainer {
    @WindowingMode
    private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;

    /**
     * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
     * if it is still empty after the timeout.
     */
    @VisibleForTesting
    @Nullable
    Runnable mAppearEmptyTimeout;

    /**
     * Creates a container with an existing activity that will be re-parented to it in a window
     * container transaction.
     */
    TaskFragmentContainer(@Nullable Activity activity, int taskId) {
    TaskFragmentContainer(@Nullable Activity activity, int taskId,
            @NonNull SplitController controller) {
        mController = controller;
        mToken = new Binder("TaskFragmentContainer");
        if (taskId == INVALID_TASK_ID) {
            throw new IllegalArgumentException("Invalid Task id");
@@ -155,12 +173,30 @@ class TaskFragmentContainer {
        return count;
    }

    /** Whether we are waiting for the TaskFragment to appear and become non-empty. */
    boolean isWaitingActivityAppear() {
        return !mIsFinished && (mInfo == null || mAppearEmptyTimeout != null);
    }

    @Nullable
    TaskFragmentInfo getInfo() {
        return mInfo;
    }

    void setInfo(@NonNull TaskFragmentInfo info) {
        if (!mIsFinished && mInfo == null && info.isEmpty()) {
            // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is
            // still empty after timeout.
            mAppearEmptyTimeout = () -> {
                mAppearEmptyTimeout = null;
                mController.onTaskFragmentAppearEmptyTimeout(this);
            };
            mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
        } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
            mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
            mAppearEmptyTimeout = null;
        }

        mInfo = info;
        if (mInfo == null || mPendingAppearedActivities.isEmpty()) {
            return;
@@ -234,6 +270,10 @@ class TaskFragmentContainer {
            @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
        if (!mIsFinished) {
            mIsFinished = true;
            if (mAppearEmptyTimeout != null) {
                mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
                mAppearEmptyTimeout = null;
            }
            finishActivities(shouldFinishDependent, presenter, wct, controller);
        }

+9 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;

@@ -29,6 +30,7 @@ import static org.mockito.Mockito.never;

import android.content.res.Configuration;
import android.graphics.Point;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken;
@@ -61,6 +63,10 @@ public class JetpackTaskFragmentOrganizerTest {
    private WindowContainerTransaction mTransaction;
    @Mock
    private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
    @Mock
    private SplitController mSplitController;
    @Mock
    private Handler mHandler;
    private JetpackTaskFragmentOrganizer mOrganizer;

    @Before
@@ -69,6 +75,7 @@ public class JetpackTaskFragmentOrganizerTest {
        mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
        mOrganizer.registerOrganizer();
        spyOn(mOrganizer);
        doReturn(mHandler).when(mSplitController).getHandler();
    }

    @Test
@@ -106,7 +113,8 @@ public class JetpackTaskFragmentOrganizerTest {

    @Test
    public void testExpandTaskFragment() {
        final TaskFragmentContainer container = new TaskFragmentContainer(null, TASK_ID);
        final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                TASK_ID, mSplitController);
        final TaskFragmentInfo info = createMockInfo(container);
        mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
        container.setInfo(info);
+36 −5
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -48,6 +49,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

/**
@@ -71,6 +73,9 @@ public class SplitControllerTest {
    private TaskFragmentInfo mInfo;
    @Mock
    private WindowContainerTransaction mTransaction;
    @Mock
    private Handler mHandler;

    private SplitController mSplitController;
    private SplitPresenter mSplitPresenter;

@@ -86,6 +91,7 @@ public class SplitControllerTest {
        activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
        doReturn(mActivityResources).when(mActivity).getResources();
        doReturn(activityConfig).when(mActivityResources).getConfiguration();
        doReturn(mHandler).when(mSplitController).getHandler();
    }

    @Test
@@ -94,28 +100,45 @@ public class SplitControllerTest {
        // tf3 is finished so is not active.
        TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
        doReturn(true).when(tf3).isFinished();
        doReturn(false).when(tf3).isWaitingActivityAppear();
        // tf2 has running activity so is active.
        TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
        doReturn(1).when(tf2).getRunningActivityCount();
        // tf1 has no running activity so is not active.
        TaskFragmentContainer tf1 = new TaskFragmentContainer(null, TASK_ID);
        TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, TASK_ID,
                mSplitController);

        taskContainer.mContainers.add(tf3);
        taskContainer.mContainers.add(tf2);
        taskContainer.mContainers.add(tf1);
        taskContainer.mContainers.add(tf2);
        taskContainer.mContainers.add(tf3);
        mSplitController.mTaskContainers.put(TASK_ID, taskContainer);

        assertWithMessage("Must return tf2 because tf3 is not active.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);

        taskContainer.mContainers.remove(tf1);
        taskContainer.mContainers.remove(tf3);

        assertWithMessage("Must return tf2 because tf2 has running activity.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);

        taskContainer.mContainers.remove(tf2);

        assertWithMessage("Must return null because tf1 has no running activity.")
        assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);

        final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
        doReturn(new ArrayList<>()).when(info).getActivities();
        doReturn(true).when(info).isEmpty();
        tf1.setInfo(info);

        assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
                + " creation.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);

        doReturn(false).when(info).isEmpty();
        tf1.setInfo(info);

        assertWithMessage("Must return null because tf1 becomes empty.")
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
    }

@@ -132,6 +155,14 @@ public class SplitControllerTest {
        verify(mActivity, never()).finish();
    }

    @Test
    public void testOnTaskFragmentAppearEmptyTimeout() {
        final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
        mSplitController.onTaskFragmentAppearEmptyTimeout(tf);

        verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */);
    }

    @Test
    public void testNewContainer() {
        // Must pass in a valid activity.
+13 −1
Original line number Diff line number Diff line
@@ -32,8 +32,11 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Test class for {@link TaskContainer}.
@@ -48,6 +51,14 @@ public class TaskContainerTest {
    private static final int TASK_ID = 10;
    private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);

    @Mock
    private SplitController mController;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testIsTaskBoundsInitialized() {
        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
@@ -126,7 +137,8 @@ public class TaskContainerTest {

        assertTrue(taskContainer.isEmpty());

        final TaskFragmentContainer tf = new TaskFragmentContainer(null, TASK_ID);
        final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, TASK_ID,
                mController);
        taskContainer.mContainers.add(tf);

        assertFalse(taskContainer.isEmpty());
Loading