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