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