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

Commit a6d5b490 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

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



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


    public SplitController() {
    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();
        ActivityThread activityThread = ActivityThread.currentActivityThread();
        // Register a callback to be notified about activities being created.
        // Register a callback to be notified about activities being created.
        activityThread.getApplication().registerActivityLifecycleCallbacks(
        activityThread.getApplication().registerActivityLifecycleCallbacks(
@@ -167,11 +170,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                // to fullscreen.
                // to fullscreen.
                cleanupForEnterPip(wct, container);
                cleanupForEnterPip(wct, container);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
            } else {
            } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                // Do not finish the dependents if this TaskFragment was cleared due to launching
                // Do not finish the dependents if this TaskFragment was cleared due to launching
                // activity in the Task.
                // activity in the Task.
                final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse();
                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
                mPresenter.cleanupContainer(container, 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) {
        } else if (wasInPip && isInPip) {
            // No update until exit PIP.
            // No update until exit PIP.
@@ -417,6 +422,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        launchPlaceholderIfNecessary(activity);
        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
     * Returns a container that this activity is registered with. An activity can only belong to one
     * container, or no container at all.
     * container, or no container at all.
@@ -452,7 +465,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (activityInTask == null) {
        if (activityInTask == null) {
            throw new IllegalArgumentException("activityInTask must not be 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)) {
        if (!mTaskContainers.contains(taskId)) {
            mTaskContainers.put(taskId, new TaskContainer(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--) {
        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
            final TaskFragmentContainer container = taskContainer.mContainers.get(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;
                return container;
            }
            }
        }
        }
@@ -920,6 +938,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return mTaskContainers.get(taskId);
        return mTaskContainers.get(taskId);
    }
    }


    Handler getHandler() {
        return mHandler;
    }

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


import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Iterator;
import java.util.List;
import java.util.List;
@@ -39,6 +41,11 @@ import java.util.List;
 * on the server side.
 * on the server side.
 */
 */
class TaskFragmentContainer {
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.
     * Client-created token that uniquely identifies the task fragment container instance.
     */
     */
@@ -51,7 +58,8 @@ class TaskFragmentContainer {
    /**
    /**
     * Server-provided task fragment information.
     * 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
     * Activities that are being reparented or being started to this container, but haven't been
@@ -80,11 +88,21 @@ class TaskFragmentContainer {
    @WindowingMode
    @WindowingMode
    private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
    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
     * Creates a container with an existing activity that will be re-parented to it in a window
     * container transaction.
     * container transaction.
     */
     */
    TaskFragmentContainer(@Nullable Activity activity, int taskId) {
    TaskFragmentContainer(@Nullable Activity activity, int taskId,
            @NonNull SplitController controller) {
        mController = controller;
        mToken = new Binder("TaskFragmentContainer");
        mToken = new Binder("TaskFragmentContainer");
        if (taskId == INVALID_TASK_ID) {
        if (taskId == INVALID_TASK_ID) {
            throw new IllegalArgumentException("Invalid Task id");
            throw new IllegalArgumentException("Invalid Task id");
@@ -155,12 +173,30 @@ class TaskFragmentContainer {
        return count;
        return count;
    }
    }


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

    @Nullable
    @Nullable
    TaskFragmentInfo getInfo() {
    TaskFragmentInfo getInfo() {
        return mInfo;
        return mInfo;
    }
    }


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


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


import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
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.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
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.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Point;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerToken;
@@ -61,6 +63,10 @@ public class JetpackTaskFragmentOrganizerTest {
    private WindowContainerTransaction mTransaction;
    private WindowContainerTransaction mTransaction;
    @Mock
    @Mock
    private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
    private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
    @Mock
    private SplitController mSplitController;
    @Mock
    private Handler mHandler;
    private JetpackTaskFragmentOrganizer mOrganizer;
    private JetpackTaskFragmentOrganizer mOrganizer;


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


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


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


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


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

    private SplitController mSplitController;
    private SplitController mSplitController;
    private SplitPresenter mSplitPresenter;
    private SplitPresenter mSplitPresenter;


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


    @Test
    @Test
@@ -94,28 +100,45 @@ public class SplitControllerTest {
        // tf3 is finished so is not active.
        // tf3 is finished so is not active.
        TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
        TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
        doReturn(true).when(tf3).isFinished();
        doReturn(true).when(tf3).isFinished();
        doReturn(false).when(tf3).isWaitingActivityAppear();
        // tf2 has running activity so is active.
        // tf2 has running activity so is active.
        TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
        TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
        doReturn(1).when(tf2).getRunningActivityCount();
        doReturn(1).when(tf2).getRunningActivityCount();
        // tf1 has no running activity so is not active.
        // 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(tf1);
        taskContainer.mContainers.add(tf2);
        taskContainer.mContainers.add(tf3);
        mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
        mSplitController.mTaskContainers.put(TASK_ID, taskContainer);


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


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


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


        taskContainer.mContainers.remove(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();
                .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
    }
    }


@@ -132,6 +155,14 @@ public class SplitControllerTest {
        verify(mActivity, never()).finish();
        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
    @Test
    public void testNewContainer() {
    public void testNewContainer() {
        // Must pass in a valid activity.
        // Must pass in a valid activity.
+13 −1
Original line number Original line 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.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;


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


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


    @Mock
    private SplitController mController;

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

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


        assertTrue(taskContainer.isEmpty());
        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);
        taskContainer.mContainers.add(tf);


        assertFalse(taskContainer.isEmpty());
        assertFalse(taskContainer.isEmpty());
Loading