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