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 android.content.Context;
20 import android.content.res.Resources;
21 import android.hardware.display.DisplayManager;
22 import android.hardware.display.DisplayManager.DisplayListener;
23 import android.view.ContextThemeWrapper;
24 import android.view.Display;
25 import android.view.Gravity;
26 import android.view.WindowManagerImpl;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.util.DisplayMetrics;
30 import android.util.Log;
31 import android.util.TypedValue;
32 
33 /**
34  * Base class for presentations.
35  * <p>
36  * 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.
40  * </p><p>
41  * 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.
46  * </p><p>
47  * 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
50  * whenever the activity itself is paused or resumed.
51  * </p>
52  *
53  * <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>
82  * <pre>
83  * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
84  * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
85  * if (route != null) {
86  *     Display presentationDisplay = route.getPresentationDisplay();
87  *     if (presentationDisplay != null) {
88  *         Presentation presentation = new MyPresentation(context, presentationDisplay);
89  *         presentation.show();
90  *     }
91  * }</pre>
92  * <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>
116  * <pre>
117  * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
118  * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
119  * if (presentationDisplays.length > 0) {
120  *     // 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();
126  * }</pre>
127  * <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.
138  * @see DisplayManager for information on how to enumerate displays and receive
139  * notifications when displays are added or removed.
140  */
141 public 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      */
Presentation(Context outerContext, Display display)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      */
Presentation(Context outerContext, Display display, int theme)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      */
getDisplay()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      */
getResources()202     public Resources getResources() {
203         return getContext().getResources();
204     }
205 
206     @Override
onStart()207     protected void onStart() {
208         super.onStart();
209         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
210 
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 dismissed because the "
217                     + "display metrics have changed since it was created.");
218             mHandler.sendEmptyMessage(MSG_CANCEL);
219         }
220     }
221 
222     @Override
onStop()223     protected void onStop() {
224         mDisplayManager.unregisterDisplayListener(mDisplayListener);
225         super.onStop();
226     }
227 
228     /**
229      * 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
show()234     public void show() {
235         super.show();
236     }
237 
238     /**
239      * 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      */
onDisplayRemoved()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      */
onDisplayChanged()260     public void onDisplayChanged() {
261     }
262 
handleDisplayRemoved()263     private void handleDisplayRemoved() {
264         onDisplayRemoved();
265         cancel();
266     }
267 
handleDisplayChanged()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             Log.i(TAG, "Presentation is being dismissed because the "
278                     + "display metrics have changed since it was created.");
279             cancel();
280         }
281     }
282 
isConfigurationStillValid()283     private boolean isConfigurationStillValid() {
284         DisplayMetrics dm = new DisplayMetrics();
285         mDisplay.getMetrics(dm);
286         return dm.equalsPhysical(getResources().getDisplayMetrics());
287     }
288 
createPresentationContext( Context outerContext, Display display, int theme)289     private static Context createPresentationContext(
290             Context outerContext, Display display, int theme) {
291         if (outerContext == null) {
292             throw new IllegalArgumentException("outerContext must not be null");
293         }
294         if (display == null) {
295             throw new IllegalArgumentException("display must not be null");
296         }
297 
298         Context displayContext = outerContext.createDisplayContext(display);
299         if (theme == 0) {
300             TypedValue outValue = new TypedValue();
301             displayContext.getTheme().resolveAttribute(
302                     com.android.internal.R.attr.presentationTheme, outValue, true);
303             theme = outValue.resourceId;
304         }
305 
306         // Derive the display's window manager from the outer window manager.
307         // We do this because the outer window manager have some extra information
308         // such as the parent window, which is important if the presentation uses
309         // an application window type.
310         final WindowManagerImpl outerWindowManager =
311                 (WindowManagerImpl)outerContext.getSystemService(Context.WINDOW_SERVICE);
312         final WindowManagerImpl displayWindowManager =
313                 outerWindowManager.createPresentationWindowManager(display);
314         return new ContextThemeWrapper(displayContext, theme) {
315             @Override
316             public Object getSystemService(String name) {
317                 if (Context.WINDOW_SERVICE.equals(name)) {
318                     return displayWindowManager;
319                 }
320                 return super.getSystemService(name);
321             }
322         };
323     }
324 
325     private final DisplayListener mDisplayListener = new DisplayListener() {
326         @Override
327         public void onDisplayAdded(int displayId) {
328         }
329 
330         @Override
331         public void onDisplayRemoved(int displayId) {
332             if (displayId == mDisplay.getDisplayId()) {
333                 handleDisplayRemoved();
334             }
335         }
336 
337         @Override
338         public void onDisplayChanged(int displayId) {
339             if (displayId == mDisplay.getDisplayId()) {
340                 handleDisplayChanged();
341             }
342         }
343     };
344 
345     private final Handler mHandler = new Handler() {
346         @Override
347         public void handleMessage(Message msg) {
348             switch (msg.what) {
349                 case MSG_CANCEL:
350                     cancel();
351                     break;
352             }
353         }
354     };
355 }
356