Home | History | Annotate | Download | only in app
      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      */
    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();
    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
    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
    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      */
    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             Log.i(TAG, "Presentation is being dismissed because the "
    278                     + "display metrics have changed since it was created.");
    279             cancel();
    280         }
    281     }
    282 
    283     private boolean isConfigurationStillValid() {
    284         DisplayMetrics dm = new DisplayMetrics();
    285         mDisplay.getMetrics(dm);
    286         return dm.equalsPhysical(getResources().getDisplayMetrics());
    287     }
    288 
    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