• 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 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