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.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; 20 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; 21 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; 22 23 import android.annotation.NonNull; 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.Build; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.util.TypedValue; 33 import android.view.ContextThemeWrapper; 34 import android.view.Display; 35 import android.view.Gravity; 36 import android.view.Window; 37 import android.view.WindowManager; 38 import android.view.WindowManager.LayoutParams.WindowType; 39 40 import java.util.Objects; 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 final Display mDisplay; 156 private final DisplayManager mDisplayManager; 157 private final Handler mHandler = new Handler(Objects.requireNonNull(Looper.myLooper(), 158 "Presentation must be constructed on a looper thread.")); 159 160 /** 161 * Creates a new presentation that is attached to the specified display 162 * using the default theme. 163 * 164 * @param outerContext The context of the application that is showing the presentation. 165 * The presentation will create its own context (see {@link #getContext()}) based 166 * on this context and information about the associated display. 167 * @param display The display to which the presentation should be attached. 168 */ Presentation(Context outerContext, Display display)169 public Presentation(Context outerContext, Display display) { 170 this(outerContext, display, 0); 171 } 172 173 /** 174 * Creates a new presentation that is attached to the specified display 175 * using the optionally specified theme. 176 * 177 * @param outerContext The context of the application that is showing the presentation. 178 * The presentation will create its own context (see {@link #getContext()}) based 179 * on this context and information about the associated display. 180 * From {@link android.os.Build.VERSION_CODES#S}, the presentation will create its own window 181 * context based on this context, information about the associated display. Customizing window 182 * type by {@link Window#setType(int) #getWindow#setType(int)} causes the mismatch of the window 183 * and the created window context, which leads to 184 * {@link android.view.WindowManager.InvalidDisplayException} when invoking {@link #show()}. 185 * @param display The display to which the presentation should be attached. 186 * @param theme A style resource describing the theme to use for the window. 187 * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes"> 188 * Style and Theme Resources</a> for more information about defining and using 189 * styles. This theme is applied on top of the current theme in 190 * <var>outerContext</var>. If 0, the default presentation theme will be used. 191 */ Presentation(Context outerContext, Display display, int theme)192 public Presentation(Context outerContext, Display display, int theme) { 193 this(outerContext, display, theme, INVALID_WINDOW_TYPE); 194 } 195 196 /** 197 * Creates a new presentation that is attached to the specified display 198 * using the optionally specified theme, and override the default window type for the 199 * presentation. 200 * @param outerContext The context of the application that is showing the presentation. 201 * The presentation will create its own context (see {@link #getContext()}) based 202 * on this context and information about the associated display. 203 * From {@link android.os.Build.VERSION_CODES#S}, the presentation will create its own window 204 * context based on this context, information about the associated display and the window type. 205 * If the window type is not specified, the presentation will choose the default type for the 206 * presentation. 207 * @param display The display to which the presentation should be attached. 208 * @param theme A style resource describing the theme to use for the window. 209 * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes"> 210 * Style and Theme Resources</a> for more information about defining and using 211 * styles. This theme is applied on top of the current theme in 212 * <var>outerContext</var>. If 0, the default presentation theme will be used. 213 * @param type Window type. 214 * 215 * @hide 216 */ Presentation(@onNull Context outerContext, @NonNull Display display, int theme, @WindowType int type)217 public Presentation(@NonNull Context outerContext, @NonNull Display display, int theme, 218 @WindowType int type) { 219 super(createPresentationContext(outerContext, display, theme, type), theme, false); 220 221 mDisplay = display; 222 mDisplayManager = getContext().getSystemService(DisplayManager.class); 223 224 final Window w = getWindow(); 225 final WindowManager.LayoutParams attr = w.getAttributes(); 226 w.setAttributes(attr); 227 w.setGravity(Gravity.FILL); 228 w.setType(getWindowType(type, display)); 229 setCanceledOnTouchOutside(false); 230 } 231 getWindowType(@indowType int type, @NonNull Display display)232 private static @WindowType int getWindowType(@WindowType int type, @NonNull Display display) { 233 if (type != INVALID_WINDOW_TYPE) { 234 return type; 235 } 236 return (display.getFlags() & Display.FLAG_PRIVATE) != 0 ? TYPE_PRIVATE_PRESENTATION 237 : TYPE_PRESENTATION; 238 } 239 240 /** 241 * Gets the {@link Display} that this presentation appears on. 242 * 243 * @return The display. 244 */ getDisplay()245 public Display getDisplay() { 246 return mDisplay; 247 } 248 249 /** 250 * Gets the {@link Resources} that should be used to inflate the layout of this presentation. 251 * This resources object has been configured according to the metrics of the 252 * display that the presentation appears on. 253 * 254 * @return The presentation resources object. 255 */ getResources()256 public Resources getResources() { 257 return getContext().getResources(); 258 } 259 260 @Override onStart()261 protected void onStart() { 262 super.onStart(); 263 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); 264 } 265 266 @Override onStop()267 protected void onStop() { 268 mDisplayManager.unregisterDisplayListener(mDisplayListener); 269 super.onStop(); 270 } 271 272 /** 273 * Inherited from {@link Dialog#show}. Will throw 274 * {@link android.view.WindowManager.InvalidDisplayException} if the specified secondary 275 * {@link Display} can't be found or if it does not have {@link Display#FLAG_PRESENTATION} set. 276 */ 277 @Override show()278 public void show() { 279 super.show(); 280 } 281 282 /** 283 * Called by the system when the {@link Display} to which the presentation 284 * is attached has been removed. 285 * 286 * The system automatically calls {@link #cancel} to dismiss the presentation 287 * after sending this event. 288 * 289 * @see #getDisplay 290 */ onDisplayRemoved()291 public void onDisplayRemoved() { 292 } 293 294 /** 295 * Called by the system when the properties of the {@link Display} to which 296 * the presentation is attached have changed. 297 * 298 * @see #getDisplay 299 */ onDisplayChanged()300 public void onDisplayChanged() { 301 } 302 handleDisplayRemoved()303 private void handleDisplayRemoved() { 304 onDisplayRemoved(); 305 cancel(); 306 } 307 handleDisplayChanged()308 private void handleDisplayChanged() { 309 onDisplayChanged(); 310 } 311 312 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@code N/A}") createPresentationContext(Context outerContext, Display display, int theme)313 private static Context createPresentationContext(Context outerContext, Display display, 314 int theme) { 315 return createPresentationContext(outerContext, display, theme, INVALID_WINDOW_TYPE); 316 } 317 createPresentationContext( Context outerContext, Display display, int theme, @WindowType int type)318 private static Context createPresentationContext( 319 Context outerContext, Display display, int theme, @WindowType int type) { 320 if (outerContext == null) { 321 throw new IllegalArgumentException("outerContext must not be null"); 322 } 323 if (display == null) { 324 throw new IllegalArgumentException("display must not be null"); 325 } 326 327 Context windowContext = outerContext.createDisplayContext(display) 328 .createWindowContext(getWindowType(type, display), null /* options */); 329 if (theme == 0) { 330 TypedValue outValue = new TypedValue(); 331 windowContext.getTheme().resolveAttribute( 332 com.android.internal.R.attr.presentationTheme, outValue, true); 333 theme = outValue.resourceId; 334 } 335 return new ContextThemeWrapper(windowContext, theme); 336 } 337 338 private final DisplayListener mDisplayListener = new DisplayListener() { 339 @Override 340 public void onDisplayAdded(int displayId) { 341 } 342 343 @Override 344 public void onDisplayRemoved(int displayId) { 345 if (displayId == mDisplay.getDisplayId()) { 346 handleDisplayRemoved(); 347 } 348 } 349 350 @Override 351 public void onDisplayChanged(int displayId) { 352 if (displayId == mDisplay.getDisplayId()) { 353 handleDisplayChanged(); 354 } 355 } 356 }; 357 } 358