1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; 20 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; 21 22 import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR; 23 import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.hardware.display.DisplayManager; 28 import android.util.SparseArray; 29 import android.view.WindowManager.LayoutParams.WindowType; 30 31 import com.android.internal.protolog.ProtoLog; 32 import com.android.internal.protolog.WmProtoLogGroups; 33 34 /** 35 * Manages presentation windows. 36 */ 37 class PresentationController implements DisplayManager.DisplayListener { 38 39 private static class Presentation { 40 @NonNull final WindowState mWin; 41 @NonNull final WindowContainerListener mPresentationListener; 42 // This is the task which started this presentation. This shouldn't be null in most cases 43 // because the intended usage of the Presentation API is that an activity that started a 44 // presentation should control the UI and lifecycle of the presentation window. 45 // However, the API doesn't necessarily requires a host activity to exist (e.g. a background 46 // service can launch a presentation), so this can be null. 47 @Nullable final Task mHostTask; 48 @Nullable final WindowContainerListener mHostTaskListener; 49 Presentation(@onNull WindowState win, @NonNull WindowContainerListener presentationListener, @Nullable Task hostTask, @Nullable WindowContainerListener hostTaskListener)50 Presentation(@NonNull WindowState win, 51 @NonNull WindowContainerListener presentationListener, 52 @Nullable Task hostTask, 53 @Nullable WindowContainerListener hostTaskListener) { 54 mWin = win; 55 mPresentationListener = presentationListener; 56 mHostTask = hostTask; 57 mHostTaskListener = hostTaskListener; 58 } 59 60 @Override toString()61 public String toString() { 62 return "{win: " + mWin.getName() + ", display: " + mWin.getDisplayId() 63 + ", hostTask: " + (mHostTask != null ? mHostTask.getName() : null) + "}"; 64 } 65 } 66 67 private final SparseArray<Presentation> mPresentations = new SparseArray(); 68 69 @Nullable getPresentation(@ullable WindowState win)70 private Presentation getPresentation(@Nullable WindowState win) { 71 if (win == null) return null; 72 for (int i = 0; i < mPresentations.size(); i++) { 73 final Presentation presentation = mPresentations.valueAt(i); 74 if (win == presentation.mWin) return presentation; 75 } 76 return null; 77 } 78 hasPresentationWindow(int displayId)79 private boolean hasPresentationWindow(int displayId) { 80 return mPresentations.contains(displayId); 81 } 82 isPresentationVisible(int displayId)83 boolean isPresentationVisible(int displayId) { 84 final Presentation presentation = mPresentations.get(displayId); 85 return presentation != null && presentation.mWin.mToken.isVisibleRequested(); 86 } 87 canPresent(@onNull WindowState win, @NonNull DisplayContent displayContent)88 boolean canPresent(@NonNull WindowState win, @NonNull DisplayContent displayContent) { 89 return canPresent(win, displayContent, win.mAttrs.type, win.getUid()); 90 } 91 92 /** 93 * Checks if a presentation window can be shown on the given display. 94 * If the given |win| is empty, a new presentation window is being created. 95 * If the given |win| is not empty, the window already exists as presentation, and we're 96 * revalidate if the |win| is still qualified to be shown. 97 */ canPresent(@ullable WindowState win, @NonNull DisplayContent displayContent, @WindowType int type, int uid)98 boolean canPresent(@Nullable WindowState win, @NonNull DisplayContent displayContent, 99 @WindowType int type, int uid) { 100 if (type == TYPE_PRIVATE_PRESENTATION) { 101 // Private presentations can only be created on private displays. 102 return displayContent.isPrivate(); 103 } 104 105 if (type != TYPE_PRESENTATION) { 106 return false; 107 } 108 109 if (!enablePresentationForConnectedDisplays()) { 110 return displayContent.getDisplay().isPublicPresentation(); 111 } 112 113 boolean allDisplaysArePresenting = true; 114 for (int i = 0; i < displayContent.mWmService.mRoot.mChildren.size(); i++) { 115 final DisplayContent dc = displayContent.mWmService.mRoot.mChildren.get(i); 116 if (displayContent.mDisplayId != dc.mDisplayId 117 && !mPresentations.contains(dc.mDisplayId)) { 118 allDisplaysArePresenting = false; 119 break; 120 } 121 } 122 if (allDisplaysArePresenting) { 123 // All displays can't present simultaneously. 124 return false; 125 } 126 127 final int displayId = displayContent.mDisplayId; 128 if (hasPresentationWindow(displayId) 129 && win != null && win != mPresentations.get(displayId).mWin) { 130 // A display can't have multiple presentations. 131 return false; 132 } 133 134 Task hostTask = null; 135 final Presentation presentation = getPresentation(win); 136 if (presentation != null) { 137 hostTask = presentation.mHostTask; 138 } else if (win == null) { 139 final Task globallyFocusedTask = 140 displayContent.mWmService.mRoot.getTopDisplayFocusedRootTask(); 141 if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { 142 hostTask = globallyFocusedTask; 143 } 144 } 145 if (hostTask != null && displayId == hostTask.getDisplayId()) { 146 // A presentation can't cover its own host task. 147 return false; 148 } 149 if (hostTask == null && !displayContent.getDisplay().isPublicPresentation()) { 150 // A globally focused host task on a different display is needed to show a 151 // presentation on a non-presenting display. 152 return false; 153 } 154 155 return true; 156 } 157 shouldOccludeActivities(int displayId)158 boolean shouldOccludeActivities(int displayId) { 159 // All activities on the presenting display must be hidden so that malicious apps can't do 160 // tap jacking (b/391466268). 161 // For now, this should only be applied to external displays because presentations can only 162 // be shown on them. 163 // TODO(b/390481621): Disallow a presentation from covering its controlling activity so that 164 // the presentation won't stop its controlling activity. 165 return enablePresentationForConnectedDisplays() && isPresentationVisible(displayId); 166 } 167 onPresentationAdded(@onNull WindowState win, int uid)168 void onPresentationAdded(@NonNull WindowState win, int uid) { 169 final int displayId = win.getDisplayId(); 170 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Presentation added to display %d: %s", 171 displayId, win); 172 win.mWmService.mDisplayManagerInternal.onPresentation(displayId, /*isShown=*/ true); 173 174 final WindowContainerListener presentationWindowListener = new WindowContainerListener() { 175 @Override 176 public void onRemoved() { 177 if (!hasPresentationWindow(displayId)) { 178 ProtoLog.e(WM_ERROR, "Failed to remove presentation on" 179 + "non-presenting display %d: %s", displayId, win); 180 return; 181 } 182 final Presentation presentation = mPresentations.get(displayId); 183 win.mToken.unregisterWindowContainerListener(presentation.mPresentationListener); 184 if (presentation.mHostTask != null) { 185 presentation.mHostTask.unregisterWindowContainerListener( 186 presentation.mHostTaskListener); 187 } 188 mPresentations.remove(displayId); 189 win.mWmService.mDisplayManagerInternal.onPresentation(displayId, false /*isShown*/); 190 } 191 }; 192 win.mToken.registerWindowContainerListener(presentationWindowListener); 193 194 Task hostTask = null; 195 if (enablePresentationForConnectedDisplays()) { 196 final Task globallyFocusedTask = 197 win.mWmService.mRoot.getTopDisplayFocusedRootTask(); 198 if (globallyFocusedTask != null && uid == globallyFocusedTask.effectiveUid) { 199 hostTask = globallyFocusedTask; 200 } 201 } 202 WindowContainerListener hostTaskListener = null; 203 if (hostTask != null) { 204 hostTaskListener = new WindowContainerListener() { 205 public void onDisplayChanged(DisplayContent dc) { 206 final Presentation presentation = mPresentations.get(dc.getDisplayId()); 207 if (presentation != null && !canPresent(presentation.mWin, dc)) { 208 removePresentation(dc.mDisplayId, "host task moved to display " 209 + dc.getDisplayId()); 210 } 211 } 212 213 public void onRemoved() { 214 removePresentation(win.getDisplayId(), "host task removed"); 215 } 216 }; 217 hostTask.registerWindowContainerListener(hostTaskListener); 218 } 219 220 mPresentations.put(displayId, new Presentation(win, presentationWindowListener, hostTask, 221 hostTaskListener)); 222 } 223 removePresentation(int displayId, @NonNull String reason)224 void removePresentation(int displayId, @NonNull String reason) { 225 final Presentation presentation = mPresentations.get(displayId); 226 if (enablePresentationForConnectedDisplays() && presentation != null) { 227 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_PRESENTATION, "Removing Presentation %s for " 228 + "reason %s", mPresentations.get(displayId), reason); 229 final WindowState win = presentation.mWin; 230 win.mWmService.mAtmService.mH.post(() -> { 231 synchronized (win.mWmService.mGlobalLock) { 232 win.removeIfPossible(); 233 } 234 }); 235 } 236 } 237 238 @Override onDisplayAdded(int displayId)239 public void onDisplayAdded(int displayId) {} 240 241 @Override onDisplayRemoved(int displayId)242 public void onDisplayRemoved(int displayId) { 243 removePresentation(displayId, "display removed " + displayId); 244 } 245 246 @Override onDisplayChanged(int displayId)247 public void onDisplayChanged(int displayId) {} 248 } 249