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.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.annotation.SystemService; 27 import android.annotation.TestApi; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Context; 30 import android.content.res.Configuration; 31 import android.os.Binder; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.ServiceManager.ServiceNotFoundException; 35 import android.util.ArrayMap; 36 import android.util.ArraySet; 37 import android.util.Slog; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.util.function.pooled.PooledLambda; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.lang.ref.WeakReference; 45 import java.time.LocalTime; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 import java.util.concurrent.Executor; 50 51 /** 52 * This class provides access to the system uimode services. These services 53 * allow applications to control UI modes of the device. 54 * It provides functionality to disable the car mode and it gives access to the 55 * night mode settings. 56 * 57 * <p>These facilities are built on top of the underlying 58 * {@link android.content.Intent#ACTION_DOCK_EVENT} broadcasts that are sent when the user 59 * physical places the device into and out of a dock. When that happens, 60 * the UiModeManager switches the system {@link android.content.res.Configuration} 61 * to the appropriate UI mode, sends broadcasts about the mode switch, and 62 * starts the corresponding mode activity if appropriate. See the 63 * broadcasts {@link #ACTION_ENTER_CAR_MODE} and 64 * {@link #ACTION_ENTER_DESK_MODE} for more information. 65 * 66 * <p>In addition, the user may manually switch the system to car mode without 67 * physically being in a dock. While in car mode -- whether by manual action 68 * from the user or being physically placed in a dock -- a notification is 69 * displayed allowing the user to exit dock mode. Thus the dock mode 70 * represented here may be different than the current state of the underlying 71 * dock event broadcast. 72 */ 73 @SystemService(Context.UI_MODE_SERVICE) 74 public class UiModeManager { 75 /** 76 * A listener with a single method that is invoked whenever the packages projecting using the 77 * {@link ProjectionType}s for which it is registered change. 78 * 79 * @hide 80 */ 81 @SystemApi 82 public interface OnProjectionStateChangedListener { 83 /** 84 * Callback invoked when projection state changes for a {@link ProjectionType} for which 85 * this listener was added. 86 * @param projectionType the listened-for {@link ProjectionType}s that have changed 87 * @param packageNames the {@link Set} of package names that have currently set those 88 * {@link ProjectionType}s. 89 */ onProjectionStateChanged(@rojectionType int projectionType, @NonNull Set<String> packageNames)90 void onProjectionStateChanged(@ProjectionType int projectionType, 91 @NonNull Set<String> packageNames); 92 } 93 94 private static final String TAG = "UiModeManager"; 95 96 /** 97 * Broadcast sent when the device's UI has switched to car mode, either 98 * by being placed in a car dock or explicit action of the user. After 99 * sending the broadcast, the system will start the intent 100 * {@link android.content.Intent#ACTION_MAIN} with category 101 * {@link android.content.Intent#CATEGORY_CAR_DOCK} 102 * to display the car UI, which typically what an application would 103 * implement to provide their own interface. However, applications can 104 * also monitor this Intent in order to be informed of mode changes or 105 * prevent the normal car UI from being displayed by setting the result 106 * of the broadcast to {@link Activity#RESULT_CANCELED}. 107 * <p> 108 * This intent is broadcast when {@link #getCurrentModeType()} transitions to 109 * {@link Configuration#UI_MODE_TYPE_CAR} from some other ui mode. 110 */ 111 public static String ACTION_ENTER_CAR_MODE = "android.app.action.ENTER_CAR_MODE"; 112 113 /** 114 * Broadcast sent when an app has entered car mode using either {@link #enableCarMode(int)} or 115 * {@link #enableCarMode(int, int)}. 116 * <p> 117 * Unlike {@link #ACTION_ENTER_CAR_MODE}, which is only sent when the global car mode state 118 * (i.e. {@link #getCurrentModeType()}) transitions to {@link Configuration#UI_MODE_TYPE_CAR}, 119 * this intent is sent any time an app declares it has entered car mode. Thus, this intent is 120 * intended for use by a component which needs to know not only when the global car mode state 121 * changed, but also when the highest priority app declaring car mode has changed. 122 * <p> 123 * This broadcast includes the package name of the app which requested to enter car mode in 124 * {@link #EXTRA_CALLING_PACKAGE}. The priority the app entered car mode at is specified in 125 * {@link #EXTRA_PRIORITY}. 126 * <p> 127 * This is primarily intended to be received by other components of the Android OS. 128 * <p> 129 * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES} 130 * @hide 131 */ 132 @SystemApi 133 public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = 134 "android.app.action.ENTER_CAR_MODE_PRIORITIZED"; 135 136 /** 137 * Broadcast sent when the device's UI has switch away from car mode back 138 * to normal mode. Typically used by a car mode app, to dismiss itself 139 * when the user exits car mode. 140 * <p> 141 * This intent is broadcast when {@link #getCurrentModeType()} transitions from 142 * {@link Configuration#UI_MODE_TYPE_CAR} to some other ui mode. 143 */ 144 public static String ACTION_EXIT_CAR_MODE = "android.app.action.EXIT_CAR_MODE"; 145 146 /** 147 * Broadcast sent when an app has exited car mode using {@link #disableCarMode(int)}. 148 * <p> 149 * Unlike {@link #ACTION_EXIT_CAR_MODE}, which is only sent when the global car mode state 150 * (i.e. {@link #getCurrentModeType()}) transitions to a non-car mode state such as 151 * {@link Configuration#UI_MODE_TYPE_NORMAL}, this intent is sent any time an app declares it 152 * has exited car mode. Thus, this intent is intended for use by a component which needs to 153 * know not only when the global car mode state changed, but also when the highest priority app 154 * declaring car mode has changed. 155 * <p> 156 * This broadcast includes the package name of the app which requested to exit car mode in 157 * {@link #EXTRA_CALLING_PACKAGE}. The priority the app originally entered car mode at is 158 * specified in {@link #EXTRA_PRIORITY}. 159 * <p> 160 * If {@link #DISABLE_CAR_MODE_ALL_PRIORITIES} is used when disabling car mode (i.e. this is 161 * initiated by the user via the persistent car mode notification), this broadcast is sent once 162 * for each priority level for which car mode is being disabled. 163 * <p> 164 * This is primarily intended to be received by other components of the Android OS. 165 * <p> 166 * Receiver requires permission: {@link android.Manifest.permission.HANDLE_CAR_MODE_CHANGES} 167 * @hide 168 */ 169 @SystemApi 170 public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = 171 "android.app.action.EXIT_CAR_MODE_PRIORITIZED"; 172 173 /** 174 * Broadcast sent when the device's UI has switched to desk mode, 175 * by being placed in a desk dock. After 176 * sending the broadcast, the system will start the intent 177 * {@link android.content.Intent#ACTION_MAIN} with category 178 * {@link android.content.Intent#CATEGORY_DESK_DOCK} 179 * to display the desk UI, which typically what an application would 180 * implement to provide their own interface. However, applications can 181 * also monitor this Intent in order to be informed of mode changes or 182 * prevent the normal desk UI from being displayed by setting the result 183 * of the broadcast to {@link Activity#RESULT_CANCELED}. 184 */ 185 public static String ACTION_ENTER_DESK_MODE = "android.app.action.ENTER_DESK_MODE"; 186 187 /** 188 * Broadcast sent when the device's UI has switched away from desk mode back 189 * to normal mode. Typically used by a desk mode app, to dismiss itself 190 * when the user exits desk mode. 191 */ 192 public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE"; 193 194 /** 195 * String extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and 196 * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the package name of the app which 197 * requested to enter or exit car mode. 198 * @hide 199 */ 200 @SystemApi 201 public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE"; 202 203 /** 204 * Integer extra used with {@link #ACTION_ENTER_CAR_MODE_PRIORITIZED} and 205 * {@link #ACTION_EXIT_CAR_MODE_PRIORITIZED} to indicate the priority level at which car mode 206 * is being disabled. 207 * @hide 208 */ 209 @SystemApi 210 public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY"; 211 212 /** @hide */ 213 @IntDef(prefix = { "MODE_" }, value = { 214 MODE_NIGHT_AUTO, 215 MODE_NIGHT_CUSTOM, 216 MODE_NIGHT_NO, 217 MODE_NIGHT_YES 218 }) 219 @Retention(RetentionPolicy.SOURCE) 220 public @interface NightMode {} 221 222 /** 223 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 224 * automatically switch night mode on and off based on the time. 225 */ 226 public static final int MODE_NIGHT_AUTO = 0; 227 228 /** 229 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 230 * automatically switch night mode on and off based on the time. 231 */ 232 public static final int MODE_NIGHT_CUSTOM = 3; 233 234 /** 235 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 236 * never run in night mode. 237 */ 238 public static final int MODE_NIGHT_NO = 1; 239 240 /** 241 * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: 242 * always run in night mode. 243 */ 244 public static final int MODE_NIGHT_YES = 2; 245 246 private IUiModeManager mService; 247 248 /** 249 * Context required for getting the opPackageName of API caller; maybe be {@code null} if the 250 * old constructor marked with UnSupportedAppUsage is used. 251 */ 252 private @Nullable Context mContext; 253 254 private final Object mLock = new Object(); 255 /** 256 * Map that stores internally created {@link InnerListener} objects keyed by their corresponding 257 * externally provided callback objects. 258 */ 259 @GuardedBy("mLock") 260 private final Map<OnProjectionStateChangedListener, InnerListener> 261 mProjectionStateListenerMap = new ArrayMap<>(); 262 263 /** 264 * Resource manager that prevents memory leakage of Contexts via binder objects if clients 265 * fail to remove listeners. 266 */ 267 @GuardedBy("mLock") 268 private final OnProjectionStateChangedListenerResourceManager 269 mOnProjectionStateChangedListenerResourceManager = 270 new OnProjectionStateChangedListenerResourceManager(); 271 272 @UnsupportedAppUsage UiModeManager()273 /*package*/ UiModeManager() throws ServiceNotFoundException { 274 this(null /* context */); 275 } 276 UiModeManager(Context context)277 /*package*/ UiModeManager(Context context) throws ServiceNotFoundException { 278 mService = IUiModeManager.Stub.asInterface( 279 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE)); 280 mContext = context; 281 } 282 283 /** 284 * Flag for use with {@link #enableCarMode(int)}: go to the car 285 * home activity as part of the enable. Enabling this way ensures 286 * a clean transition between the current activity (in non-car-mode) and 287 * the car home activity that will serve as home while in car mode. This 288 * will switch to the car home activity even if we are already in car mode. 289 */ 290 public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 0x0001; 291 292 /** 293 * Flag for use with {@link #enableCarMode(int)}: allow sleep mode while in car mode. 294 * By default, when this flag is not set, the system may hold a full wake lock to keep the 295 * screen turned on and prevent the system from entering sleep mode while in car mode. 296 * Setting this flag disables such behavior and the system may enter sleep mode 297 * if there is no other user activity and no other wake lock held. 298 * Setting this flag can be relevant for a car dock application that does not require the 299 * screen kept on. 300 */ 301 public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 0x0002; 302 303 /** @hide */ 304 @IntDef(prefix = {"ENABLE_CAR_MODE_"}, value = { 305 ENABLE_CAR_MODE_GO_CAR_HOME, 306 ENABLE_CAR_MODE_ALLOW_SLEEP 307 }) 308 @Retention(RetentionPolicy.SOURCE) 309 public @interface EnableCarMode {} 310 311 /** 312 * Force device into car mode, like it had been placed in the car dock. 313 * This will cause the device to switch to the car home UI as part of 314 * the mode switch. 315 * @param flags Must be 0. 316 */ enableCarMode(int flags)317 public void enableCarMode(int flags) { 318 enableCarMode(DEFAULT_PRIORITY, flags); 319 } 320 321 /** 322 * Force device into car mode, like it had been placed in the car dock. This will cause the 323 * device to switch to the car home UI as part of the mode switch. 324 * <p> 325 * An app may request to enter car mode when the system is already in car mode. The app may 326 * specify a "priority" when entering car mode. The device will remain in car mode 327 * (i.e. {@link #getCurrentModeType()} is {@link Configuration#UI_MODE_TYPE_CAR}) as long as 328 * there is a priority level at which car mode have been enabled. 329 * <p> 330 * Specifying a priority level when entering car mode is important in cases where multiple apps 331 * on a device implement a car-mode {@link android.telecom.InCallService} (see 332 * {@link android.telecom.TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}). The 333 * {@link android.telecom.InCallService} associated with the highest priority app which entered 334 * car mode will be bound to by Telecom and provided with information about ongoing calls on 335 * the device. 336 * <p> 337 * System apps holding the required permission can enable car mode when the app determines the 338 * correct conditions exist for that app to be in car mode. The device maker should ensure that 339 * where multiple apps exist on the device which can potentially enter car mode, appropriate 340 * priorities are used to ensure that calls delivered by the 341 * {@link android.telecom.InCallService} API are sent to the highest priority app given the 342 * desired behavior of the car mode experience on the device. 343 * <p> 344 * If app A and app B both meet their own criteria to enable car mode, and it is desired that 345 * app B should be the one which should receive call information in that scenario, the priority 346 * for app B should be higher than the one for app A. The higher priority of app B compared to 347 * A means it will be bound to during calls and app A will not. When app B no longer meets its 348 * criteria for providing a car mode experience it uses {@link #disableCarMode(int)} to disable 349 * car mode at its priority level. The system will then unbind from app B and bind to app A as 350 * it has the next highest priority. 351 * <p> 352 * When an app enables car mode at a certain priority, it can disable car mode at the specified 353 * priority level using {@link #disableCarMode(int)}. An app may only enable car mode at a 354 * single priority. 355 * <p> 356 * Public apps are assumed to enter/exit car mode at the lowest priority, 357 * {@link #DEFAULT_PRIORITY}. 358 * 359 * @param priority The declared priority for the caller, where {@link #DEFAULT_PRIORITY} (0) is 360 * the lowest priority and higher numbers represent a higher priority. 361 * The priorities apps declare when entering car mode is determined by the 362 * device manufacturer based on the desired car mode experience. 363 * @param flags Car mode flags. 364 * @hide 365 */ 366 @SystemApi 367 @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) enableCarMode(@ntRangefrom = 0) int priority, @EnableCarMode int flags)368 public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) { 369 if (mService != null) { 370 try { 371 mService.enableCarMode(flags, priority, 372 mContext == null ? null : mContext.getOpPackageName()); 373 } catch (RemoteException e) { 374 throw e.rethrowFromSystemServer(); 375 } 376 } 377 } 378 379 /** 380 * Flag for use with {@link #disableCarMode(int)}: go to the normal 381 * home activity as part of the disable. Disabling this way ensures 382 * a clean transition between the current activity (in car mode) and 383 * the original home activity (which was typically last running without 384 * being in car mode). 385 */ 386 public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001; 387 388 /** 389 * Flag for use with {@link #disableCarMode(int)}: Disables car mode at ALL priority levels. 390 * Primarily intended for use from {@link com.android.internal.app.DisableCarModeActivity} to 391 * provide the user with a means to exit car mode at all priority levels. 392 * @hide 393 */ 394 public static final int DISABLE_CAR_MODE_ALL_PRIORITIES = 0x0002; 395 396 /** @hide */ 397 @IntDef(prefix = { "DISABLE_CAR_MODE_" }, value = { 398 DISABLE_CAR_MODE_GO_HOME 399 }) 400 @Retention(RetentionPolicy.SOURCE) 401 public @interface DisableCarMode {} 402 403 /** 404 * The default priority used for entering car mode. 405 * <p> 406 * Callers of the {@link #enableCarMode(int)} priority will be assigned the default priority. 407 * This is considered the lowest possible priority for enabling car mode. 408 * <p> 409 * System apps can specify a priority other than the default priority when using 410 * {@link #enableCarMode(int, int)} to enable car mode. 411 * @hide 412 */ 413 @SystemApi 414 public static final int DEFAULT_PRIORITY = 0; 415 416 /** 417 * Turn off special mode if currently in car mode. 418 * @param flags One of the disable car mode flags. 419 */ disableCarMode(@isableCarMode int flags)420 public void disableCarMode(@DisableCarMode int flags) { 421 if (mService != null) { 422 try { 423 mService.disableCarModeByCallingPackage(flags, 424 mContext == null ? null : mContext.getOpPackageName()); 425 } catch (RemoteException e) { 426 throw e.rethrowFromSystemServer(); 427 } 428 } 429 } 430 431 /** 432 * Return the current running mode type. May be one of 433 * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, 434 * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, 435 * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, 436 * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TELEVISION}, 437 * {@link Configuration#UI_MODE_TYPE_APPLIANCE Configuration.UI_MODE_TYPE_APPLIANCE}, 438 * {@link Configuration#UI_MODE_TYPE_WATCH Configuration.UI_MODE_TYPE_WATCH}, or 439 * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}. 440 */ getCurrentModeType()441 public int getCurrentModeType() { 442 if (mService != null) { 443 try { 444 return mService.getCurrentModeType(); 445 } catch (RemoteException e) { 446 throw e.rethrowFromSystemServer(); 447 } 448 } 449 return Configuration.UI_MODE_TYPE_NORMAL; 450 } 451 452 /** 453 * Sets the system-wide night mode. 454 * <p> 455 * The mode can be one of: 456 * <ul> 457 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 458 * {@code notnight} mode</li> 459 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 460 * {@code night} mode</li> 461 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 462 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 463 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 464 * {@code night} and {@code notnight} based on the device's current 465 * location and certain other sensors</li> 466 * </ul> 467 * <p> 468 * <strong>Note:</strong> On API 22 and below, changes to the night mode 469 * are only effective when the {@link Configuration#UI_MODE_TYPE_CAR car} 470 * or {@link Configuration#UI_MODE_TYPE_DESK desk} mode is enabled on a 471 * device. On API 23 through API 28, changes to night mode are always effective. 472 * <p> 473 * Starting in API 29, when the device is in car mode and this method is called, night mode 474 * will change, but the new setting is not persisted and the previously persisted setting 475 * will be restored when the device exits car mode. 476 * <p> 477 * Changes to night mode take effect globally and will result in a configuration change 478 * (and potentially an Activity lifecycle event) being applied to all running apps. 479 * Developers interested in an app-local implementation of night mode should consider using 480 * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or 481 * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the 482 * backward compatible implementation. 483 * 484 * @param mode the night mode to set 485 * @see #getNightMode() 486 * @see #setApplicationNightMode(int) 487 */ setNightMode(@ightMode int mode)488 public void setNightMode(@NightMode int mode) { 489 if (mService != null) { 490 try { 491 mService.setNightMode(mode); 492 } catch (RemoteException e) { 493 throw e.rethrowFromSystemServer(); 494 } 495 } 496 } 497 498 /** 499 * Sets and persist the night mode for this application. 500 * <p> 501 * The mode can be one of: 502 * <ul> 503 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 504 * {@code notnight} mode</li> 505 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 506 * {@code night} mode</li> 507 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 508 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 509 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 510 * {@code night} and {@code notnight} based on the device's current 511 * location and certain other sensors</li> 512 * </ul> 513 * <p> 514 * Changes to night mode take effect locally and will result in a configuration change 515 * (and potentially an Activity lifecycle event) being applied to this application. The mode 516 * is persisted for this application until it is either modified by the application, the 517 * user clears the data for the application, or this application is uninstalled. 518 * <p> 519 * Developers interested in a non-persistent app-local implementation of night mode should 520 * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} 521 * to manage the -night qualifier locally. 522 * 523 * @param mode the night mode to set 524 * @see #setNightMode(int) 525 */ setApplicationNightMode(@ightMode int mode)526 public void setApplicationNightMode(@NightMode int mode) { 527 if (mService != null) { 528 try { 529 mService.setApplicationNightMode(mode); 530 } catch (RemoteException e) { 531 throw e.rethrowFromSystemServer(); 532 } 533 } 534 } 535 536 /** 537 * Returns the currently configured night mode. 538 * <p> 539 * May be one of: 540 * <ul> 541 * <li>{@link #MODE_NIGHT_NO}</li> 542 * <li>{@link #MODE_NIGHT_YES}</li> 543 * <li>{@link #MODE_NIGHT_AUTO}</li> 544 * <li>{@link #MODE_NIGHT_CUSTOM}</li> 545 * <li>{@code -1} on error</li> 546 * </ul> 547 * 548 * @return the current night mode, or {@code -1} on error 549 * @see #setNightMode(int) 550 */ getNightMode()551 public @NightMode int getNightMode() { 552 if (mService != null) { 553 try { 554 return mService.getNightMode(); 555 } catch (RemoteException e) { 556 throw e.rethrowFromSystemServer(); 557 } 558 } 559 return -1; 560 } 561 562 /** 563 * @return If UI mode is locked or not. When UI mode is locked, calls to change UI mode 564 * like {@link #enableCarMode(int)} will silently fail. 565 * @hide 566 */ 567 @TestApi isUiModeLocked()568 public boolean isUiModeLocked() { 569 if (mService != null) { 570 try { 571 return mService.isUiModeLocked(); 572 } catch (RemoteException e) { 573 throw e.rethrowFromSystemServer(); 574 } 575 } 576 return true; 577 } 578 579 /** 580 * Returns whether night mode is locked or not. 581 * <p> 582 * When night mode is locked, only privileged system components may change 583 * night mode and calls from non-privileged applications to change night 584 * mode will fail silently. 585 * 586 * @return {@code true} if night mode is locked or {@code false} otherwise 587 * @hide 588 */ 589 @TestApi isNightModeLocked()590 public boolean isNightModeLocked() { 591 if (mService != null) { 592 try { 593 return mService.isNightModeLocked(); 594 } catch (RemoteException e) { 595 throw e.rethrowFromSystemServer(); 596 } 597 } 598 return true; 599 } 600 601 /** 602 * Activating night mode for the current user 603 * 604 * @return {@code true} if the change is successful 605 * @hide 606 */ setNightModeActivated(boolean active)607 public boolean setNightModeActivated(boolean active) { 608 if (mService != null) { 609 try { 610 return mService.setNightModeActivated(active); 611 } catch (RemoteException e) { 612 throw e.rethrowFromSystemServer(); 613 } 614 } 615 return false; 616 } 617 618 /** 619 * Returns the time of the day Dark theme activates 620 * <p> 621 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 622 * this time set to activate it automatically. 623 */ 624 @NonNull getCustomNightModeStart()625 public LocalTime getCustomNightModeStart() { 626 if (mService != null) { 627 try { 628 return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000); 629 } catch (RemoteException e) { 630 throw e.rethrowFromSystemServer(); 631 } 632 } 633 return LocalTime.MIDNIGHT; 634 } 635 636 /** 637 * Sets the time of the day Dark theme activates 638 * <p> 639 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 640 * this time set to activate it automatically 641 * @param time The time of the day Dark theme should activate 642 */ setCustomNightModeStart(@onNull LocalTime time)643 public void setCustomNightModeStart(@NonNull LocalTime time) { 644 if (mService != null) { 645 try { 646 mService.setCustomNightModeStart(time.toNanoOfDay() / 1000); 647 } catch (RemoteException e) { 648 throw e.rethrowFromSystemServer(); 649 } 650 } 651 } 652 653 /** 654 * Returns the time of the day Dark theme deactivates 655 * <p> 656 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 657 * this time set to deactivate it automatically. 658 */ 659 @NonNull getCustomNightModeEnd()660 public LocalTime getCustomNightModeEnd() { 661 if (mService != null) { 662 try { 663 return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000); 664 } catch (RemoteException e) { 665 throw e.rethrowFromSystemServer(); 666 } 667 } 668 return LocalTime.MIDNIGHT; 669 } 670 671 /** 672 * Sets the time of the day Dark theme deactivates 673 * <p> 674 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 675 * this time set to deactivate it automatically. 676 * @param time The time of the day Dark theme should deactivate 677 */ setCustomNightModeEnd(@onNull LocalTime time)678 public void setCustomNightModeEnd(@NonNull LocalTime time) { 679 if (mService != null) { 680 try { 681 mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000); 682 } catch (RemoteException e) { 683 throw e.rethrowFromSystemServer(); 684 } 685 } 686 } 687 688 /** 689 * Indicates no projection type. Can be used to compare with the {@link ProjectionType} in 690 * {@link OnProjectionStateChangedListener#onProjectionStateChanged(int, Set)}. 691 * 692 * @hide 693 */ 694 @SystemApi 695 @TestApi 696 public static final int PROJECTION_TYPE_NONE = 0x0000; 697 /** 698 * Automotive projection prevents degradation of GPS to save battery, routes incoming calls to 699 * the automotive role holder, etc. For use with {@link #requestProjection(int)} and 700 * {@link #clearProjectionState(int)}. 701 * 702 * @hide 703 */ 704 @SystemApi 705 @TestApi 706 public static final int PROJECTION_TYPE_AUTOMOTIVE = 0x0001; 707 /** 708 * Indicates all projection types. For use with 709 * {@link #addOnProjectionStateChangedListener(int, Executor, OnProjectionStateChangedListener)} 710 * and {@link #getProjectingPackages(int)}. 711 * 712 * @hide 713 */ 714 @SystemApi 715 @TestApi 716 public static final int PROJECTION_TYPE_ALL = -1; // All bits on 717 718 /** @hide */ 719 @IntDef(prefix = {"PROJECTION_TYPE_"}, value = { 720 PROJECTION_TYPE_NONE, 721 PROJECTION_TYPE_AUTOMOTIVE, 722 PROJECTION_TYPE_ALL, 723 }) 724 @Retention(RetentionPolicy.SOURCE) 725 public @interface ProjectionType { 726 } 727 728 /** 729 * Sets the given {@link ProjectionType}. 730 * 731 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 732 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 733 * @param projectionType the type of projection to request. This must be a single 734 * {@link ProjectionType} and cannot be a bitmask. 735 * @return true if the projection was successfully set 736 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 737 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 738 * 739 * @hide 740 */ 741 @SystemApi 742 @TestApi 743 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 744 conditional = true) requestProjection(@rojectionType int projectionType)745 public boolean requestProjection(@ProjectionType int projectionType) { 746 if (mService != null) { 747 try { 748 return mService.requestProjection(new Binder(), projectionType, 749 mContext.getOpPackageName()); 750 } catch (RemoteException e) { 751 throw e.rethrowFromSystemServer(); 752 } 753 } 754 return false; 755 } 756 757 /** 758 * Releases the given {@link ProjectionType}. 759 * 760 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 761 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 762 * @param projectionType the type of projection to release. This must be a single 763 * {@link ProjectionType} and cannot be a bitmask. 764 * @return true if the package had set projection and it was successfully released 765 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 766 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 767 * 768 * @hide 769 */ 770 @SystemApi 771 @TestApi 772 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 773 conditional = true) releaseProjection(@rojectionType int projectionType)774 public boolean releaseProjection(@ProjectionType int projectionType) { 775 if (mService != null) { 776 try { 777 return mService.releaseProjection(projectionType, mContext.getOpPackageName()); 778 } catch (RemoteException e) { 779 throw e.rethrowFromSystemServer(); 780 } 781 } 782 return false; 783 } 784 785 /** 786 * Gets the packages that are currently projecting. 787 * 788 * @param projectionType the {@link ProjectionType}s to consider when computing which packages 789 * are projecting. Use {@link #PROJECTION_TYPE_ALL} to get all projecting 790 * packages. 791 * 792 * @hide 793 */ 794 @SystemApi 795 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) 796 @NonNull getProjectingPackages(@rojectionType int projectionType)797 public Set<String> getProjectingPackages(@ProjectionType int projectionType) { 798 if (mService != null) { 799 try { 800 return new ArraySet<>(mService.getProjectingPackages(projectionType)); 801 } catch (RemoteException e) { 802 throw e.rethrowFromSystemServer(); 803 } 804 } 805 return Set.of(); 806 } 807 808 /** 809 * Gets the {@link ProjectionType}s that are currently active. 810 * 811 * @hide 812 */ 813 @SystemApi 814 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) getActiveProjectionTypes()815 public @ProjectionType int getActiveProjectionTypes() { 816 if (mService != null) { 817 try { 818 return mService.getActiveProjectionTypes(); 819 } catch (RemoteException e) { 820 throw e.rethrowFromSystemServer(); 821 } 822 } 823 return PROJECTION_TYPE_NONE; 824 } 825 826 /** 827 * Configures the listener to receive callbacks when the packages projecting using the given 828 * {@link ProjectionType}s change. 829 * 830 * @param projectionType one or more {@link ProjectionType}s to listen for changes regarding 831 * @param executor an {@link Executor} on which to invoke the callbacks 832 * @param listener the {@link OnProjectionStateChangedListener} to add 833 * 834 * @hide 835 */ 836 @SystemApi 837 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) addOnProjectionStateChangedListener(@rojectionType int projectionType, @NonNull @CallbackExecutor Executor executor, @NonNull OnProjectionStateChangedListener listener)838 public void addOnProjectionStateChangedListener(@ProjectionType int projectionType, 839 @NonNull @CallbackExecutor Executor executor, 840 @NonNull OnProjectionStateChangedListener listener) { 841 synchronized (mLock) { 842 if (mProjectionStateListenerMap.containsKey(listener)) { 843 Slog.i(TAG, "Attempted to add listener that was already added."); 844 return; 845 } 846 if (mService != null) { 847 InnerListener innerListener = new InnerListener(executor, listener, 848 mOnProjectionStateChangedListenerResourceManager); 849 try { 850 mService.addOnProjectionStateChangedListener(innerListener, projectionType); 851 mProjectionStateListenerMap.put(listener, innerListener); 852 } catch (RemoteException e) { 853 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 854 throw e.rethrowFromSystemServer(); 855 } 856 } 857 } 858 } 859 860 /** 861 * Removes the listener so it stops receiving updates for all {@link ProjectionType}s. 862 * 863 * @param listener the {@link OnProjectionStateChangedListener} to remove 864 * 865 * @hide 866 */ 867 @SystemApi 868 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) removeOnProjectionStateChangedListener( @onNull OnProjectionStateChangedListener listener)869 public void removeOnProjectionStateChangedListener( 870 @NonNull OnProjectionStateChangedListener listener) { 871 synchronized (mLock) { 872 InnerListener innerListener = mProjectionStateListenerMap.get(listener); 873 if (innerListener == null) { 874 Slog.i(TAG, "Attempted to remove listener that was not added."); 875 return; 876 } 877 if (mService != null) { 878 try { 879 mService.removeOnProjectionStateChangedListener(innerListener); 880 } catch (RemoteException e) { 881 throw e.rethrowFromSystemServer(); 882 } 883 } 884 mProjectionStateListenerMap.remove(listener); 885 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 886 } 887 } 888 889 private static class InnerListener extends IOnProjectionStateChangedListener.Stub { 890 private final WeakReference<OnProjectionStateChangedListenerResourceManager> 891 mResourceManager; 892 InnerListener(@onNull Executor executor, @NonNull OnProjectionStateChangedListener outerListener, @NonNull OnProjectionStateChangedListenerResourceManager resourceManager)893 private InnerListener(@NonNull Executor executor, 894 @NonNull OnProjectionStateChangedListener outerListener, 895 @NonNull OnProjectionStateChangedListenerResourceManager resourceManager) { 896 resourceManager.put(this, executor, outerListener); 897 mResourceManager = new WeakReference<>(resourceManager); 898 } 899 900 @Override onProjectionStateChanged(int activeProjectionTypes, List<String> projectingPackages)901 public void onProjectionStateChanged(int activeProjectionTypes, 902 List<String> projectingPackages) { 903 OnProjectionStateChangedListenerResourceManager resourceManager = 904 mResourceManager.get(); 905 if (resourceManager == null) { 906 Slog.w(TAG, "Can't execute onProjectionStateChanged, resource manager is gone."); 907 return; 908 } 909 910 OnProjectionStateChangedListener outerListener = resourceManager.getOuterListener(this); 911 Executor executor = resourceManager.getExecutor(this); 912 if (outerListener == null || executor == null) { 913 Slog.w(TAG, "Can't execute onProjectionStatechanged, references are null."); 914 return; 915 } 916 917 executor.execute(PooledLambda.obtainRunnable( 918 OnProjectionStateChangedListener::onProjectionStateChanged, 919 outerListener, 920 activeProjectionTypes, 921 new ArraySet<>(projectingPackages)).recycleOnUse()); 922 } 923 } 924 925 /** 926 * Wrapper class that ensures we don't leak {@link Activity} or other large {@link Context} in 927 * which this {@link UiModeManager} resides if/when it ends without unregistering associated 928 * callback objects. 929 */ 930 private static class OnProjectionStateChangedListenerResourceManager { 931 private final Map<InnerListener, OnProjectionStateChangedListener> mOuterListenerMap = 932 new ArrayMap<>(1); 933 private final Map<InnerListener, Executor> mExecutorMap = new ArrayMap<>(1); 934 put(@onNull InnerListener innerListener, @NonNull Executor executor, OnProjectionStateChangedListener outerListener)935 void put(@NonNull InnerListener innerListener, @NonNull Executor executor, 936 OnProjectionStateChangedListener outerListener) { 937 mOuterListenerMap.put(innerListener, outerListener); 938 mExecutorMap.put(innerListener, executor); 939 } 940 remove(InnerListener innerListener)941 void remove(InnerListener innerListener) { 942 mOuterListenerMap.remove(innerListener); 943 mExecutorMap.remove(innerListener); 944 } 945 getOuterListener(@onNull InnerListener innerListener)946 OnProjectionStateChangedListener getOuterListener(@NonNull InnerListener innerListener) { 947 return mOuterListenerMap.get(innerListener); 948 } 949 getExecutor(@onNull InnerListener innerListener)950 Executor getExecutor(@NonNull InnerListener innerListener) { 951 return mExecutorMap.get(innerListener); 952 } 953 } 954 } 955