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