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