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

Commit 513a9ffc authored by Chris Li's avatar Chris Li
Browse files

Handle TaskFragmentOrganizer#onTransactionReady in sync

Refactor TaskFragmentOrganizer#onTransactionReady to
SplitController#onTransactionReady to make sure that the
transaction is handled in one sync block.

Bug: 240519866
Test: pass existing for refactor
Change-Id: I9e7191e2b71ba262e1a3f8c947f9354698382972
parent 9e6b8ad6
Loading
Loading
Loading
Loading
+22 −6
Original line number Diff line number Diff line
@@ -47,11 +47,24 @@ import java.util.concurrent.Executor;
public class TaskFragmentOrganizer extends WindowOrganizer {

    /**
     * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}.
     * Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}.
     * @hide
     */
    public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable";

    /**
     * Key to the {@link TaskFragmentInfo} in
     * {@link TaskFragmentTransaction.Change#getErrorBundle()}.
     * @hide
     */
    public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";

    /**
     * Key to the {@link WindowContainerTransaction.HierarchyOp} in
     * {@link TaskFragmentTransaction.Change#getErrorBundle()}.
     * @hide
     */
    private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception";
    private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
    private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
    public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";

    /**
     * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
@@ -61,7 +74,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
    public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
            @Nullable TaskFragmentInfo info, int opType) {
        final Bundle errorBundle = new Bundle();
        errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
        errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception);
        if (info != null) {
            errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
        }
@@ -342,6 +355,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
     * @hide
     */
    public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
        // TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole
        // transaction is handled in one sync block. Keep the implementation below to keep CTS
        // compatibility. Remove in the next release.
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
        for (TaskFragmentTransaction.Change change : changes) {
@@ -391,7 +407,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
                            errorBundle.getParcelable(
                                    KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
                            errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
                            errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
                            errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE,
                                    java.lang.Throwable.class));
                    break;
                case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+7 −52
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -29,6 +28,7 @@ import android.util.ArrayMap;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
@@ -62,18 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
     * Callback that notifies the controller about changes to task fragments.
     */
    interface TaskFragmentCallback {
        void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
                @NonNull TaskFragmentInfo taskFragmentInfo);
        void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
                @NonNull TaskFragmentInfo taskFragmentInfo);
        void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
                @NonNull TaskFragmentInfo taskFragmentInfo);
        void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
                int taskId, @NonNull Configuration parentConfig);
        void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
                int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
        void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
                @Nullable TaskFragmentInfo taskFragmentInfo, int opType);
        void onTransactionReady(@NonNull TaskFragmentTransaction transaction);
    }

    /**
@@ -270,50 +259,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
        wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
    }

    @Override
    public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
        mFragmentInfos.put(fragmentToken, taskFragmentInfo);
        mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
    }

    @Override
    public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
        mFragmentInfos.put(fragmentToken, taskFragmentInfo);
        mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
    void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
        mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
    }

    @Override
    public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
    void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
        mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
        mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
    }

    @Override
    public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Configuration parentConfig) {
        mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
    }

    @Override
    public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
        mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
    }

    @Override
    public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
            @NonNull IBinder errorCallbackToken,
            @Nullable TaskFragmentInfo taskFragmentInfo,
            int opType, @NonNull Throwable exception) {
        if (taskFragmentInfo != null) {
            final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
            mFragmentInfos.put(fragmentToken, taskFragmentInfo);
        }
        mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
    public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
        mCallback.onTransactionReady(transaction);
    }
}
+280 −161
Original line number Diff line number Diff line
@@ -19,6 +19,15 @@ package androidx.window.extensions.embedding;
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.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.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;

@@ -53,6 +62,7 @@ import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;

import androidx.annotation.GuardedBy;
@@ -144,11 +154,78 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
    }

    /**
     * Called when the transaction is ready so that the organizer can update the TaskFragments based
     * on the changes in transaction.
     */
    @Override
    public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
    public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
        synchronized (mLock) {
            TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
            for (TaskFragmentTransaction.Change change : changes) {
                final int taskId = change.getTaskId();
                final TaskFragmentInfo info = change.getTaskFragmentInfo();
                switch (change.getType()) {
                    case TYPE_TASK_FRAGMENT_APPEARED:
                        mPresenter.updateTaskFragmentInfo(info);
                        onTaskFragmentAppeared(wct, info);
                        break;
                    case TYPE_TASK_FRAGMENT_INFO_CHANGED:
                        mPresenter.updateTaskFragmentInfo(info);
                        onTaskFragmentInfoChanged(wct, info);
                        break;
                    case TYPE_TASK_FRAGMENT_VANISHED:
                        mPresenter.removeTaskFragmentInfo(info);
                        onTaskFragmentVanished(wct, info);
                        break;
                    case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
                        onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
                        break;
                    case TYPE_TASK_FRAGMENT_ERROR:
                        final Bundle errorBundle = change.getErrorBundle();
                        final IBinder errorToken = change.getErrorCallbackToken();
                        final TaskFragmentInfo errorTaskFragmentInfo = errorBundle.getParcelable(
                                KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
                        final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE);
                        final Throwable exception = errorBundle.getSerializable(
                                KEY_ERROR_CALLBACK_THROWABLE, Throwable.class);
                        if (errorTaskFragmentInfo != null) {
                            mPresenter.updateTaskFragmentInfo(errorTaskFragmentInfo);
                        }
                        onTaskFragmentError(wct, errorToken, errorTaskFragmentInfo, opType,
                                exception);
                        break;
                    case TYPE_ACTIVITY_REPARENTED_TO_TASK:
                        onActivityReparentedToTask(
                                wct,
                                taskId,
                                change.getActivityIntent(),
                                change.getActivityToken());
                        break;
                    default:
                        throw new IllegalArgumentException(
                                "Unknown TaskFragmentEvent=" + change.getType());
                }
            }

            // Notify the server, and the server should apply the WindowContainerTransaction.
            mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct);
            updateCallbackIfNecessary();
        }
    }

    /**
     * Called when a TaskFragment is created and organized by this organizer.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param taskFragmentInfo  Info of the TaskFragment that is created.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        if (container == null) {
            return;
        }
@@ -160,15 +237,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // Update with the latest Task configuration.
            updateContainer(wct, container);
        }
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
    /**
     * Called when the status of an organized TaskFragment is changed.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param taskFragmentInfo  Info of the TaskFragment that is changed.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
        synchronized (mLock) {
            TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        if (container == null) {
            return;
        }
@@ -209,16 +290,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // needed.
            updateContainer(wct, container);
        }
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
    /**
     * Called when an organized TaskFragment is removed.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param taskFragmentInfo  Info of the TaskFragment that is removed.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
            @NonNull TaskFragmentInfo taskFragmentInfo) {
        synchronized (mLock) {
            final TaskFragmentContainer container = getContainer(
                    taskFragmentInfo.getFragmentToken());
        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        if (container != null) {
            // Cleanup if the TaskFragment vanished is not requested by the organizer.
            removeContainer(container);
@@ -228,16 +312,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            if (newTopContainer != null) {
                updateContainer(wct, newTopContainer);
            }
                updateCallbackIfNecessary();
        }
        cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
    }
    }

    @Override
    public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
    /**
     * Called when the parent leaf Task of organized TaskFragments is changed.
     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
     * transaction.
     *
     * For case like screen size change, it will trigger {@link #onTaskFragmentParentInfoChanged}
     * with new Task bounds, but may not trigger {@link #onTaskFragmentInfoChanged} because there
     * can be an override bounds.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param taskId    Id of the parent Task that is changed.
     * @param parentConfig  Config of the parent Task.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Configuration parentConfig) {
        synchronized (mLock) {
        onTaskConfigurationChanged(taskId, parentConfig);
        if (isInPictureInPicture(parentConfig)) {
            // No need to update presentation in PIP until the Task exit PIP.
@@ -259,15 +354,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                updateContainer(wct, container);
            }
        }
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
    /**
     * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
     * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
     * original Task. In this case, we need to notify the organizer so that it can check if the
     * Activity matches any split rule.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param taskId            The Task that the activity is reparented to.
     * @param activityIntent    The intent that the activity is original launched with.
     * @param activityToken     If the activity belongs to the same process as the organizer, this
     *                          will be the actual activity token; if the activity belongs to a
     *                          different process, the server will generate a temporary token that
     *                          the organizer can use to reparent the activity through
     *                          {@link WindowContainerTransaction} if needed.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
            int taskId, @NonNull Intent activityIntent,
            @NonNull IBinder activityToken) {
        synchronized (mLock) {
        // If the activity belongs to the current app process, we treat it as a new activity
        // launch.
        final Activity activity = getActivity(activityToken);
@@ -281,7 +389,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                // like a normal launch.
                placeActivityInTopContainer(wct, activity);
            }
                updateCallbackIfNecessary();
            return;
        }

@@ -309,12 +416,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        // Because the activity does not belong to the organizer process, we wait until
        // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
    }
    }

    @Override
    public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
            @Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
        synchronized (mLock) {
    /**
     * Called when the {@link WindowContainerTransaction} created with
     * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
     *
     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
     * @param errorCallbackToken    token set in
     *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
     * @param taskFragmentInfo  The {@link TaskFragmentInfo}. This could be {@code null} if no
     *                          TaskFragment created.
     * @param opType            The {@link WindowContainerTransaction.HierarchyOp} of the failed
     *                          transaction operation.
     * @param exception             exception from the server side.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
            @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
            int opType, @NonNull Throwable exception) {
        Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
        switch (opType) {
            case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
            case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
@@ -332,8 +453,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                container.setInfo(wct, taskFragmentInfo);
                container.clearPendingAppearedActivities();
                if (container.isEmpty()) {
                        mPresenter.cleanupContainer(wct, container,
                                false /* shouldFinishDependent */);
                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
                }
                break;
            }
@@ -342,7 +462,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                        + ", opType = " + opType);
        }
    }
    }

    /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
    private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
+9 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.graphics.Point;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

@@ -129,6 +130,14 @@ public class JetpackTaskFragmentOrganizerTest {
                WINDOWING_MODE_UNDEFINED);
    }

    @Test
    public void testOnTransactionReady() {
        final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
        mOrganizer.onTransactionReady(transaction);

        verify(mCallback).onTransactionReady(transaction);
    }

    private TaskFragmentInfo createMockInfo(TaskFragmentContainer container) {
        return new TaskFragmentInfo(container.getTaskFragmentToken(),
                mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
+101 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading