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 /** 247 * Granular types for {@link #setNightModeCustomType(int)} 248 * @hide 249 */ 250 @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { 251 MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, 252 MODE_NIGHT_CUSTOM_TYPE_BEDTIME, 253 }) 254 @Retention(RetentionPolicy.SOURCE) 255 public @interface NightModeCustomType {} 256 257 /** 258 * Granular types for {@link #getNightModeCustomType()} 259 * @hide 260 */ 261 @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = { 262 MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, 263 MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, 264 MODE_NIGHT_CUSTOM_TYPE_BEDTIME, 265 }) 266 @Retention(RetentionPolicy.SOURCE) 267 public @interface NightModeCustomReturnType {} 268 269 /** 270 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is unknown. 271 * <p> 272 * This is the default value when the night mode is set to value other than 273 * {@link #MODE_NIGHT_CUSTOM}. 274 * @hide 275 */ 276 @SystemApi 277 public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; 278 279 /** 280 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on a custom schedule. 281 * <p> 282 * This is the default value when night mode is set to {@link #MODE_NIGHT_CUSTOM} unless the 283 * the night mode custom type is specified by calling {@link #setNightModeCustomType(int)}. 284 * @hide 285 */ 286 @SystemApi 287 public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; 288 289 /** 290 * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on the bedtime schedule. 291 * @hide 292 */ 293 @SystemApi 294 public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; 295 296 private IUiModeManager mService; 297 298 /** 299 * Context required for getting the opPackageName of API caller; maybe be {@code null} if the 300 * old constructor marked with UnSupportedAppUsage is used. 301 */ 302 private @Nullable Context mContext; 303 304 private final Object mLock = new Object(); 305 /** 306 * Map that stores internally created {@link InnerListener} objects keyed by their corresponding 307 * externally provided callback objects. 308 */ 309 @GuardedBy("mLock") 310 private final Map<OnProjectionStateChangedListener, InnerListener> 311 mProjectionStateListenerMap = new ArrayMap<>(); 312 313 /** 314 * Resource manager that prevents memory leakage of Contexts via binder objects if clients 315 * fail to remove listeners. 316 */ 317 @GuardedBy("mLock") 318 private final OnProjectionStateChangedListenerResourceManager 319 mOnProjectionStateChangedListenerResourceManager = 320 new OnProjectionStateChangedListenerResourceManager(); 321 322 @UnsupportedAppUsage UiModeManager()323 /*package*/ UiModeManager() throws ServiceNotFoundException { 324 this(null /* context */); 325 } 326 UiModeManager(Context context)327 /*package*/ UiModeManager(Context context) throws ServiceNotFoundException { 328 mService = IUiModeManager.Stub.asInterface( 329 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE)); 330 mContext = context; 331 } 332 333 /** 334 * Flag for use with {@link #enableCarMode(int)}: go to the car 335 * home activity as part of the enable. Enabling this way ensures 336 * a clean transition between the current activity (in non-car-mode) and 337 * the car home activity that will serve as home while in car mode. This 338 * will switch to the car home activity even if we are already in car mode. 339 */ 340 public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 0x0001; 341 342 /** 343 * Flag for use with {@link #enableCarMode(int)}: allow sleep mode while in car mode. 344 * By default, when this flag is not set, the system may hold a full wake lock to keep the 345 * screen turned on and prevent the system from entering sleep mode while in car mode. 346 * Setting this flag disables such behavior and the system may enter sleep mode 347 * if there is no other user activity and no other wake lock held. 348 * Setting this flag can be relevant for a car dock application that does not require the 349 * screen kept on. 350 */ 351 public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 0x0002; 352 353 /** @hide */ 354 @IntDef(prefix = {"ENABLE_CAR_MODE_"}, value = { 355 ENABLE_CAR_MODE_GO_CAR_HOME, 356 ENABLE_CAR_MODE_ALLOW_SLEEP 357 }) 358 @Retention(RetentionPolicy.SOURCE) 359 public @interface EnableCarMode {} 360 361 /** 362 * Force device into car mode, like it had been placed in the car dock. 363 * This will cause the device to switch to the car home UI as part of 364 * the mode switch. 365 * @param flags Must be 0. 366 */ enableCarMode(int flags)367 public void enableCarMode(int flags) { 368 enableCarMode(DEFAULT_PRIORITY, flags); 369 } 370 371 /** 372 * Force device into car mode, like it had been placed in the car dock. This will cause the 373 * device to switch to the car home UI as part of the mode switch. 374 * <p> 375 * An app may request to enter car mode when the system is already in car mode. The app may 376 * specify a "priority" when entering car mode. The device will remain in car mode 377 * (i.e. {@link #getCurrentModeType()} is {@link Configuration#UI_MODE_TYPE_CAR}) as long as 378 * there is a priority level at which car mode have been enabled. 379 * <p> 380 * Specifying a priority level when entering car mode is important in cases where multiple apps 381 * on a device implement a car-mode {@link android.telecom.InCallService} (see 382 * {@link android.telecom.TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}). The 383 * {@link android.telecom.InCallService} associated with the highest priority app which entered 384 * car mode will be bound to by Telecom and provided with information about ongoing calls on 385 * the device. 386 * <p> 387 * System apps holding the required permission can enable car mode when the app determines the 388 * correct conditions exist for that app to be in car mode. The device maker should ensure that 389 * where multiple apps exist on the device which can potentially enter car mode, appropriate 390 * priorities are used to ensure that calls delivered by the 391 * {@link android.telecom.InCallService} API are sent to the highest priority app given the 392 * desired behavior of the car mode experience on the device. 393 * <p> 394 * If app A and app B both meet their own criteria to enable car mode, and it is desired that 395 * app B should be the one which should receive call information in that scenario, the priority 396 * for app B should be higher than the one for app A. The higher priority of app B compared to 397 * A means it will be bound to during calls and app A will not. When app B no longer meets its 398 * criteria for providing a car mode experience it uses {@link #disableCarMode(int)} to disable 399 * car mode at its priority level. The system will then unbind from app B and bind to app A as 400 * it has the next highest priority. 401 * <p> 402 * When an app enables car mode at a certain priority, it can disable car mode at the specified 403 * priority level using {@link #disableCarMode(int)}. An app may only enable car mode at a 404 * single priority. 405 * <p> 406 * Public apps are assumed to enter/exit car mode at the lowest priority, 407 * {@link #DEFAULT_PRIORITY}. 408 * 409 * @param priority The declared priority for the caller, where {@link #DEFAULT_PRIORITY} (0) is 410 * the lowest priority and higher numbers represent a higher priority. 411 * The priorities apps declare when entering car mode is determined by the 412 * device manufacturer based on the desired car mode experience. 413 * @param flags Car mode flags. 414 * @hide 415 */ 416 @SystemApi 417 @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) enableCarMode(@ntRangefrom = 0) int priority, @EnableCarMode int flags)418 public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) { 419 if (mService != null) { 420 try { 421 mService.enableCarMode(flags, priority, 422 mContext == null ? null : mContext.getOpPackageName()); 423 } catch (RemoteException e) { 424 throw e.rethrowFromSystemServer(); 425 } 426 } 427 } 428 429 /** 430 * Flag for use with {@link #disableCarMode(int)}: go to the normal 431 * home activity as part of the disable. Disabling this way ensures 432 * a clean transition between the current activity (in car mode) and 433 * the original home activity (which was typically last running without 434 * being in car mode). 435 */ 436 public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001; 437 438 /** 439 * Flag for use with {@link #disableCarMode(int)}: Disables car mode at ALL priority levels. 440 * Primarily intended for use from {@link com.android.internal.app.DisableCarModeActivity} to 441 * provide the user with a means to exit car mode at all priority levels. 442 * @hide 443 */ 444 public static final int DISABLE_CAR_MODE_ALL_PRIORITIES = 0x0002; 445 446 /** @hide */ 447 @IntDef(prefix = { "DISABLE_CAR_MODE_" }, value = { 448 DISABLE_CAR_MODE_GO_HOME 449 }) 450 @Retention(RetentionPolicy.SOURCE) 451 public @interface DisableCarMode {} 452 453 /** 454 * The default priority used for entering car mode. 455 * <p> 456 * Callers of the {@link #enableCarMode(int)} priority will be assigned the default priority. 457 * This is considered the lowest possible priority for enabling car mode. 458 * <p> 459 * System apps can specify a priority other than the default priority when using 460 * {@link #enableCarMode(int, int)} to enable car mode. 461 * @hide 462 */ 463 @SystemApi 464 public static final int DEFAULT_PRIORITY = 0; 465 466 /** 467 * Turn off special mode if currently in car mode. 468 * @param flags One of the disable car mode flags. 469 */ disableCarMode(@isableCarMode int flags)470 public void disableCarMode(@DisableCarMode int flags) { 471 if (mService != null) { 472 try { 473 mService.disableCarModeByCallingPackage(flags, 474 mContext == null ? null : mContext.getOpPackageName()); 475 } catch (RemoteException e) { 476 throw e.rethrowFromSystemServer(); 477 } 478 } 479 } 480 481 /** 482 * Return the current running mode type. May be one of 483 * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, 484 * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, 485 * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, 486 * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TELEVISION}, 487 * {@link Configuration#UI_MODE_TYPE_APPLIANCE Configuration.UI_MODE_TYPE_APPLIANCE}, 488 * {@link Configuration#UI_MODE_TYPE_WATCH Configuration.UI_MODE_TYPE_WATCH}, or 489 * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}. 490 */ getCurrentModeType()491 public int getCurrentModeType() { 492 if (mService != null) { 493 try { 494 return mService.getCurrentModeType(); 495 } catch (RemoteException e) { 496 throw e.rethrowFromSystemServer(); 497 } 498 } 499 return Configuration.UI_MODE_TYPE_NORMAL; 500 } 501 502 /** 503 * Sets the system-wide night mode. 504 * <p> 505 * The mode can be one of: 506 * <ul> 507 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 508 * {@code notnight} mode</li> 509 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 510 * {@code night} mode</li> 511 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 512 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 513 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 514 * {@code night} and {@code notnight} based on the device's current 515 * location and certain other sensors</li> 516 * </ul> 517 * <p> 518 * <strong>Note:</strong> On API 22 and below, changes to the night mode 519 * are only effective when the {@link Configuration#UI_MODE_TYPE_CAR car} 520 * or {@link Configuration#UI_MODE_TYPE_DESK desk} mode is enabled on a 521 * device. On API 23 through API 28, changes to night mode are always effective. 522 * <p> 523 * Starting in API 29, when the device is in car mode and this method is called, night mode 524 * will change, but the new setting is not persisted and the previously persisted setting 525 * will be restored when the device exits car mode. 526 * <p> 527 * Changes to night mode take effect globally and will result in a configuration change 528 * (and potentially an Activity lifecycle event) being applied to all running apps. 529 * Developers interested in an app-local implementation of night mode should consider using 530 * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or 531 * {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} for the 532 * backward compatible implementation. 533 * 534 * @param mode the night mode to set 535 * @see #getNightMode() 536 * @see #setApplicationNightMode(int) 537 */ setNightMode(@ightMode int mode)538 public void setNightMode(@NightMode int mode) { 539 if (mService != null) { 540 try { 541 mService.setNightMode(mode); 542 } catch (RemoteException e) { 543 throw e.rethrowFromSystemServer(); 544 } 545 } 546 } 547 548 /** 549 * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type 550 * {@code nightModeCustomType}. 551 * 552 * @param nightModeCustomType 553 * @throws IllegalArgumentException if passed an unsupported type to 554 * {@code nightModeCustomType}. 555 * @hide 556 */ 557 @SystemApi 558 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeCustomType(@ightModeCustomType int nightModeCustomType)559 public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) { 560 if (mService != null) { 561 try { 562 mService.setNightModeCustomType(nightModeCustomType); 563 } catch (RemoteException e) { 564 throw e.rethrowFromSystemServer(); 565 } 566 } 567 } 568 569 /** 570 * Returns the custom night mode type. 571 * <p> 572 * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns 573 * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}. 574 * @hide 575 */ 576 @SystemApi 577 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) getNightModeCustomType()578 public @NightModeCustomReturnType int getNightModeCustomType() { 579 if (mService != null) { 580 try { 581 return mService.getNightModeCustomType(); 582 } catch (RemoteException e) { 583 throw e.rethrowFromSystemServer(); 584 } 585 } 586 return MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; 587 } 588 589 /** 590 * Sets and persist the night mode for this application. 591 * <p> 592 * The mode can be one of: 593 * <ul> 594 * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into 595 * {@code notnight} mode</li> 596 * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into 597 * {@code night} mode</li> 598 * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between 599 * {@code night} and {@code notnight} based on the custom time set (or default)</li> 600 * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between 601 * {@code night} and {@code notnight} based on the device's current 602 * location and certain other sensors</li> 603 * </ul> 604 * <p> 605 * Changes to night mode take effect locally and will result in a configuration change 606 * (and potentially an Activity lifecycle event) being applied to this application. The mode 607 * is persisted for this application until it is either modified by the application, the 608 * user clears the data for the application, or this application is uninstalled. 609 * <p> 610 * Developers interested in a non-persistent app-local implementation of night mode should 611 * consider using {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} 612 * to manage the -night qualifier locally. 613 * 614 * @param mode the night mode to set 615 * @see #setNightMode(int) 616 */ setApplicationNightMode(@ightMode int mode)617 public void setApplicationNightMode(@NightMode int mode) { 618 if (mService != null) { 619 try { 620 mService.setApplicationNightMode(mode); 621 } catch (RemoteException e) { 622 throw e.rethrowFromSystemServer(); 623 } 624 } 625 } 626 627 /** 628 * Returns the currently configured night mode. 629 * <p> 630 * May be one of: 631 * <ul> 632 * <li>{@link #MODE_NIGHT_NO}</li> 633 * <li>{@link #MODE_NIGHT_YES}</li> 634 * <li>{@link #MODE_NIGHT_AUTO}</li> 635 * <li>{@link #MODE_NIGHT_CUSTOM}</li> 636 * <li>{@code -1} on error</li> 637 * </ul> 638 * 639 * @return the current night mode, or {@code -1} on error 640 * @see #setNightMode(int) 641 */ getNightMode()642 public @NightMode int getNightMode() { 643 if (mService != null) { 644 try { 645 return mService.getNightMode(); 646 } catch (RemoteException e) { 647 throw e.rethrowFromSystemServer(); 648 } 649 } 650 return -1; 651 } 652 653 /** 654 * @return If UI mode is locked or not. When UI mode is locked, calls to change UI mode 655 * like {@link #enableCarMode(int)} will silently fail. 656 * @hide 657 */ 658 @TestApi isUiModeLocked()659 public boolean isUiModeLocked() { 660 if (mService != null) { 661 try { 662 return mService.isUiModeLocked(); 663 } catch (RemoteException e) { 664 throw e.rethrowFromSystemServer(); 665 } 666 } 667 return true; 668 } 669 670 /** 671 * Returns whether night mode is locked or not. 672 * <p> 673 * When night mode is locked, only privileged system components may change 674 * night mode and calls from non-privileged applications to change night 675 * mode will fail silently. 676 * 677 * @return {@code true} if night mode is locked or {@code false} otherwise 678 * @hide 679 */ 680 @TestApi isNightModeLocked()681 public boolean isNightModeLocked() { 682 if (mService != null) { 683 try { 684 return mService.isNightModeLocked(); 685 } catch (RemoteException e) { 686 throw e.rethrowFromSystemServer(); 687 } 688 } 689 return true; 690 } 691 692 /** 693 * [De]activating night mode for the current user if the current night mode is custom and the 694 * custom type matches {@code nightModeCustomType}. 695 * 696 * @param nightModeCustomType the specify type of custom mode 697 * @param active {@code true} to activate night mode. Otherwise, deactivate night mode 698 * @return {@code true} if night mode has successfully activated for the requested 699 * {@code nightModeCustomType}. 700 * @hide 701 */ 702 @SystemApi 703 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeActivatedForCustomMode(@ightModeCustomType int nightModeCustomType, boolean active)704 public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType, 705 boolean active) { 706 if (mService != null) { 707 try { 708 return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active); 709 } catch (RemoteException e) { 710 throw e.rethrowFromSystemServer(); 711 } 712 } 713 return false; 714 } 715 716 /** 717 * Activating night mode for the current user 718 * 719 * @return {@code true} if the change is successful 720 * @hide 721 */ 722 @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) setNightModeActivated(boolean active)723 public boolean setNightModeActivated(boolean active) { 724 if (mService != null) { 725 try { 726 return mService.setNightModeActivated(active); 727 } catch (RemoteException e) { 728 throw e.rethrowFromSystemServer(); 729 } 730 } 731 return false; 732 } 733 734 /** 735 * Returns the time of the day Dark theme activates 736 * <p> 737 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 738 * this time set to activate it automatically. 739 */ 740 @NonNull getCustomNightModeStart()741 public LocalTime getCustomNightModeStart() { 742 if (mService != null) { 743 try { 744 return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000); 745 } catch (RemoteException e) { 746 throw e.rethrowFromSystemServer(); 747 } 748 } 749 return LocalTime.MIDNIGHT; 750 } 751 752 /** 753 * Sets the time of the day Dark theme activates 754 * <p> 755 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 756 * this time set to activate it automatically 757 * @param time The time of the day Dark theme should activate 758 */ setCustomNightModeStart(@onNull LocalTime time)759 public void setCustomNightModeStart(@NonNull LocalTime time) { 760 if (mService != null) { 761 try { 762 mService.setCustomNightModeStart(time.toNanoOfDay() / 1000); 763 } catch (RemoteException e) { 764 throw e.rethrowFromSystemServer(); 765 } 766 } 767 } 768 769 /** 770 * Returns the time of the day Dark theme deactivates 771 * <p> 772 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 773 * this time set to deactivate it automatically. 774 */ 775 @NonNull getCustomNightModeEnd()776 public LocalTime getCustomNightModeEnd() { 777 if (mService != null) { 778 try { 779 return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000); 780 } catch (RemoteException e) { 781 throw e.rethrowFromSystemServer(); 782 } 783 } 784 return LocalTime.MIDNIGHT; 785 } 786 787 /** 788 * Sets the time of the day Dark theme deactivates 789 * <p> 790 * When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses 791 * this time set to deactivate it automatically. 792 * @param time The time of the day Dark theme should deactivate 793 */ setCustomNightModeEnd(@onNull LocalTime time)794 public void setCustomNightModeEnd(@NonNull LocalTime time) { 795 if (mService != null) { 796 try { 797 mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000); 798 } catch (RemoteException e) { 799 throw e.rethrowFromSystemServer(); 800 } 801 } 802 } 803 804 /** 805 * Indicates no projection type. Can be used to compare with the {@link ProjectionType} in 806 * {@link OnProjectionStateChangedListener#onProjectionStateChanged(int, Set)}. 807 * 808 * @hide 809 */ 810 @SystemApi 811 @TestApi 812 public static final int PROJECTION_TYPE_NONE = 0x0000; 813 /** 814 * Automotive projection prevents degradation of GPS to save battery, routes incoming calls to 815 * the automotive role holder, etc. For use with {@link #requestProjection(int)} and 816 * {@link #clearProjectionState(int)}. 817 * 818 * @hide 819 */ 820 @SystemApi 821 @TestApi 822 public static final int PROJECTION_TYPE_AUTOMOTIVE = 0x0001; 823 /** 824 * Indicates all projection types. For use with 825 * {@link #addOnProjectionStateChangedListener(int, Executor, OnProjectionStateChangedListener)} 826 * and {@link #getProjectingPackages(int)}. 827 * 828 * @hide 829 */ 830 @SystemApi 831 @TestApi 832 public static final int PROJECTION_TYPE_ALL = -1; // All bits on 833 834 /** @hide */ 835 @IntDef(prefix = {"PROJECTION_TYPE_"}, value = { 836 PROJECTION_TYPE_NONE, 837 PROJECTION_TYPE_AUTOMOTIVE, 838 PROJECTION_TYPE_ALL, 839 }) 840 @Retention(RetentionPolicy.SOURCE) 841 public @interface ProjectionType { 842 } 843 844 /** 845 * Sets the given {@link ProjectionType}. 846 * 847 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 848 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 849 * @param projectionType the type of projection to request. This must be a single 850 * {@link ProjectionType} and cannot be a bitmask. 851 * @return true if the projection was successfully set 852 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 853 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 854 * 855 * @hide 856 */ 857 @SystemApi 858 @TestApi 859 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 860 conditional = true) requestProjection(@rojectionType int projectionType)861 public boolean requestProjection(@ProjectionType int projectionType) { 862 if (mService != null) { 863 try { 864 return mService.requestProjection(new Binder(), projectionType, 865 mContext.getOpPackageName()); 866 } catch (RemoteException e) { 867 throw e.rethrowFromSystemServer(); 868 } 869 } 870 return false; 871 } 872 873 /** 874 * Releases the given {@link ProjectionType}. 875 * 876 * Caller must have {@link android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION} if 877 * argument is {@link #PROJECTION_TYPE_AUTOMOTIVE}. 878 * @param projectionType the type of projection to release. This must be a single 879 * {@link ProjectionType} and cannot be a bitmask. 880 * @return true if the package had set projection and it was successfully released 881 * @throws IllegalArgumentException if passed {@link #PROJECTION_TYPE_NONE}, 882 * {@link #PROJECTION_TYPE_ALL}, or any combination of more than one {@link ProjectionType}. 883 * 884 * @hide 885 */ 886 @SystemApi 887 @TestApi 888 @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, 889 conditional = true) releaseProjection(@rojectionType int projectionType)890 public boolean releaseProjection(@ProjectionType int projectionType) { 891 if (mService != null) { 892 try { 893 return mService.releaseProjection(projectionType, mContext.getOpPackageName()); 894 } catch (RemoteException e) { 895 throw e.rethrowFromSystemServer(); 896 } 897 } 898 return false; 899 } 900 901 /** 902 * Gets the packages that are currently projecting. 903 * 904 * @param projectionType the {@link ProjectionType}s to consider when computing which packages 905 * are projecting. Use {@link #PROJECTION_TYPE_ALL} to get all projecting 906 * packages. 907 * 908 * @hide 909 */ 910 @SystemApi 911 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) 912 @NonNull getProjectingPackages(@rojectionType int projectionType)913 public Set<String> getProjectingPackages(@ProjectionType int projectionType) { 914 if (mService != null) { 915 try { 916 return new ArraySet<>(mService.getProjectingPackages(projectionType)); 917 } catch (RemoteException e) { 918 throw e.rethrowFromSystemServer(); 919 } 920 } 921 return Set.of(); 922 } 923 924 /** 925 * Gets the {@link ProjectionType}s that are currently active. 926 * 927 * @hide 928 */ 929 @SystemApi 930 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) getActiveProjectionTypes()931 public @ProjectionType int getActiveProjectionTypes() { 932 if (mService != null) { 933 try { 934 return mService.getActiveProjectionTypes(); 935 } catch (RemoteException e) { 936 throw e.rethrowFromSystemServer(); 937 } 938 } 939 return PROJECTION_TYPE_NONE; 940 } 941 942 /** 943 * Configures the listener to receive callbacks when the packages projecting using the given 944 * {@link ProjectionType}s change. 945 * 946 * @param projectionType one or more {@link ProjectionType}s to listen for changes regarding 947 * @param executor an {@link Executor} on which to invoke the callbacks 948 * @param listener the {@link OnProjectionStateChangedListener} to add 949 * 950 * @hide 951 */ 952 @SystemApi 953 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) addOnProjectionStateChangedListener(@rojectionType int projectionType, @NonNull @CallbackExecutor Executor executor, @NonNull OnProjectionStateChangedListener listener)954 public void addOnProjectionStateChangedListener(@ProjectionType int projectionType, 955 @NonNull @CallbackExecutor Executor executor, 956 @NonNull OnProjectionStateChangedListener listener) { 957 synchronized (mLock) { 958 if (mProjectionStateListenerMap.containsKey(listener)) { 959 Slog.i(TAG, "Attempted to add listener that was already added."); 960 return; 961 } 962 if (mService != null) { 963 InnerListener innerListener = new InnerListener(executor, listener, 964 mOnProjectionStateChangedListenerResourceManager); 965 try { 966 mService.addOnProjectionStateChangedListener(innerListener, projectionType); 967 mProjectionStateListenerMap.put(listener, innerListener); 968 } catch (RemoteException e) { 969 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 970 throw e.rethrowFromSystemServer(); 971 } 972 } 973 } 974 } 975 976 /** 977 * Removes the listener so it stops receiving updates for all {@link ProjectionType}s. 978 * 979 * @param listener the {@link OnProjectionStateChangedListener} to remove 980 * 981 * @hide 982 */ 983 @SystemApi 984 @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) removeOnProjectionStateChangedListener( @onNull OnProjectionStateChangedListener listener)985 public void removeOnProjectionStateChangedListener( 986 @NonNull OnProjectionStateChangedListener listener) { 987 synchronized (mLock) { 988 InnerListener innerListener = mProjectionStateListenerMap.get(listener); 989 if (innerListener == null) { 990 Slog.i(TAG, "Attempted to remove listener that was not added."); 991 return; 992 } 993 if (mService != null) { 994 try { 995 mService.removeOnProjectionStateChangedListener(innerListener); 996 } catch (RemoteException e) { 997 throw e.rethrowFromSystemServer(); 998 } 999 } 1000 mProjectionStateListenerMap.remove(listener); 1001 mOnProjectionStateChangedListenerResourceManager.remove(innerListener); 1002 } 1003 } 1004 1005 private static class InnerListener extends IOnProjectionStateChangedListener.Stub { 1006 private final WeakReference<OnProjectionStateChangedListenerResourceManager> 1007 mResourceManager; 1008 InnerListener(@onNull Executor executor, @NonNull OnProjectionStateChangedListener outerListener, @NonNull OnProjectionStateChangedListenerResourceManager resourceManager)1009 private InnerListener(@NonNull Executor executor, 1010 @NonNull OnProjectionStateChangedListener outerListener, 1011 @NonNull OnProjectionStateChangedListenerResourceManager resourceManager) { 1012 resourceManager.put(this, executor, outerListener); 1013 mResourceManager = new WeakReference<>(resourceManager); 1014 } 1015 1016 @Override onProjectionStateChanged(int activeProjectionTypes, List<String> projectingPackages)1017 public void onProjectionStateChanged(int activeProjectionTypes, 1018 List<String> projectingPackages) { 1019 OnProjectionStateChangedListenerResourceManager resourceManager = 1020 mResourceManager.get(); 1021 if (resourceManager == null) { 1022 Slog.w(TAG, "Can't execute onProjectionStateChanged, resource manager is gone."); 1023 return; 1024 } 1025 1026 OnProjectionStateChangedListener outerListener = resourceManager.getOuterListener(this); 1027 Executor executor = resourceManager.getExecutor(this); 1028 if (outerListener == null || executor == null) { 1029 Slog.w(TAG, "Can't execute onProjectionStatechanged, references are null."); 1030 return; 1031 } 1032 1033 executor.execute(PooledLambda.obtainRunnable( 1034 OnProjectionStateChangedListener::onProjectionStateChanged, 1035 outerListener, 1036 activeProjectionTypes, 1037 new ArraySet<>(projectingPackages)).recycleOnUse()); 1038 } 1039 } 1040 1041 /** 1042 * Wrapper class that ensures we don't leak {@link Activity} or other large {@link Context} in 1043 * which this {@link UiModeManager} resides if/when it ends without unregistering associated 1044 * callback objects. 1045 */ 1046 private static class OnProjectionStateChangedListenerResourceManager { 1047 private final Map<InnerListener, OnProjectionStateChangedListener> mOuterListenerMap = 1048 new ArrayMap<>(1); 1049 private final Map<InnerListener, Executor> mExecutorMap = new ArrayMap<>(1); 1050 put(@onNull InnerListener innerListener, @NonNull Executor executor, OnProjectionStateChangedListener outerListener)1051 void put(@NonNull InnerListener innerListener, @NonNull Executor executor, 1052 OnProjectionStateChangedListener outerListener) { 1053 mOuterListenerMap.put(innerListener, outerListener); 1054 mExecutorMap.put(innerListener, executor); 1055 } 1056 remove(InnerListener innerListener)1057 void remove(InnerListener innerListener) { 1058 mOuterListenerMap.remove(innerListener); 1059 mExecutorMap.remove(innerListener); 1060 } 1061 getOuterListener(@onNull InnerListener innerListener)1062 OnProjectionStateChangedListener getOuterListener(@NonNull InnerListener innerListener) { 1063 return mOuterListenerMap.get(innerListener); 1064 } 1065 getExecutor(@onNull InnerListener innerListener)1066 Executor getExecutor(@NonNull InnerListener innerListener) { 1067 return mExecutorMap.get(innerListener); 1068 } 1069 } 1070 } 1071