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

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

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

parents 81612332 8f222043
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