• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.content.Context.DISPLAY_SERVICE;
20 import static android.content.Context.WINDOW_SERVICE;
21 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
22 
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.hardware.display.DisplayManager;
26 import android.hardware.display.DisplayManager.DisplayListener;
27 import android.os.Binder;
28 import android.os.IBinder;
29 import android.view.ContextThemeWrapper;
30 import android.view.Display;
31 import android.view.Gravity;
32 import android.view.Window;
33 import android.view.WindowManager;
34 import android.view.WindowManagerImpl;
35 import android.os.Handler;
36 import android.os.Message;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.util.TypedValue;
40 
41 /**
42  * Base class for presentations.
43  * <p>
44  * 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.
48  * </p><p>
49  * 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.
54  * </p><p>
55  * 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
58  * whenever the activity itself is paused or resumed.
59  * </p>
60  *
61  * <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>
90  * <pre>
91  * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
92  * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
93  * if (route != null) {
94  *     Display presentationDisplay = route.getPresentationDisplay();
95  *     if (presentationDisplay != null) {
96  *         Presentation presentation = new MyPresentation(context, presentationDisplay);
97  *         presentation.show();
98  *     }
99  * }</pre>
100  * <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>
124  * <pre>
125  * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
126  * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
127  * if (presentationDisplays.length > 0) {
128  *     // 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();
134  * }</pre>
135  * <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.
146  * @see DisplayManager for information on how to enumerate displays and receive
147  * notifications when displays are added or removed.
148  */
149 public 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;
156     private final IBinder mToken = new Binder();
157 
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      */
Presentation(Context outerContext, Display display)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      */
Presentation(Context outerContext, Display display, int theme)185     public Presentation(Context outerContext, Display display, int theme) {
186         super(createPresentationContext(outerContext, display, theme), theme, false);
187 
188         mDisplay = display;
189         mDisplayManager = (DisplayManager)getContext().getSystemService(DISPLAY_SERVICE);
190 
191         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);
197         setCanceledOnTouchOutside(false);
198     }
199 
200     /**
201      * Gets the {@link Display} that this presentation appears on.
202      *
203      * @return The display.
204      */
getDisplay()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      */
getResources()216     public Resources getResources() {
217         return getContext().getResources();
218     }
219 
220     @Override
onStart()221     protected void onStart() {
222         super.onStart();
223         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
224 
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()) {
230             Log.i(TAG, "Presentation is being dismissed because the "
231                     + "display metrics have changed since it was created.");
232             mHandler.sendEmptyMessage(MSG_CANCEL);
233         }
234     }
235 
236     @Override
onStop()237     protected void onStop() {
238         mDisplayManager.unregisterDisplayListener(mDisplayListener);
239         super.onStop();
240     }
241 
242     /**
243      * 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
show()248     public void show() {
249         super.show();
250     }
251 
252     /**
253      * 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      */
onDisplayRemoved()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      */
onDisplayChanged()274     public void onDisplayChanged() {
275     }
276 
handleDisplayRemoved()277     private void handleDisplayRemoved() {
278         onDisplayRemoved();
279         cancel();
280     }
281 
handleDisplayChanged()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()) {
291             Log.i(TAG, "Presentation is being dismissed because the "
292                     + "display metrics have changed since it was created.");
293             cancel();
294         }
295     }
296 
isConfigurationStillValid()297     private boolean isConfigurationStillValid() {
298         DisplayMetrics dm = new DisplayMetrics();
299         mDisplay.getMetrics(dm);
300         return dm.equalsPhysical(getResources().getDisplayMetrics());
301     }
302 
createPresentationContext( Context outerContext, Display display, int theme)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 =
325                 (WindowManagerImpl)outerContext.getSystemService(WINDOW_SERVICE);
326         final WindowManagerImpl displayWindowManager =
327                 outerWindowManager.createPresentationWindowManager(displayContext);
328         return new ContextThemeWrapper(displayContext, theme) {
329             @Override
330             public Object getSystemService(String name) {
331                 if (WINDOW_SERVICE.equals(name)) {
332                     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 }
370