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

Commit 41b21255 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

Add timeout to abort PiP if needed

Make sure we abort enter-PiP via abort
upon a timeout after moveActivityToPinnedRootTask()

We remove any timeout callbacks set up if we detect
that activity windowing mode is being changed directly
in WindowOrganizerController (applies to PiP1 only) and
if the windowing mode is requested to be the same as its task.

Bug: 334038395
Test: N/A until a proof-of-concept repro path is produced
Change-Id: I334bb336e11c774a4e22d646e16ba99cace42a7d
parent 849d1be1
Loading
Loading
Loading
Loading
+71 −0
Original line number Diff line number Diff line
@@ -175,6 +175,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
    private static final int SET_SCREEN_BRIGHTNESS_OVERRIDE = 1;
    private static final int SET_USER_ACTIVITY_TIMEOUT = 2;
    private static final int MSG_SEND_SLEEP_TRANSITION = 3;
    private static final int PINNED_TASK_ABORT_TIMEOUT = 1000;

    static final String TAG_TASKS = TAG + POSTFIX_TASKS;
    static final String TAG_STATES = TAG + POSTFIX_STATES;
@@ -298,6 +299,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>

    };

    // TODO: b/335866033 Remove the abort PiP on timeout once PiP2 flag is on.
    @Nullable private Runnable mMaybeAbortPipEnterRunnable = null;

    private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();

    static class FindTaskResult implements Predicate<Task> {
@@ -2247,8 +2251,67 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
        resumeFocusedTasksTopActivities();

        notifyActivityPipModeChanged(r.getTask(), r);

        if (!isPip2ExperimentEnabled()) {
            // TODO: b/335866033 Remove the abort PiP on timeout once PiP2 flag is on.
            // Set up a timeout callback to potentially abort PiP enter if in an inconsistent state.
            scheduleTimeoutAbortPipEnter(rootTask);
        }
    }

    private void scheduleTimeoutAbortPipEnter(Task rootTask) {
        if (mMaybeAbortPipEnterRunnable != null) {
            // If there is an abort enter PiP check pending already remove it and abort
            // immediately since we are trying to enter PiP in an inconsistent state
            mHandler.removeCallbacks(mMaybeAbortPipEnterRunnable);
            mMaybeAbortPipEnterRunnable.run();
        }
        // Snapshot a throwable early on to display the callstack upon abort later on timeout.
        final Throwable enterPipThrowable = new Throwable();
        // Set up a timeout to potentially roll back the task change to PINNED mode
        // by aborting PiP.
        mMaybeAbortPipEnterRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mService.mGlobalLock) {
                    if (mTransitionController.inTransition()) {
                        // If this task is a part an active transition aborting PiP might break
                        // it; so run the timeout callback directly once idle.

                        final Runnable expectedMaybeAbortAtTimeout = mMaybeAbortPipEnterRunnable;
                        mTransitionController.mStateValidators.add(() -> {
                            // If a second PiP transition comes in, it runs the abort runnable for
                            // the first transition pre-emptively, so we need to avoid calling
                            // the same runnable twice when validating states.
                            if (expectedMaybeAbortAtTimeout != mMaybeAbortPipEnterRunnable) return;
                            mMaybeAbortPipEnterRunnable = null;
                            run();
                        });
                        return;
                    } else {
                        mMaybeAbortPipEnterRunnable = null;
                    }
                    mService.deferWindowLayout();
                    final ActivityRecord top = rootTask.getTopMostActivity();
                    final ActivityManager.RunningTaskInfo beforeTaskInfo =
                            rootTask.getTaskInfo();
                    if (top != null && !top.inPinnedWindowingMode()
                            && rootTask.abortPipEnter(top)) {
                        Slog.wtf(TAG, "Enter PiP was aborted via a scheduled timeout"
                                        + "task_state_before=" + beforeTaskInfo
                                        + "task_state_after=" + rootTask.getTaskInfo(),
                                enterPipThrowable);
                    }
                    mService.continueWindowLayout();
                }
            }
        };
        mHandler.postDelayed(mMaybeAbortPipEnterRunnable, PINNED_TASK_ABORT_TIMEOUT);
        Slog.d(TAG, "a delayed check for potentially aborting PiP if "
                + "in a wrong state is scheduled.");
    }


    /**
     * Notifies when an activity enters or leaves PIP mode.
     *
@@ -2867,6 +2930,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
        mService.mH.post(mDestroyAllActivitiesRunnable);
    }

    void removeAllMaybeAbortPipEnterRunnable() {
        if (mMaybeAbortPipEnterRunnable == null) {
            return;
        }
        mHandler.removeCallbacks(mMaybeAbortPipEnterRunnable);
        mMaybeAbortPipEnterRunnable = null;
    }

    // Tries to put all activity tasks to sleep. Returns true if all tasks were
    // successfully put to sleep.
    boolean putTasksToSleep(boolean allowDelay, boolean shuttingDown) {
+5 −2
Original line number Diff line number Diff line
@@ -4851,11 +4851,13 @@ class Task extends TaskFragment {
    /**
     * Abort an incomplete pip-entry. If left in this state, it will cover everything but remain
     * paused. If this is needed, there is a bug -- this should only be used for recovery.
     *
     * @return true if there is an inconsistency in the task and activity state.
     */
    void abortPipEnter(ActivityRecord top) {
    boolean abortPipEnter(ActivityRecord top) {
        // an incomplete state has the task PINNED but the activity not.
        if (!inPinnedWindowingMode() || top.inPinnedWindowingMode() || !canMoveTaskToBack(this)) {
            return;
            return false;
        }
        final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */,
                mTransitionController, mWmService.mSyncEngine);
@@ -4877,6 +4879,7 @@ class Task extends TaskFragment {
            top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
            top.mWaitForEnteringPinnedMode = false;
        }
        return true;
    }

    void resumeNextFocusAfterReparent() {
+12 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
@@ -848,6 +849,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
        }

        final int childWindowingMode = c.getActivityWindowingMode();
        if (!ActivityTaskManagerService.isPip2ExperimentEnabled()
                && tr.getWindowingMode() == WINDOWING_MODE_PINNED
                && (childWindowingMode == WINDOWING_MODE_PINNED
                || childWindowingMode == WINDOWING_MODE_UNDEFINED)) {
            // If setActivityWindowingMode requested to match its pinned task's windowing mode,
            // remove any inconsistency checking timeout callbacks for PiP.
            Slog.d(TAG, "Task and activity windowing modes match, so remove any timeout "
                    + "abort PiP callbacks scheduled if needed; task_win_mode="
                    + tr.getWindowingMode() + ", activity_win_mode=" + childWindowingMode);
            mService.mRootWindowContainer.removeAllMaybeAbortPipEnterRunnable();
        }
        if (childWindowingMode > -1) {
            tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
        }