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