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

Commit d4d0fee8 authored by Andrii Kulian's avatar Andrii Kulian Committed by Android (Google) Code Review
Browse files

Merge "Synchronize SplitController" into tm-dev

parents 1f9afd72 40a67faa
Loading
Loading
Loading
Loading
+196 −159
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;

import androidx.annotation.GuardedBy;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;

import com.android.internal.annotations.VisibleForTesting;
@@ -65,9 +66,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    private static final String TAG = "SplitController";

    @VisibleForTesting
    @GuardedBy("mLock")
    final SplitPresenter mPresenter;

    // Currently applied split configuration.
    @GuardedBy("mLock")
    private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
    /**
     * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
@@ -76,6 +79,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * organizer.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();

    // Callback to Jetpack to notify about changes to split states.
@@ -83,6 +87,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    private Consumer<List<SplitInfo>> mEmbeddingCallback;
    private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
    private final Handler mHandler;
    private final Object mLock = new Object();

    public SplitController() {
        final MainThreadExecutor executor = new MainThreadExecutor();
@@ -100,45 +105,34 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    /** Updates the embedding rules applied to future activity launches. */
    @Override
    public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
        synchronized (mLock) {
            mSplitRules.clear();
            mSplitRules.addAll(rules);
            for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
                updateAnimationOverride(mTaskContainers.valueAt(i));
            }
        }
    }

    @NonNull
    public List<EmbeddingRule> getSplitRules() {
    List<EmbeddingRule> getSplitRules() {
        return mSplitRules;
    }

    /**
     * Starts an activity to side of the launchingActivity with the provided split config.
     */
    public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
            @Nullable Bundle options, @NonNull SplitRule sideRule,
            @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
        try {
            mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
                    isPlaceholder);
        } catch (Exception e) {
            if (failureCallback != null) {
                failureCallback.accept(e);
            }
        }
    }

    /**
     * Registers the split organizer callback to notify about changes to active splits.
     */
    @Override
    public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
        synchronized (mLock) {
            mEmbeddingCallback = callback;
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
        synchronized (mLock) {
            TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
            if (container == null) {
                return;
@@ -150,9 +144,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
        synchronized (mLock) {
            TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
            if (container == null) {
                return;
@@ -162,21 +158,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            final boolean wasInPip = isInPictureInPicture(container);
            container.setInfo(taskFragmentInfo);
            final boolean isInPip = isInPictureInPicture(container);
        // Check if there are no running activities - consider the container empty if there are no
        // non-finishing activities left.
            // Check if there are no running activities - consider the container empty if there are
            // no non-finishing activities left.
            if (!taskFragmentInfo.hasRunningActivity()) {
                if (taskFragmentInfo.isTaskFragmentClearedForPip()) {
                    // 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.
                    // Instead, the original split should be cleanup, and the dependent may be
                    // expanded to fullscreen.
                    cleanupForEnterPip(wct, container);
                    mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
                } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                // Do not finish the dependents if this TaskFragment was cleared due to launching
                // activity in the Task.
                    // Do not finish the dependents if this TaskFragment was cleared due to
                    // launching activity in the Task.
                    mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
                } else if (!container.isWaitingActivityAppear()) {
                // Do not finish the container before the expected activity appear until timeout.
                    // Do not finish the container before the expected activity appear until
                    // timeout.
                    mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
                }
            } else if (wasInPip && isInPip) {
@@ -190,16 +187,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                cleanupForEnterPip(wct, container);
            } else if (wasInPip) {
                // Exit PIP.
            // Updates the presentation of the container. Expand or launch placeholder if needed.
                // Updates the presentation of the container. Expand or launch placeholder if
                // needed.
                updateContainer(wct, container);
            }
            mPresenter.applyTransaction(wct);
            updateCallbackIfNecessary();
        }
    }

    @Override
    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
        final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
        synchronized (mLock) {
            final TaskFragmentContainer container = getContainer(
                    taskFragmentInfo.getFragmentToken());
            if (container != null) {
                // Cleanup if the TaskFragment vanished is not requested by the organizer.
                removeContainer(container);
@@ -215,10 +216,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            }
            cleanupTaskFragment(taskFragmentInfo.getFragmentToken());
        }
    }

    @Override
    public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
            @NonNull Configuration parentConfig) {
        synchronized (mLock) {
            final TaskFragmentContainer container = getContainer(fragmentToken);
            if (container != null) {
                onTaskConfigurationChanged(container.getTaskId(), parentConfig);
@@ -230,17 +233,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                updateCallbackIfNecessary();
            }
        }
    }

    @Override
    public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
            @NonNull IBinder activityToken) {
        // If the activity belongs to the current app process, we treat it as a new activity launch.
        synchronized (mLock) {
            // If the activity belongs to the current app process, we treat it as a new activity
            // launch.
            final Activity activity = getActivity(activityToken);
            if (activity != null) {
                // We don't allow split as primary for new launch because we currently only support
                // launching to top. We allow split as primary for activity reparent because the
            // activity may be split as primary before it is reparented out. In that case, we want
            // to show it as primary again when it is reparented back.
                // activity may be split as primary before it is reparented out. In that case, we
                // want to show it as primary again when it is reparented back.
                if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
                    // When there is no embedding rule matched, try to place it in the top container
                    // like a normal launch.
@@ -256,25 +262,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                return;
            }

        // If the activity belongs to a different app process, we treat it as starting new intent,
        // since both actions might result in a new activity that should appear in an organized
        // TaskFragment.
            // If the activity belongs to a different app process, we treat it as starting new
            // intent, since both actions might result in a new activity that should appear in an
            // organized TaskFragment.
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
                    activityIntent, null /* launchingActivity */);
            if (targetContainer == null) {
            // When there is no embedding rule matched, try to place it in the top container like a
            // normal launch.
                // When there is no embedding rule matched, try to place it in the top container
                // like a normal launch.
                targetContainer = taskContainer.getTopTaskFragmentContainer();
            }
            if (targetContainer == null) {
                return;
            }
        wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken);
            wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
                    activityToken);
            mPresenter.applyTransaction(wct);
            // Because the activity does not belong to the organizer process, we wait until
            // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
        }
    }

    /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
    private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
