summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/PresentationController.java224
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java131
4 files changed, 340 insertions, 34 deletions
diff --git a/services/core/java/com/android/server/wm/PresentationController.java b/services/core/java/com/android/server/wm/PresentationController.java
index b3cff9c6cc3d..913f0b9a981d 100644
--- a/services/core/java/com/android/server/wm/PresentationController.java
+++ b/services/core/java/com/android/server/wm/PresentationController.java
@@ -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;
@@ -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;
+ }
- // TODO(b/395475549): Add support for display add/remove, and activity move across displays.
- private final IntArray mPresentingDisplayIds = new IntArray();
+ private boolean hasPresentationWindow(int displayId) {
+ return mPresentations.contains(displayId);
+ }
- PresentationController() {}
+ private boolean isPresentationVisible(int displayId) {
+ final Presentation presentation = mPresentations.get(displayId);
+ return presentation != null && presentation.mWin.mToken.isVisibleRequested();
+ }
- private boolean isPresenting(int displayId) {
- return mPresentingDisplayIds.contains(displayId);
+ 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;
+ }
+
+ final int displayId = displayContent.mDisplayId;
+ if (hasPresentationWindow(displayId)
+ && win != null && win != mPresentations.get(displayId).mWin) {
+ // A display can't have multiple presentations.
+ return false;
+ }
+
+ 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;
+ }
+
+ return true;
}
boolean shouldOccludeActivities(int displayId) {
@@ -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)) {
- return;
+ 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;
+ }
+ 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;
+ }
+ }
+ 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);
}
- 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);
+
+ 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();
+ }
+ });
}
- win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ false);
}
+
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ removePresentation(displayId, "display removed " + displayId);
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8aed91b2dc66..6ebda77d3bec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -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.");
@@ -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);
@@ -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);
}
}
@@ -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;
@@ -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;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bfedc90497ae..17c5e96dea01 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2439,7 +2439,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);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
index 2d4101e40615..6e0f7fbbf388 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PresentationControllerTests.java
@@ -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;
@@ -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;
@@ -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);
@@ -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);