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