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