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