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

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

Merge "Keep track of the WCT changes to determine the transition type" into tm-qpr-dev

parents 74787ef1 d34770e6
Loading
Loading
Loading
Loading
+50 −21
Original line number Diff line number Diff line
@@ -20,10 +20,11 @@ import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -76,6 +77,7 @@ import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsProvider;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;

import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    final SplitPresenter mPresenter;

    @VisibleForTesting
    @GuardedBy("mLock")
    final TransactionManager mTransactionManager;

    // Currently applied split configuration.
    @GuardedBy("mLock")
    private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -150,6 +156,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        final MainThreadExecutor executor = new MainThreadExecutor();
        mHandler = executor.mHandler;
        mPresenter = new SplitPresenter(executor, this);
        mTransactionManager = new TransactionManager(mPresenter);
        final ActivityThread activityThread = ActivityThread.currentActivityThread();
        final Application application = activityThread.getApplication();
        // Register a callback to be notified about activities being created.
@@ -167,7 +174,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        @Override
        public void accept(List<CommonFoldingFeature> foldingFeatures) {
            synchronized (mLock) {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                final TransactionRecord transactionRecord = mTransactionManager
                        .startNewTransaction();
                final WindowContainerTransaction wct = transactionRecord.getTransaction();
                for (int i = 0; i < mTaskContainers.size(); i++) {
                    final TaskContainer taskContainer = mTaskContainers.valueAt(i);
                    if (!taskContainer.isVisible()) {
@@ -186,7 +195,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                    updateContainersInTask(wct, taskContainer);
                    updateAnimationOverride(taskContainer);
                }
                mPresenter.applyTransaction(wct);
                // The WCT should be applied and merged to the device state change transition if
                // there is one.
                transactionRecord.apply(false /* shouldApplyIndependently */);
            }
        }
    }
@@ -256,7 +267,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @Override
    public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
        synchronized (mLock) {
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
                    transaction.getTransactionToken());
            final WindowContainerTransaction wct = transactionRecord.getTransaction();
            final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
            for (TaskFragmentTransaction.Change change : changes) {
                final int taskId = change.getTaskId();
@@ -307,8 +320,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

            // Notify the server, and the server should apply and merge the
            // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
            mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
                    getTransitionType(wct), false /* shouldApplyIndependently */);
            transactionRecord.apply(false /* shouldApplyIndependently */);
            updateCallbackIfNecessary();
        }
    }
@@ -333,6 +345,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

        container.setInfo(wct, taskFragmentInfo);
        if (container.isFinished()) {
            mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
            mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
        } else {
            // Update with the latest Task configuration.
@@ -368,15 +381,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                // Do not finish the dependents if the last activity is reparented to PiP.
                // Instead, the original split should be cleanup, and the dependent may be
                // expanded to fullscreen.
                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                cleanupForEnterPip(wct, container);
                mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
            } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                // Do not finish the dependents if this TaskFragment was cleared due to
                // launching activity in the Task.
                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
            } else if (!container.isWaitingActivityAppear()) {
                // Do not finish the container before the expected activity appear until
                // timeout.
                mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
            }
        } else if (wasInPip && isInPip) {
@@ -571,6 +587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                container.setInfo(wct, taskFragmentInfo);
                container.clearPendingAppearedActivities();
                if (container.isEmpty()) {
                    mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
                }
                break;
@@ -1009,11 +1026,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
    @GuardedBy("mLock")
    void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        onTaskFragmentAppearEmptyTimeout(wct, container);
        final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
        onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
        // Can be applied independently as a timeout callback.
        mPresenter.applyTransaction(wct, getTransitionType(wct),
                true /* shouldApplyIndependently */);
        transactionRecord.apply(true /* shouldApplyIndependently */);
    }

    /**
@@ -1023,6 +1039,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @GuardedBy("mLock")
    void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentContainer container) {
        mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
        mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
    }

@@ -1562,6 +1579,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * @param isOnCreated       whether this happens during the primary activity onCreated.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    @Nullable
    Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
        // Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1569,6 +1587,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // Check if the primary is resumed or if this is called when the primary is onCreated
        // (not resumed yet).
        if (isOnCreated || primaryActivity.isResumed()) {
            // Only set trigger type if the launch happens in foreground.
            mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
            return null;
        }
        final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1595,6 +1615,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (SplitPresenter.shouldShowSplit(splitAttributes)) {
            return false;
        }

        mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
        mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
                false /* shouldFinishDependent */);
        return true;
