• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> &nbsp; 0 corresponds to the default contrast </li>
1514      *     <li>       -1 corresponds to the minimum contrast </li>
1515      *     <li> &nbsp; 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