• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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