Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +16 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +103 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -540,6 +556,10 @@ class TaskFragmentContainer { mAppearEmptyTimeout = null; } if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { clearActivityLaunchHintIfNecessary(mInfo, info); } mHasCrossProcessActivities = false; mInfo = info; if (mInfo == null || mInfo.isEmpty()) { Loading Loading @@ -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 */); Loading Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +16 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +103 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -540,6 +556,10 @@ class TaskFragmentContainer { mAppearEmptyTimeout = null; } if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { clearActivityLaunchHintIfNecessary(mInfo, info); } mHasCrossProcessActivities = false; mInfo = info; if (mInfo == null || mInfo.isEmpty()) { Loading Loading @@ -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 */); Loading