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 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      */
    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      */
    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      */
    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      */
    216     public Resources getResources() {
    217         return getContext().getResources();
    218     }
    219 
    220     @Override
    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
    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
    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      */
    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      */
    274     public void onDisplayChanged() {
    275     }
    276 
    277     private void handleDisplayRemoved() {
    278         onDisplayRemoved();
    279         cancel();
    280     }
    281 
    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 
    297     private boolean isConfigurationStillValid() {
    298         DisplayMetrics dm = new DisplayMetrics();
    299         mDisplay.getMetrics(dm);
    300         return dm.equalsPhysical(getResources().getDisplayMetrics());
    301     }
    302 
    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