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