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

Commit bfc2dd44 authored by Kazuki Takise's avatar Kazuki Takise
Browse files

Add transition support to presentation

With this change, adding/removing a presentation triggers a shell
transition. The entry point of these operation is WMS#addWindow() or
Session#remove(), where there shouldn't be any collecting transition.

This change also moves the places to call DMS#onPresentation to
WS#onSurfaceVisibilityChanged().

Flag: com.android.window.flags.enable_presentation_for_connected_displays
Bug: 390482707
Test: WmTests:PresentationControllerTests
Change-Id: I6a3c9a569e8c37745162ac9fa15de14cad0de716
parent 7470419c
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -56,11 +56,6 @@ class PresentationController {
        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s",
                win.getDisplayId(), win);
        mPresentingDisplayIds.add(win.getDisplayId());
        if (enablePresentationForConnectedDisplays()) {
            // A presentation hides all activities behind on the same display.
            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
                    /*notifyClients=*/ true);
        }
        win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true);
    }

@@ -76,11 +71,6 @@ class PresentationController {
        if (displayIdIndex != -1) {
            mPresentingDisplayIds.remove(displayIdIndex);
        }
        if (enablePresentationForConnectedDisplays()) {
            // A presentation hides all activities behind on the same display.
            win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
                    /*notifyClients=*/ true);
        }
        win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
    }
}
+23 −2
Original line number Diff line number Diff line
@@ -157,6 +157,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;

@@ -1820,9 +1821,29 @@ public class WindowManagerService extends IWindowManager.Stub
            final boolean hideSystemAlertWindows = shouldHideNonSystemOverlayWindow(win);
            win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);

            // Only a presentation window needs a transition because its visibility affets the
            // lifecycle of apps below (b/390481865).
            if (enablePresentationForConnectedDisplays() && win.isPresentation()) {
                Transition transition = null;
                if (!win.mTransitionController.isCollecting()) {
                    transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
                }
                win.mTransitionController.collect(win.mToken);
                res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
                        outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
                // A presentation hides all activities behind on the same display.
                win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
                        /*notifyClients=*/ true);
                win.mTransitionController.getCollectingTransition().setReady(win.mToken, true);
                if (transition != null) {
                    win.mTransitionController.requestStartTransition(transition, null,
                            null /* remoteTransition */, null /* displayChange */);
                }
            } else {
                res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
                        outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs);
            }
        }

        Binder.restoreCallingIdentity(origId);

+29 −10
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
@@ -182,6 +183,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;

import android.annotation.CallSuper;
@@ -2297,11 +2299,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
            dc.updateImeInputAndControlTarget(null);
        }

        final int type = mAttrs.type;

        if (isPresentation()) {
            mWmService.mPresentationController.onPresentationRemoved(this);
        }
        // Check if window provides non decor insets before clearing its provided insets.
        final boolean windowProvidesDisplayDecorInsets = providesDisplayDecorInsets();

@@ -2442,11 +2439,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
                }
            }

            // Only a presentation window needs a transition because its visibility affets the
            // lifecycle of apps below (b/390481865).
            if (enablePresentationForConnectedDisplays() && isPresentation()) {
                Transition transition = null;
                if (!mTransitionController.isCollecting()) {
                    transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
                }
                mTransitionController.collect(mToken);
                mAnimatingExit = true;
                mRemoveOnExit = true;
                mToken.setVisibleRequested(false);
                mWmService.mPresentationController.onPresentationRemoved(this);
                // A presentation hides all activities behind on the same display.
                mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
                        /*notifyClients=*/ true);
                mTransitionController.getCollectingTransition().setReady(mToken, true);
                if (transition != null) {
                    mTransitionController.requestStartTransition(transition, null,
                            null /* remoteTransition */, null /* displayChange */);
                }
            } else {
                removeImmediately();
                mWmService.updateFocusedWindowLocked(isFocused()
                                ? UPDATE_FOCUS_REMOVING_FOCUS
                                : UPDATE_FOCUS_NORMAL,
                        true /*updateInputWindows*/);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
+48 −5
Original line number Diff line number Diff line
@@ -17,14 +17,18 @@
package com.android.server.wm;

import static android.view.Display.FLAG_PRESENTATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;

import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.UserHandle;
import android.os.UserManager;
@@ -41,6 +45,7 @@ import android.view.WindowManagerGlobal;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -53,9 +58,16 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
public class PresentationControllerTests extends WindowTestsBase {

    TestTransitionPlayer mPlayer;

    @Before
    public void setUp() {
        mPlayer = registerTestTransitionPlayer();
    }

    @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS)
    @Test
    public void testPresentationHidesActivitiesBehind() {
    public void testPresentationShowAndHide() {
        final DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.flags = FLAG_PRESENTATION;
@@ -64,7 +76,6 @@ public class PresentationControllerTests extends WindowTestsBase {
        doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId);
        final ActivityRecord activity = createActivityRecord(createTask(dc));
        assertTrue(activity.isVisible());

        doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
        final int uid = 100000; // uid for non-system user
        final Session session = createTestSession(mAtm, 1234 /* pid */, uid);
@@ -72,16 +83,48 @@ public class PresentationControllerTests extends WindowTestsBase {
        doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_PRESENTATION);

        final IWindow clientWindow = new TestIWindow();

        // Show a Presentation window, which requests the activity to be stopped.
        final int result = mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId,
                userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
                new InsetsSourceControl.Array(), new Rect(), new float[1]);
        assertTrue(result >= WindowManagerGlobal.ADD_OKAY);
        assertFalse(activity.isVisibleRequested());
        assertTrue(activity.isVisible());
        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
        window.mHasSurface = true;
        final Transition addTransition = window.mTransitionController.getCollectingTransition();
        assertEquals(TRANSIT_OPEN, addTransition.mType);
        assertTrue(addTransition.isInTransition(window));
        assertTrue(addTransition.isInTransition(activity));

        // Completing the transition makes the activity invisible.
        completeTransition(addTransition, /*abortSync=*/ true);
        assertFalse(activity.isVisible());

        final WindowState window = mWm.windowForClientLocked(session, clientWindow, false);
        window.removeImmediately();
        // Remove a Presentation window, which requests the activity to be resumed back.
        window.removeIfPossible();
        final Transition removeTransition = window.mTransitionController.getCollectingTransition();
        assertEquals(TRANSIT_CLOSE, removeTransition.mType);
        assertTrue(removeTransition.isInTransition(window));
        assertTrue(removeTransition.isInTransition(activity));
        assertTrue(activity.isVisibleRequested());
        assertFalse(activity.isVisible());

        // Completing the transition makes the activity visible.
        completeTransition(removeTransition, /*abortSync=*/ false);
        assertTrue(activity.isVisible());
    }

    private void completeTransition(@NonNull Transition transition, boolean abortSync) {
        final ActionChain chain = ActionChain.testFinish(transition);
        if (abortSync) {
            // Forcefully finishing the active sync for testing purpose.
            mWm.mSyncEngine.abort(transition.getSyncId());
        } else {
            transition.onTransactionReady(transition.getSyncId(), mTransaction);
        }
        transition.finishTransition(chain);
    }
}