Loading services/core/java/com/android/server/wm/PresentationController.java +198 −26 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR; import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays; import android.annotation.NonNull; import android.util.IntArray; import android.annotation.Nullable; import android.hardware.display.DisplayManager; import android.util.SparseArray; import android.view.WindowManager.LayoutParams.WindowType; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.WmProtoLogGroups; Loading @@ -27,15 +34,125 @@ import com.android.internal.protolog.WmProtoLogGroups; /** * Manages presentation windows. */ class PresentationController { class PresentationController implements DisplayManager.DisplayListener { private static class Presentation { @NonNull final WindowState mWin; @NonNull final WindowContainerListener mPresentationListener; // This is the task which started this presentation. This shouldn't be null in most cases // because the intended usage of the Presentation API is that an activity that started a // presentation should control the UI and lifecycle of the presentation window. // However, the API doesn't necessarily requires a host activity to exist (e.g. a background // service can launch a presentation), so this can be null. @Nullable final Task mHostTask; @Nullable final WindowContainerListener mHostTaskListener; Presentation(@NonNull WindowState win, @NonNull WindowContainerListener presentationListener, @Nullable Task hostTask, @Nullable WindowContainerListener hostTaskListener) { mWin = win; mPresentationListener = presentationListener; mHostTask = hostTask; mHostTaskListener = hostTaskListener; } @Override public String toString() { return "{win: " + mWin.getName() + ", display: " + mWin.getDisplayId() + ", hostTask: " + (mHostTask != null ? mHostTask.getName() : null) + "}"; } } private final SparseArray<Presentation> mPresentations = new SparseArray(); @Nullable private Presentation getPresentation(@Nullable WindowState win) { if (win == null) return null; for (int i = 0; i < mPresentations.size(); i++) { final Presentation presentation = mPresentations.valueAt(i); if (win == presentation.mWin) return presentation; } return null; } private boolean hasPresentationWindow(int displayId) { return mPresentations.contains(displayId); } private boolean isPresentationVisible(int displayId) { final Presentation presentation = mPresentations.get(displayId); return presentation != null && presentation.mWin.mToken.isVisibleRequested(); } boolean canPresent(@NonNull WindowState win, @NonNull DisplayContent displayContent) { return canPresent(win, displayContent, win.mAttrs.type, win.getUid()); } /** * Checks if a presentation window can be shown on the given display. * If the given |win| is empty, a new presentation window is being created. * If the given |win| is not empty, the window already exists as presentation, and we're * revalidate if the |win| is still qualified to be shown. */ boolean canPresent(@Nullable WindowState win, @NonNull DisplayContent displayContent, @WindowType int type, int uid) { if (type == TYPE_PRIVATE_PRESENTATION) { // Private presentations can only be created on private displays. return displayContent.isPrivate(); } if (type != TYPE_PRESENTATION) { return false; } if (!enablePresentationForConnectedDisplays()) { return displayContent.getDisplay().isPublicPresentation(); } boolean allDisplaysArePresenting = true; for (int i = 0; i < displayContent.mWmService.mRoot.mChildren.size(); i++) { final DisplayContent dc = displayContent.mWmService.mRoot.mChildren.get(i); if (displayContent.mDisplayId != dc.mDisplayId && !mPresentations.contains(dc.mDisplayId)) { allDisplaysArePresenting = false; break; } } if (allDisplaysArePresenting) { // All displays can't present simultaneously. return false; } // TODO(b/395475549): Add support for display add/remove, and activity move across displays. private final IntArray mPresentingDisplayIds = new IntArray(); final int displayId = displayContent.mDisplayId; if (hasPresentationWindow(displayId) && win != null && win != mPresentations.get(displayId).mWin) { // A display can't have multiple presentations. return false; } PresentationController() {} Task hostTask = null; final Presentation presentation = getPresentation(win); if (presentation != null) { hostTask = presentation.mHostTask; } else if (win == null) { final Task globallyFocusedTask = displayContent.mWmService.mRoot.getTopDisplayFocusedRootTask(); if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { hostTask = globallyFocusedTask; } } if (hostTask != null && displayId == hostTask.getDisplayId()) { // A presentation can't cover its own host task. return false; } if (hostTask == null && !displayContent.getDisplay().isPublicPresentation()) { // A globally focused host task on a different display is needed to show a // presentation on a non-presenting display. return false; } private boolean isPresenting(int displayId) { return mPresentingDisplayIds.contains(displayId); return true; } boolean shouldOccludeActivities(int displayId) { Loading @@ -45,32 +162,87 @@ class PresentationController { // be shown on them. // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that // the presentation won't stop its controlling activity. return enablePresentationForConnectedDisplays() && isPresenting(displayId); return enablePresentationForConnectedDisplays() && isPresentationVisible(displayId); } void onPresentationAdded(@NonNull WindowState win) { void onPresentationAdded(@NonNull WindowState win, int uid) { final int displayId = win.getDisplayId(); if (isPresenting(displayId)) { return; } ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s", win.getDisplayId(), win); mPresentingDisplayIds.add(win.getDisplayId()); displayId, win); win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true); } void onPresentationRemoved(@NonNull WindowState win) { final int displayId = win.getDisplayId(); if (!isPresenting(displayId)) { final WindowContainerListener presentationWindowListener = new WindowContainerListener() { @Override public void onRemoved() { if (!hasPresentationWindow(displayId)) { ProtoLog.e(WM_ERROR, "Failed to remove presentation on" + "non-presenting display %d: %s", displayId, win); return; } ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation removed from display %d: %s", win.getDisplayId(), win); // TODO(b/393945496): Make sure that there's one presentation at most per display. final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId); if (displayIdIndex != -1) { mPresentingDisplayIds.remove(displayIdIndex); final Presentation presentation = mPresentations.get(displayId); win.mToken.unregisterWindowContainerListener(presentation.mPresentationListener); if (presentation.mHostTask != null) { presentation.mHostTask.unregisterWindowContainerListener( presentation.mHostTaskListener); } mPresentations.remove(displayId); win.mWmService.mDisplayManagerInternal.onPresentation(displayId, false /*isShown*/); } }; win.mToken.registerWindowContainerListener(presentationWindowListener); Task hostTask = null; if (enablePresentationForConnectedDisplays()) { final Task globallyFocusedTask = win.mWmService.mRoot.getTopDisplayFocusedRootTask(); if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { hostTask = globallyFocusedTask; } } win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false); WindowContainerListener hostTaskListener = null; if (hostTask != null) { hostTaskListener = new WindowContainerListener() { public void onDisplayChanged(DisplayContent dc) { final Presentation presentation = mPresentations.get(dc.getDisplayId()); if (presentation != null && !canPresent(presentation.mWin, dc)) { removePresentation(dc.mDisplayId, "host task moved to display " + dc.getDisplayId()); } } public void onRemoved() { removePresentation(win.getDisplayId(), "host task removed"); } }; hostTask.registerWindowContainerListener(hostTaskListener); } mPresentations.put(displayId, new Presentation(win, presentationWindowListener, hostTask, hostTaskListener)); } void removePresentation(int displayId, @NonNull String reason) { final Presentation presentation = mPresentations.get(displayId); if (enablePresentationForConnectedDisplays() && presentation != null) { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Removing Presentation %s for " + "reason %s", mPresentations.get(displayId), reason); final WindowState win = presentation.mWin; win.mWmService.mAtmService.mH.post(() -> { synchronized (win.mWmService.mGlobalLock) { win.removeIfPossible(); } }); } } @Override public void onDisplayAdded(int displayId) {} @Override public void onDisplayRemoved(int displayId) { removePresentation(displayId, "display removed " + displayId); } @Override public void onDisplayChanged(int displayId) {} } services/core/java/com/android/server/wm/WindowManagerService.java +12 −6 Original line number Diff line number Diff line Loading @@ -1583,14 +1583,18 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { if (type == TYPE_PRIVATE_PRESENTATION && !mPresentationController.canPresent(null /*win*/, displayContent, type, callingUid)) { ProtoLog.w(WM_ERROR, "Attempted to add private presentation window to a non-private display. " + "Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; } if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) { if (type == TYPE_PRESENTATION && !mPresentationController.canPresent(null /*win*/, displayContent, type, callingUid)) { ProtoLog.w(WM_ERROR, "Attempted to add presentation window to a non-suitable display. " + "Aborting."); Loading Loading @@ -1830,7 +1834,8 @@ public class WindowManagerService extends IWindowManager.Stub } win.mTransitionController.collect(win.mToken); res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs, callingUid); // A presentation hides all activities behind on the same display. win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true); Loading @@ -1841,7 +1846,8 @@ public class WindowManagerService extends IWindowManager.Stub } } else { res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs, callingUid); } } Loading @@ -1854,7 +1860,7 @@ public class WindowManagerService extends IWindowManager.Stub @NonNull ActivityRecord activity, @NonNull DisplayContent displayContent, @NonNull InsetsState outInsetsState, @NonNull Rect outAttachedFrame, @NonNull InsetsSourceControl.Array outActiveControls, @NonNull IWindow client, @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs) { @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs, int uid) { int res = 0; final int type = attrs.type; boolean imMayMove = true; Loading Loading @@ -1971,7 +1977,7 @@ public class WindowManagerService extends IWindowManager.Stub outSizeCompatScale[0] = win.getCompatScaleForClient(); if (res >= ADD_OKAY && win.isPresentation()) { mPresentationController.onPresentationAdded(win); mPresentationController.onPresentationAdded(win, uid); } return res; Loading services/core/java/com/android/server/wm/WindowState.java +0 −1 Original line number Diff line number Diff line Loading @@ -2435,7 +2435,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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); Loading services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java +130 −1 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRESENTATION; import static android.view.Display.FLAG_TRUSTED; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS; Loading @@ -30,6 +33,7 @@ import static org.mockito.ArgumentMatchers.eq; import android.annotation.NonNull; import android.graphics.Rect; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; Loading Loading @@ -118,6 +122,112 @@ public class PresentationControllerTests extends WindowTestsBase { assertFalse(window.isAttached()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotCoverHostTask() { int uid = Binder.getCallingUid(); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task task = createTask(presentationDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Adding a presentation window over its host task must fail. assertAddPresentationWindowFails(uid, presentationDisplay.mDisplayId); // Adding a presentation window on the other display must succeed. final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Moving the host task to the presenting display will remove the presentation. task.reparent(mDefaultDisplay.getDefaultTaskDisplayArea(), true); waitHandlerIdle(window.mWmService.mAtmService.mH); final Transition removeTransition = window.mTransitionController.getCollectingTransition(); assertEquals(TRANSIT_CLOSE, removeTransition.mType); completeTransition(removeTransition, /*abortSync=*/ false); assertFalse(window.isVisible()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotLaunchOnAllDisplays() { final int uid = Binder.getCallingUid(); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task task = createTask(presentationDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Add a presentation window on the default display. final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Adding another presentation window over the task even if it's a different UID because // it would end up showing presentations on all displays. assertAddPresentationWindowFails(uid + 1, presentationDisplay.mDisplayId); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotLaunchOnNonPresentationDisplayWithoutHostHavingGlobalFocus() { final int uid = Binder.getCallingUid(); // Adding a presentation window on an internal display requires a host task // with global focus on another display. assertAddPresentationWindowFails(uid, DEFAULT_DISPLAY); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task taskWiSameUid = createTask(presentationDisplay); taskWiSameUid.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(taskWiSameUid); assertTrue(activity.isVisible()); final Task taskWithDifferentUid = createTask(presentationDisplay); taskWithDifferentUid.effectiveUid = uid + 1; createActivityRecord(taskWithDifferentUid); assertEquals(taskWithDifferentUid, presentationDisplay.getFocusedRootTask()); // The task with the same UID is covered by another task with a different UID, so this must // also fail. assertAddPresentationWindowFails(uid, DEFAULT_DISPLAY); // Moving the task with the same UID to front and giving it global focus allows a // presentation to show on the default display. taskWiSameUid.moveToFront("test"); final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testReparentingActivityToSameDisplayClosesPresentation() { final int uid = Binder.getCallingUid(); final Task task = createTask(mDefaultDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Add a presentation window on a presentation display. final DisplayContent presentationDisplay = createPresentationDisplay(); final WindowState window = addPresentationWindow(uid, presentationDisplay.getDisplayId()); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Reparenting the host task below the presentation must close the presentation. task.reparent(presentationDisplay.getDefaultTaskDisplayArea(), true); waitHandlerIdle(window.mWmService.mAtmService.mH); final Transition removeTransition = window.mTransitionController.getCollectingTransition(); // It's a WAKE transition instead of CLOSE because assertEquals(TRANSIT_WAKE, removeTransition.mType); completeTransition(removeTransition, /*abortSync=*/ false); assertFalse(window.isVisible()); } private WindowState addPresentationWindow(int uid, int displayId) { final Session session = createTestSession(mAtm, 1234 /* pid */, uid); final int userId = UserHandle.getUserId(uid); Loading @@ -134,10 +244,29 @@ public class PresentationControllerTests extends WindowTestsBase { return window; } private void assertAddPresentationWindowFails(int uid, int displayId) { final Session session = createTestSession(mAtm, 1234 /* pid */, uid); final IWindow clientWindow = new TestIWindow(); final int res = addPresentationWindowInner(uid, displayId, session, clientWindow); assertEquals(WindowManagerGlobal.ADD_INVALID_DISPLAY, res); } private int addPresentationWindowInner(int uid, int displayId, Session session, IWindow clientWindow) { final int userId = UserHandle.getUserId(uid); doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_PRESENTATION); return mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(), new InsetsSourceControl.Array(), new Rect(), new float[1]); } private DisplayContent createPresentationDisplay() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); displayInfo.flags = FLAG_PRESENTATION; displayInfo.flags = FLAG_PRESENTATION | FLAG_TRUSTED; displayInfo.displayId = DEFAULT_DISPLAY + 1; final DisplayContent dc = createNewDisplay(displayInfo); final int displayId = dc.getDisplayId(); doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); Loading Loading
services/core/java/com/android/server/wm/PresentationController.java +198 −26 Original line number Diff line number Diff line Loading @@ -16,10 +16,17 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR; import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays; import android.annotation.NonNull; import android.util.IntArray; import android.annotation.Nullable; import android.hardware.display.DisplayManager; import android.util.SparseArray; import android.view.WindowManager.LayoutParams.WindowType; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.WmProtoLogGroups; Loading @@ -27,15 +34,125 @@ import com.android.internal.protolog.WmProtoLogGroups; /** * Manages presentation windows. */ class PresentationController { class PresentationController implements DisplayManager.DisplayListener { private static class Presentation { @NonNull final WindowState mWin; @NonNull final WindowContainerListener mPresentationListener; // This is the task which started this presentation. This shouldn't be null in most cases // because the intended usage of the Presentation API is that an activity that started a // presentation should control the UI and lifecycle of the presentation window. // However, the API doesn't necessarily requires a host activity to exist (e.g. a background // service can launch a presentation), so this can be null. @Nullable final Task mHostTask; @Nullable final WindowContainerListener mHostTaskListener; Presentation(@NonNull WindowState win, @NonNull WindowContainerListener presentationListener, @Nullable Task hostTask, @Nullable WindowContainerListener hostTaskListener) { mWin = win; mPresentationListener = presentationListener; mHostTask = hostTask; mHostTaskListener = hostTaskListener; } @Override public String toString() { return "{win: " + mWin.getName() + ", display: " + mWin.getDisplayId() + ", hostTask: " + (mHostTask != null ? mHostTask.getName() : null) + "}"; } } private final SparseArray<Presentation> mPresentations = new SparseArray(); @Nullable private Presentation getPresentation(@Nullable WindowState win) { if (win == null) return null; for (int i = 0; i < mPresentations.size(); i++) { final Presentation presentation = mPresentations.valueAt(i); if (win == presentation.mWin) return presentation; } return null; } private boolean hasPresentationWindow(int displayId) { return mPresentations.contains(displayId); } private boolean isPresentationVisible(int displayId) { final Presentation presentation = mPresentations.get(displayId); return presentation != null && presentation.mWin.mToken.isVisibleRequested(); } boolean canPresent(@NonNull WindowState win, @NonNull DisplayContent displayContent) { return canPresent(win, displayContent, win.mAttrs.type, win.getUid()); } /** * Checks if a presentation window can be shown on the given display. * If the given |win| is empty, a new presentation window is being created. * If the given |win| is not empty, the window already exists as presentation, and we're * revalidate if the |win| is still qualified to be shown. */ boolean canPresent(@Nullable WindowState win, @NonNull DisplayContent displayContent, @WindowType int type, int uid) { if (type == TYPE_PRIVATE_PRESENTATION) { // Private presentations can only be created on private displays. return displayContent.isPrivate(); } if (type != TYPE_PRESENTATION) { return false; } if (!enablePresentationForConnectedDisplays()) { return displayContent.getDisplay().isPublicPresentation(); } boolean allDisplaysArePresenting = true; for (int i = 0; i < displayContent.mWmService.mRoot.mChildren.size(); i++) { final DisplayContent dc = displayContent.mWmService.mRoot.mChildren.get(i); if (displayContent.mDisplayId != dc.mDisplayId && !mPresentations.contains(dc.mDisplayId)) { allDisplaysArePresenting = false; break; } } if (allDisplaysArePresenting) { // All displays can't present simultaneously. return false; } // TODO(b/395475549): Add support for display add/remove, and activity move across displays. private final IntArray mPresentingDisplayIds = new IntArray(); final int displayId = displayContent.mDisplayId; if (hasPresentationWindow(displayId) && win != null && win != mPresentations.get(displayId).mWin) { // A display can't have multiple presentations. return false; } PresentationController() {} Task hostTask = null; final Presentation presentation = getPresentation(win); if (presentation != null) { hostTask = presentation.mHostTask; } else if (win == null) { final Task globallyFocusedTask = displayContent.mWmService.mRoot.getTopDisplayFocusedRootTask(); if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { hostTask = globallyFocusedTask; } } if (hostTask != null && displayId == hostTask.getDisplayId()) { // A presentation can't cover its own host task. return false; } if (hostTask == null && !displayContent.getDisplay().isPublicPresentation()) { // A globally focused host task on a different display is needed to show a // presentation on a non-presenting display. return false; } private boolean isPresenting(int displayId) { return mPresentingDisplayIds.contains(displayId); return true; } boolean shouldOccludeActivities(int displayId) { Loading @@ -45,32 +162,87 @@ class PresentationController { // be shown on them. // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that // the presentation won't stop its controlling activity. return enablePresentationForConnectedDisplays() && isPresenting(displayId); return enablePresentationForConnectedDisplays() && isPresentationVisible(displayId); } void onPresentationAdded(@NonNull WindowState win) { void onPresentationAdded(@NonNull WindowState win, int uid) { final int displayId = win.getDisplayId(); if (isPresenting(displayId)) { return; } ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s", win.getDisplayId(), win); mPresentingDisplayIds.add(win.getDisplayId()); displayId, win); win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true); } void onPresentationRemoved(@NonNull WindowState win) { final int displayId = win.getDisplayId(); if (!isPresenting(displayId)) { final WindowContainerListener presentationWindowListener = new WindowContainerListener() { @Override public void onRemoved() { if (!hasPresentationWindow(displayId)) { ProtoLog.e(WM_ERROR, "Failed to remove presentation on" + "non-presenting display %d: %s", displayId, win); return; } ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation removed from display %d: %s", win.getDisplayId(), win); // TODO(b/393945496): Make sure that there's one presentation at most per display. final int displayIdIndex = mPresentingDisplayIds.indexOf(displayId); if (displayIdIndex != -1) { mPresentingDisplayIds.remove(displayIdIndex); final Presentation presentation = mPresentations.get(displayId); win.mToken.unregisterWindowContainerListener(presentation.mPresentationListener); if (presentation.mHostTask != null) { presentation.mHostTask.unregisterWindowContainerListener( presentation.mHostTaskListener); } mPresentations.remove(displayId); win.mWmService.mDisplayManagerInternal.onPresentation(displayId, false /*isShown*/); } }; win.mToken.registerWindowContainerListener(presentationWindowListener); Task hostTask = null; if (enablePresentationForConnectedDisplays()) { final Task globallyFocusedTask = win.mWmService.mRoot.getTopDisplayFocusedRootTask(); if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { hostTask = globallyFocusedTask; } } win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false); WindowContainerListener hostTaskListener = null; if (hostTask != null) { hostTaskListener = new WindowContainerListener() { public void onDisplayChanged(DisplayContent dc) { final Presentation presentation = mPresentations.get(dc.getDisplayId()); if (presentation != null && !canPresent(presentation.mWin, dc)) { removePresentation(dc.mDisplayId, "host task moved to display " + dc.getDisplayId()); } } public void onRemoved() { removePresentation(win.getDisplayId(), "host task removed"); } }; hostTask.registerWindowContainerListener(hostTaskListener); } mPresentations.put(displayId, new Presentation(win, presentationWindowListener, hostTask, hostTaskListener)); } void removePresentation(int displayId, @NonNull String reason) { final Presentation presentation = mPresentations.get(displayId); if (enablePresentationForConnectedDisplays() && presentation != null) { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Removing Presentation %s for " + "reason %s", mPresentations.get(displayId), reason); final WindowState win = presentation.mWin; win.mWmService.mAtmService.mH.post(() -> { synchronized (win.mWmService.mGlobalLock) { win.removeIfPossible(); } }); } } @Override public void onDisplayAdded(int displayId) {} @Override public void onDisplayRemoved(int displayId) { removePresentation(displayId, "display removed " + displayId); } @Override public void onDisplayChanged(int displayId) {} }
services/core/java/com/android/server/wm/WindowManagerService.java +12 −6 Original line number Diff line number Diff line Loading @@ -1583,14 +1583,18 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { if (type == TYPE_PRIVATE_PRESENTATION && !mPresentationController.canPresent(null /*win*/, displayContent, type, callingUid)) { ProtoLog.w(WM_ERROR, "Attempted to add private presentation window to a non-private display. " + "Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; } if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) { if (type == TYPE_PRESENTATION && !mPresentationController.canPresent(null /*win*/, displayContent, type, callingUid)) { ProtoLog.w(WM_ERROR, "Attempted to add presentation window to a non-suitable display. " + "Aborting."); Loading Loading @@ -1830,7 +1834,8 @@ public class WindowManagerService extends IWindowManager.Stub } win.mTransitionController.collect(win.mToken); res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs, callingUid); // A presentation hides all activities behind on the same display. win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null, /*notifyClients=*/ true); Loading @@ -1841,7 +1846,8 @@ public class WindowManagerService extends IWindowManager.Stub } } else { res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState, outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs); outAttachedFrame, outActiveControls, client, outSizeCompatScale, attrs, callingUid); } } Loading @@ -1854,7 +1860,7 @@ public class WindowManagerService extends IWindowManager.Stub @NonNull ActivityRecord activity, @NonNull DisplayContent displayContent, @NonNull InsetsState outInsetsState, @NonNull Rect outAttachedFrame, @NonNull InsetsSourceControl.Array outActiveControls, @NonNull IWindow client, @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs) { @NonNull float[] outSizeCompatScale, @NonNull LayoutParams attrs, int uid) { int res = 0; final int type = attrs.type; boolean imMayMove = true; Loading Loading @@ -1971,7 +1977,7 @@ public class WindowManagerService extends IWindowManager.Stub outSizeCompatScale[0] = win.getCompatScaleForClient(); if (res >= ADD_OKAY && win.isPresentation()) { mPresentationController.onPresentationAdded(win); mPresentationController.onPresentationAdded(win, uid); } return res; Loading
services/core/java/com/android/server/wm/WindowState.java +0 −1 Original line number Diff line number Diff line Loading @@ -2435,7 +2435,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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); Loading
services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java +130 −1 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRESENTATION; import static android.view.Display.FLAG_TRUSTED; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.window.flags.Flags.FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS; Loading @@ -30,6 +33,7 @@ import static org.mockito.ArgumentMatchers.eq; import android.annotation.NonNull; import android.graphics.Rect; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; Loading Loading @@ -118,6 +122,112 @@ public class PresentationControllerTests extends WindowTestsBase { assertFalse(window.isAttached()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotCoverHostTask() { int uid = Binder.getCallingUid(); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task task = createTask(presentationDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Adding a presentation window over its host task must fail. assertAddPresentationWindowFails(uid, presentationDisplay.mDisplayId); // Adding a presentation window on the other display must succeed. final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Moving the host task to the presenting display will remove the presentation. task.reparent(mDefaultDisplay.getDefaultTaskDisplayArea(), true); waitHandlerIdle(window.mWmService.mAtmService.mH); final Transition removeTransition = window.mTransitionController.getCollectingTransition(); assertEquals(TRANSIT_CLOSE, removeTransition.mType); completeTransition(removeTransition, /*abortSync=*/ false); assertFalse(window.isVisible()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotLaunchOnAllDisplays() { final int uid = Binder.getCallingUid(); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task task = createTask(presentationDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Add a presentation window on the default display. final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Adding another presentation window over the task even if it's a different UID because // it would end up showing presentations on all displays. assertAddPresentationWindowFails(uid + 1, presentationDisplay.mDisplayId); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testPresentationCannotLaunchOnNonPresentationDisplayWithoutHostHavingGlobalFocus() { final int uid = Binder.getCallingUid(); // Adding a presentation window on an internal display requires a host task // with global focus on another display. assertAddPresentationWindowFails(uid, DEFAULT_DISPLAY); final DisplayContent presentationDisplay = createPresentationDisplay(); final Task taskWiSameUid = createTask(presentationDisplay); taskWiSameUid.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(taskWiSameUid); assertTrue(activity.isVisible()); final Task taskWithDifferentUid = createTask(presentationDisplay); taskWithDifferentUid.effectiveUid = uid + 1; createActivityRecord(taskWithDifferentUid); assertEquals(taskWithDifferentUid, presentationDisplay.getFocusedRootTask()); // The task with the same UID is covered by another task with a different UID, so this must // also fail. assertAddPresentationWindowFails(uid, DEFAULT_DISPLAY); // Moving the task with the same UID to front and giving it global focus allows a // presentation to show on the default display. taskWiSameUid.moveToFront("test"); final WindowState window = addPresentationWindow(uid, DEFAULT_DISPLAY); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); } @EnableFlags(FLAG_ENABLE_PRESENTATION_FOR_CONNECTED_DISPLAYS) @Test public void testReparentingActivityToSameDisplayClosesPresentation() { final int uid = Binder.getCallingUid(); final Task task = createTask(mDefaultDisplay); task.effectiveUid = uid; final ActivityRecord activity = createActivityRecord(task); assertTrue(activity.isVisible()); // Add a presentation window on a presentation display. final DisplayContent presentationDisplay = createPresentationDisplay(); final WindowState window = addPresentationWindow(uid, presentationDisplay.getDisplayId()); final Transition addTransition = window.mTransitionController.getCollectingTransition(); completeTransition(addTransition, /*abortSync=*/ true); assertTrue(window.isVisible()); // Reparenting the host task below the presentation must close the presentation. task.reparent(presentationDisplay.getDefaultTaskDisplayArea(), true); waitHandlerIdle(window.mWmService.mAtmService.mH); final Transition removeTransition = window.mTransitionController.getCollectingTransition(); // It's a WAKE transition instead of CLOSE because assertEquals(TRANSIT_WAKE, removeTransition.mType); completeTransition(removeTransition, /*abortSync=*/ false); assertFalse(window.isVisible()); } private WindowState addPresentationWindow(int uid, int displayId) { final Session session = createTestSession(mAtm, 1234 /* pid */, uid); final int userId = UserHandle.getUserId(uid); Loading @@ -134,10 +244,29 @@ public class PresentationControllerTests extends WindowTestsBase { return window; } private void assertAddPresentationWindowFails(int uid, int displayId) { final Session session = createTestSession(mAtm, 1234 /* pid */, uid); final IWindow clientWindow = new TestIWindow(); final int res = addPresentationWindowInner(uid, displayId, session, clientWindow); assertEquals(WindowManagerGlobal.ADD_INVALID_DISPLAY, res); } private int addPresentationWindowInner(int uid, int displayId, Session session, IWindow clientWindow) { final int userId = UserHandle.getUserId(uid); doReturn(true).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId)); final WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_PRESENTATION); return mWm.addWindow(session, clientWindow, params, View.VISIBLE, displayId, userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(), new InsetsSourceControl.Array(), new Rect(), new float[1]); } private DisplayContent createPresentationDisplay() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); displayInfo.flags = FLAG_PRESENTATION; displayInfo.flags = FLAG_PRESENTATION | FLAG_TRUSTED; displayInfo.displayId = DEFAULT_DISPLAY + 1; final DisplayContent dc = createNewDisplay(displayInfo); final int displayId = dc.getDisplayId(); doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); Loading