1 /* 2 * Copyright (C) 2010 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.app.Flags.enableNightModeBinderCache; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.FlaggedApi; 23 import android.annotation.FloatRange; 24 import android.annotation.IntDef; 25 import android.annotation.IntRange; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemApi; 30 import android.annotation.SystemService; 31 import android.annotation.TestApi; 32 import android.compat.annotation.UnsupportedAppUsage; 33 import android.content.Context; 34 import android.content.res.Configuration; 35 import android.os.Binder; 36 import android.os.IpcDataCache; 37 import android.os.RemoteException; 38 import android.os.ServiceManager; 39 import android.os.ServiceManager.ServiceNotFoundException; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.Log; 43 import android.util.Slog; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.util.function.pooled.PooledLambda; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.lang.ref.WeakReference; 51 import java.time.LocalTime; 52 import java.util.Comparator; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Objects; 56 import java.util.Set; 57 import java.util.concurrent.Executor; 58 import java.util.stream.Stream; 59 60 /** 61 * This class provides access to the system uimode services. These services 62 * allow applications to control UI modes of the device. 63 * It provides functionality to disable the car mode and it gives access to the 64 * night mode settings. 65 * 66 * <p>These facilities are built on top of the underlying 67 * {@link android.content.Intent#ACTION_DOCK_EVENT} broadcasts that are sent when the user 68 * physical places the device into and out of a dock. When that happens, 69 * the UiModeManager switches the system {@link android.content.res.Configuration} 70 * to the appropriate UI mode, sends broadcasts about the mode switch, and 71 * starts the corresponding mode activity if appropriate. See the 72 * broadcasts {@link #ACTION_ENTER_CAR_MODE} and 73 * {@link #ACTION_ENTER_DESK_MODE} for more information. 74 * 75 * <p>In addition, the user may manually switch the system to car mode without 76 * physically being in a dock. While in car mode -- whether by manual action 77 * from the user or being physically placed in a dock -- a notification is 78 * displayed allowing the user to exit dock mode. Thus the dock mode 79 * represented here may be different than the current state of the underlying 80 * dock event broadcast. 81 */ 82 @SystemService(Context.UI_MODE_SERVICE) 83 public class UiModeManager { 84 85 private static final String TAG = "UiModeManager"; 86 87 88 /** 89 * A listener with a single method that is invoked whenever the packages projecting using the 90 * {@link ProjectionType}s for which it is registered change. 91 * 92 * @hide 93 */ 94 @SystemApi 95 public interface OnProjectionStateChangedListener { 96 /** 97 * Callback invoked when projection state changes for a {@link ProjectionType} for which 98 * this listener was added. 99 * @param projectionType the listened-for {@link ProjectionType}s that have changed 100 * @param packageNames the {@link Set} of package names that have currently set those 101 * {@link ProjectionType}s. 102 */ onProjectionStateChanged(@rojectionType int projectionType, @NonNull Set<String> packageNames)103 void onProjectionStateChanged(@ProjectionType int projectionType, 104 @NonNull Set<String> packageNames); 105 } 106 107 /** 108 * Listener for the UI contrast. To listen for changes to 109 * the UI contrast on the device, implement this interface and 110 * register it with the system by calling {@link #addContrastChangeListener}. 111 */ 112 public interface ContrastChangeListener { 113 114 /** 115 * Called when the color contrast enabled state changes. 116 * 117 * @param contrast The color contrast as in {@link #getContrast} 118 */ onContrastChanged(@loatRangefrom = -1.0f, to = 1.0f) float contrast)119 void onContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float contrast); 120 } 121 122 /** 123 * Broadcast sent when the device's UI has switched to car mode, either 124 * by being placed in a car dock or explicit action of the user. After 125 * sending the broadcast, the system will start the intent 126 * {@link android.content.Intent#ACTION_MAIN} with category 127 * {@link android.content.Intent#CATEGORY_CAR_DOCK} 128 * to display the car UI, which typically what an application would 129 * implement to provide their own interface. However, applications can 130 * also monitor this Intent in order to be informed of mode changes or 131 * prevent the normal car UI from being displayed by setting the result 132 * of the broadcast to {@link Activity#RESULT_CANCELED}. 133 * <p> 134 * This intent is broadcast when {@link #getCurrentModeType()} transitions to 135 * {@link Configuration#UI_MODE_TYPE_CAR} from some other ui mode. 136 */ 137 public static String ACTION_ENTER_CAR_MODE = "android.app.action.ENTER_CAR_MODE"; 138 139 /** 140 * Broadcast sent when an app has entered car mode using either {@link #enableCarMode(int)} or 141 * {@link #enableCarMode(int, int)}. 142 * <p> 143 * Unlike {@link #ACTION_ENTER_CAR_MODE}, which is only sent when the global car mode state 144 * (i.e. {@link #getCurrentModeType()}) transitions to {@link Configuration#UI_MODE_TYPE_CAR}, 145 * this intent is sent any time an app declares it has entered car mode. Thus, this intent is 146 * intended for use by a component which needs to know not only when the global car mode state 147 * changed, but also when the highest priority app declaring car mode has changed. 148 * <p> 149 * This broadcast includes the package name of the app which requested to enter car mode in 150 * {@link #EXTRA_CALLING_PACKAGE}. The priority the app entered car mode at is specified in 151 * {@link #EXTRA_PRIORITY}. 152 * <p> 153 * This is primarily intended to be received by other components of the Android OS. 154 * <p> 155 * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES} 156 * @hide 157 */ 158 @SystemApi 159 public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = 160 "android.app.action.ENTER_CAR_MODE_PRIORITIZED"; 161 162 /** 163 * Broadcast sent when the device's UI has switch away from car mode back 164 * to normal mode. Typically used by a car mode app, to dismiss itself 165 * when the user exits car mode. 166 * <p> 167 * This intent is broadcast when {@link #getCurrentModeType()} transitions from 168 * {@link Configuration#UI_MODE_TYPE_CAR} to some other ui mode. 169 */ 170 public static String ACTION_EXIT_CAR_MODE = "android.app.action.EXIT_CAR_MODE"; 171 172 /** 173 * Broadcast sent when an app has exited car mode using {@link #disableCarMode(int)}. 174 * <p> 175 * Unlike {@link #ACTION_EXIT_CAR_MODE}, which is only sent when the global car mode state 176 * (i.e. {@link #getCurrentModeType()}) transitions to a non-car mode state such as 177 * {@link Configuration#UI_MODE_TYPE_NORMAL}, this intent is sent any time an app declares it 178 * has exited car mode. Thus, this intent is intended for use by a component which needs to 179 * know not only when the global car mode state changed, but also when the highest priority app 180 * declaring car mode has changed. 181 * <p> 182 * This broadcast includes the package name of the app which requested to exit car mode in 183 * {@link #EXTRA_CALLING_PACKAGE}. The priority the app originally entered car mode at is 184 * specified in {@link #EXTRA_PRIORITY}. 185 * <p> 186 * If {@link #DISABLE_CAR_MODE_ALL_PRIORITIES} is used when disabling car mode (i.e. this is 187 * initiated by the user via the persistent car mode notification), this broadcast is sent once 188 * for each priority level for which car mode is being disabled. 189 * <p> 190 * This is primarily intended to be received by other components of the Android OS. 191 * <p> 192 * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES} 193 * @hide 194 */ 195 @SystemApi 196 public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = 197 "android.app.action.EXIT_CAR_MODE_PRIORITIZED"; 198 199 /** 200 * Broadcast sent when the device's UI has switched to desk mode, 201 * by being placed in a desk dock. After 202 * sending the broadcast, the system will start the intent 203 * {@link android.content.Intent#ACTION_MAIN} with category 204 * {@link android.content.Intent#CATEGORY_DESK_DOCK} 205 * to display the desk UI, which typically what an application would 206 * implement to provide their own interface. However, applications can 207 * also monitor this Intent in order to be informed of mode changes or 208 * prevent the normal desk UI from being displayed by setting the result 209 * of the broadcast to {@link Activity#RESULT_CANCELED}. 210 */ 211 public static String ACTION_ENTER_DESK_MODE = "android.app.action.ENTER_DESK_MODE"; 212 213 /** 214 * Broadcast sent when the device's UI has switched away from desk mode back 215 * to normal mode. Typically used by a desk mode app, to dismiss itself 216 * when the user exits desk mode. 217 */ 218 public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE"; 219 220 /** 221 * String extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and 222 * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the package name of the app which 223 * requested to enter or exit car mode. 224 * @hide 225 */ 226 @SystemApi 227 public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE"; 228 229 /** 230 * Integer extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and 231 * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the priority level at which car mode 232 * is being disabled. 233 * @hide 234 */ 235 @SystemApi 236 public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY"; 237 238 /** @hide */ 239 @IntDef(prefix = { "MODE_" }, value = { 240 MODE_NIGHT_AUTO, 241 MODE_NIGHT_CUSTOM, 242 MODE_NIGHT_NO, 243 MODE_NIGHT_YES 244 }) 245 @Retention(RetentionPolicy.SOURCE) 246 public @interface NightMode {} 247 248 /** 249 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 250 * automatically switch night mode on and off based on the time. 251 */ 252 public static final int MODE_NIGHT_AUTO = 0; 253 254 /** 255 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 256 * automatically switch night mode on and off based on the time. 257 */ 258 public static final int MODE_NIGHT_CUSTOM = 3; 259 260 /** 261 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 262 * never run in night mode. 263 */ 264 public static final int MODE_NIGHT_NO = 1; 265 266 /** 267 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 268 * always run in night mode. 269 */ 270 public static final int MODE_NIGHT_YES = 2; 271 272 /** @hide */ 273 @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = { 274 MODE_ATTENTION_THEME_OVERLAY_OFF, 275 MODE_ATTENTION_THEME_OVERLAY_NIGHT, 276 MODE_ATTENTION_THEME_OVERLAY_DAY 277 }) 278 @Retention(RetentionPolicy.SOURCE) 279 public @interface AttentionModeThemeOverlayType {} 280 281 /** @hide */ 282 @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = { 283 MODE_ATTENTION_THEME_OVERLAY_OFF, 284 MODE_ATTENTION_THEME_OVERLAY_NIGHT, 285 MODE_ATTENTION_THEME_OVERLAY_DAY, 286 MODE_ATTENTION_THEME_OVERLAY_UNKNOWN 287 }) 288 @Retention(RetentionPolicy.SOURCE) 289 public @interface AttentionModeThemeOverlayReturnType {} 290 291 /** 292 * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link 293 * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}. 294 * @hide 295 */ 296 @FlaggedApi(Flags.FLAG_MODES_API) 297 @TestApi 298 public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; 299 300 /** 301 * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link 302 * #getAttentionModeThemeOverlay()}: Maintains night mode always on. 303 * @hide 304 */ 305 @FlaggedApi(Flags.FLAG_MODES_API) 306 @TestApi 307 public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; 308 309 /** 310 * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link 311 * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light). 312 * @hide 313 */ 314 @FlaggedApi(Flags.FLAG_MODES_API) 315 @TestApi 316 public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; 317 318 /** 319 * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server. 320 * @hide 321 */ 322 @FlaggedApi(Flags.FLAG_MODES_API) 323 @TestApi 324 public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; 325 326 /** 327 * Granular types for {@link #setNightModeCustomType(int)} 328 * @hide 329 */ 330 @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { 331 MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, 332 MODE_NIGHT_CUSTOM_TYPE_BEDTIME, 333 }) 334 @Retention(RetentionPolicy.SOURCE) 335 public @interface NightModeCustomType {} 336 337 /** 338 * Granular types for {@link #getNightModeCustomType()} 339 * @hide 340 */ 341 @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { 342 MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, 343 MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, 344 MODE_NIGHT_CUSTOM_TYPE_BEDTIME, 345 }) 346 @Retention(RetentionPolicy.SOURCE) 347 public @interface NightModeCustomReturnType {} 348 349 /** 350 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is unknown. 351 * <p> 352 * This is the default value when the night mode is set to value other than 353 * {@link #MODE_NIGHT_CUSTOM}. 354 * @hide 355 */ 356 @SystemApi 357 public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; 358 359 /** 360 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on a custom schedule. 361 * <p> 362 * This is the default value when night mode is set to {@link #MODE_NIGHT_CUSTOM} unless the 363 * the night mode custom type is specified by calling {@link #setNightModeCustomType(int)}. 364 * @hide 365 */ 366 @SystemApi 367 public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; 368 369 /** 370 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on the bedtime schedule. 371 * @hide 372 */ 373 @SystemApi 374 public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; 375 376 private static Globals sGlobals; 377 378 /** 379 * Context required for getting the opPackageName of API caller; maybe be {@code null} if the 380 * old constructor marked with UnSupportedAppUsage is used. 381 */ 382 private @Nullable Context mContext; 383 384 private final Object mLock = new Object(); 385 /** 386 * Map that stores internally created {@link InnerListener} objects keyed by their corresponding 387 * externally provided callback objects. 388 */ 389 @GuardedBy("mLock") 390 private final Map<OnProjectionStateChangedListener, InnerListener> 391 mProjectionStateListenerMap = new ArrayMap<>(); 392 393 /** 394 * Resource manager that prevents memory leakage of Contexts via binder objects if clients 395 * fail to remove listeners. 396 */ 397 @GuardedBy("mLock") 398 private final OnProjectionStateChangedListenerResourceManager 399 mOnProjectionStateChangedListenerResourceManager = 400 new OnProjectionStateChangedListenerResourceManager(); 401 402 private static class Globals extends IUiModeManagerCallback.Stub { 403 404 private final IUiModeManager mService; 405 private final Object mGlobalsLock = new Object(); 406 407 private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE; 408 409 /** 410 * Map that stores user provided {@link ContrastChangeListener} callbacks, 411 * and the executors on which these callbacks should be called. 412 */ 413 private final ArrayMap<ContrastChangeListener, Executor> 414 mContrastChangeListeners = new ArrayMap<>(); 415 Globals(IUiModeManager service)416 Globals(IUiModeManager service) { 417 mService = service; 418 try { 419 mService.addCallback(this); 420 mContrast = mService.getContrast(); 421 } catch (RemoteException e) { 422 Log.e(TAG, "Setup failed: UiModeManagerService is dead", e); 423 } 424 } 425 getContrast()426 private float getContrast() { 427 synchronized (mGlobalsLock) { 428 return mContrast; 429 } 430 } 431 addContrastChangeListener(ContrastChangeListener listener, Executor executor)432 private void addContrastChangeListener(ContrastChangeListener listener, Executor executor) { 433 synchronized (mGlobalsLock) { 434 mContrastChangeListeners.put(listener, executor); 435 } 436 } 437 removeContrastChangeListener(ContrastChangeListener listener)438 private void removeContrastChangeListener(ContrastChangeListener listener) { 439 synchronized (mGlobalsLock) { 440 mContrastChangeListeners.remove(listener); 441 } 442 } 443 444 @Override notifyContrastChanged(float contrast)445 public void notifyContrastChanged(float contrast) { 446 synchronized (mGlobalsLock) { 447 // if value changed in the settings, update the cached value and notify listeners 448 if (Math.abs(mContrast - contrast) < 1e-10) return; 449 mContrast = contrast; 450 mContrastChangeListeners.forEach((listener, executor) -> executor.execute( 451 () -> listener.onContrastChanged(contrast))); 452 } 453 } 454 } 455 456 /** 457 * Define constants and conversions between {@link ContrastLevel}s and contrast values. 458 * <p> 459 * Contrast values are floats defined in [-1, 1], as defined in {@link #getContrast}. 460 * This is the official data type for contrast; 461 * all methods from the public API return contrast values. 462 * </p> 463 * <p> 464 * {@code ContrastLevel}, on the other hand, is an internal-only enumeration of contrasts that 465 * can be set from the system ui. Each {@code ContrastLevel} has an associated contrast value. 466 * </p> 467 * <p> 468 * Currently, a user chan chose from three contrast levels: 469 * <ul> 470 * <li>{@link #CONTRAST_LEVEL_STANDARD}, corresponding to the default contrast value 0f</li> 471 * <li>{@link #CONTRAST_LEVEL_MEDIUM}, corresponding to the contrast value 0.5f</li> 472 * <li>{@link #CONTRAST_LEVEL_HIGH}, corresponding to the maximum contrast value 1f</li> 473 * </ul> 474 * </p> 475 * 476 * @hide 477 */ 478 public static class ContrastUtils { 479 480 private static final float CONTRAST_MIN_VALUE = -1f; 481 private static final float CONTRAST_MAX_VALUE = 1f; 482 public static final float CONTRAST_DEFAULT_VALUE = 0f; 483 484 @IntDef(flag = true, prefix = { "CONTRAST_LEVEL_" }, value = { 485 CONTRAST_LEVEL_STANDARD, 486 CONTRAST_LEVEL_MEDIUM, 487 CONTRAST_LEVEL_HIGH 488 }) 489 @Retention(RetentionPolicy.SOURCE) 490 public @interface ContrastLevel {} 491 492 public static final int CONTRAST_LEVEL_STANDARD = 0; 493 public static final int CONTRAST_LEVEL_MEDIUM = 1; 494 public static final int CONTRAST_LEVEL_HIGH = 2; 495 allContrastLevels()496 private static Stream<Integer> allContrastLevels() { 497 return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH); 498 } 499 500 /** 501 * Convert a contrast value in [-1, 1] to its associated {@link ContrastLevel} 502 */ toContrastLevel(float contrast)503 public static @ContrastLevel int toContrastLevel(float contrast) { 504 if (contrast < CONTRAST_MIN_VALUE || contrast > CONTRAST_MAX_VALUE) { 505 throw new IllegalArgumentException("contrast values should be in [-1, 1]"); 506 } 507 return allContrastLevels().min(Comparator.comparingDouble(contrastLevel -> 508 Math.abs(contrastLevel - 2 * contrast))).orElseThrow(); 509 } 510 511 /** 512 * Convert a {@link ContrastLevel} to its associated contrast value in [-1, 1] 513 */ fromContrastLevel(@ontrastLevel int contrastLevel)514 public static float fromContrastLevel(@ContrastLevel int contrastLevel) { 515 if (allContrastLevels().noneMatch(level -> level == contrastLevel)) { 516 throw new IllegalArgumentException("unrecognized contrast level: " + contrastLevel); 517 } 518 return contrastLevel / 2f; 519 } 520 } 521 522 @UnsupportedAppUsage UiModeManager()523 /*package*/ UiModeManager() throws ServiceNotFoundException { 524 this(null /* context */); 525 } 526 UiModeManager(Context context)527 /*package*/ UiModeManager(Context context) throws ServiceNotFoundException { 528 IUiModeManager service = IUiModeManager.Stub.asInterface( 529 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE)); 530 mContext = context; 531 if (service == null) return; 532 synchronized (mLock) { 533 if (sGlobals == null) sGlobals = new Globals(service); 534 } 535 } 536 537 /** 538 * Flag for use with {@link #enableCarMode(int)}: go to the car 539 * home activity as part of the enable. Enabling this way ensures 540 * a clean transition between the current activity (in non-car-mode) and 541 * the car home activity that will serve as home while in car mode. This 542 * will switch to the car home activity even if we are already in car mode. 543 */ 544 public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 0x0001; 545 546 /** 547 * Flag for use with {@link #enableCarMode(int)}: allow sleep mode while in car mode. 548 * By default, when this flag is not set, the system may hold a full wake lock to keep the 549 * screen turned on and prevent the system from entering sleep mode while in car mode. 550 * Setting this flag disables such behavior and the system may enter sleep mode 551 * if there is no other user activity and no other wake lock held. 552 * Setting this flag can be relevant for a car dock application that does not require the 553 * screen kept on. 554 */ 555 public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 0x0002; 556 557 /** @hide */ 558 @IntDef(prefix = {"ENABLE_CAR_MODE_"}, value = { 559 ENABLE_CAR_MODE_GO_CAR_HOME, 560 ENABLE_CAR_MODE_ALLOW_SLEEP 561 }) 562 @Retention(RetentionPolicy.SOURCE) 563 public @interface EnableCarMode {} 564 565 /** 566 * Force device into car mode, like it had been placed in the car dock. 567 * This will cause the device to switch to the car home UI as part of 568 * the mode switch. 569 * @param flags Must be 0. 570 */ enableCarMode(int flags)571 public void enableCarMode(int flags) { 572 enableCarMode(DEFAULT_PRIORITY, flags); 573 } 574 575 /** 576 * Force device into car mode, like it had been placed in the car dock. This will cause the 577 * device to switch to the car home UI as part of the mode switch. 578 * <p> 579 * An app may request to enter car mode when the system is already in car mode. The app may 580 * specify a "priority" when entering car mode. The device will remain in car mode 581 * (i.e. {@link #getCurrentModeType()} is {@link Configuration#UI_MODE_TYPE_CAR}) as long as 582 * there is a priority level at which car mode have been enabled. 583 * <p> 584 * Specifying a priority level when entering car mode is important in cases where multiple apps 585 * on a device implement a car-mode {@link android.telecom.InCallService} (see 586 * {@link android.telecom.TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}). The 587 * {@link android.telecom.InCallService} associated with the highest priority app which entered 588 * car mode will be bound to by Telecom and provided with information about ongoing calls on 589 * the device. 590 * <p> 591 * System apps holding the required permission can enable car mode when the app determines the 592 * correct conditions exist for that app to be in car mode. The device maker should ensure that 593 * where multiple apps exist on the device which can potentially enter car mode, appropriate 594 * priorities are used to ensure that calls delivered by the 595 * {@link android.telecom.InCallService} API are sent to the highest priority app given the 596 * desired behavior of the car mode experience on the device. 597 * <p> 598 * If app A and app B both meet their own criteria to enable car mode, and it is desired that 599 * app B should be the one which should receive call information in that scenario, the priority 600 * for app B should be higher than the one for app A. The higher priority of app B compared to 601 * A means it will be bound to during calls and app A will not. When app B no longer meets its 602 * criteria for providing a car mode experience it uses {@link #disableCarMode(int)} to disable 603 * car mode at its priority level. The system will then unbind from app B and bind to app A as 604 * it has the next highest priority. 605 * <p> 606 * When an app enables car mode at a certain priority, it can disable car mode at the specified 607 * priority level using {@link #disableCarMode(int)}. An app may only enable car mode at a 608 * single priority. 609 * <p> 610 * Public apps are assumed to enter/exit car mode at the lowest priority, 611 * {@link #DEFAULT_PRIORITY}. 612 * 613 * @param priority The declared priority for the caller, where {@link #DEFAULT_PRIORITY} (0) is 614 * the lowest priority and higher numbers represent a higher priority. 615 * The priorities apps declare when entering car mode is determined by the 616 * device manufacturer based on the desired car mode experience. 617 * @param flags Car mode flags. 618 * @hide 619 */ 620 @SystemApi 621 @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) enableCarMode(@ntRangefrom = 0) int priority, @EnableCarMode int flags)622 public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) { 623 if (sGlobals != null) { 624 try { 625 sGlobals.mService.enableCarMode(flags, priority, 626 mContext == null ? null : mContext.getOpPackageName()); 627 } catch (RemoteException e) { 628 throw e.rethrowFromSystemServer(); 629 } 630 } 631 } 632 633 /** 634 * Flag for use with {@link #disableCarMode(int)}: go to the normal 635 * home activity as part of the disable. Disabling this way ensures 636 * a clean transition between the current activity (in car mode) and 637 * the original home activity (which was typically last running without 638 * being in car mode). 639 */ 640 public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001; 641 642 /** 643 * Flag for use with {@link #disableCarMode(int)}: Disables car mode at ALL priority levels. 644 * Primarily intended for use from {@link com.android.internal.app.DisableCarModeActivity} to 645 * provide the user with a means to exit car mode at all priority levels. 646 * @hide 647 */ 648 public static final int DISABLE_CAR_MODE_ALL_PRIORITIES = 0x0002; 649 650 /** @hide */ 651 @IntDef(prefix = { "DISABLE_CAR_MODE_" }, value = { 652 DISABLE_CAR_MODE_GO_HOME 653 }) 654 @Retention(RetentionPolicy.SOURCE) 655 public @interface DisableCarMode {} 656 657 /** 658 * The default priority used for entering car mode. 659 * <p> 660 * Callers of the {@link #enableCarMode(int)} priority will be assigned the default priority. 661 * This is considered the lowest possible priority for enabling car mode. 662 * <p> 663 * System apps can specify a priority other than the default priority when using 664 * {@link #enableCarMode(int, int)} to enable car mode. 665 * @hide 666 */ 667 @SystemApi 668 public static final int DEFAULT_PRIORITY = 0; 669 670 /** 671 * Turn off special mode if currently in car mode. 672 * @param flags One of the disable car mode flags. 673 */ disableCarMode(@isableCarMode int flags)674 public void disableCarMode(@DisableCarMode int flags) { 675 if (sGlobals != null) { 676 try { 677 sGlobals.mService.disableCarModeByCallingPackage(flags, 678 mContext == null ? null : mContext.getOpPackageName()); 679 } catch (RemoteException e) { 680 throw e.rethrowFromSystemServer(); 681 } 682 } 683 } 684 685 /** 686 * Return the current running mode type. May be one of 687 * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, 688 * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, 689 * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, 690 * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TELEVISION}, 691 * {@link Configuration#UI_MODE_TYPE_APPLIANCE Configuration.UI_MODE_TYPE_APPLIANCE}, 692 * {@link Configuration#UI_MODE_TYPE_WATCH Configuration.UI_MODE_TYPE_WATCH}, or 693 * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}. 694 */ getCurrentModeType()695 public int getCurrentModeType() { 696 if (sGlobals != null) { 697 try { 698 return sGlobals.mService.getCurrentModeType(); 699 } catch (RemoteException e) { 700 throw e.rethrowFromSystemServer(); 701 } 702 } 703 return Configuration.UI_MODE_TYPE_NORMAL; 704 } 705 706 /** 707 * Sets the system-wide night mode. 708 * <p> 709 * The mode can be one of: 710 * <ul> 711 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 712 * {@code notnight} mode</li> 713 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 714 * {@code night} mode</li> 715 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 716 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 717 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 718 * {@code night} and {@code notnight} based on the device's current 719 * location and certain other sensors</li> 720 * </ul> 721 * <p> 722 * <strong>Note:</strong> On API 22 and below, changes to the night mode 723 * are only effective when the {@link Configuration#UI_MODE_TYPE_CAR car} 724 * or {@link Configuration#UI_MODE_TYPE_DESK desk} mode is enabled on a 725 * device. On API 23 through API 28, changes to night mode are always effective. 726 * <p> 727 * Starting in API 29, when the device is in car mode and this method is called, night mode 728 * will change, but the new setting is not persisted and the previously persisted setting 729 * will be restored when the device exits car mode. 730 * <p> 731 * Changes to night mode take effect globally and will result in a configuration change 732 * (and potentially an Activity lifecycle event) being applied to all running apps. 733 * Developers interested in an app-local implementation of night mode should consider using 734 * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or 735 * {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} for the 736 * backward compatible implementation. 737 * 738 * @param mode the night mode to set 739 * @see #getNightMode() 740 * @see #setApplicationNightMode(int) 741 */ setNightMode(@ightMode int mode)742 public void setNightMode(@NightMode int mode) { 743 if (sGlobals != null) { 744 try { 745 sGlobals.mService.setNightMode(mode); 746 } catch (RemoteException e) { 747 throw e.rethrowFromSystemServer(); 748 } 749 } 750 } 751 752 /** 753 * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type 754 * {@code nightModeCustomType}. 755 * 756 * @param nightModeCustomType 757 * @throws IllegalArgumentException if passed an unsupported type to 758 * {@code nightModeCustomType}. 759 * @hide 760 */ 761 @SystemApi 762 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeCustomType(@ightModeCustomType int nightModeCustomType)763 public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { 764 if (sGlobals != null) { 765 try { 766 sGlobals.mService.setNightModeCustomType(nightModeCustomType); 767 } catch (RemoteException e) { 768 throw e.rethrowFromSystemServer(); 769 } 770 } 771 } 772 773 /** 774 * Returns the custom night mode type. 775 * <p> 776 * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns 777 * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. 778 * @hide 779 */ 780 @SystemApi 781 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) getNightModeCustomType()782 public @NightModeCustomReturnType int getNightModeCustomType() { 783 if (sGlobals != null) { 784 try { 785 return sGlobals.mService.getNightModeCustomType(); 786 } catch (RemoteException e) { 787 throw e.rethrowFromSystemServer(); 788 } 789 } 790 return MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; 791 } 792 793 /** 794 * Overlays current Attention mode Night Mode overlay. 795 * {@code attentionModeThemeOverlayType}. 796 * 797 * @throws IllegalArgumentException if passed an unsupported type to 798 * {@code AttentionModeThemeOverlayType}. 799 * @hide 800 */ 801 @FlaggedApi(Flags.FLAG_MODES_API) 802 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setAttentionModeThemeOverlay( @ttentionModeThemeOverlayType int attentionModeThemeOverlayType)803 public void setAttentionModeThemeOverlay( 804 @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { 805 if (sGlobals != null) { 806 try { 807 sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType); 808 } catch (RemoteException e) { 809 throw e.rethrowFromSystemServer(); 810 } 811 } 812 } 813 814 /** 815 * Returns the currently configured Attention Mode theme overlay. 816 * <p> 817 * May be one of: 818 * <ul> 819 * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li> 820 * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li> 821 * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li> 822 * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li> 823 * </ul> 824 * </p> 825 * 826 * @hide 827 */ 828 @FlaggedApi(Flags.FLAG_MODES_API) 829 @TestApi 830 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) getAttentionModeThemeOverlay()831 public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() { 832 if (sGlobals != null) { 833 try { 834 return sGlobals.mService.getAttentionModeThemeOverlay(); 835 } catch (RemoteException e) { 836 throw e.rethrowFromSystemServer(); 837 } 838 } 839 return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN; 840 } 841 842 /** 843 * Sets and persist the night mode for this application. 844 * <p> 845 * The mode can be one of: 846 * <ul> 847 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 848 * {@code notnight} mode</li> 849 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 850 * {@code night} mode</li> 851 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 852 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 853 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 854 * {@code night} and {@code notnight} based on the device's current 855 * location and certain other sensors</li> 856 * </ul> 857 * <p> 858 * Changes to night mode take effect locally and will result in a configuration change 859 * (and potentially an Activity lifecycle event) being applied to this application. The mode 860 * is persisted for this application until it is either modified by the application, the 861 * user clears the data for the application, or this application is uninstalled. 862 * <p> 863 * Developers interested in a non-persistent app-local implementation of night mode should 864 * consider using {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} 865 * to manage the -night qualifier locally. 866 * 867 * @param mode the night mode to set 868 * @see #setNightMode(int) 869 */ setApplicationNightMode(@ightMode int mode)870 public void setApplicationNightMode(@NightMode int mode) { 871 if (sGlobals != null) { 872 try { 873 sGlobals.mService.setApplicationNightMode(mode); 874 } catch (RemoteException e) { 875 throw e.rethrowFromSystemServer(); 876 } 877 } 878 } 879 getNightModeFromServer()880 private Integer getNightModeFromServer() { 881 try { 882 if (sGlobals != null) { 883 return sGlobals.mService.getNightMode(); 884 } 885 return -1; 886 } catch (RemoteException e) { 887 throw e.rethrowFromSystemServer(); 888 } 889 } 890 891 892 /** 893 * Retrieve the night mode for the user. 894 */ 895 private final IpcDataCache.QueryHandler<Void, Integer> mNightModeQuery = 896 new IpcDataCache.QueryHandler<>() { 897 898 @Override 899 @NonNull 900 public Integer apply(Void query) { 901 return getNightModeFromServer(); 902 } 903 }; 904 905 private static final String NIGHT_MODE_API = "getNightMode"; 906 907 /** 908 * Cache the night mode for a user. 909 */ 910 private final IpcDataCache<Void, Integer> mNightModeCache = 911 new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM, 912 NIGHT_MODE_API, /* cacheName= */ "NightModeCache", mNightModeQuery); 913 914 /** 915 * Invalidate the night mode cache. 916 * 917 * @hide 918 */ 919 @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_BINDER_CACHE) invalidateNightModeCache()920 public static void invalidateNightModeCache() { 921 IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, 922 NIGHT_MODE_API); 923 } 924 925 /** 926 * Returns the currently configured night mode. 927 * <p> 928 * May be one of: 929 * <ul> 930 * <li>{@link #MODE_NIGHT_NO}</li> 931 * <li>{@link #MODE_NIGHT_YES}</li> 932 * <li>{@link #MODE_NIGHT_AUTO}</li> 933 * <li>{@link #MODE_NIGHT_CUSTOM}</li> 934 * <li>{@code -1} on error</li> 935 * </ul> 936 * 937 * @return the current night mode, or {@code -1} on error 938 * @see #setNightMode(int) 939 */ getNightMode()940 public @NightMode int getNightMode() { 941 if (enableNightModeBinderCache()) { 942 return mNightModeCache.query(null); 943 } else { 944 return getNightModeFromServer(); 945 } 946 } 947 948 /** 949 * @return If UI mode is locked or not. When UI mode is locked, calls to change UI mode 950 * like {@link #enableCarMode(int)} will silently fail. 951 * @hide 952 */ 953 @TestApi isUiModeLocked()954 public boolean isUiModeLocked() { 955 if (sGlobals != null) { 956 try { 957 return sGlobals.mService.isUiModeLocked(); 958 } catch (RemoteException e) { 959 throw e.rethrowFromSystemServer(); 960 } 961 } 962 return true; 963 } 964 965 /** 966 * Returns whether night mode is locked or not. 967 * <p> 968 * When night mode is locked, only privileged system components may change 969 * night mode and calls from non-privileged applications to change night 970 * mode will fail silently. 971 * 972 * @return {@code true} if night mode is locked or {@code false} otherwise 973 * @hide 974 */ 975 @TestApi isNightModeLocked()976 public boolean isNightModeLocked() { 977 if (sGlobals != null) { 978 try { 979 return sGlobals.mService.isNightModeLocked(); 980 } catch (RemoteException e) { 981 throw e.rethrowFromSystemServer(); 982 } 983 } 984 return true; 985 } 986 987 /** 988 * [De]activating night mode for the current user if the current night mode is custom and the 989 * custom type matches {@code nightModeCustomType}. 990 * 991 * @param nightModeCustomType the specify type of custom mode 992 * @param active {@code true} to activate night mode. Otherwise, deactivate night mode 993 * @return {@code true} if night mode has successfully activated for the requested 994 * {@code nightModeCustomType}. 995 * @hide 996 */ 997 @SystemApi 998 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeActivatedForCustomMode(@ightModeCustomType int nightModeCustomType, boolean active)999 public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType, 1000 boolean active) { 1001 if (sGlobals != null) { 1002 try { 1003 return sGlobals.mService.setNightModeActivatedForCustomMode( 1004 nightModeCustomType, active); 1005 } catch (RemoteException e) { 1006 throw e.rethrowFromSystemServer(); 1007 } 1008 } 1009 return false; 1010 } 1011 1012 /** 1013 * Activating night mode for the current user 1014 * 1015 * @return {@code true} if the change is successful 1016 * @hide 1017 */ 1018 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeActivated(boolean active)1019 public boolean setNightModeActivated(boolean active) { 1020 if (sGlobals != null) { 1021 try { 1022 return sGlobals.mService.setNightModeActivated(active); 1023 } catch (RemoteException e) { 1024 throw e.rethrowFromSystemServer(); 1025 } 1026 } 1027 return false; 1028 } 1029 1030 /** 1031 * Returns the time of the day Dark theme activates 1032 * <p> 1033 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 1034 * this time set to activate it automatically. 1035 */ 1036 @NonNull getCustomNightModeStart()1037 public LocalTime getCustomNightModeStart() { 1038 if (sGlobals != null) { 1039 try { 1040 return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeStart() * 1000); 1041 } catch (RemoteException e) { 1042 throw e.rethrowFromSystemServer(); 1043 } 1044 } 1045 return LocalTime.MIDNIGHT; 1046 } 1047 1048 /** 1049 * Sets the time of the day Dark theme activates 1050 * <p> 1051 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 1052 * this time set to activate it automatically 1053 * @param time The time of the day Dark theme should activate 1054 */ setCustomNightModeStart(@onNull LocalTime time)1055 public void setCustomNightModeStart(@NonNull LocalTime time) { 1056 if (sGlobals != null) { 1057 try { 1058 sGlobals.mService.setCustomNightModeStart(time.toNanoOfDay() / 1000); 1059 } catch (RemoteException e) { 1060 throw e.rethrowFromSystemServer(); 1061 } 1062 } 1063 } 1064 1065 /** 1066 * Returns the time of the day Dark theme deactivates 1067 * <p> 1068 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 1069 * this time set to deactivate it automatically. 1070 */ 1071 @NonNull getCustomNightModeEnd()1072 public LocalTime getCustomNightModeEnd() { 1073 if (sGlobals != null) { 1074 try { 1075 return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeEnd() * 1000); 1076 } catch (RemoteException e) { 1077 throw e.rethrowFromSystemServer(); 1078 } 1079 } 1080 return LocalTime.MIDNIGHT; 1081 } 1082 1083 /** 1084 * Sets the time of the day Dark theme deactivates 1085 * <p> 1086 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 1087 * this time set to deactivate it automatically. 1088 * @param time The time of the day Dark theme should deactivate 1089 */ setCustomNightModeEnd(@onNull LocalTime time)1090 public void setCustomNightModeEnd(@NonNull LocalTime time) { 1091 if (sGlobals != null) { 1092 try { 1093 sGlobals.mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000); 1094 } catch (RemoteException e) { 1095 throw e.rethrowFromSystemServer(); 1096 } 1097 } 1098 } 1099 1100 /** 1101 * Indicates no projection type. Can be used to compare with the {@link ProjectionType} in 1102 * {@link OnProjectionStateChangedListener#onProjectionStateChanged(int, Set)}. 1103 * 1104 * @hide 1105 */ 1106 @SystemApi 1107 @TestApi 1108 public static final int PROJECTION_TYPE_NONE = 0x0000; 1109 /** 1110 * Automotive projection prevents degradation of GPS to save battery, routes incoming calls to 1111 * the automotive role holder, etc. For use with {@link #requestProjection(int)} and 1112 * {@link #clearProjectionState(int)}. 1113 * 1114 * @hide 1115 */ 1116 @SystemApi 1117 @TestApi 1118 public static final int PROJECTION_TYPE_AUTOMOTIVE = 0x0001; 1119 /** 1120 * Indicates all projection types. For use with 1121 * {@link #addOnProjectionStateChangedListener(int, Executor, OnProjectionStateChangedListener)} 1122 * and {@link #getProjectingPackages(int)}. 1123 * 1124 * @hide 1125 */ 1126 @SystemApi 1127 @TestApi 1128 public static final int PROJECTION_TYPE_ALL = -1; // All bits on 1129 1130 /** @hide */ 1131 @IntDef(prefix = {"PROJECTION_TYPE_"}, value = { 1132 PROJECTION_TYPE_NONE, 1133 PROJECTION_TYPE_AUTOMOTIVE, 1134 PROJECTION_TYPE_ALL, 1135 }) 1136 @Retention(RetentionPolicy.SOURCE) 1137 public @interface ProjectionType { 1138 } 1139 1140 /** 1141 * Sets the given {@link ProjectionType}. 1142 * 1143 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 1144 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 1145 * @param projectionType the type of projection to request. This must be a single 1146 * {@link ProjectionType} and cannot be a bitmask. 1147 * @return true if the projection was successfully set 1148 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 1149 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 1150 * 1151 * @hide 1152 */ 1153 @SystemApi 1154 @TestApi 1155 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 1156 conditional = true) requestProjection(@rojectionType int projectionType)1157 public boolean requestProjection(@ProjectionType int projectionType) { 1158 if (sGlobals != null) { 1159 try { 1160 return sGlobals.mService.requestProjection(new Binder(), projectionType, 1161 mContext.getOpPackageName()); 1162 } catch (RemoteException e) { 1163 throw e.rethrowFromSystemServer(); 1164 } 1165 } 1166 return false; 1167 } 1168 1169 /** 1170 * Releases the given {@link ProjectionType}. 1171 * 1172 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 1173 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 1174 * @param projectionType the type of projection to release. This must be a single 1175 * {@link ProjectionType} and cannot be a bitmask. 1176 * @return true if the package had set projection and it was successfully released 1177 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 1178 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 1179 * 1180 * @hide 1181 */ 1182 @SystemApi 1183 @TestApi 1184 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 1185 conditional = true) releaseProjection(@rojectionType int projectionType)1186 public boolean releaseProjection(@ProjectionType int projectionType) { 1187 if (sGlobals != null) { 1188 try { 1189 return sGlobals.mService.releaseProjection( 1190 projectionType, mContext.getOpPackageName()); 1191 } catch (RemoteException e) { 1192 throw e.rethrowFromSystemServer(); 1193 } 1194 } 1195 return false; 1196 } 1197 1198 /** 1199 * Gets the packages that are currently projecting. 1200 * 1201 * @param projectionType the {@link ProjectionType}s to consider when computing which packages 1202 * are projecting. Use {@link #PROJECTION_TYPE_ALL} to get all projecting 1203 * packages. 1204 * 1205 * @hide 1206 */ 1207 @SystemApi 1208 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) 1209 @NonNull getProjectingPackages(@rojectionType int projectionType)1210 public Set<String> getProjectingPackages(@ProjectionType int projectionType) { 1211 if (sGlobals != null) { 1212 try { 1213 return new ArraySet<>(sGlobals.mService.getProjectingPackages(projectionType)); 1214 } catch (RemoteException e) { 1215 throw e.rethrowFromSystemServer(); 1216 } 1217 } 1218 return Set.of(); 1219 } 1220 1221 /** 1222 * Gets the {@link ProjectionType}s that are currently active. 1223 * 1224 * @hide 1225 */ 1226 @SystemApi 1227 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) getActiveProjectionTypes()1228 public @ProjectionType int getActiveProjectionTypes() { 1229 if (sGlobals != null) { 1230 try { 1231 return sGlobals.mService.getActiveProjectionTypes(); 1232 } catch (RemoteException e) { 1233 throw e.rethrowFromSystemServer(); 1234 } 1235 } 1236 return PROJECTION_TYPE_NONE; 1237 } 1238 1239 /** 1240 * Configures the listener to receive callbacks when the packages projecting using the given 1241 * {@link ProjectionType}s change. 1242 * 1243 * @param projectionType one or more {@link ProjectionType}s to listen for changes regarding 1244 * @param executor an {@link Executor} on which to invoke the callbacks 1245 * @param listener the {@link OnProjectionStateChangedListener} to add 1246 * 1247 * @hide 1248 */ 1249 @SystemApi 1250 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) addOnProjectionStateChangedListener(@rojectionType int projectionType, @NonNull @CallbackExecutor Executor executor, @NonNull OnProjectionStateChangedListener listener)1251 public void addOnProjectionStateChangedListener(@ProjectionType int projectionType, 1252 @NonNull @CallbackExecutor Executor executor, 1253 @NonNull OnProjectionStateChangedListener listener) { 1254 synchronized (mLock) { 1255 if (mProjectionStateListenerMap.containsKey(listener)) { 1256 Slog.i(TAG, "Attempted to add listener that was already added."); 1257 return; 1258 } 1259 if (sGlobals != null) { 1260 InnerListener innerListener = new InnerListener(executor, listener, 1261 mOnProjectionStateChangedListenerResourceManager); 1262 try { 1263 sGlobals.mService.addOnProjectionStateChangedListener( 1264 innerListener, projectionType); 1265 mProjectionStateListenerMap.put(listener, innerListener); 1266 } catch (RemoteException e) { 1267 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 1268 throw e.rethrowFromSystemServer(); 1269 } 1270 } 1271 } 1272 } 1273 1274 /** 1275 * Removes the listener so it stops receiving updates for all {@link ProjectionType}s. 1276 * 1277 * @param listener the {@link OnProjectionStateChangedListener} to remove 1278 * 1279 * @hide 1280 */ 1281 @SystemApi 1282 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) removeOnProjectionStateChangedListener( @onNull OnProjectionStateChangedListener listener)1283 public void removeOnProjectionStateChangedListener( 1284 @NonNull OnProjectionStateChangedListener listener) { 1285 synchronized (mLock) { 1286 InnerListener innerListener = mProjectionStateListenerMap.get(listener); 1287 if (innerListener == null) { 1288 Slog.i(TAG, "Attempted to remove listener that was not added."); 1289 return; 1290 } 1291 if (sGlobals != null) { 1292 try { 1293 sGlobals.mService.removeOnProjectionStateChangedListener(innerListener); 1294 } catch (RemoteException e) { 1295 throw e.rethrowFromSystemServer(); 1296 } 1297 } 1298 mProjectionStateListenerMap.remove(listener); 1299 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 1300 } 1301 } 1302 1303 private static class InnerListener extends IOnProjectionStateChangedListener.Stub { 1304 private final WeakReference<OnProjectionStateChangedListenerResourceManager> 1305 mResourceManager; 1306 InnerListener(@onNull Executor executor, @NonNull OnProjectionStateChangedListener outerListener, @NonNull OnProjectionStateChangedListenerResourceManager resourceManager)1307 private InnerListener(@NonNull Executor executor, 1308 @NonNull OnProjectionStateChangedListener outerListener, 1309 @NonNull OnProjectionStateChangedListenerResourceManager resourceManager) { 1310 resourceManager.put(this, executor, outerListener); 1311 mResourceManager = new WeakReference<>(resourceManager); 1312 } 1313 1314 @Override onProjectionStateChanged(int activeProjectionTypes, List<String> projectingPackages)1315 public void onProjectionStateChanged(int activeProjectionTypes, 1316 List<String> projectingPackages) { 1317 OnProjectionStateChangedListenerResourceManager resourceManager = 1318 mResourceManager.get(); 1319 if (resourceManager == null) { 1320 Slog.w(TAG, "Can't execute onProjectionStateChanged, resource manager is gone."); 1321 return; 1322 } 1323 1324 OnProjectionStateChangedListener outerListener = resourceManager.getOuterListener(this); 1325 Executor executor = resourceManager.getExecutor(this); 1326 if (outerListener == null || executor == null) { 1327 Slog.w(TAG, "Can't execute onProjectionStatechanged, references are null."); 1328 return; 1329 } 1330 1331 executor.execute(PooledLambda.obtainRunnable( 1332 OnProjectionStateChangedListener::onProjectionStateChanged, 1333 outerListener, 1334 activeProjectionTypes, 1335 new ArraySet<>(projectingPackages)).recycleOnUse()); 1336 } 1337 } 1338 1339 /** 1340 * Wrapper class that ensures we don't leak {@link Activity} or other large {@link Context} in 1341 * which this {@link UiModeManager} resides if/when it ends without unregistering associated 1342 * callback objects. 1343 */ 1344 private static class OnProjectionStateChangedListenerResourceManager { 1345 private final Map<InnerListener, OnProjectionStateChangedListener> mOuterListenerMap = 1346 new ArrayMap<>(1); 1347 private final Map<InnerListener, Executor> mExecutorMap = new ArrayMap<>(1); 1348 put(@onNull InnerListener innerListener, @NonNull Executor executor, OnProjectionStateChangedListener outerListener)1349 void put(@NonNull InnerListener innerListener, @NonNull Executor executor, 1350 OnProjectionStateChangedListener outerListener) { 1351 mOuterListenerMap.put(innerListener, outerListener); 1352 mExecutorMap.put(innerListener, executor); 1353 } 1354 remove(InnerListener innerListener)1355 void remove(InnerListener innerListener) { 1356 mOuterListenerMap.remove(innerListener); 1357 mExecutorMap.remove(innerListener); 1358 } 1359 getOuterListener(@onNull InnerListener innerListener)1360 OnProjectionStateChangedListener getOuterListener(@NonNull InnerListener innerListener) { 1361 return mOuterListenerMap.get(innerListener); 1362 } 1363 getExecutor(@onNull InnerListener innerListener)1364 Executor getExecutor(@NonNull InnerListener innerListener) { 1365 return mExecutorMap.get(innerListener); 1366 } 1367 } 1368 1369 /** 1370 * Returns the color contrast for the user. 1371 * <p> 1372 * <strong>Note:</strong> You need to query this only if your application is 1373 * doing its own rendering and does not rely on the material rendering pipeline. 1374 * </p> 1375 * @return The color contrast, float in [-1, 1] where 1376 * <ul> 1377 * <li> 0 corresponds to the default contrast </li> 1378 * <li> -1 corresponds to the minimum contrast </li> 1379 * <li> 1 corresponds to the maximum contrast </li> 1380 * </ul> 1381 */ 1382 @FloatRange(from = -1.0f, to = 1.0f) getContrast()1383 public float getContrast() { 1384 return sGlobals.getContrast(); 1385 } 1386 1387 /** 1388 * Registers a {@link ContrastChangeListener} for the current user. 1389 * 1390 * @param executor The executor on which the listener should be called back. 1391 * @param listener The listener. 1392 */ addContrastChangeListener( @onNull @allbackExecutor Executor executor, @NonNull ContrastChangeListener listener)1393 public void addContrastChangeListener( 1394 @NonNull @CallbackExecutor Executor executor, 1395 @NonNull ContrastChangeListener listener) { 1396 Objects.requireNonNull(executor); 1397 Objects.requireNonNull(listener); 1398 sGlobals.addContrastChangeListener(listener, executor); 1399 } 1400 1401 /** 1402 * Unregisters a {@link ContrastChangeListener} for the current user. 1403 * If the listener was not registered, does nothing and returns. 1404 * 1405 * @param listener The listener to unregister. 1406 */ removeContrastChangeListener(@onNull ContrastChangeListener listener)1407 public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) { 1408 Objects.requireNonNull(listener); 1409 sGlobals.removeContrastChangeListener(listener); 1410 } 1411 } 1412