@@ -1905,23 +1927,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // that we don't launch it if an activity itself already requested something to be
            // launched to side.
            synchronized (mLock) {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                SplitController.this.onActivityCreated(wct, activity);
                final TransactionRecord transactionRecord = mTransactionManager
                        .startNewTransaction();
                transactionRecord.setOriginType(TRANSIT_OPEN);
                SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
                        activity);
                // The WCT should be applied and merged to the activity launch transition.
                mPresenter.applyTransaction(wct, getTransitionType(wct),
                        false /* shouldApplyIndependently */);
                transactionRecord.apply(false /* shouldApplyIndependently */);
            }
        }

        @Override
        public void onActivityConfigurationChanged(@NonNull Activity activity) {
            synchronized (mLock) {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                SplitController.this.onActivityConfigurationChanged(wct, activity);
                final TransactionRecord transactionRecord = mTransactionManager
                        .startNewTransaction();
                SplitController.this.onActivityConfigurationChanged(
                        transactionRecord.getTransaction(), activity);
                // The WCT should be applied and merged to the Task change transition so that the
                // placeholder is launched in the same transition.
                mPresenter.applyTransaction(wct, getTransitionType(wct),
                        false /* shouldApplyIndependently */);
                transactionRecord.apply(false /* shouldApplyIndependently */);
            }
        }

@@ -1977,7 +2002,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }

            synchronized (mLock) {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                final TransactionRecord transactionRecord = mTransactionManager
                        .startNewTransaction();
                transactionRecord.setOriginType(TRANSIT_OPEN);
                final WindowContainerTransaction wct = transactionRecord.getTransaction();
                final TaskFragmentContainer launchedInTaskFragment;
                if (launchingActivity != null) {
                    final int taskId = getTaskId(launchingActivity);
@@ -1990,13 +2018,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                if (launchedInTaskFragment != null) {
                    // Make sure the WCT is applied immediately instead of being queued so that the
                    // TaskFragment will be ready before activity attachment.
                    mPresenter.applyTransaction(wct, getTransitionType(wct),
                            false /* shouldApplyIndependently */);
                    transactionRecord.apply(false /* shouldApplyIndependently */);
                    // Amend the request to let the WM know that the activity should be placed in
                    // the dedicated container.
                    options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                            launchedInTaskFragment.getTaskFragmentToken());
                    mCurrentIntent = intent;
                } else {
                    transactionRecord.abort();
                }
            }

+201 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.window.extensions.embedding;

import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_NONE;

