• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 android.app;
18 
19 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
20 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
21 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
22 
23 import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
24 
25 import android.annotation.NonNull;
26 import android.compat.annotation.UnsupportedAppUsage;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.hardware.display.DisplayManager;
30 import android.hardware.display.DisplayManager.DisplayListener;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.util.TypedValue;
35 import android.view.ContextThemeWrapper;
36 import android.view.Display;
37 import android.view.Gravity;
38 import android.view.Window;
39 import android.view.WindowInsets;
40 import android.view.WindowInsetsController;
41 import android.view.WindowManager;
42 import android.view.WindowManager.LayoutParams.WindowType;
43 
44 import java.util.Objects;
45 
46 /**
47  * Base class for presentations.
48  * <p>
49  * A presentation is a special kind of dialog whose purpose is to present
50  * content on a secondary display.  A {@link Presentation} is associated with
51  * the target {@link Display} at creation time and configures its context and
52  * resource configuration according to the display's metrics.
53  * </p><p>
54  * Notably, the {@link Context} of a presentation is different from the context
55  * of its containing {@link Activity}.  It is important to inflate the layout
56  * of a presentation and load other resources using the presentation's own context
57  * to ensure that assets of the correct size and density for the target display
58  * are loaded.
59  * </p><p>
60  * A presentation is automatically canceled (see {@link Dialog#cancel()}) when
61  * the display to which it is attached is removed.  An activity should take
62  * care of pausing and resuming whatever content is playing within the presentation
63  * whenever the activity itself is paused or resumed.
64  * </p>
65  *
66  * <h3>Choosing a presentation display</h3>
67  * <p>
68  * Before showing a {@link Presentation} it's important to choose the {@link Display}
69  * on which it will appear.  Choosing a presentation display is sometimes difficult
70  * because there may be multiple displays attached.  Rather than trying to guess
71  * which display is best, an application should let the system choose a suitable
72  * presentation display.
73  * </p><p>
74  * There are two main ways to choose a {@link Display}.
75  * </p>
76  *
77  * <h4>Using the media router to choose a presentation display</h4>
78  * <p>
79  * The easiest way to choose a presentation display is to use the
80  * {@link android.media.MediaRouter MediaRouter} API.  The media router service keeps
81  * track of which audio and video routes are available on the system.
82  * The media router sends notifications whenever routes are selected or unselected
83  * or when the preferred presentation display of a route changes.
84  * So an application can simply watch for these notifications and show or dismiss
85  * a presentation on the preferred presentation display automatically.
86  * </p><p>
87  * The preferred presentation display is the display that the media router recommends
88  * that the application should use if it wants to show content on the secondary display.
89  * Sometimes there may not be a preferred presentation display in which
90  * case the application should show its content locally without using a presentation.
91  * </p><p>
92  * Here's how to use the media router to create and show a presentation on the preferred
93  * presentation display using {@link android.media.MediaRouter.RouteInfo#getPresentationDisplay()}.
94  * </p>
95  * <pre>
96  * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
97  * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
98  * if (route != null) {
99  *     Display presentationDisplay = route.getPresentationDisplay();
100  *     if (presentationDisplay != null) {
101  *         Presentation presentation = new MyPresentation(context, presentationDisplay);
102  *         presentation.show();
103  *     }
104  * }</pre>
105  * <p>
106  * The following sample code from <code>ApiDemos</code> demonstrates how to use the media
107  * router to automatically switch between showing content in the main activity and showing
108  * the content in a presentation when a presentation display is available.
109  * </p>
110  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
111  *      activity}
112  *
113  * <h4>Using the display manager to choose a presentation display</h4>
114  * <p>
115  * Another way to choose a presentation display is to use the {@link DisplayManager} API
116  * directly.  The display manager service provides functions to enumerate and describe all
117  * displays that are attached to the system including displays that may be used
118  * for presentations.
119  * </p><p>
120  * The display manager keeps track of all displays in the system.  However, not all
121  * displays are appropriate for showing presentations.  For example, if an activity
122  * attempted to show a presentation on the main display it might obscure its own content
123  * (it's like opening a dialog on top of your activity).  Creating a presentation on the main
124  * display will result in {@link android.view.WindowManager.InvalidDisplayException} being thrown
125  * when invoking {@link #show()}.
126  * </p><p>
127  * Here's how to identify suitable displays for showing presentations using
128  * {@link DisplayManager#getDisplays(String)} and the
129  * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION} category.
130  * </p>
131  * <pre>
132  * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
133  * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
134  * if (presentationDisplays.length > 0) {
135  *     // If there is more than one suitable presentation display, then we could consider
136  *     // giving the user a choice.  For this example, we simply choose the first display
137  *     // which is the one the system recommends as the preferred presentation display.
138  *     Display display = presentationDisplays[0];
139  *     Presentation presentation = new MyPresentation(context, presentationDisplay);
140  *     presentation.show();
141  * }</pre>
142  * <p>
143  * The following sample code from <code>ApiDemos</code> demonstrates how to use the display
144  * manager to enumerate displays and show content on multiple presentation displays
145  * simultaneously.
146  * </p>
147  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
148  *      activity}
149  *
150  * @see android.media.MediaRouter#ROUTE_TYPE_LIVE_VIDEO for information on about live
151  * video routes and how to obtain the preferred presentation display for the
152  * current media route.
153  * @see DisplayManager for information on how to enumerate displays and receive
154  * notifications when displays are added or removed.
155  */
156 public class Presentation extends Dialog {
157     private static final String TAG = "Presentation";
158 
159     private final Display mDisplay;
160     private final DisplayManager mDisplayManager;
161     private final Handler mHandler = new Handler(Objects.requireNonNull(Looper.myLooper(),
162             "Presentation must be constructed on a looper thread."));
163 
164     /**
165      * Creates a new presentation that is attached to the specified display
166      * using the default theme.
167      *
168      * @param outerContext The context of the application that is showing the presentation.
169      * The presentation will create its own context (see {@link #getContext()}) based
170      * on this context and information about the associated display.
171      * @param display The display to which the presentation should be attached.
172      */
Presentation(Context outerContext, Display display)173     public Presentation(Context outerContext, Display display) {
174         this(outerContext, display, 0);
175     }
176 
177     /**
178      * Creates a new presentation that is attached to the specified display
179      * using the optionally specified theme.
180      *
181      * @param outerContext The context of the application that is showing the presentation.
182      * The presentation will create its own context (see {@link #getContext()}) based
183      * on this context and information about the associated display.
184      * From {@link android.os.Build.VERSION_CODES#S}, the presentation will create its own window
185      * context based on this context, information about the associated display. Customizing window
186      * type by {@link Window#setType(int) #getWindow#setType(int)} causes the mismatch of the window
187      * and the created window context, which leads to
188      * {@link android.view.WindowManager.InvalidDisplayException} when invoking {@link #show()}.
189      * @param display The display to which the presentation should be attached.
190      * @param theme A style resource describing the theme to use for the window.
191      * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
192      * Style and Theme Resources</a> for more information about defining and using
193      * styles.  This theme is applied on top of the current theme in
194      * <var>outerContext</var>.  If 0, the default presentation theme will be used.
195      */
Presentation(Context outerContext, Display display, int theme)196     public Presentation(Context outerContext, Display display, int theme) {
197         this(outerContext, display, theme, INVALID_WINDOW_TYPE);
198     }
199 
200     /**
201      * Creates a new presentation that is attached to the specified display
202      * using the optionally specified theme, and override the default window type for the
203      * presentation.
204      * @param outerContext The context of the application that is showing the presentation.
205      * The presentation will create its own context (see {@link #getContext()}) based
206      * on this context and information about the associated display.
207      * From {@link android.os.Build.VERSION_CODES#S}, the presentation will create its own window
208      * context based on this context, information about the associated display and the window type.
209      * If the window type is not specified, the presentation will choose the default type for the
210      * presentation.
211      * @param display The display to which the presentation should be attached.
212      * @param theme A style resource describing the theme to use for the window.
213      * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
214      * Style and Theme Resources</a> for more information about defining and using
215      * styles.  This theme is applied on top of the current theme in
216      * <var>outerContext</var>.  If 0, the default presentation theme will be used.
217      * @param type Window type.
218      *
219      * @hide
220      */
Presentation(@onNull Context outerContext, @NonNull Display display, int theme, @WindowType int type)221     public Presentation(@NonNull Context outerContext, @NonNull Display display, int theme,
222             @WindowType int type) {
223         super(createPresentationContext(outerContext, display, theme, type), theme, false);
224 
225         mDisplay = display;
226         mDisplayManager = getContext().getSystemService(DisplayManager.class);
227 
228         final Window w = getWindow();
229         final WindowManager.LayoutParams attr = w.getAttributes();
230         w.setAttributes(attr);
231         w.setGravity(Gravity.FILL);
232         w.setType(getWindowType(type, display));
233         setCanceledOnTouchOutside(false);
234     }
235 
getWindowType(@indowType int type, @NonNull Display display)236     private static @WindowType int getWindowType(@WindowType int type, @NonNull Display display) {
237         if (type != INVALID_WINDOW_TYPE) {
238             return type;
239         }
240         return (display.getFlags() & Display.FLAG_PRIVATE) != 0 ? TYPE_PRIVATE_PRESENTATION
241                 : TYPE_PRESENTATION;
242     }
243 
244     /**
245      * Gets the {@link Display} that this presentation appears on.
246      *
247      * @return The display.
248      */
getDisplay()249     public Display getDisplay() {
250         return mDisplay;
251     }
252 
253     /**
254      * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
255      * This resources object has been configured according to the metrics of the
256      * display that the presentation appears on.
257      *
258      * @return The presentation resources object.
259      */
getResources()260     public Resources getResources() {
261         return getContext().getResources();
262     }
263 
264     @Override
onStart()265     protected void onStart() {
266         super.onStart();
267         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
268     }
269 
270     @Override
onStop()271     protected void onStop() {
272         mDisplayManager.unregisterDisplayListener(mDisplayListener);
273         super.onStop();
274     }
275 
276     /**
277      * Inherited from {@link Dialog#show}. Will throw
278      * {@link android.view.WindowManager.InvalidDisplayException} if the specified secondary
279      * {@link Display} can't be found or if it does not have {@link Display#FLAG_PRESENTATION} set.
280      */
281     @Override
show()282     public void show() {
283         super.show();
284 
285         WindowInsetsController controller = getWindow().getInsetsController();
286         if (controller != null && enablePresentationForConnectedDisplays()) {
287             controller.hide(WindowInsets.Type.systemBars());
288         }
289     }
290 
291     /**
292      * Called by the system when the {@link Display} to which the presentation
293      * is attached has been removed.
294      *
295      * The system automatically calls {@link #cancel} to dismiss the presentation
296      * after sending this event.
297      *
298      * @see #getDisplay
299      */
onDisplayRemoved()300     public void onDisplayRemoved() {
301     }
302 
303     /**
304      * Called by the system when the properties of the {@link Display} to which
305      * the presentation is attached have changed.
306      *
307      * @see #getDisplay
308      */
onDisplayChanged()309     public void onDisplayChanged() {
310     }
311 
handleDisplayRemoved()312     private void handleDisplayRemoved() {
313         onDisplayRemoved();
314         cancel();
315     }
316 
handleDisplayChanged()317     private void handleDisplayChanged() {
318         onDisplayChanged();
319     }
320 
321     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@code N/A}")
createPresentationContext(Context outerContext, Display display, int theme)322     private static Context createPresentationContext(Context outerContext, Display display,
323             int theme) {
324         return createPresentationContext(outerContext, display, theme, INVALID_WINDOW_TYPE);
325     }
326 
createPresentationContext( Context outerContext, Display display, int theme, @WindowType int type)327     private static Context createPresentationContext(
328             Context outerContext, Display display, int theme, @WindowType int type) {
329         if (outerContext == null) {
330             throw new IllegalArgumentException("outerContext must not be null");
331         }
332         if (display == null) {
333             throw new IllegalArgumentException("display must not be null");
334         }
335 
336         Context windowContext = outerContext.createDisplayContext(display)
337                 .createWindowContext(getWindowType(type, display), null /* options */);
338         if (theme == 0) {
339             TypedValue outValue = new TypedValue();
340             windowContext.getTheme().resolveAttribute(
341                     com.android.internal.R.attr.presentationTheme, outValue, true);
342             theme = outValue.resourceId;
343         }
344         return new ContextThemeWrapper(windowContext, theme);
345     }
346 
347     private final DisplayListener mDisplayListener = new DisplayListener() {
348         @Override
349         public void onDisplayAdded(int displayId) {
350         }
351 
352         @Override
353         public void onDisplayRemoved(int displayId) {
354             if (displayId == mDisplay.getDisplayId()) {
355                 handleDisplayRemoved();
356             }
357         }
358 
359         @Override
360         public void onDisplayChanged(int displayId) {
361             if (displayId == mDisplay.getDisplayId()) {
362                 handleDisplayChanged();
363             }
364         }
365     };
366 }
367