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

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

Merge "Fix activity relaunch issue with ActivityEmbedding" into tm-qpr-dev am:...

Merge "Fix activity relaunch issue with ActivityEmbedding" into tm-qpr-dev am: bac0f96f am: 7a40b19c

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



Change-Id: Id10da5de8ec911a8fd8d91b6fb5bdab0b383ed3a
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents f285efe7 7a40b19c
Loading
Loading
Loading
Loading
+28 −5
Original line number Diff line number Diff line
@@ -965,10 +965,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @VisibleForTesting
    @GuardedBy("mLock")
    void onActivityDestroyed(@NonNull Activity activity) {
        if (!activity.isFinishing()) {
            // onDestroyed is triggered without finishing. This happens when the activity is
            // relaunched. In this case, we don't want to cleanup the record.
            return;
        }
        // Remove any pending appeared activity, as the server won't send finished activity to the
        // organizer.
        final IBinder activityToken = activity.getActivityToken();
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            mTaskContainers.valueAt(i).onActivityDestroyed(activity);
            mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
        }
        // We didn't trigger the callback if there were any pending appeared activities, so check
        // again after the pending is removed.
@@ -1170,16 +1176,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Returns a container that this activity is registered with. An activity can only belong to one
     * container, or no container at all.
     */
    @GuardedBy("mLock")
    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull Activity activity) {
        final IBinder activityToken = activity.getActivityToken();
        return getContainerWithActivity(activity.getActivityToken());
    }

    @GuardedBy("mLock")
    @Nullable
    TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
        // Check pending appeared activity first because there can be a delay for the server
        // update.
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            for (int j = containers.size() - 1; j >= 0; j--) {
                final TaskFragmentContainer container = containers.get(j);
                if (container.hasPendingAppearedActivity(activityToken)) {
                    return container;
                }
            }
        }

        // Check appeared activity if there is no such pending appeared activity.
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
            // Traverse from top to bottom in case an activity is added to top pending, and hasn't
            // received update from server yet.
            for (int j = containers.size() - 1; j >= 0; j--) {
                final TaskFragmentContainer container = containers.get(j);
                if (container.hasActivity(activityToken)) {
                if (container.hasAppearedActivity(activityToken)) {
                    return container;
                }
            }
+4 −4
Original line number Diff line number Diff line
@@ -166,16 +166,16 @@ class TaskContainer {
    }

    /** Called when the activity is destroyed. */
    void onActivityDestroyed(@NonNull Activity activity) {
    void onActivityDestroyed(@NonNull IBinder activityToken) {
        for (TaskFragmentContainer container : mContainers) {
            container.onActivityDestroyed(activity);
            container.onActivityDestroyed(activityToken);
        }
    }

    /** Removes the pending appeared activity from all TaskFragments in this Task. */
    void cleanupPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
    void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) {
        for (TaskFragmentContainer container : mContainers) {
            container.removePendingAppearedActivity(pendingAppearedActivity);
            container.removePendingAppearedActivity(activityToken);
        }
    }

+60 −40
Original line number Diff line number Diff line
@@ -43,6 +43,9 @@ import java.util.List;
 * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
 * on the server side.
 */
// Suppress GuardedBy warning because all the TaskFragmentContainers are stored in
// SplitController.mTaskContainers which is guarded.
@SuppressWarnings("GuardedBy")
class TaskFragmentContainer {
    private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;

@@ -66,11 +69,11 @@ class TaskFragmentContainer {
    TaskFragmentInfo mInfo;

    /**
     * Activities that are being reparented or being started to this container, but haven't been
     * added to {@link #mInfo} yet.
     * Activity tokens that are being reparented or being started to this container, but haven't
     * been added to {@link #mInfo} yet.
     */
    @VisibleForTesting
    final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
    final ArrayList<IBinder> mPendingAppearedActivities = new ArrayList<>();

    /**
     * When this container is created for an {@link Intent} to start within, we store that Intent
@@ -84,8 +87,11 @@ class TaskFragmentContainer {
    private final List<TaskFragmentContainer> mContainersToFinishOnExit =
            new ArrayList<>();

    /** Individual associated activities in different containers that should be finished on exit. */
    private final List<Activity> mActivitiesToFinishOnExit = new ArrayList<>();
    /**
     * Individual associated activity tokens in different containers that should be finished on
     * exit.
     */
    private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();

    /** Indicates whether the container was cleaned up after the last activity was removed. */
    private boolean mIsFinished;
@@ -158,8 +164,9 @@ class TaskFragmentContainer {
        // in this intermediate state.
        // Place those on top of the list since they will be on the top after reported from the
        // server.
        for (Activity activity : mPendingAppearedActivities) {
            if (!activity.isFinishing()) {
        for (IBinder token : mPendingAppearedActivities) {
            final Activity activity = mController.getActivity(token);
            if (activity != null && !activity.isFinishing()) {
                allActivities.add(activity);
            }
        }
@@ -203,55 +210,58 @@ class TaskFragmentContainer {

    /** Adds the activity that will be reparented to this container. */
    void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
        if (hasActivity(pendingAppearedActivity.getActivityToken())) {
        final IBinder activityToken = pendingAppearedActivity.getActivityToken();
        if (hasActivity(activityToken)) {
            return;
        }
        // Remove the pending activity from other TaskFragments.
        mTaskContainer.cleanupPendingAppearedActivity(pendingAppearedActivity);
        mPendingAppearedActivities.add(pendingAppearedActivity);
        updateActivityClientRecordTaskFragmentToken(pendingAppearedActivity);
        // Remove the pending activity from other TaskFragments in case the activity is reparented
        // again before the server update.
        mTaskContainer.cleanupPendingAppearedActivity(activityToken);
        mPendingAppearedActivities.add(activityToken);
        updateActivityClientRecordTaskFragmentToken(activityToken);
    }

    /**
     * Updates the {@link ActivityThread.ActivityClientRecord#mTaskFragmentToken} for the
     * activity. This makes sure the token is up-to-date if the activity is relaunched later.
     */
    private void updateActivityClientRecordTaskFragmentToken(@NonNull Activity activity) {
    private void updateActivityClientRecordTaskFragmentToken(@NonNull IBinder activityToken) {
        final ActivityThread.ActivityClientRecord record = ActivityThread
                .currentActivityThread().getActivityClient(activity.getActivityToken());
                .currentActivityThread().getActivityClient(activityToken);
        if (record != null) {
            record.mTaskFragmentToken = mToken;
        }
    }

    void removePendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
        mPendingAppearedActivities.remove(pendingAppearedActivity);
    void removePendingAppearedActivity(@NonNull IBinder activityToken) {
        mPendingAppearedActivities.remove(activityToken);
    }

    void clearPendingAppearedActivities() {
        final List<Activity> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
        final List<IBinder> cleanupActivities = new ArrayList<>(mPendingAppearedActivities);
        // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the
        // current TaskFragment.
        mPendingAppearedActivities.clear();
        mPendingAppearedIntent = null;

        // For removed pending activities, we need to update the them to their previous containers.
        for (Activity activity : cleanupActivities) {
        for (IBinder activityToken : cleanupActivities) {
            final TaskFragmentContainer curContainer = mController.getContainerWithActivity(
                    activity);
                    activityToken);
            if (curContainer != null) {
                curContainer.updateActivityClientRecordTaskFragmentToken(activity);
                curContainer.updateActivityClientRecordTaskFragmentToken(activityToken);
            }
        }
    }

    /** Called when the activity is destroyed. */
    void onActivityDestroyed(@NonNull Activity activity) {
        removePendingAppearedActivity(activity);
    void onActivityDestroyed(@NonNull IBinder activityToken) {
        removePendingAppearedActivity(activityToken);
        if (mInfo != null) {
            // Remove the activity now because there can be a delay before the server callback.
            mInfo.getActivities().remove(activity.getActivityToken());
            mInfo.getActivities().remove(activityToken);
        }
        mActivitiesToFinishOnExit.remove(activityToken);
    }

    @Nullable
@@ -275,16 +285,24 @@ class TaskFragmentContainer {
        mPendingAppearedIntent = null;
    }

    boolean hasActivity(@NonNull IBinder token) {
        if (mInfo != null && mInfo.getActivities().contains(token)) {
            return true;
        }
        for (Activity activity : mPendingAppearedActivities) {
            if (activity.getActivityToken().equals(token)) {
                return true;
    boolean hasActivity(@NonNull IBinder activityToken) {
        // Instead of using (hasAppearedActivity() || hasPendingAppearedActivity), we want to make
        // sure the controller considers this container as the one containing the activity.
        // This is needed when the activity is added as pending appeared activity to one
        // TaskFragment while it is also an appeared activity in another.
        return mController.getContainerWithActivity(activityToken) == this;
    }

    /** Whether this activity has appeared in the TaskFragment on the server side. */
    boolean hasAppearedActivity(@NonNull IBinder activityToken) {
        return mInfo != null && mInfo.getActivities().contains(activityToken);
    }
        return false;

    /**
     * Whether we are waiting for this activity to appear in the TaskFragment on the server side.
     */
    boolean hasPendingAppearedActivity(@NonNull IBinder activityToken) {
        return mPendingAppearedActivities.contains(activityToken);
    }

    int getRunningActivityCount() {
@@ -342,8 +360,8 @@ class TaskFragmentContainer {
        // Cleanup activities that were being re-parented
        List<IBinder> infoActivities = mInfo.getActivities();
        for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
            final Activity activity = mPendingAppearedActivities.get(i);
            if (infoActivities.contains(activity.getActivityToken())) {
            final IBinder activityToken = mPendingAppearedActivities.get(i);
            if (infoActivities.contains(activityToken)) {
                mPendingAppearedActivities.remove(i);
            }
        }
@@ -392,7 +410,7 @@ class TaskFragmentContainer {
        if (mIsFinished) {
            return;
        }
        mActivitiesToFinishOnExit.add(activityToFinish);
        mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
    }

    /**
@@ -402,7 +420,7 @@ class TaskFragmentContainer {
        if (mIsFinished) {
            return;
        }
        mActivitiesToFinishOnExit.remove(activityToRemove);
        mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
    }

    /** Removes all dependencies that should be finished when this container is finished. */
@@ -470,8 +488,9 @@ class TaskFragmentContainer {
        mContainersToFinishOnExit.clear();

        // Finish associated activities
        for (Activity activity : mActivitiesToFinishOnExit) {
            if (activity.isFinishing()
        for (IBinder activityToken : mActivitiesToFinishOnExit) {
            final Activity activity = mController.getActivity(activityToken);
            if (activity == null || activity.isFinishing()
                    || controller.shouldRetainAssociatedActivity(this, activity)) {
                continue;
            }
@@ -540,7 +559,8 @@ class TaskFragmentContainer {
        }
        int maxMinWidth = mInfo.getMinimumWidth();
        int maxMinHeight = mInfo.getMinimumHeight();
        for (Activity activity : mPendingAppearedActivities) {
        for (IBinder activityToken : mPendingAppearedActivities) {
            final Activity activity = mController.getActivity(activityToken);
            final Size minDimensions = SplitPresenter.getMinDimensions(activity);
            if (minDimensions == null) {
                continue;
+12 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;

import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

@@ -45,6 +46,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

// Suppress GuardedBy warning on unit tests
@SuppressWarnings("GuardedBy")
public class EmbeddingTestUtils {
    static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
    static final int TASK_ID = 10;
@@ -191,6 +194,15 @@ public class EmbeddingTestUtils {
        return new TaskContainer(TASK_ID, activity);
    }

    static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
        final TaskContainer taskContainer = createTestTaskContainer();
        final int taskId = taskContainer.getTaskId();
        // Should not call to create TaskContainer with the same task id twice.
        assertFalse(controller.mTaskContainers.contains(taskId));
        controller.mTaskContainers.put(taskId, taskContainer);
        return taskContainer;
    }

    static WindowLayoutInfo createWindowLayoutInfo() {
        final FoldingFeature foldingFeature = new FoldingFeature(
                new Rect(
+8 −0
Original line number Diff line number Diff line
@@ -242,6 +242,14 @@ public class SplitControllerTest {

        assertTrue(tf.hasActivity(mActivity.getActivityToken()));

        // When the activity is not finishing, do not clear the record.
        doReturn(false).when(mActivity).isFinishing();
        mSplitController.onActivityDestroyed(mActivity);

        assertTrue(tf.hasActivity(mActivity.getActivityToken()));

        // Clear the record when the activity is finishing and destroyed.
        doReturn(true).when(mActivity).isFinishing();
        mSplitController.onActivityDestroyed(mActivity);

        assertFalse(tf.hasActivity(mActivity.getActivityToken()));
Loading