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