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