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

Commit ee065e15 authored by Evan Rosky's avatar Evan Rosky
Browse files

Add ability to pause activity configuration/effect client dispatch

This enables flows which can make changes on WMCore side
without notifying the client process until a later time. Usage
is basically: pauseConfigurationDispatch(); do stuff; later:
resumeConfigurationDispatch(); During "do stuff" WMCore will
already have the updated configuration but the client won't
know about it yet and can continue running as-if nothing
happened.

The primary use-case for this is PIP where we want to change it's
type/size but allow the client to continue drawing it's fullscreen
config until the transition animation completes.

Bug: 202201326
Bug: 290992727
Test: atest ActivityRecordTests#testPauseConfigDispatch
Change-Id: I923cc8c42ebf21b04af2376fd3383b75cb5fb996
parent 9bbc5ba4
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -1867,6 +1867,12 @@
      "group": "WM_DEBUG_STATES",
      "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
    },
    "-483957611": {
      "message": "Resuming configuration dispatch for %s",
      "level": "VERBOSE",
      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
      "at": "com\/android\/server\/wm\/ActivityRecord.java"
    },
    "-481924678": {
      "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
      "level": "DEBUG",
@@ -4021,6 +4027,12 @@
      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
      "at": "com\/android\/server\/wm\/Transition.java"
    },
    "1473051122": {
      "message": "Pausing configuration dispatch for  %s",
      "level": "VERBOSE",
      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
      "at": "com\/android\/server\/wm\/ActivityRecord.java"
    },
    "1494644409": {
      "message": "  Rejecting as detached: %s",
      "level": "VERBOSE",
+68 −0
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +992,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    private CustomAppTransition mCustomOpenTransition;
    private CustomAppTransition mCustomCloseTransition;

    /** Non-zero to pause dispatching configuration changes to the client. */
    int mPauseConfigurationDispatchCount = 0;

    private final Runnable mPauseTimeoutRunnable = new Runnable() {
        @Override
        public void run() {
@@ -9276,6 +9280,59 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        }
    }

    @Override
    void dispatchConfigurationToChild(WindowState child, Configuration config) {
        if (isConfigurationDispatchPaused()) {
            return;
        }
        super.dispatchConfigurationToChild(child, config);
    }

    /**
     * Pauses dispatch of configuration changes to the client. This includes any
     * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
     * a lifecycle change comes from another source (eg. stop), it will still run but will use the
     * paused configuration.
     *
     * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
     * That method is responsible for evaluating whether the activity needs to be relaunched and
     * sending configurations.
     */
    void pauseConfigurationDispatch() {
        ++mPauseConfigurationDispatchCount;
        if (mPauseConfigurationDispatchCount == 1) {
            ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
                    + " %s", this);
        }
    }

    /** @return `true` if configuration actually changed. */
    boolean resumeConfigurationDispatch() {
        --mPauseConfigurationDispatchCount;
        if (mPauseConfigurationDispatchCount > 0) {
            return false;
        }
        ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
        if (mPauseConfigurationDispatchCount < 0) {
            Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
            mPauseConfigurationDispatchCount = 0;
            return false;
        }
        if (mLastReportedDisplayId == getDisplayId()
                && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
            return false;
        }
        for (int i = getChildCount() - 1; i >= 0; --i) {
            dispatchConfigurationToChild(getChildAt(i), getConfiguration());
        }
        updateReportedConfigurationAndSend();
        return true;
    }

    boolean isConfigurationDispatchPaused() {
        return mPauseConfigurationDispatchCount > 0;
    }

    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
            Rect containingBounds) {
        return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9582,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            return true;
        }

        if (isConfigurationDispatchPaused()) {
            return true;
        }

        return updateReportedConfigurationAndSend();
    }

    boolean updateReportedConfigurationAndSend() {
        if (isConfigurationDispatchPaused()) {
            Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
        }
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
                + "configuration: %s", this);

+5 −0
Original line number Diff line number Diff line
@@ -5187,6 +5187,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
        if (mSurfaceControl == null) {
            return;
        }
        if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
            // Don't update surface-position while dispatch paused. This is calculated from
            // the server-side activity configuration so return early.
            return;
        }

        if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
                && !mSurfacePlacementNeeded) {
+4 −1
Original line number Diff line number Diff line
@@ -639,9 +639,12 @@ class WindowToken extends WindowContainer<WindowState> {

    @Override
    void updateSurfacePosition(SurfaceControl.Transaction t) {
        final ActivityRecord r = asActivityRecord();
        if (r != null && r.isConfigurationDispatchPaused()) {
            return;
        }
        super.updateSurfacePosition(t);
        if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
            final ActivityRecord r = asActivityRecord();
            final Task rootTask = r != null ? r.getRootTask() : null;
            // Don't transform the activity in PiP because the PiP task organizer will handle it.
            if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
+62 −0
Original line number Diff line number Diff line
@@ -3722,6 +3722,68 @@ public class ActivityRecordTests extends WindowTestsBase {
        assertFalse(ar.moveFocusableActivityToTop("test"));
    }

    @Test
    public void testPauseConfigDispatch() throws RemoteException {
        final Task task = new TaskBuilder(mSupervisor)
                .setDisplay(mDisplayContent).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopNonFinishingActivity();
        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                TYPE_BASE_APPLICATION);
        attrs.setTitle("AppWindow");
        final TestWindowState appWindow = createWindowState(attrs, activity);
        activity.addWindow(appWindow);

        clearInvocations(mClientLifecycleManager);
        clearInvocations(activity);

        Configuration ro = activity.getRequestedOverrideConfiguration();
        ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
        activity.onRequestedOverrideConfigurationChanged(ro);
        activity.ensureActivityConfiguration();
        mWm.mRoot.performSurfacePlacement();

        // policy will center the bounds, so just check for matching size here.
        assertEquals(100, activity.getWindowConfiguration().getBounds().width());
        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
        // No scheduled transactions since it asked for a restart.
        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
        assertTrue(appWindow.mResizeReported);

        // act like everything drew and went idle
        appWindow.mResizeReported = false;
        makeLastConfigReportedToClient(appWindow, true);

        // Now pause dispatch and try to resize
        activity.pauseConfigurationDispatch();

        ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
        activity.onRequestedOverrideConfigurationChanged(ro);
        activity.ensureActivityConfiguration();
        mWm.mRoot.performSurfacePlacement();

        // Activity should get new config (core-side)
        assertEquals(130, activity.getWindowConfiguration().getBounds().width());
        // But windows should not get new config.
        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
        // The client shouldn't receive any changes
        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
        // and lastReported shouldn't be set.
        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
        // There should be no resize reported to client.
        assertFalse(appWindow.mResizeReported);

        // Now resume dispatch
        activity.resumeConfigurationDispatch();
        mWm.mRoot.performSurfacePlacement();

        // Windows and client should now receive updates
        verify(activity, times(2)).setLastReportedConfiguration(any(), any());
        verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
        assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
        assertTrue(appWindow.mResizeReported);
    }

    private ICompatCameraControlCallback getCompatCameraControlCallback() {
        return new ICompatCameraControlCallback.Stub() {
            @Override