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

Commit 8f7aa9a8 authored by Jiaming Liu's avatar Jiaming Liu Committed by Android (Google) Code Review
Browse files

Merge "Do not finish TaskFragment if a new activity is expected" into main

parents 19519870 eedc8088
Loading
Loading
Loading
Loading
+16 −5
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams;

import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch;

import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
@@ -815,12 +817,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                        .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
                mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
            } else if (!container.isWaitingActivityAppear()) {
                // Do not finish the container before the expected activity appear until
                // timeout.
                if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()
                        && container.hasActivityLaunchHint()) {
                    // If we have recently attempted to launch a new activity into this
                    // TaskFragment, we schedule delayed cleanup. If the new activity appears in
                    // this TaskFragment, we no longer need to finish the TaskFragment.
                    container.scheduleDelayedTaskFragmentCleanup();
                } else {
                    mTransactionManager.getCurrentTransactionRecord()
                            .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
                    mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
                }
            }
        } else if (wasInPip && isInPip) {
            // No update until exit PIP.
            return;
@@ -3164,6 +3172,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
                    // TODO(b/229680885): skip override launching TaskFragment token by split-rule
                    options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                            launchedInTaskFragment.getTaskFragmentToken());
                    if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) {
                        launchedInTaskFragment.setActivityLaunchHint();
                    }
                    mCurrentIntent = intent;
                } else {
                    transactionRecord.abort();
+103 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package androidx.window.extensions.embedding;

import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch;

import android.app.Activity;
import android.app.ActivityThread;
import android.app.WindowConfiguration.WindowingMode;
@@ -53,6 +55,8 @@ import java.util.Objects;
class TaskFragmentContainer {
    private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000;

    private static final int DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS = 500;

    /** Parcelable data of this TaskFragmentContainer. */
    @NonNull
    private final ParcelableTaskFragmentContainerData mParcelableData;
@@ -165,6 +169,18 @@ class TaskFragmentContainer {
     */
    private boolean mLastDimOnTask;

    /** The timestamp of the latest pending activity launch attempt. 0 means no pending launch. */
    private long mLastActivityLaunchTimestampMs = 0;

    /**
     * The scheduled runnable for delayed TaskFragment cleanup. This is used when the TaskFragment
     * becomes empty, but we expect a new activity to appear in it soon.
     *
     * It should be {@code null} when not scheduled.
     */
    @Nullable
    private Runnable mDelayedTaskFragmentCleanupRunnable;

    /**
     * Creates a container with an existing activity that will be re-parented to it in a window
     * container transaction.
@@ -540,6 +556,10 @@ class TaskFragmentContainer {
            mAppearEmptyTimeout = null;
        }

        if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) {
            clearActivityLaunchHintIfNecessary(mInfo, info);
        }

        mHasCrossProcessActivities = false;
        mInfo = info;
        if (mInfo == null || mInfo.isEmpty()) {
@@ -1064,6 +1084,89 @@ class TaskFragmentContainer {
        return isOverlay() && mParcelableData.mAssociatedActivityToken != null;
    }

    /**
     * Indicates whether there is possibly a pending activity launching into this TaskFragment.
     *
     * This should only be used as a hint because we cannot reliably determine if the new activity
     * is going to appear into this TaskFragment.
     *
     * TODO(b/293800510) improve activity launch tracking in TaskFragment.
     */
    boolean hasActivityLaunchHint() {
        if (mLastActivityLaunchTimestampMs == 0) {
            return false;
        }
        if (System.currentTimeMillis() > mLastActivityLaunchTimestampMs + APPEAR_EMPTY_TIMEOUT_MS) {
            // The hint has expired after APPEAR_EMPTY_TIMEOUT_MS.
            mLastActivityLaunchTimestampMs = 0;
            return false;
        }
        return true;
    }

    /** Records the latest activity launch attempt. */
    void setActivityLaunchHint() {
        mLastActivityLaunchTimestampMs = System.currentTimeMillis();
    }

    /**
     * If we get a new info showing that the TaskFragment has more activities than the previous
     * info, we clear the new activity launch hint.
     *
     * Note that this is not a reliable way and cannot cover situations when the attempted
     * activity launch did not cause TaskFragment info activity count changes, such as trampoline
     * launches or single top launches.
     *
     * TODO(b/293800510) improve activity launch tracking in TaskFragment.
     */
    private void clearActivityLaunchHintIfNecessary(
            @Nullable TaskFragmentInfo oldInfo, @NonNull TaskFragmentInfo newInfo) {
        final int previousActivityCount = oldInfo == null ? 0 : oldInfo.getRunningActivityCount();
        if (newInfo.getRunningActivityCount() > previousActivityCount) {
            mLastActivityLaunchTimestampMs = 0;
            cancelDelayedTaskFragmentCleanup();
        }
    }

    /**
     * Schedules delayed TaskFragment cleanup due to pending activity launch. The scheduled cleanup
     * will be canceled if a new activity appears in this TaskFragment.
     */
    void scheduleDelayedTaskFragmentCleanup() {
        if (mDelayedTaskFragmentCleanupRunnable != null) {
            // Remove the previous callback if there is already one scheduled.
            mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable);
        }
        mDelayedTaskFragmentCleanupRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mController.mLock) {
                    if (mDelayedTaskFragmentCleanupRunnable != this) {
                        // The scheduled cleanup runnable has been canceled or rescheduled, so
                        // skipping.
                        return;
                    }
                    if (isEmpty()) {
                        mLastActivityLaunchTimestampMs = 0;
                        mController.onTaskFragmentAppearEmptyTimeout(
                                TaskFragmentContainer.this);
                    }
                    mDelayedTaskFragmentCleanupRunnable = null;
                }
            }
        };
        mController.getHandler().postDelayed(
                mDelayedTaskFragmentCleanupRunnable, DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS);
    }

    private void cancelDelayedTaskFragmentCleanup() {
        if (mDelayedTaskFragmentCleanupRunnable == null) {
            return;
        }
        mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable);
        mDelayedTaskFragmentCleanupRunnable = null;
    }

    @Override
    public String toString() {
        return toString(true /* includeContainersToFinishOnExit */);