import android.os.IBinder;
import android.view.WindowManager.TransitionType;
import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
 * state changes and app interactions.
 *
 * A typical use flow:
 * 1. Call {@link #startNewTransaction} to start tracking the changes.
 * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
 *    will start a new transition on system server.
 * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
 *    changes.
 * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
 *    the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
 *    dispose the current one.
 *
 * Note:
 * There should be only one transaction at a time. The caller should not call
 * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
 * {@link TransactionRecord#abort()} to the previous transaction.
 */
class TransactionManager {

    @NonNull
    private final TaskFragmentOrganizer mOrganizer;

    @Nullable
    private TransactionRecord mCurrentTransaction;

    TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
        mOrganizer = organizer;
    }

    @NonNull
    TransactionRecord startNewTransaction() {
        return startNewTransaction(null /* taskFragmentTransactionToken */);
    }

    /**
     * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
     * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
     * transaction until {@link TransactionRecord#apply(boolean)} or
     * {@link TransactionRecord#abort()} is called.
     * @param taskFragmentTransactionToken  {@link android.window.TaskFragmentTransaction
     *                                      #getTransactionToken()} if this is a response to a
     *                                      {@link android.window.TaskFragmentTransaction}.
     */
    @NonNull
    TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
        if (mCurrentTransaction != null) {
            mCurrentTransaction = null;
            throw new IllegalStateException(
                    "The previous transaction has not been applied or aborted,");
        }
        mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
        return mCurrentTransaction;
    }

    /**
     * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
     */
    @NonNull
    TransactionRecord getCurrentTransactionRecord() {
        if (mCurrentTransaction == null) {
            throw new IllegalStateException("startNewTransaction() is not invoked before calling"
                    + " getCurrentTransactionRecord().");
        }
        return mCurrentTransaction;
    }

    /** The current transaction. The manager should only handle one transaction at a time. */
    class TransactionRecord {
        /**
         * {@link WindowContainerTransaction} containing the current change.
         * @see #startNewTransaction(IBinder)
         * @see #apply (boolean)
         */
        @NonNull
        private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();

        /**
         * If the current transaction is a response to a
         * {@link android.window.TaskFragmentTransaction}, this is the
         * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
         * @see #startNewTransaction(IBinder)
         */
        @Nullable
        private final IBinder mTaskFragmentTransactionToken;

        /**
         * To track of the origin type of the current {@link #mTransaction}. When
         * {@link #apply (boolean)} to start a new transition, this is the type to request.
         * @see #setOriginType(int)
         * @see #getTransactionTransitionType()
         */
        @TransitionType
        private int mOriginType = TRANSIT_NONE;

        TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
            mTaskFragmentTransactionToken = taskFragmentTransactionToken;
        }

        @NonNull
        WindowContainerTransaction getTransaction() {
            ensureCurrentTransaction();
            return mTransaction;
        }

        /**
         * Sets the {@link TransitionType} that triggers this transaction. If there are multiple
         * calls, only the first call will be respected as the "origin" type.
         */
        void setOriginType(@TransitionType int type) {
            ensureCurrentTransaction();
            if (mOriginType != TRANSIT_NONE) {
                // Skip if the origin type has already been set.
                return;
            }
            mOriginType = type;
        }

        /**
         * Requests the system server to apply the current transaction started from
         * {@link #startNewTransaction}.
         * @param shouldApplyIndependently  If {@code true}, the {@link #mCurrentTransaction} will
         *                                  request a new transition, which will be queued until the
         *                                  sync engine is free if there is any other active sync.
         *                                  If {@code false}, the {@link #startNewTransaction} will
         *                                  be directly applied to the active sync.
         */
        void apply(boolean shouldApplyIndependently) {
            ensureCurrentTransaction();
            if (mTaskFragmentTransactionToken != null) {
                // If this is a response to a TaskFragmentTransaction.
                mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
                        getTransactionTransitionType(), shouldApplyIndependently);
            } else {
                mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
                        shouldApplyIndependently);
            }
            dispose();
        }

        /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
        void abort() {
            ensureCurrentTransaction();
            dispose();
        }

        private void dispose() {
            TransactionManager.this.mCurrentTransaction = null;
        }

        private void ensureCurrentTransaction() {
            if (TransactionManager.this.mCurrentTransaction != this) {
                throw new IllegalStateException(
                        "This transaction has already been apply() or abort().");
            }
        }

        /**
         * Gets the {@link TransitionType} that we will request transition with for the
         * current {@link WindowContainerTransaction}.
         */
        @VisibleForTesting
        @TransitionType
        int getTransactionTransitionType() {
            // Use TRANSIT_CHANGE as default if there is not opening/closing window.
            return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
        }
    }
}
+13 −2
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ public class SplitControllerTest {

    private SplitController mSplitController;
    private SplitPresenter mSplitPresenter;
    private TransactionManager mTransactionManager;

    @Before
    public void setUp() {
@@ -140,8 +141,10 @@ public class SplitControllerTest {
                .getCurrentWindowLayoutInfo(anyInt(), any());
        mSplitController = new SplitController(mWindowLayoutComponent);
        mSplitPresenter = mSplitController.mPresenter;
        mTransactionManager = mSplitController.mTransactionManager;
        spyOn(mSplitController);
        spyOn(mSplitPresenter);
        spyOn(mTransactionManager);
        doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
        final Configuration activityConfig = new Configuration();
        activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -212,6 +215,8 @@ public class SplitControllerTest {

    @Test
    public void testOnTaskFragmentAppearEmptyTimeout() {
        // Setup to make sure a transaction record is started.
        mTransactionManager.startNewTransaction();
        final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
        doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
        mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -615,6 +620,8 @@ public class SplitControllerTest {

    @Test
    public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
        // Setup to make sure a transaction record is started.
        mTransactionManager.startNewTransaction();
        setupPlaceholderRule(mActivity);
        final SplitPlaceholderRule placeholderRule =
                (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +654,8 @@ public class SplitControllerTest {

    @Test
    public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
        // Setup to make sure a transaction record is started.
        mTransactionManager.startNewTransaction();
        setupPlaceholderRule(mActivity);
        final SplitPlaceholderRule placeholderRule =
                (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +688,8 @@ public class SplitControllerTest {

    @Test
    public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
        // Setup to make sure a transaction record is started.
        mTransactionManager.startNewTransaction();
        setupPlaceholderRule(mActivity);
        final SplitPlaceholderRule placeholderRule =
                (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -961,6 +972,8 @@ public class SplitControllerTest {

    @Test
    public void testGetPlaceholderOptions() {
        // Setup to make sure a transaction record is started.
        mTransactionManager.startNewTransaction();
        doReturn(true).when(mActivity).isResumed();

        assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,8 +1160,6 @@ public class SplitControllerTest {
                        + "of other properties",
                SplitController.haveSamePresentation(splitRule1, splitRule2,
                        new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));


    }

    /** Creates a mock activity in the organizer process. */
+204 −0

File added.

Preview size limit exceeded, changes collapsed.