@@ -480,6 +488,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        mPresenter.applyTransaction(wct);
    }

    /**
     * Starts an activity to side of the launchingActivity with the provided split config.
     */
    private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
            @Nullable Bundle options, @NonNull SplitRule sideRule,
            @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
        try {
            mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
                    isPlaceholder);
        } catch (Exception e) {
            if (failureCallback != null) {
                failureCallback.accept(e);
            }
        }
    }

    /**
     * Expands the given activity by either expanding the TaskFragment it is currently in or putting
     * it into a new expanded TaskFragment.
@@ -1382,10 +1406,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

        @Override
        public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
            synchronized (mLock) {
                final IBinder activityToken = activity.getActivityToken();
                final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
            // If the activity is not embedded, then it will not have an initial task fragment token
            // so no further action is needed.
                // If the activity is not embedded, then it will not have an initial task fragment
                // token so no further action is needed.
                if (initialTaskFragmentToken == null) {
                    return;
                }
@@ -1395,16 +1420,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                    for (int j = containers.size() - 1; j >= 0; j--) {
                        final TaskFragmentContainer container = containers.get(j);
                        if (!container.hasActivity(activityToken)
                            && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
                        // The onTaskFragmentInfoChanged callback containing this activity has not
                        // reached the client yet, so add the activity to the pending appeared
                        // activities.
                                && container.getTaskFragmentToken()
                                .equals(initialTaskFragmentToken)) {
                            // The onTaskFragmentInfoChanged callback containing this activity has
                            // not reached the client yet, so add the activity to the pending
                            // appeared activities.
                            container.addPendingAppearedActivity(activity);
                            return;
                        }
                    }
                }
            }
        }

        @Override
        public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
@@ -1412,19 +1439,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // first. In case of a configured placeholder activity we want to make sure
            // that we don't launch it if an activity itself already requested something to be
            // launched to side.
            synchronized (mLock) {
                SplitController.this.onActivityCreated(activity);
            }
        }

        @Override
        public void onActivityConfigurationChanged(Activity activity) {
            synchronized (mLock) {
                SplitController.this.onActivityConfigurationChanged(activity);
            }
        }

        @Override
        public void onActivityPostDestroyed(Activity activity) {
            synchronized (mLock) {
                SplitController.this.onActivityDestroyed(activity);
            }
        }
    }

    /** Executor that posts on the main application thread. */
    private static class MainThreadExecutor implements Executor {
@@ -1457,17 +1490,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                return super.onStartActivity(who, intent, options);
            }

            synchronized (mLock) {
                final int taskId = getTaskId(launchingActivity);
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
                        taskId, intent, launchingActivity);
                if (launchedInTaskFragment != null) {
                    mPresenter.applyTransaction(wct);
                // Amend the request to let the WM know that the activity should be placed in the
                // dedicated container.
                    // 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());
                }
            }

            return super.onStartActivity(who, intent, options);
        }
@@ -1479,8 +1514,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     */
    @Override
    public boolean isActivityEmbedded(@NonNull Activity activity) {
        synchronized (mLock) {
            return mPresenter.isActivityEmbedded(activity.getActivityToken());
        }
    }

    /**
     * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if