1 /*
2  * Copyright (C) 2008 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 com.android.server;
18 
19 import static android.app.Flags.modesApi;
20 import static android.app.Flags.enableNightModeBinderCache;
21 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
22 import static android.app.UiModeManager.DEFAULT_PRIORITY;
23 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
24 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
25 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
26 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
27 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE;
28 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
29 import static android.app.UiModeManager.MODE_NIGHT_NO;
30 import static android.app.UiModeManager.MODE_NIGHT_YES;
31 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
32 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
33 import static android.os.UserHandle.USER_SYSTEM;
34 import static android.os.UserHandle.getCallingUserId;
35 import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
36 import static android.provider.Settings.Secure.CONTRAST_LEVEL;
37 import static android.util.TimeUtils.isTimeBetween;
38 
39 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
40 
41 import android.annotation.IntRange;
42 import android.annotation.NonNull;
43 import android.annotation.Nullable;
44 import android.app.Activity;
45 import android.app.ActivityManager;
46 import android.app.ActivityTaskManager;
47 import android.app.AlarmManager;
48 import android.app.IOnProjectionStateChangedListener;
49 import android.app.IUiModeManager;
50 import android.app.IUiModeManagerCallback;
51 import android.app.KeyguardManager;
52 import android.app.Notification;
53 import android.app.NotificationManager;
54 import android.app.PendingIntent;
55 import android.app.StatusBarManager;
56 import android.app.UiModeManager;
57 import android.app.UiModeManager.AttentionModeThemeOverlayType;
58 import android.app.UiModeManager.NightModeCustomReturnType;
59 import android.app.UiModeManager.NightModeCustomType;
60 import android.content.BroadcastReceiver;
61 import android.content.Context;
62 import android.content.Intent;
63 import android.content.IntentFilter;
64 import android.content.pm.PackageManager;
65 import android.content.res.Configuration;
66 import android.content.res.Resources;
67 import android.database.ContentObserver;
68 import android.net.Uri;
69 import android.os.BatteryManager;
70 import android.os.Binder;
71 import android.os.Handler;
72 import android.os.IBinder;
73 import android.os.PermissionEnforcer;
74 import android.os.PowerManager;
75 import android.os.PowerManager.ServiceType;
76 import android.os.PowerManagerInternal;
77 import android.os.Process;
78 import android.os.RemoteCallbackList;
79 import android.os.RemoteException;
80 import android.os.ResultReceiver;
81 import android.os.ServiceManager;
82 import android.os.ShellCallback;
83 import android.os.ShellCommand;
84 import android.os.SystemProperties;
85 import android.os.UserHandle;
86 import android.provider.Settings;
87 import android.provider.Settings.Secure;
88 import android.service.dreams.DreamManagerInternal;
89 import android.service.dreams.Sandman;
90 import android.service.vr.IVrManager;
91 import android.service.vr.IVrStateCallbacks;
92 import android.util.ArraySet;
93 import android.util.Slog;
94 import android.util.SparseArray;
95 
96 import com.android.internal.R;
97 import com.android.internal.annotations.GuardedBy;
98 import com.android.internal.annotations.VisibleForTesting;
99 import com.android.internal.app.DisableCarModeActivity;
100 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
101 import com.android.internal.notification.SystemNotificationChannels;
102 import com.android.internal.util.DumpUtils;
103 import com.android.server.pm.UserManagerService;
104 import com.android.server.twilight.TwilightListener;
105 import com.android.server.twilight.TwilightManager;
106 import com.android.server.twilight.TwilightState;
107 import com.android.server.wm.ActivityTaskManagerInternal;
108 import com.android.server.wm.WindowManagerInternal;
109 
110 import java.io.FileDescriptor;
111 import java.io.PrintWriter;
112 import java.time.DateTimeException;
113 import java.time.LocalDateTime;
114 import java.time.LocalTime;
115 import java.time.ZoneId;
116 import java.util.ArrayList;
117 import java.util.Arrays;
118 import java.util.Collection;
119 import java.util.HashMap;
120 import java.util.List;
121 import java.util.Map;
122 import java.util.Set;
123 
124 final class UiModeManagerService extends SystemService {
125     private static final String TAG = UiModeManager.class.getSimpleName();
126     private static final boolean LOG = false;
127 
128     // Enable launching of applications when entering the dock.
129     private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
130     private static final String SYSTEM_PROPERTY_DEVICE_THEME = "persist.sys.theme";
131     @VisibleForTesting
132     public static final Set<Integer> SUPPORTED_NIGHT_MODE_CUSTOM_TYPES = new ArraySet(
133             new Integer[]{MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, MODE_NIGHT_CUSTOM_TYPE_BEDTIME});
134 
135     private final Injector mInjector;
136     private final Object mLock = new Object();
137 
138     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
139 
140     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
141 
142     private final NightMode mNightMode = new NightMode(){
143         private int mNightModeValue = UiModeManager.MODE_NIGHT_NO;
144 
145         @Override
146         public int get() {
147             return mNightModeValue;
148         }
149 
150         @Override
151         public void set(int mode) {
152             mNightModeValue = mode;
153             if (enableNightModeBinderCache()) {
154                 UiModeManager.invalidateNightModeCache();
155             }
156         }
157     };
158     private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
159     private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
160     private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
161     private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
162     private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
163     private LocalTime mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME;
164 
165     private Map<Integer, String> mCarModePackagePriority = new HashMap<>();
166     private boolean mCarModeEnabled = false;
167     private boolean mCharging = false;
168     private boolean mPowerSave = false;
169     // Do not change configuration now. wait until the device is inactive (eg screen off, dreaming)
170     // This prevents jank and activity restart when the user
171     // is actively using the device
172     private boolean mWaitForDeviceInactive = false;
173     private int mDefaultUiModeType;
174     private boolean mCarModeKeepsScreenOn;
175     private boolean mDeskModeKeepsScreenOn;
176     private boolean mTelevision;
177     private boolean mCar;
178     private boolean mWatch;
179     private boolean mVrHeadset;
180     private boolean mComputedNightMode;
181     private boolean mLastBedtimeRequestedNightMode = false;
182     private int mCarModeEnableFlags;
183     private boolean mSetupWizardComplete;
184 
185     // flag set by resource, whether to start dream immediately upon docking even if unlocked.
186     private boolean mStartDreamImmediatelyOnDock = true;
187     // flag set by resource, whether to disable dreams when ambient mode suppression is enabled.
188     private boolean mDreamsDisabledByAmbientModeSuppression = false;
189     // flag set by resource, whether to enable Car dock launch when starting car mode.
190     private boolean mEnableCarDockLaunch = true;
191     // flag set by resource, whether to lock UI mode to the default one or not.
192     private boolean mUiModeLocked = false;
193     // flag set by resource, whether to night mode change for normal all or not.
194     private boolean mNightModeLocked = false;
195 
196     int mCurUiMode = 0;
197     private int mSetUiMode = 0;
198     private boolean mHoldingConfiguration = false;
199     private int mCurrentUser;
200 
201     private Configuration mConfiguration = new Configuration();
202     boolean mSystemReady;
203 
204     private final Handler mHandler = new Handler();
205 
206     private TwilightManager mTwilightManager;
207     private NotificationManager mNotificationManager;
208     private StatusBarManager mStatusBarManager;
209     private WindowManagerInternal mWindowManager;
210     private ActivityTaskManagerInternal mActivityTaskManager;
211     private AlarmManager mAlarmManager;
212     private PowerManager mPowerManager;
213     private KeyguardManager mKeyguardManager;
214 
215     // In automatic scheduling, the user is able
216     // to override the computed night mode until the two match
217     // Example: Activate dark mode in the day time until sunrise the next day
218     private boolean mOverrideNightModeOn;
219     private boolean mOverrideNightModeOff;
220     private int mOverrideNightModeUser = USER_SYSTEM;
221 
222     private PowerManager.WakeLock mWakeLock;
223 
224     private final LocalService mLocalService = new LocalService();
225     private PowerManagerInternal mLocalPowerManager;
226     private DreamManagerInternal mDreamManagerInternal;
227 
228     private final IUiModeManager.Stub mService;
229 
230     @GuardedBy("mLock")
231     private final SparseArray<RemoteCallbackList<IUiModeManagerCallback>> mUiModeManagerCallbacks =
232             new SparseArray<>();
233 
234     @GuardedBy("mLock")
235     @Nullable
236     private SparseArray<List<ProjectionHolder>> mProjectionHolders;
237     @GuardedBy("mLock")
238     @Nullable
239     private SparseArray<RemoteCallbackList<IOnProjectionStateChangedListener>> mProjectionListeners;
240 
241     @GuardedBy("mLock")
242     private final SparseArray<Float> mContrasts = new SparseArray<>();
243 
UiModeManagerService(Context context)244     public UiModeManagerService(Context context) {
245         this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
246     }
247 
248     @VisibleForTesting
UiModeManagerService(Context context, boolean setupWizardComplete, TwilightManager tm, Injector injector)249     protected UiModeManagerService(Context context, boolean setupWizardComplete,
250             TwilightManager tm, Injector injector) {
251         super(context);
252         mService = new Stub(context);
253         mConfiguration.setToDefaults();
254         mSetupWizardComplete = setupWizardComplete;
255         mTwilightManager = tm;
256         mInjector = injector;
257     }
258 
buildHomeIntent(String category)259     private static Intent buildHomeIntent(String category) {
260         Intent intent = new Intent(Intent.ACTION_MAIN);
261         intent.addCategory(category);
262         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
263                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
264         return intent;
265     }
266 
267     // The broadcast receiver which receives the result of the ordered broadcast sent when
268     // the dock state changes. The original ordered broadcast is sent with an initial result
269     // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
270     // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
271     private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
272         @Override
273         public void onReceive(Context context, Intent intent) {
274             if (getResultCode() != Activity.RESULT_OK) {
275                 if (LOG) {
276                     Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
277                             + ": canceled: " + getResultCode());
278                 }
279                 return;
280             }
281 
282             final int enableFlags = intent.getIntExtra("enableFlags", 0);
283             final int disableFlags = intent.getIntExtra("disableFlags", 0);
284             synchronized (mLock) {
285                 updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
286             }
287         }
288     };
289 
290     private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
291         @Override
292         public void onReceive(Context context, Intent intent) {
293             int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
294                     Intent.EXTRA_DOCK_STATE_UNDOCKED);
295             updateDockState(state);
296         }
297     };
298 
299     private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
300         @Override
301         public void onReceive(Context context, Intent intent) {
302             switch (intent.getAction()) {
303                 case Intent.ACTION_BATTERY_CHANGED:
304                     mCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
305                     break;
306             }
307             synchronized (mLock) {
308                 if (mSystemReady) {
309                     updateLocked(0, 0);
310                 }
311             }
312         }
313     };
314 
315     private final TwilightListener mTwilightListener = new TwilightListener() {
316         @Override
317         public void onTwilightStateChanged(@Nullable TwilightState state) {
318             synchronized (mLock) {
319                 if (mNightMode.get() == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) {
320                     if (shouldApplyAutomaticChangesImmediately()) {
321                         updateLocked(0, 0);
322                     } else {
323                         registerDeviceInactiveListenerLocked();
324                     }
325                 }
326             }
327         }
328     };
329 
330     /**
331      * DO NOT USE DIRECTLY
332      * see register registerScreenOffEvent and unregisterScreenOffEvent
333      */
334     private final BroadcastReceiver mDeviceInactiveListener = new BroadcastReceiver() {
335         @Override
336         public void onReceive(Context context, Intent intent) {
337             synchronized (mLock) {
338                 // must unregister first before updating
339                 unregisterDeviceInactiveListenerLocked();
340                 updateLocked(0, 0);
341             }
342         }
343     };
344 
345     private final BroadcastReceiver mOnTimeChangedHandler = new BroadcastReceiver() {
346         @Override
347         public void onReceive(Context context, Intent intent) {
348             synchronized (mLock) {
349                 updateCustomTimeLocked();
350             }
351         }
352     };
353 
354     private final AlarmManager.OnAlarmListener mCustomTimeListener = () -> {
355         synchronized (mLock) {
356             updateCustomTimeLocked();
357         }
358     };
359 
360     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
361         @Override
362         public void onVrStateChanged(boolean enabled) {
363             synchronized (mLock) {
364                 mVrHeadset = enabled;
365                 if (mSystemReady) {
366                     updateLocked(0, 0);
367                 }
368             }
369         }
370     };
371 
372     private final ContentObserver mSetupWizardObserver = new ContentObserver(mHandler) {
373         @Override
374         public void onChange(boolean selfChange, Uri uri) {
375             synchronized (mLock) {
376                 // setup wizard is done now so we can unblock
377                 if (setupWizardCompleteForCurrentUser() && !selfChange) {
378                     mSetupWizardComplete = true;
379                     getContext().getContentResolver()
380                             .unregisterContentObserver(mSetupWizardObserver);
381                     // update night mode
382                     Context context = getContext();
383                     updateNightModeFromSettingsLocked(context, context.getResources(),
384                             UserHandle.getCallingUserId());
385                     updateLocked(0, 0);
386                 }
387             }
388         }
389     };
390 
391     private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) {
392         @Override
393         public void onChange(boolean selfChange, Uri uri) {
394             updateSystemProperties();
395         }
396     };
397 
398     private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
399         @Override
400         public void onChange(boolean selfChange, Uri uri) {
401             synchronized (mLock) {
402                 if (updateContrastLocked()) {
403                     float contrast = getContrastLocked();
404                     mUiModeManagerCallbacks.get(mCurrentUser, new RemoteCallbackList<>())
405                             .broadcast(ignoreRemoteException(
406                                     callback -> callback.notifyContrastChanged(contrast)));
407                 }
408             }
409         }
410     };
411 
updateSystemProperties()412     private void updateSystemProperties() {
413         int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
414                 mNightMode.get(), 0);
415         if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
416             mode = MODE_NIGHT_YES;
417         }
418         SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
419     }
420 
421     @VisibleForTesting
setStartDreamImmediatelyOnDock(boolean startDreamImmediatelyOnDock)422     void setStartDreamImmediatelyOnDock(boolean startDreamImmediatelyOnDock) {
423         mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;
424     }
425 
426     @VisibleForTesting
setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression)427     void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) {
428         mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression;
429     }
430 
431     @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)432     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
433         mCurrentUser = to.getUserIdentifier();
434         if (mNightMode.get() == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
435         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
436         verifySetupWizardCompleted();
437         synchronized (mLock) {
438             updateNightModeFromSettingsLocked(getContext(), getContext().getResources(),
439                     to.getUserIdentifier());
440             updateLocked(0, 0);
441         }
442     }
443 
444     @Override
onBootPhase(int phase)445     public void onBootPhase(int phase) {
446         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
447             synchronized (mLock) {
448                 final Context context = getContext();
449                 mSystemReady = true;
450                 mKeyguardManager = context.getSystemService(KeyguardManager.class);
451                 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
452                 mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
453                 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
454                 mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
455                 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
456                 TwilightManager twilightManager = getLocalService(TwilightManager.class);
457                 if (twilightManager != null) mTwilightManager = twilightManager;
458                 mLocalPowerManager =
459                         LocalServices.getService(PowerManagerInternal.class);
460                 mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
461                 initPowerSave();
462                 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
463                 registerVrStateListener();
464                 // register listeners
465                 context.getContentResolver()
466                         .registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
467                                 false, mDarkThemeObserver, 0);
468                 context.getContentResolver().registerContentObserver(
469                         Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
470                         mContrastObserver, UserHandle.USER_ALL);
471                 context.registerReceiver(mDockModeReceiver,
472                         new IntentFilter(Intent.ACTION_DOCK_EVENT));
473                 IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
474                 context.registerReceiver(mBatteryReceiver, batteryFilter);
475                 context.registerReceiver(mSettingsRestored,
476                         new IntentFilter(Intent.ACTION_SETTING_RESTORED));
477                 context.registerReceiver(mOnShutdown,
478                         new IntentFilter(Intent.ACTION_SHUTDOWN));
479                 updateConfigurationLocked();
480                 applyConfigurationExternallyLocked();
481             }
482         }
483     }
484 
485     @Override
onStart()486     public void onStart() {
487         final Context context = getContext();
488         // If setup isn't complete for this user listen for completion so we can unblock
489         // being able to send a night mode configuration change event
490         verifySetupWizardCompleted();
491 
492         final Resources res = context.getResources();
493         mNightMode.set(res.getInteger(
494                 com.android.internal.R.integer.config_defaultNightMode));
495         mStartDreamImmediatelyOnDock = res.getBoolean(
496                 com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
497         mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
498                 com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
499         mDefaultUiModeType = res.getInteger(
500                 com.android.internal.R.integer.config_defaultUiModeType);
501         mCarModeKeepsScreenOn = (res.getInteger(
502                 com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
503         mDeskModeKeepsScreenOn = (res.getInteger(
504                 com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
505         mEnableCarDockLaunch = res.getBoolean(
506                 com.android.internal.R.bool.config_enableCarDockHomeLaunch);
507         mUiModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockUiMode);
508         mNightModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockDayNightMode);
509         final PackageManager pm = context.getPackageManager();
510         mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
511                 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
512         mCar = pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
513         mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
514 
515         // Update the initial, static configurations.
516         SystemServerInitThreadPool.submit(() -> {
517             synchronized (mLock) {
518                 TwilightManager twilightManager = getLocalService(TwilightManager.class);
519                 if (twilightManager != null) mTwilightManager = twilightManager;
520                 updateNightModeFromSettingsLocked(context, res, UserHandle.getCallingUserId());
521                 updateSystemProperties();
522             }
523 
524         }, TAG + ".onStart");
525         publishBinderService(Context.UI_MODE_SERVICE, mService);
526         publishLocalService(UiModeManagerInternal.class, mLocalService);
527     }
528 
529     private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
530         @Override
531         public void onReceive(Context context, Intent intent) {
532             if (mNightMode.get() == MODE_NIGHT_AUTO) {
533                 persistComputedNightMode(mCurrentUser);
534             }
535         }
536     };
537 
persistComputedNightMode(int userId)538     private void persistComputedNightMode(int userId) {
539         Secure.putIntForUser(getContext().getContentResolver(),
540                 Secure.UI_NIGHT_MODE_LAST_COMPUTED, mComputedNightMode ? 1 : 0,
541                 userId);
542     }
543 
544     private final BroadcastReceiver mSettingsRestored = new BroadcastReceiver() {
545         @Override
546         public void onReceive(Context context, Intent intent) {
547             List<String> settings = Arrays.asList(
548                     Secure.UI_NIGHT_MODE, Secure.DARK_THEME_CUSTOM_START_TIME,
549                     Secure.DARK_THEME_CUSTOM_END_TIME);
550             if (settings.contains(intent.getExtras().getCharSequence(Intent.EXTRA_SETTING_NAME))) {
551                 synchronized (mLock) {
552                     updateNightModeFromSettingsLocked(context, context.getResources(),
553                             UserHandle.getCallingUserId());
554                     updateConfigurationLocked();
555                 }
556             }
557         }
558     };
559 
initPowerSave()560     private void initPowerSave() {
561         mPowerSave =
562                 mLocalPowerManager.getLowPowerState(ServiceType.NIGHT_MODE)
563                         .batterySaverEnabled;
564         mLocalPowerManager.registerLowPowerModeObserver(ServiceType.NIGHT_MODE, state -> {
565             synchronized (mLock) {
566                 if (mPowerSave == state.batterySaverEnabled) {
567                     return;
568                 }
569                 mPowerSave = state.batterySaverEnabled;
570                 if (mSystemReady) {
571                     updateLocked(0, 0);
572                 }
573             }
574         });
575     }
576 
577     @VisibleForTesting
getService()578     protected IUiModeManager getService() {
579         return mService;
580     }
581 
582     @VisibleForTesting
getConfiguration()583     protected Configuration getConfiguration() {
584         return mConfiguration;
585     }
586 
587     // Records whether setup wizard has happened or not and adds an observer for this user if not.
verifySetupWizardCompleted()588     private void verifySetupWizardCompleted() {
589         final Context context = getContext();
590         final int userId = UserHandle.getCallingUserId();
591         if (!setupWizardCompleteForCurrentUser()) {
592             mSetupWizardComplete = false;
593             context.getContentResolver().registerContentObserver(
594                     Secure.getUriFor(
595                             Secure.USER_SETUP_COMPLETE), false, mSetupWizardObserver, userId);
596         } else {
597             mSetupWizardComplete = true;
598         }
599     }
600 
setupWizardCompleteForCurrentUser()601     private boolean setupWizardCompleteForCurrentUser() {
602         return Secure.getIntForUser(getContext().getContentResolver(),
603                 Secure.USER_SETUP_COMPLETE, 0, UserHandle.getCallingUserId()) == 1;
604     }
605 
updateCustomTimeLocked()606     private void updateCustomTimeLocked() {
607         if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
608         if (shouldApplyAutomaticChangesImmediately()) {
609             updateLocked(0, 0);
610         } else {
611             registerDeviceInactiveListenerLocked();
612         }
613         scheduleNextCustomTimeListener();
614     }
615 
616     /**
617      * Updates the night mode setting in Settings.Secure
618      *
619      * @param context A valid context
620      * @param res     A valid resource object
621      * @param userId  The user to update the setting for
622      */
updateNightModeFromSettingsLocked(Context context, Resources res, int userId)623     private void updateNightModeFromSettingsLocked(Context context, Resources res, int userId) {
624         if (mCarModeEnabled || mCar) {
625             return;
626         }
627         if (mSetupWizardComplete) {
628             mNightMode.set(Secure.getIntForUser(context.getContentResolver(),
629                     Secure.UI_NIGHT_MODE, res.getInteger(
630                             com.android.internal.R.integer.config_defaultNightMode), userId));
631             mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
632                     Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
633                     mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
634                     Secure.UI_NIGHT_MODE_OVERRIDE_ON, 0, userId) != 0;
635             mOverrideNightModeOff = Secure.getIntForUser(context.getContentResolver(),
636                     Secure.UI_NIGHT_MODE_OVERRIDE_OFF, 0, userId) != 0;
637             mCustomAutoNightModeStartMilliseconds = LocalTime.ofNanoOfDay(
638                     Secure.getLongForUser(context.getContentResolver(),
639                             Secure.DARK_THEME_CUSTOM_START_TIME,
640                             DEFAULT_CUSTOM_NIGHT_START_TIME.toNanoOfDay() / 1000L, userId) * 1000);
641             mCustomAutoNightModeEndMilliseconds = LocalTime.ofNanoOfDay(
642                     Secure.getLongForUser(context.getContentResolver(),
643                             Secure.DARK_THEME_CUSTOM_END_TIME,
644                             DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
645             if (mNightMode.get() == MODE_NIGHT_AUTO) {
646                 mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
647                         Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
648             }
649         }
650     }
651 
toMilliSeconds(LocalTime t)652     private static long toMilliSeconds(LocalTime t) {
653         return t.toNanoOfDay() / 1000;
654     }
655 
fromMilliseconds(long t)656     private static LocalTime fromMilliseconds(long t) {
657         return LocalTime.ofNanoOfDay(t * 1000);
658     }
659 
registerDeviceInactiveListenerLocked()660     private void registerDeviceInactiveListenerLocked() {
661         if (mPowerSave) return;
662         mWaitForDeviceInactive = true;
663         final IntentFilter intentFilter =
664                 new IntentFilter(Intent.ACTION_SCREEN_OFF);
665         intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
666         getContext().registerReceiver(mDeviceInactiveListener, intentFilter);
667     }
668 
cancelCustomAlarm()669     private void cancelCustomAlarm() {
670         mAlarmManager.cancel(mCustomTimeListener);
671     }
672 
unregisterDeviceInactiveListenerLocked()673     private void unregisterDeviceInactiveListenerLocked() {
674         mWaitForDeviceInactive = false;
675         try {
676             getContext().unregisterReceiver(mDeviceInactiveListener);
677         } catch (IllegalArgumentException e) {
678             // we ignore this exception if the receiver is unregistered already.
679         }
680     }
681 
registerTimeChangeEvent()682     private void registerTimeChangeEvent() {
683         final IntentFilter intentFilter =
684                 new IntentFilter(Intent.ACTION_TIME_CHANGED);
685         intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
686         getContext().registerReceiver(mOnTimeChangedHandler, intentFilter);
687     }
688 
unregisterTimeChangeEvent()689     private void unregisterTimeChangeEvent() {
690         try {
691             getContext().unregisterReceiver(mOnTimeChangedHandler);
692         } catch (IllegalArgumentException e) {
693             // we ignore this exception if the receiver is unregistered already.
694         }
695     }
696 
697     private final class Stub extends IUiModeManager.Stub {
Stub(Context context)698         Stub(Context context) {
699             super(PermissionEnforcer.fromContext(context));
700         }
701 
702         @Override
addCallback(IUiModeManagerCallback callback)703         public void addCallback(IUiModeManagerCallback callback) {
704             int userId = getCallingUserId();
705             synchronized (mLock) {
706                 if (!mUiModeManagerCallbacks.contains(userId)) {
707                     mUiModeManagerCallbacks.put(userId, new RemoteCallbackList<>());
708                 }
709                 mUiModeManagerCallbacks.get(userId).register(callback);
710             }
711         }
712 
713         @Override
enableCarMode(@iModeManager.EnableCarMode int flags, @IntRange(from = 0) int priority, String callingPackage)714         public void enableCarMode(@UiModeManager.EnableCarMode int flags,
715                 @IntRange(from = 0) int priority, String callingPackage) {
716             if (isUiModeLocked()) {
717                 Slog.e(TAG, "enableCarMode while UI mode is locked");
718                 return;
719             }
720 
721             if (priority != UiModeManager.DEFAULT_PRIORITY
722                     && getContext().checkCallingOrSelfPermission(
723                     android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED)
724                     != PackageManager.PERMISSION_GRANTED) {
725                 throw new SecurityException("Enabling car mode with a priority requires "
726                         + "permission ENTER_CAR_MODE_PRIORITIZED");
727             }
728 
729             // Allow the user to enable car mode using the shell,
730             // e.g. 'adb shell cmd uimode car yes'
731             boolean isShellCaller = mInjector.getCallingUid() == Process.SHELL_UID;
732             if (!isShellCaller) {
733               assertLegit(callingPackage);
734             }
735 
736             final long ident = Binder.clearCallingIdentity();
737             try {
738                 synchronized (mLock) {
739                     setCarModeLocked(true, flags, priority, callingPackage);
740                     if (mSystemReady) {
741                         updateLocked(flags, 0);
742                     }
743                 }
744             } finally {
745                 Binder.restoreCallingIdentity(ident);
746             }
747         }
748 
749         /**
750          * This method is only kept around for the time being; the AIDL has an UnsupportedAppUsage
751          * tag which means this method is technically considered part of the greylist "API".
752          * @param flags
753          */
754         @Override
disableCarMode(@iModeManager.DisableCarMode int flags)755         public void disableCarMode(@UiModeManager.DisableCarMode int flags) {
756             disableCarModeByCallingPackage(flags, null /* callingPackage */);
757         }
758 
759         /**
760          * Handles requests to disable car mode.
761          * @param flags Disable car mode flags
762          * @param callingPackage
763          */
764         @Override
disableCarModeByCallingPackage(@iModeManager.DisableCarMode int flags, String callingPackage)765         public void disableCarModeByCallingPackage(@UiModeManager.DisableCarMode int flags,
766                 String callingPackage) {
767             if (isUiModeLocked()) {
768                 Slog.e(TAG, "disableCarMode while UI mode is locked");
769                 return;
770             }
771 
772             // If the caller is the system, we will allow the DISABLE_CAR_MODE_ALL_PRIORITIES car
773             // mode flag to be specified; this is so that the user can disable car mode at all
774             // priorities using the persistent notification.
775             //
776             // We also allow the user to disable car mode using the shell,
777             // e.g. 'adb shell cmd uimode car no'
778             int callingUid = mInjector.getCallingUid();
779             boolean isSystemCaller = callingUid == Process.SYSTEM_UID;
780             boolean isShellCaller = callingUid == Process.SHELL_UID;
781             if (!isSystemCaller && !isShellCaller) {
782                 assertLegit(callingPackage);
783             }
784             final int carModeFlags =
785                     isSystemCaller ? flags : flags & ~UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES;
786 
787             final long ident = Binder.clearCallingIdentity();
788             try {
789                 synchronized (mLock) {
790                     // Determine if the caller has enabled car mode at a priority other than the
791                     // default one.  If they have, then attempt to disable at that priority.
792                     int priority = mCarModePackagePriority.entrySet()
793                             .stream()
794                             .filter(e -> e.getValue().equals(callingPackage))
795                             .findFirst()
796                             .map(Map.Entry::getKey)
797                             .orElse(UiModeManager.DEFAULT_PRIORITY);
798 
799                     setCarModeLocked(false, carModeFlags, priority, callingPackage);
800                     if (mSystemReady) {
801                         updateLocked(0, flags);
802                     }
803                 }
804             } finally {
805                 Binder.restoreCallingIdentity(ident);
806             }
807         }
808 
809         @Override
getCurrentModeType()810         public int getCurrentModeType() {
811             final long ident = Binder.clearCallingIdentity();
812             try {
813                 synchronized (mLock) {
814                     return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
815                 }
816             } finally {
817                 Binder.restoreCallingIdentity(ident);
818             }
819         }
820 
821         @Override
setNightMode(int mode)822         public void setNightMode(int mode) {
823             // MODE_NIGHT_CUSTOM_TYPE_SCHEDULE is the default for MODE_NIGHT_CUSTOM.
824             int customModeType = mode == MODE_NIGHT_CUSTOM
825                     ? MODE_NIGHT_CUSTOM_TYPE_SCHEDULE
826                     : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
827             setNightModeInternal(mode, customModeType);
828         }
829 
setNightModeInternal(int mode, int customModeType)830         private void setNightModeInternal(int mode, int customModeType) {
831             if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
832                     android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
833                     != PackageManager.PERMISSION_GRANTED)) {
834                 Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
835                 return;
836             }
837             switch (mode) {
838                 case UiModeManager.MODE_NIGHT_NO:
839                 case UiModeManager.MODE_NIGHT_YES:
840                 case MODE_NIGHT_AUTO:
841                     break;
842                 case MODE_NIGHT_CUSTOM:
843                     if (SUPPORTED_NIGHT_MODE_CUSTOM_TYPES.contains(customModeType)) {
844                         break;
845                     }
846                     throw new IllegalArgumentException(
847                             "Can't set the custom type to " + customModeType);
848                 default:
849                     throw new IllegalArgumentException("Unknown mode: " + mode);
850             }
851 
852             final int user = UserHandle.getCallingUserId();
853             enforceValidCallingUser(user);
854 
855             final long ident = Binder.clearCallingIdentity();
856             try {
857                 synchronized (mLock) {
858                     if (mNightMode.get() != mode || mNightModeCustomType != customModeType) {
859                         if (mNightMode.get() == MODE_NIGHT_AUTO
860                                 || mNightMode.get() == MODE_NIGHT_CUSTOM) {
861                             unregisterDeviceInactiveListenerLocked();
862                             cancelCustomAlarm();
863                         }
864                         mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
865                                 ? customModeType
866                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
867                         mNightMode.set(mode);
868                         //deactivates AttentionMode if user toggles DarkTheme
869                         mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
870                         resetNightModeOverrideLocked();
871                         persistNightMode(user);
872                         // on screen off will update configuration instead
873                         if ((mNightMode.get() != MODE_NIGHT_AUTO
874                                 && mNightMode.get() != MODE_NIGHT_CUSTOM)
875                                 || shouldApplyAutomaticChangesImmediately()) {
876                             unregisterDeviceInactiveListenerLocked();
877                             updateLocked(0, 0);
878                         } else {
879                             registerDeviceInactiveListenerLocked();
880                         }
881                     }
882                 }
883             } finally {
884                 Binder.restoreCallingIdentity(ident);
885             }
886         }
887 
888         @Override
getNightMode()889         public int getNightMode() {
890             synchronized (mLock) {
891                 return mNightMode.get();
892             }
893         }
894 
895         @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
896         @Override
setNightModeCustomType(@ightModeCustomType int nightModeCustomType)897         public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
898             setNightModeCustomType_enforcePermission();
899             setNightModeInternal(MODE_NIGHT_CUSTOM, nightModeCustomType);
900         }
901 
902         @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
903         @Override
getNightModeCustomType()904         public  @NightModeCustomReturnType int getNightModeCustomType() {
905             getNightModeCustomType_enforcePermission();
906             synchronized (mLock) {
907                 return mNightModeCustomType;
908             }
909         }
910 
911         @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
912         @Override
setAttentionModeThemeOverlay( @ttentionModeThemeOverlayType int attentionModeThemeOverlayType)913             public void setAttentionModeThemeOverlay(
914                 @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
915             setAttentionModeThemeOverlay_enforcePermission();
916 
917             enforceValidCallingUser(UserHandle.getCallingUserId());
918 
919             synchronized (mLock) {
920                 if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
921                     mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
922                     Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
923                 }
924             }
925         }
926 
927         @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
928         @Override
getAttentionModeThemeOverlay()929         public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
930             getAttentionModeThemeOverlay_enforcePermission();
931             synchronized (mLock) {
932                 return mAttentionModeThemeOverlay;
933             }
934         }
935 
936         @Override
setApplicationNightMode(@iModeManager.NightMode int mode)937         public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
938             switch (mode) {
939                 case UiModeManager.MODE_NIGHT_NO:
940                 case UiModeManager.MODE_NIGHT_YES:
941                 case UiModeManager.MODE_NIGHT_AUTO:
942                 case UiModeManager.MODE_NIGHT_CUSTOM:
943                     break;
944                 default:
945                     throw new IllegalArgumentException("Unknown mode: " + mode);
946             }
947             final int configNightMode;
948             switch (mode) {
949                 case MODE_NIGHT_YES:
950                     configNightMode = Configuration.UI_MODE_NIGHT_YES;
951                     break;
952                 case MODE_NIGHT_NO:
953                     configNightMode = Configuration.UI_MODE_NIGHT_NO;
954                     break;
955                 default:
956                     configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
957             }
958             final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
959                     mActivityTaskManager.createPackageConfigurationUpdater();
960             updater.setNightMode(configNightMode);
961             updater.commit();
962         }
963 
964         @Override
isUiModeLocked()965         public boolean isUiModeLocked() {
966             synchronized (mLock) {
967                 return mUiModeLocked;
968             }
969         }
970 
971         @Override
isNightModeLocked()972         public boolean isNightModeLocked() {
973             synchronized (mLock) {
974                 return mNightModeLocked;
975             }
976         }
977 
978         @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)979         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
980                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
981             new Shell(mService).exec(mService, in, out, err, args, callback, resultReceiver);
982         }
983 
984         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)985         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
986             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
987             dumpImpl(pw);
988         }
989 
990         @Override
setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active)991         public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
992             return setNightModeActivatedForModeInternal(modeNightCustomType, active);
993         }
994 
995         @Override
setNightModeActivated(boolean active)996         public boolean setNightModeActivated(boolean active) {
997             return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
998         }
999 
setNightModeActivatedForModeInternal(int modeCustomType, boolean active)1000         private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
1001             if (getContext().checkCallingOrSelfPermission(
1002                     android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
1003                     != PackageManager.PERMISSION_GRANTED) {
1004                 Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
1005                 return false;
1006             }
1007             final int user = Binder.getCallingUserHandle().getIdentifier();
1008             enforceValidCallingUser(user);
1009 
1010             if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
1011                     android.Manifest.permission.INTERACT_ACROSS_USERS)
1012                     != PackageManager.PERMISSION_GRANTED) {
1013                 Slog.e(TAG, "Target user is not current user,"
1014                         + " INTERACT_ACROSS_USERS permission is required");
1015                 return false;
1016 
1017             }
1018             // Store the last requested bedtime night mode state so that we don't need to notify
1019             // anyone if the user decides to switch to the night mode to bedtime.
1020             if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
1021                 mLastBedtimeRequestedNightMode = active;
1022             }
1023             if (modeCustomType != mNightModeCustomType) {
1024                 return false;
1025             }
1026             synchronized (mLock) {
1027                 final long ident = Binder.clearCallingIdentity();
1028                 try {
1029                     if (mNightMode.get() == MODE_NIGHT_AUTO
1030                             || mNightMode.get() == MODE_NIGHT_CUSTOM) {
1031                         unregisterDeviceInactiveListenerLocked();
1032                         mOverrideNightModeOff = !active;
1033                         mOverrideNightModeOn = active;
1034                         mOverrideNightModeUser = mCurrentUser;
1035                         persistNightModeOverrides(mCurrentUser);
1036                     } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
1037                             && active) {
1038                         mNightMode.set(UiModeManager.MODE_NIGHT_YES);
1039                     } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
1040                             && !active) {
1041                         mNightMode.set(UiModeManager.MODE_NIGHT_NO);
1042                     }
1043                     updateConfigurationLocked();
1044                     applyConfigurationExternallyLocked();
1045                     persistNightMode(mCurrentUser);
1046                     return true;
1047                 } finally {
1048                     Binder.restoreCallingIdentity(ident);
1049                 }
1050             }
1051         }
1052 
1053         @Override
getCustomNightModeStart()1054         public long getCustomNightModeStart() {
1055             return mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000;
1056         }
1057 
1058         @Override
setCustomNightModeStart(long time)1059         public void setCustomNightModeStart(long time) {
1060             if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
1061                     android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
1062                     != PackageManager.PERMISSION_GRANTED) {
1063                 Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission");
1064                 return;
1065             }
1066             final int user = UserHandle.getCallingUserId();
1067             enforceValidCallingUser(user);
1068 
1069             final long ident = Binder.clearCallingIdentity();
1070             try {
1071                 LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
1072                 if (newTime == null) return;
1073                 mCustomAutoNightModeStartMilliseconds = newTime;
1074                 persistNightMode(user);
1075                 onCustomTimeUpdated(user);
1076             } catch (DateTimeException e) {
1077                 unregisterDeviceInactiveListenerLocked();
1078             } finally {
1079                 Binder.restoreCallingIdentity(ident);
1080             }
1081         }
1082 
1083         @Override
getCustomNightModeEnd()1084         public long getCustomNightModeEnd() {
1085             return mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000;
1086         }
1087 
1088         @Override
setCustomNightModeEnd(long time)1089         public void setCustomNightModeEnd(long time) {
1090             if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
1091                     android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
1092                     != PackageManager.PERMISSION_GRANTED) {
1093                 Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission");
1094                 return;
1095             }
1096             final int user = UserHandle.getCallingUserId();
1097             enforceValidCallingUser(user);
1098 
1099             final long ident = Binder.clearCallingIdentity();
1100             try {
1101                 LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
1102                 if (newTime == null) return;
1103                 mCustomAutoNightModeEndMilliseconds = newTime;
1104                 onCustomTimeUpdated(user);
1105             } catch (DateTimeException e) {
1106                 unregisterDeviceInactiveListenerLocked();
1107             } finally {
1108                 Binder.restoreCallingIdentity(ident);
1109             }
1110         }
1111 
1112         @Override
requestProjection(IBinder binder, @UiModeManager.ProjectionType int projectionType, @NonNull String callingPackage)1113         public boolean requestProjection(IBinder binder,
1114                 @UiModeManager.ProjectionType int projectionType,
1115                 @NonNull String callingPackage) {
1116             assertLegit(callingPackage);
1117             assertSingleProjectionType(projectionType);
1118             enforceProjectionTypePermissions(projectionType);
1119             enforceValidCallingUser(getCallingUserId());
1120 
1121             synchronized (mLock) {
1122                 if (mProjectionHolders == null) {
1123                     mProjectionHolders = new SparseArray<>(1);
1124                 }
1125                 if (!mProjectionHolders.contains(projectionType)) {
1126                     mProjectionHolders.put(projectionType, new ArrayList<>(1));
1127                 }
1128                 List<ProjectionHolder> currentHolders = mProjectionHolders.get(projectionType);
1129 
1130                 // For all projection types, it's a noop if already held.
1131                 for (int i = 0; i < currentHolders.size(); ++i) {
1132                     if (callingPackage.equals(currentHolders.get(i).mPackageName)) {
1133                         return true;
1134                     }
1135                 }
1136 
1137                 // Enforce projection type-specific restrictions here.
1138 
1139                 // Automotive projection can only be set if it is currently unset. The case where it
1140                 // is already set by the calling package is taken care of above.
1141                 if (projectionType == PROJECTION_TYPE_AUTOMOTIVE && !currentHolders.isEmpty()) {
1142                     return false;
1143                 }
1144 
1145                 ProjectionHolder projectionHolder = new ProjectionHolder(callingPackage,
1146                         projectionType, binder,
1147                         UiModeManagerService.this::releaseProjectionUnchecked);
1148                 if (!projectionHolder.linkToDeath()) {
1149                     return false;
1150                 }
1151                 currentHolders.add(projectionHolder);
1152                 Slog.d(TAG, "Package " + callingPackage + " set projection type "
1153                         + projectionType + ".");
1154                 onProjectionStateChangedLocked(projectionType);
1155             }
1156             return true;
1157         }
1158 
1159         @Override
releaseProjection(@iModeManager.ProjectionType int projectionType, @NonNull String callingPackage)1160         public boolean releaseProjection(@UiModeManager.ProjectionType int projectionType,
1161                 @NonNull String callingPackage) {
1162             assertLegit(callingPackage);
1163             assertSingleProjectionType(projectionType);
1164             enforceProjectionTypePermissions(projectionType);
1165             enforceValidCallingUser(getCallingUserId());
1166 
1167             return releaseProjectionUnchecked(projectionType, callingPackage);
1168         }
1169 
1170         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
1171         @Override
getActiveProjectionTypes()1172         public @UiModeManager.ProjectionType int getActiveProjectionTypes() {
1173             getActiveProjectionTypes_enforcePermission();
1174             @UiModeManager.ProjectionType int projectionTypeFlag = PROJECTION_TYPE_NONE;
1175             synchronized (mLock) {
1176                 if (mProjectionHolders != null) {
1177                     for (int i = 0; i < mProjectionHolders.size(); ++i) {
1178                         if (!mProjectionHolders.valueAt(i).isEmpty()) {
1179                             projectionTypeFlag = projectionTypeFlag | mProjectionHolders.keyAt(i);
1180                         }
1181                     }
1182                 }
1183             }
1184             return projectionTypeFlag;
1185         }
1186 
1187         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
1188         @Override
getProjectingPackages( @iModeManager.ProjectionType int projectionType)1189         public List<String> getProjectingPackages(
1190                 @UiModeManager.ProjectionType int projectionType) {
1191             getProjectingPackages_enforcePermission();
1192             synchronized (mLock) {
1193                 List<String> packageNames = new ArrayList<>();
1194                 populateWithRelevantActivePackageNames(projectionType, packageNames);
1195                 return packageNames;
1196             }
1197         }
1198 
1199         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
addOnProjectionStateChangedListener(IOnProjectionStateChangedListener listener, @UiModeManager.ProjectionType int projectionType)1200         public void addOnProjectionStateChangedListener(IOnProjectionStateChangedListener listener,
1201                 @UiModeManager.ProjectionType int projectionType) {
1202             addOnProjectionStateChangedListener_enforcePermission();
1203             if (projectionType == PROJECTION_TYPE_NONE) {
1204                 return;
1205             }
1206 
1207             enforceValidCallingUser(getCallingUserId());
1208 
1209             synchronized (mLock) {
1210                 if (mProjectionListeners == null) {
1211                     mProjectionListeners = new SparseArray<>(1);
1212                 }
1213                 if (!mProjectionListeners.contains(projectionType)) {
1214                     mProjectionListeners.put(projectionType, new RemoteCallbackList<>());
1215                 }
1216                 if (mProjectionListeners.get(projectionType).register(listener)) {
1217                     // If any of those types are active, send a callback immediately.
1218                     List<String> packageNames = new ArrayList<>();
1219                     @UiModeManager.ProjectionType int activeProjectionTypes =
1220                             populateWithRelevantActivePackageNames(projectionType, packageNames);
1221                     if (!packageNames.isEmpty()) {
1222                         try {
1223                             listener.onProjectionStateChanged(activeProjectionTypes, packageNames);
1224                         } catch (RemoteException e) {
1225                             Slog.w(TAG,
1226                                     "Failed a call to onProjectionStateChanged() during listener "
1227                                             + "registration.");
1228                         }
1229                     }
1230                 }
1231             }
1232         }
1233 
1234 
1235         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
removeOnProjectionStateChangedListener( IOnProjectionStateChangedListener listener)1236         public void removeOnProjectionStateChangedListener(
1237                 IOnProjectionStateChangedListener listener) {
1238             removeOnProjectionStateChangedListener_enforcePermission();
1239             synchronized (mLock) {
1240                 if (mProjectionListeners != null) {
1241                     for (int i = 0; i < mProjectionListeners.size(); ++i) {
1242                         mProjectionListeners.valueAt(i).unregister(listener);
1243                     }
1244                 }
1245             }
1246         }
1247 
1248         @Override
getContrast()1249         public float getContrast() {
1250             synchronized (mLock) {
1251                 return getContrastLocked();
1252             }
1253         }
1254     };
1255 
1256     // This method validates whether calling user is valid in visible background users
1257     // feature. Valid user is the current user or the system or in the same profile group as
1258     // the current user.
enforceValidCallingUser(int userId)1259     private void enforceValidCallingUser(int userId) {
1260         if (!isVisibleBackgroundUsersEnabled()) {
1261             return;
1262         }
1263         if (LOG) {
1264             Slog.d(TAG, "enforceValidCallingUser: userId=" + userId
1265                     + " isSystemUser=" + (userId == USER_SYSTEM) + " current user=" + mCurrentUser
1266                     + " callingPid=" + Binder.getCallingPid()
1267                     + " callingUid=" + mInjector.getCallingUid());
1268         }
1269         long ident = Binder.clearCallingIdentity();
1270         try {
1271             if (userId != USER_SYSTEM && userId != mCurrentUser
1272                     && !UserManagerService.getInstance().isSameProfileGroup(userId, mCurrentUser)) {
1273                 throw new SecurityException(
1274                         "Calling user is not valid for level-1 compatibility in MUMD. "
1275                                 + "callingUserId=" + userId + " currentUserId=" + mCurrentUser);
1276             }
1277         } finally {
1278             Binder.restoreCallingIdentity(ident);
1279         }
1280     }
1281 
enforceProjectionTypePermissions(@iModeManager.ProjectionType int p)1282     private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
1283         if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) {
1284             getContext().enforceCallingPermission(
1285                     android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
1286                     "toggleProjection");
1287         }
1288     }
1289 
assertSingleProjectionType(@iModeManager.ProjectionType int p)1290     private static void assertSingleProjectionType(@UiModeManager.ProjectionType int p) {
1291         // To be a single projection type it must be non-zero and an exact power of two.
1292         boolean projectionTypeIsPowerOfTwoOrZero = (p & p - 1) == 0;
1293         if (p == 0 || !projectionTypeIsPowerOfTwoOrZero) {
1294             throw new IllegalArgumentException("Must specify exactly one projection type.");
1295         }
1296     }
1297 
toPackageNameList(Collection<ProjectionHolder> c)1298     private static List<String> toPackageNameList(Collection<ProjectionHolder> c) {
1299         List<String> packageNames = new ArrayList<>();
1300         for (ProjectionHolder p : c) {
1301             packageNames.add(p.mPackageName);
1302         }
1303         return packageNames;
1304     }
1305 
1306     /**
1307      * Populates a list with the package names that have set any of the given projection types.
1308      * @param projectionType the projection types to include
1309      * @param packageNames the list to populate with package names
1310      * @return the active projection types
1311      */
1312     @GuardedBy("mLock")
1313     @UiModeManager.ProjectionType
populateWithRelevantActivePackageNames( @iModeManager.ProjectionType int projectionType, List<String> packageNames)1314     private int populateWithRelevantActivePackageNames(
1315             @UiModeManager.ProjectionType int projectionType, List<String> packageNames) {
1316         packageNames.clear();
1317         @UiModeManager.ProjectionType int projectionTypeFlag = PROJECTION_TYPE_NONE;
1318         if (mProjectionHolders != null) {
1319             for (int i = 0; i < mProjectionHolders.size(); ++i) {
1320                 int key = mProjectionHolders.keyAt(i);
1321                 List<ProjectionHolder> holders = mProjectionHolders.valueAt(i);
1322                 if ((projectionType & key) != 0) {
1323                     if (packageNames.addAll(toPackageNameList(holders))) {
1324                         projectionTypeFlag = projectionTypeFlag | key;
1325                     }
1326                 }
1327             }
1328         }
1329         return projectionTypeFlag;
1330     }
1331 
releaseProjectionUnchecked(@iModeManager.ProjectionType int projectionType, @NonNull String pkg)1332     private boolean releaseProjectionUnchecked(@UiModeManager.ProjectionType int projectionType,
1333             @NonNull String pkg) {
1334         synchronized (mLock) {
1335             boolean removed = false;
1336             if (mProjectionHolders != null) {
1337                 List<ProjectionHolder> holders = mProjectionHolders.get(projectionType);
1338                 if (holders != null) {
1339                     // Iterate backward so we can safely remove while iterating.
1340                     for (int i = holders.size() - 1; i >= 0; --i) {
1341                         ProjectionHolder holder = holders.get(i);
1342                         if (pkg.equals(holder.mPackageName)) {
1343                             holder.unlinkToDeath();
1344                             Slog.d(TAG, "Projection type " + projectionType + " released by "
1345                                     + pkg + ".");
1346                             holders.remove(i);
1347                             removed = true;
1348                         }
1349                     }
1350                 }
1351             }
1352             if (removed) {
1353                 onProjectionStateChangedLocked(projectionType);
1354             } else {
1355                 Slog.w(TAG, pkg + " tried to release projection type " + projectionType
1356                         + " but was not set by that package.");
1357             }
1358             return removed;
1359         }
1360     }
1361 
1362     /**
1363      * Return the contrast for the current user. If not cached, fetch it from the settings.
1364      */
1365     @GuardedBy("mLock")
getContrastLocked()1366     private float getContrastLocked() {
1367         if (!mContrasts.contains(mCurrentUser)) updateContrastLocked();
1368         return mContrasts.get(mCurrentUser);
1369     }
1370 
1371     /**
1372      * Read the contrast setting for the current user and update {@link #mContrasts}
1373      * if the contrast changed. Returns true if {@link #mContrasts} was updated.
1374      */
1375     @GuardedBy("mLock")
updateContrastLocked()1376     private boolean updateContrastLocked() {
1377         float contrast = Settings.Secure.getFloatForUser(getContext().getContentResolver(),
1378                 CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, mCurrentUser);
1379         if (Math.abs(mContrasts.get(mCurrentUser, Float.MAX_VALUE) - contrast) >= 1e-10) {
1380             mContrasts.put(mCurrentUser, contrast);
1381             return true;
1382         }
1383         return false;
1384     }
1385 
1386     private static class ProjectionHolder implements IBinder.DeathRecipient {
1387         private final String mPackageName;
1388         private final @UiModeManager.ProjectionType int mProjectionType;
1389         private final IBinder mBinder;
1390         private final ProjectionReleaser mProjectionReleaser;
1391 
ProjectionHolder(String packageName, @UiModeManager.ProjectionType int projectionType, IBinder binder, ProjectionReleaser projectionReleaser)1392         private ProjectionHolder(String packageName,
1393                 @UiModeManager.ProjectionType int projectionType, IBinder binder,
1394                 ProjectionReleaser projectionReleaser) {
1395             mPackageName = packageName;
1396             mProjectionType = projectionType;
1397             mBinder = binder;
1398             mProjectionReleaser = projectionReleaser;
1399         }
1400 
linkToDeath()1401         private boolean linkToDeath() {
1402             try {
1403                 mBinder.linkToDeath(this, 0);
1404             } catch (RemoteException e) {
1405                 Slog.e(TAG, "linkToDeath failed for projection requester: " + mPackageName + ".",
1406                         e);
1407                 return false;
1408             }
1409             return true;
1410         }
1411 
unlinkToDeath()1412         private void unlinkToDeath() {
1413             mBinder.unlinkToDeath(this, 0);
1414         }
1415 
1416         @Override
binderDied()1417         public void binderDied() {
1418             Slog.w(TAG, "Projection holder " + mPackageName
1419                     + " died. Releasing projection type " + mProjectionType + ".");
1420             mProjectionReleaser.release(mProjectionType, mPackageName);
1421         }
1422 
1423         private interface ProjectionReleaser {
release(@iModeManager.ProjectionType int projectionType, @NonNull String packageName)1424             boolean release(@UiModeManager.ProjectionType int projectionType,
1425                     @NonNull String packageName);
1426         }
1427     }
1428 
assertLegit(@onNull String packageName)1429     private void assertLegit(@NonNull String packageName) {
1430         if (!doesPackageHaveCallingUid(packageName)) {
1431             throw new SecurityException("Caller claimed bogus packageName: " + packageName + ".");
1432         }
1433     }
1434 
doesPackageHaveCallingUid(@onNull String packageName)1435     private boolean doesPackageHaveCallingUid(@NonNull String packageName) {
1436         int callingUid = mInjector.getCallingUid();
1437         int callingUserId = UserHandle.getUserId(callingUid);
1438         final long ident = Binder.clearCallingIdentity();
1439         try {
1440             return getContext().getPackageManager().getPackageUidAsUser(packageName,
1441                     callingUserId) == callingUid;
1442         } catch (PackageManager.NameNotFoundException e) {
1443             return false;
1444         } finally {
1445             Binder.restoreCallingIdentity(ident);
1446         }
1447     }
1448 
1449     @GuardedBy("mLock")
onProjectionStateChangedLocked( @iModeManager.ProjectionType int changedProjectionType)1450     private void onProjectionStateChangedLocked(
1451             @UiModeManager.ProjectionType int changedProjectionType) {
1452         if (mProjectionListeners == null) {
1453             return;
1454         }
1455         for (int i = 0; i < mProjectionListeners.size(); ++i) {
1456             int listenerProjectionType = mProjectionListeners.keyAt(i);
1457             // Every listener that is affected must be called back with all the state they are
1458             // listening for.
1459             if ((changedProjectionType & listenerProjectionType) != 0) {
1460                 RemoteCallbackList<IOnProjectionStateChangedListener> listeners =
1461                         mProjectionListeners.valueAt(i);
1462                 List<String> packageNames = new ArrayList<>();
1463                 @UiModeManager.ProjectionType int activeProjectionTypes =
1464                         populateWithRelevantActivePackageNames(listenerProjectionType,
1465                                 packageNames);
1466                 int listenerCount = listeners.beginBroadcast();
1467                 for (int j = 0; j < listenerCount; ++j) {
1468                     try {
1469                         listeners.getBroadcastItem(j).onProjectionStateChanged(
1470                                 activeProjectionTypes, packageNames);
1471                     } catch (RemoteException e) {
1472                         Slog.w(TAG, "Failed a call to onProjectionStateChanged().");
1473                     }
1474                 }
1475                 listeners.finishBroadcast();
1476             }
1477         }
1478     }
1479 
onCustomTimeUpdated(int user)1480     private void onCustomTimeUpdated(int user) {
1481         persistNightMode(user);
1482         if (mNightMode.get() != MODE_NIGHT_CUSTOM) return;
1483         if (shouldApplyAutomaticChangesImmediately()) {
1484             unregisterDeviceInactiveListenerLocked();
1485             updateLocked(0, 0);
1486         } else {
1487             registerDeviceInactiveListenerLocked();
1488         }
1489     }
1490 
dumpImpl(PrintWriter pw)1491     void dumpImpl(PrintWriter pw) {
1492         synchronized (mLock) {
1493             pw.println("Current UI Mode Service state:");
1494             pw.print("  mDockState="); pw.print(mDockState);
1495             pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
1496 
1497             pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
1498 
1499             pw.print("  mNightMode="); pw.print(mNightMode.get()); pw.print(" (");
1500             pw.print(Shell.nightModeToStr(mNightMode.get(), mNightModeCustomType)); pw.print(") ");
1501             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
1502             pw.print("/"); pw.print(mOverrideNightModeOff);
1503             pw.print("  mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
1504             pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
1505 
1506             pw.print("  mCarModeEnabled="); pw.print(mCarModeEnabled);
1507             pw.print(" (carModeApps=");
1508             for (Map.Entry<Integer, String> entry : mCarModePackagePriority.entrySet()) {
1509                 pw.print(entry.getKey());
1510                 pw.print(":");
1511                 pw.print(entry.getValue());
1512                 pw.print(" ");
1513             }
1514             pw.println("");
1515             pw.print(" mWaitForDeviceInactive="); pw.print(mWaitForDeviceInactive);
1516             pw.print(" mComputedNightMode="); pw.print(mComputedNightMode);
1517             pw.print(" customStart="); pw.print(mCustomAutoNightModeStartMilliseconds);
1518             pw.print(" customEnd"); pw.print(mCustomAutoNightModeEndMilliseconds);
1519             pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
1520             pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
1521 
1522             pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
1523             pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
1524             pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
1525 
1526             pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
1527             pw.print(" mSystemReady="); pw.println(mSystemReady);
1528 
1529             if (mTwilightManager != null) {
1530                 // We may not have a TwilightManager.
1531                 pw.print("  mTwilightService.getLastTwilightState()=");
1532                 pw.println(mTwilightManager.getLastTwilightState());
1533             }
1534         }
1535     }
1536 
1537     /**
1538      * Updates the global car mode state.
1539      * The device is considered to be in car mode if there exists an app at any priority level which
1540      * has entered car mode.
1541      *
1542      * @param enabled {@code true} if the caller wishes to enable car mode, {@code false} otherwise.
1543      * @param flags Flags used when enabling/disabling car mode.
1544      * @param priority The priority level for entering or exiting car mode; defaults to
1545      *                 {@link UiModeManager#DEFAULT_PRIORITY} for callers using
1546      *                 {@link UiModeManager#enableCarMode(int)}.  Callers using
1547      *                 {@link UiModeManager#enableCarMode(int, int)} may specify a priority.
1548      * @param packageName The package name of the app which initiated the request to enable or
1549      *                    disable car mode.
1550      */
setCarModeLocked(boolean enabled, int flags, int priority, String packageName)1551     void setCarModeLocked(boolean enabled, int flags, int priority, String packageName) {
1552         if (enabled) {
1553             enableCarMode(priority, packageName);
1554         } else {
1555             disableCarMode(flags, priority, packageName);
1556         }
1557         boolean isCarModeNowEnabled = isCarModeEnabled();
1558 
1559         if (mCarModeEnabled != isCarModeNowEnabled) {
1560             mCarModeEnabled = isCarModeNowEnabled;
1561             // When exiting car mode, restore night mode from settings
1562             if (!isCarModeNowEnabled) {
1563                 Context context = getContext();
1564                 updateNightModeFromSettingsLocked(context,
1565                         context.getResources(),
1566                         UserHandle.getCallingUserId());
1567             }
1568         }
1569         mCarModeEnableFlags = flags;
1570     }
1571 
1572     /**
1573      * Handles disabling car mode.
1574      * <p>
1575      * Car mode can be disabled at a priority level if any of the following is true:
1576      * 1. The priority being disabled is the {@link UiModeManager#DEFAULT_PRIORITY}.
1577      * 2. The priority level is enabled and the caller is the app who originally enabled it.
1578      * 3. The {@link UiModeManager#DISABLE_CAR_MODE_ALL_PRIORITIES} flag was specified, meaning all
1579      *    car mode priorities are disabled.
1580      *
1581      * @param flags Car mode flags.
1582      * @param priority The priority level at which to disable car mode.
1583      * @param packageName The calling package which initiated the request.
1584      */
disableCarMode(int flags, int priority, String packageName)1585     private void disableCarMode(int flags, int priority, String packageName) {
1586         boolean isDisableAll = (flags & UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES) != 0;
1587         boolean isPriorityTracked = mCarModePackagePriority.keySet().contains(priority);
1588         boolean isDefaultPriority = priority == UiModeManager.DEFAULT_PRIORITY;
1589         boolean isChangeAllowed =
1590                 // Anyone can disable the default priority.
1591                 isDefaultPriority
1592                 // If priority was enabled, only enabling package can disable it.
1593                 || isPriorityTracked && mCarModePackagePriority.get(priority).equals(
1594                 packageName)
1595                 // Disable all priorities flag can disable all regardless.
1596                 || isDisableAll;
1597         if (isChangeAllowed) {
1598             Slog.d(TAG, "disableCarMode: disabling, priority=" + priority
1599                     + ", packageName=" + packageName);
1600             if (isDisableAll) {
1601                 Set<Map.Entry<Integer, String>> entries =
1602                         new ArraySet<>(mCarModePackagePriority.entrySet());
1603                 mCarModePackagePriority.clear();
1604 
1605                 for (Map.Entry<Integer, String> entry : entries) {
1606                     notifyCarModeDisabled(entry.getKey(), entry.getValue());
1607                 }
1608             } else {
1609                 mCarModePackagePriority.remove(priority);
1610                 notifyCarModeDisabled(priority, packageName);
1611             }
1612         }
1613     }
1614 
1615     /**
1616      * Handles enabling car mode.
1617      * <p>
1618      * Car mode can be enabled at any priority if it has not already been enabled at that priority.
1619      * The calling package is tracked for the first app which enters priority at the
1620      * {@link UiModeManager#DEFAULT_PRIORITY}, though any app can disable it at that priority.
1621      *
1622      * @param priority The priority for enabling car mode.
1623      * @param packageName The calling package which initiated the request.
1624      */
enableCarMode(int priority, String packageName)1625     private void enableCarMode(int priority, String packageName) {
1626         boolean isPriorityTracked = mCarModePackagePriority.containsKey(priority);
1627         boolean isPackagePresent = mCarModePackagePriority.containsValue(packageName);
1628         if (!isPriorityTracked && !isPackagePresent) {
1629             Slog.d(TAG, "enableCarMode: enabled at priority=" + priority + ", packageName="
1630                     + packageName);
1631             mCarModePackagePriority.put(priority, packageName);
1632             notifyCarModeEnabled(priority, packageName);
1633         } else {
1634             Slog.d(TAG, "enableCarMode: car mode at priority " + priority + " already enabled.");
1635         }
1636 
1637     }
1638 
notifyCarModeEnabled(int priority, String packageName)1639     private void notifyCarModeEnabled(int priority, String packageName) {
1640         Intent intent = new Intent(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
1641         intent.putExtra(UiModeManager.EXTRA_CALLING_PACKAGE, packageName);
1642         intent.putExtra(UiModeManager.EXTRA_PRIORITY, priority);
1643         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
1644                 android.Manifest.permission.HANDLE_CAR_MODE_CHANGES);
1645     }
1646 
notifyCarModeDisabled(int priority, String packageName)1647     private void notifyCarModeDisabled(int priority, String packageName) {
1648         Intent intent = new Intent(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
1649         intent.putExtra(UiModeManager.EXTRA_CALLING_PACKAGE, packageName);
1650         intent.putExtra(UiModeManager.EXTRA_PRIORITY, priority);
1651         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
1652                 android.Manifest.permission.HANDLE_CAR_MODE_CHANGES);
1653     }
1654 
1655     /**
1656      * Determines if car mode is enabled at any priority level.
1657      * @return {@code true} if car mode is enabled, {@code false} otherwise.
1658      */
isCarModeEnabled()1659     private boolean isCarModeEnabled() {
1660         return mCarModePackagePriority.size() > 0;
1661     }
1662 
updateDockState(int newState)1663     private void updateDockState(int newState) {
1664         synchronized (mLock) {
1665             if (newState != mDockState) {
1666                 mDockState = newState;
1667                 setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR, 0,
1668                         UiModeManager.DEFAULT_PRIORITY, "" /* packageName */);
1669                 if (mSystemReady) {
1670                     updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
1671                 }
1672             }
1673         }
1674     }
1675 
isDeskDockState(int state)1676     private static boolean isDeskDockState(int state) {
1677         switch (state) {
1678             case Intent.EXTRA_DOCK_STATE_DESK:
1679             case Intent.EXTRA_DOCK_STATE_LE_DESK:
1680             case Intent.EXTRA_DOCK_STATE_HE_DESK:
1681                 return true;
1682             default:
1683                 return false;
1684         }
1685     }
1686 
persistNightMode(int user)1687     private void persistNightMode(int user) {
1688         // Only persist setting if not in car mode
1689         if (mCarModeEnabled || mCar) return;
1690         Secure.putIntForUser(getContext().getContentResolver(),
1691                 Secure.UI_NIGHT_MODE, mNightMode.get(), user);
1692         Secure.putLongForUser(getContext().getContentResolver(),
1693                 Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
1694         Secure.putLongForUser(getContext().getContentResolver(),
1695                 Secure.DARK_THEME_CUSTOM_START_TIME,
1696                 mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user);
1697         Secure.putLongForUser(getContext().getContentResolver(),
1698                 Secure.DARK_THEME_CUSTOM_END_TIME,
1699                 mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000, user);
1700     }
1701 
persistNightModeOverrides(int user)1702     private void persistNightModeOverrides(int user) {
1703         // Only persist setting if not in car mode
1704         if (mCarModeEnabled || mCar) return;
1705         Secure.putIntForUser(getContext().getContentResolver(),
1706                 Secure.UI_NIGHT_MODE_OVERRIDE_ON, mOverrideNightModeOn ? 1 : 0, user);
1707         Secure.putIntForUser(getContext().getContentResolver(),
1708                 Secure.UI_NIGHT_MODE_OVERRIDE_OFF, mOverrideNightModeOff ? 1 : 0, user);
1709     }
1710 
updateConfigurationLocked()1711     private void updateConfigurationLocked() {
1712         int uiMode = mDefaultUiModeType;
1713         if (mUiModeLocked) {
1714             // no-op, keeps default one
1715         } else if (mTelevision) {
1716             uiMode = Configuration.UI_MODE_TYPE_TELEVISION;
1717         } else if (mWatch) {
1718             uiMode = Configuration.UI_MODE_TYPE_WATCH;
1719         } else if (mCarModeEnabled) {
1720             uiMode = Configuration.UI_MODE_TYPE_CAR;
1721         } else if (isDeskDockState(mDockState)) {
1722             uiMode = Configuration.UI_MODE_TYPE_DESK;
1723         } else if (mVrHeadset) {
1724             uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
1725         }
1726 
1727         if (mNightMode.get() == MODE_NIGHT_YES || mNightMode.get() == UiModeManager.MODE_NIGHT_NO) {
1728             updateComputedNightModeLocked(mNightMode.get() == MODE_NIGHT_YES);
1729         }
1730 
1731         if (mNightMode.get() == MODE_NIGHT_AUTO) {
1732             boolean activateNightMode = mComputedNightMode;
1733             if (mTwilightManager != null) {
1734                 mTwilightManager.registerListener(mTwilightListener, mHandler);
1735                 final TwilightState lastState = mTwilightManager.getLastTwilightState();
1736                 activateNightMode = lastState == null ? mComputedNightMode : lastState.isNight();
1737             }
1738             updateComputedNightModeLocked(activateNightMode);
1739         } else {
1740             if (mTwilightManager != null) {
1741                 mTwilightManager.unregisterListener(mTwilightListener);
1742             }
1743         }
1744 
1745         if (mNightMode.get() == MODE_NIGHT_CUSTOM) {
1746             if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
1747                 updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
1748             } else {
1749                 registerTimeChangeEvent();
1750                 final boolean activate = computeCustomNightMode();
1751                 updateComputedNightModeLocked(activate);
1752                 scheduleNextCustomTimeListener();
1753             }
1754         } else {
1755             unregisterTimeChangeEvent();
1756         }
1757 
1758         // Override night mode in power save mode if not in car mode
1759         if (mPowerSave && !mCarModeEnabled && !mCar) {
1760             uiMode &= ~Configuration.UI_MODE_NIGHT_NO;
1761             uiMode |= Configuration.UI_MODE_NIGHT_YES;
1762         } else {
1763             uiMode = getComputedUiModeConfiguration(uiMode);
1764         }
1765 
1766         if (LOG) {
1767             Slog.d(TAG,
1768                     "updateConfigurationLocked: mDockState=" + mDockState
1769                     + "; mCarMode=" + mCarModeEnabled
1770                     + "; mNightMode=" + mNightMode
1771                     + "; mNightModeCustomType=" + mNightModeCustomType
1772                     + "; uiMode=" + uiMode);
1773         }
1774 
1775         mCurUiMode = uiMode;
1776         if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
1777             mConfiguration.uiMode = uiMode;
1778         }
1779     }
1780 
1781     @UiModeManager.NightMode
getComputedUiModeConfiguration(int uiMode)1782     private int getComputedUiModeConfiguration(int uiMode) {
1783         uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
1784                 : Configuration.UI_MODE_NIGHT_NO;
1785         uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
1786                 : ~Configuration.UI_MODE_NIGHT_YES;
1787         return uiMode;
1788     }
1789 
computeCustomNightMode()1790     private boolean computeCustomNightMode() {
1791         return isTimeBetween(LocalTime.now(),
1792                 mCustomAutoNightModeStartMilliseconds,
1793                 mCustomAutoNightModeEndMilliseconds);
1794     }
1795 
applyConfigurationExternallyLocked()1796     private void applyConfigurationExternallyLocked() {
1797         if (mSetUiMode != mConfiguration.uiMode) {
1798             mSetUiMode = mConfiguration.uiMode;
1799             // load splash screen instead of screenshot
1800             mWindowManager.clearSnapshotCache();
1801             try {
1802                 ActivityTaskManager.getService().updateConfiguration(mConfiguration);
1803             } catch (RemoteException e) {
1804                 Slog.w(TAG, "Failure communicating with activity manager", e);
1805             } catch (SecurityException e) {
1806                 Slog.e(TAG, "Activity does not have the ", e);
1807             }
1808         }
1809     }
1810 
shouldApplyAutomaticChangesImmediately()1811     private boolean shouldApplyAutomaticChangesImmediately() {
1812         return mCar || !mPowerManager.isInteractive()
1813                 || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME
1814                 || mDreamManagerInternal.isDreaming();
1815     }
1816 
scheduleNextCustomTimeListener()1817     private void scheduleNextCustomTimeListener() {
1818         cancelCustomAlarm();
1819         LocalDateTime now = LocalDateTime.now();
1820         final boolean active = computeCustomNightMode();
1821         final LocalDateTime next = active
1822                 ? getDateTimeAfter(mCustomAutoNightModeEndMilliseconds, now)
1823                 : getDateTimeAfter(mCustomAutoNightModeStartMilliseconds, now);
1824         final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
1825         mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, mCustomTimeListener, null);
1826     }
1827 
getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime)1828     private LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
1829         final LocalDateTime ldt = LocalDateTime.of(compareTime.toLocalDate(), localTime);
1830 
1831         // Check if the local time has passed, if so return the same time tomorrow.
1832         return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
1833     }
1834 
updateLocked(int enableFlags, int disableFlags)1835     void updateLocked(int enableFlags, int disableFlags) {
1836         String action = null;
1837         String oldAction = null;
1838         if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
1839             adjustStatusBarCarModeLocked();
1840             oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
1841         } else if (isDeskDockState(mLastBroadcastState)) {
1842             oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
1843         }
1844 
1845         if (mCarModeEnabled) {
1846             if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
1847                 adjustStatusBarCarModeLocked();
1848                 if (oldAction != null) {
1849                     sendForegroundBroadcastToAllUsers(oldAction);
1850                 }
1851                 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
1852                 action = UiModeManager.ACTION_ENTER_CAR_MODE;
1853             }
1854         } else if (isDeskDockState(mDockState)) {
1855             if (!isDeskDockState(mLastBroadcastState)) {
1856                 if (oldAction != null) {
1857                     sendForegroundBroadcastToAllUsers(oldAction);
1858                 }
1859                 mLastBroadcastState = mDockState;
1860                 action = UiModeManager.ACTION_ENTER_DESK_MODE;
1861             }
1862         } else {
1863             mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
1864             action = oldAction;
1865         }
1866 
1867         if (action != null) {
1868             if (LOG) {
1869                 Slog.v(TAG, String.format(
1870                     "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
1871                     action, enableFlags, disableFlags));
1872             }
1873 
1874             // Send the ordered broadcast; the result receiver will receive after all
1875             // broadcasts have been sent. If any broadcast receiver changes the result
1876             // code from the initial value of RESULT_OK, then the result receiver will
1877             // not launch the corresponding dock application. This gives apps a chance
1878             // to override the behavior and stay in their app even when the device is
1879             // placed into a dock.
1880             Intent intent = new Intent(action);
1881             intent.putExtra("enableFlags", enableFlags);
1882             intent.putExtra("disableFlags", disableFlags);
1883             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1884             getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
1885                     mResultReceiver, null, Activity.RESULT_OK, null, null);
1886 
1887             // Attempting to make this transition a little more clean, we are going
1888             // to hold off on doing a configuration change until we have finished
1889             // the broadcast and started the home activity.
1890             mHoldingConfiguration = true;
1891             updateConfigurationLocked();
1892         } else {
1893             String category = null;
1894             if (mCarModeEnabled) {
1895                 if (mEnableCarDockLaunch
1896                         && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
1897                     category = Intent.CATEGORY_CAR_DOCK;
1898                 }
1899             } else if (isDeskDockState(mDockState)) {
1900                 if (ENABLE_LAUNCH_DESK_DOCK_APP
1901                         && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
1902                     category = Intent.CATEGORY_DESK_DOCK;
1903                 }
1904             } else {
1905                 if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
1906                     category = Intent.CATEGORY_HOME;
1907                 }
1908             }
1909 
1910             if (LOG) {
1911                 Slog.v(TAG, "updateLocked: null action, mDockState="
1912                         + mDockState +", category=" + category);
1913             }
1914 
1915             sendConfigurationAndStartDreamOrDockAppLocked(category);
1916         }
1917 
1918         // keep screen on when charging and in car mode
1919         boolean keepScreenOn = mCharging &&
1920                 ((mCarModeEnabled && mCarModeKeepsScreenOn &&
1921                 (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
1922                 (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
1923         if (keepScreenOn != mWakeLock.isHeld()) {
1924             if (keepScreenOn) {
1925                 mWakeLock.acquire();
1926             } else {
1927                 mWakeLock.release();
1928             }
1929         }
1930     }
1931 
sendForegroundBroadcastToAllUsers(String action)1932     private void sendForegroundBroadcastToAllUsers(String action) {
1933         getContext().sendBroadcastAsUser(new Intent(action)
1934                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), UserHandle.ALL);
1935     }
1936 
updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags)1937     private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
1938         // Launch a dock activity
1939         String category = null;
1940         if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
1941             // Only launch car home when car mode is enabled and the caller
1942             // has asked us to switch to it.
1943             if (mEnableCarDockLaunch
1944                     && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
1945                 category = Intent.CATEGORY_CAR_DOCK;
1946             }
1947         } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
1948             // Only launch car home when desk mode is enabled and the caller
1949             // has asked us to switch to it.  Currently re-using the car
1950             // mode flag since we don't have a formal API for "desk mode".
1951             if (ENABLE_LAUNCH_DESK_DOCK_APP
1952                     && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
1953                 category = Intent.CATEGORY_DESK_DOCK;
1954             }
1955         } else {
1956             // Launch the standard home app if requested.
1957             if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
1958                 category = Intent.CATEGORY_HOME;
1959             }
1960         }
1961 
1962         if (LOG) {
1963             Slog.v(TAG, String.format(
1964                     "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
1965                             + "category=%s",
1966                     action, enableFlags, disableFlags, category));
1967         }
1968 
1969         sendConfigurationAndStartDreamOrDockAppLocked(category);
1970     }
1971 
sendConfigurationAndStartDreamOrDockAppLocked(String category)1972     private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
1973         // Update the configuration but don't send it yet.
1974         mHoldingConfiguration = false;
1975         updateConfigurationLocked();
1976 
1977         // Start the dock app, if there is one.
1978         boolean dockAppStarted = false;
1979         if (category != null) {
1980             // Now we are going to be careful about switching the
1981             // configuration and starting the activity -- we need to
1982             // do this in a specific order under control of the
1983             // activity manager, to do it cleanly.  So compute the
1984             // new config, but don't set it yet, and let the
1985             // activity manager take care of both the start and config
1986             // change.
1987             Intent homeIntent = buildHomeIntent(category);
1988             if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
1989                 try {
1990                     int result = ActivityTaskManager.getService().startActivityWithConfig(
1991                             null, getContext().getBasePackageName(),
1992                             getContext().getAttributionTag(), homeIntent, null, null, null, 0, 0,
1993                             mConfiguration, null, UserHandle.USER_CURRENT);
1994                     if (ActivityManager.isStartResultSuccessful(result)) {
1995                         dockAppStarted = true;
1996                     } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
1997                         Slog.e(TAG, "Could not start dock app: " + homeIntent
1998                                 + ", startActivityWithConfig result " + result);
1999                     }
2000                 } catch (RemoteException ex) {
2001                     Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
2002                 }
2003             }
2004         }
2005 
2006         // Send the new configuration.
2007         applyConfigurationExternallyLocked();
2008 
2009         final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression
2010                 && mLocalPowerManager.isAmbientDisplaySuppressed();
2011 
2012         // If we did not start a dock app, then start dreaming if appropriate.
2013         if (category != null && !dockAppStarted && !dreamsSuppressed && (
2014                 mStartDreamImmediatelyOnDock
2015                         || mWindowManager.isKeyguardShowingAndNotOccluded()
2016                         || !mPowerManager.isInteractive())) {
2017             mInjector.startDreamWhenDockedIfAppropriate(getContext());
2018         }
2019     }
2020 
adjustStatusBarCarModeLocked()2021     private void adjustStatusBarCarModeLocked() {
2022         final Context context = getContext();
2023         if (mStatusBarManager == null) {
2024             mStatusBarManager = (StatusBarManager)
2025                     context.getSystemService(Context.STATUS_BAR_SERVICE);
2026         }
2027 
2028         // Fear not: StatusBarManagerService manages a list of requests to disable
2029         // features of the status bar; these are ORed together to form the
2030         // active disabled list. So if (for example) the device is locked and
2031         // the status bar should be totally disabled, the calls below will
2032         // have no effect until the device is unlocked.
2033         if (mStatusBarManager != null) {
2034             mStatusBarManager.disable(mCarModeEnabled
2035                     ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
2036                     : StatusBarManager.DISABLE_NONE);
2037         }
2038 
2039         if (mNotificationManager == null) {
2040             mNotificationManager = (NotificationManager)
2041                     context.getSystemService(Context.NOTIFICATION_SERVICE);
2042         }
2043 
2044         if (mNotificationManager != null) {
2045             if (mCarModeEnabled) {
2046                 Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
2047 
2048                 Notification.Builder n =
2049                         new Notification.Builder(context, SystemNotificationChannels.CAR_MODE)
2050                         .setSmallIcon(R.drawable.stat_notify_car_mode)
2051                         .setDefaults(Notification.DEFAULT_LIGHTS)
2052                         .setOngoing(true)
2053                         .setWhen(0)
2054                         .setColor(context.getColor(
2055                                 com.android.internal.R.color.system_notification_accent_color))
2056                         .setContentTitle(
2057                                 context.getString(R.string.car_mode_disable_notification_title))
2058                         .setContentText(
2059                                 context.getString(R.string.car_mode_disable_notification_message))
2060 
2061                         .setContentIntent(
2062                                 // TODO(b/173744200) Please replace FLAG_MUTABLE_UNAUDITED below
2063                                 // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
2064                                 PendingIntent.getActivityAsUser(context, 0,
2065                                         carModeOffIntent, PendingIntent.FLAG_MUTABLE,
2066                                         null, UserHandle.CURRENT));
2067                 mNotificationManager.notifyAsUser(null,
2068                         SystemMessage.NOTE_CAR_MODE_DISABLE, n.build(), UserHandle.ALL);
2069             } else {
2070                 mNotificationManager.cancelAsUser(null,
2071                         SystemMessage.NOTE_CAR_MODE_DISABLE, UserHandle.ALL);
2072             }
2073         }
2074     }
2075 
updateComputedNightModeLocked(boolean activate)2076     private void updateComputedNightModeLocked(boolean activate) {
2077         boolean newComputedValue = activate;
2078         if (mNightMode.get() != MODE_NIGHT_YES && mNightMode.get() != UiModeManager.MODE_NIGHT_NO) {
2079             if (mOverrideNightModeOn && !newComputedValue) {
2080                 newComputedValue = true;
2081             } else if (mOverrideNightModeOff && newComputedValue) {
2082                 newComputedValue = false;
2083             }
2084         }
2085 
2086         if (modesApi()) {
2087             // Computes final night mode values based on Attention Mode.
2088             mComputedNightMode = switch (mAttentionModeThemeOverlay) {
2089                 case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
2090                 case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
2091                 default -> newComputedValue; // case OFF
2092             };
2093         } else {
2094             mComputedNightMode = newComputedValue;
2095         }
2096 
2097         if (mNightMode.get() != MODE_NIGHT_AUTO || (mTwilightManager != null
2098                 && mTwilightManager.getLastTwilightState() != null)) {
2099             resetNightModeOverrideLocked();
2100         }
2101     }
2102 
resetNightModeOverrideLocked()2103     private boolean resetNightModeOverrideLocked() {
2104         if (mOverrideNightModeOff || mOverrideNightModeOn) {
2105             mOverrideNightModeOff = false;
2106             mOverrideNightModeOn = false;
2107             persistNightModeOverrides(mOverrideNightModeUser);
2108             mOverrideNightModeUser = USER_SYSTEM;
2109             return true;
2110         }
2111         return false;
2112     }
2113 
registerVrStateListener()2114     private void registerVrStateListener() {
2115         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
2116                 Context.VR_SERVICE));
2117         try {
2118             if (vrManager != null) {
2119                 vrManager.registerListener(mVrStateCallbacks);
2120             }
2121         } catch (RemoteException e) {
2122             Slog.e(TAG, "Failed to register VR mode state listener: " + e);
2123         }
2124     }
2125 
2126     /**
2127      * Handles "adb shell" commands.
2128      */
2129     private static class Shell extends ShellCommand {
2130         public static final String NIGHT_MODE_STR_YES = "yes";
2131         public static final String NIGHT_MODE_STR_NO = "no";
2132         public static final String NIGHT_MODE_STR_AUTO = "auto";
2133         public static final String NIGHT_MODE_STR_CUSTOM_SCHEDULE = "custom_schedule";
2134         public static final String NIGHT_MODE_STR_CUSTOM_BEDTIME = "custom_bedtime";
2135         public static final String NIGHT_MODE_STR_UNKNOWN = "unknown";
2136         private final IUiModeManager mInterface;
2137 
Shell(IUiModeManager iface)2138         Shell(IUiModeManager iface) {
2139             mInterface = iface;
2140         }
2141 
2142         @Override
onHelp()2143         public void onHelp() {
2144             final PrintWriter pw = getOutPrintWriter();
2145             pw.println("UiModeManager service (uimode) commands:");
2146             pw.println("  help");
2147             pw.println("    Print this help text.");
2148             pw.println("  night [yes|no|auto|custom_schedule|custom_bedtime]");
2149             pw.println("    Set or read night mode.");
2150             pw.println("  car [yes|no]");
2151             pw.println("    Set or read car mode.");
2152             pw.println("  time [start|end] <ISO time>");
2153             pw.println("    Set custom start/end schedule time"
2154                     + " (night mode must be set to custom to apply).");
2155         }
2156 
2157         @Override
onCommand(String cmd)2158         public int onCommand(String cmd) {
2159             if (cmd == null) {
2160                 return handleDefaultCommands(cmd);
2161             }
2162 
2163             try {
2164                 switch (cmd) {
2165                     case "night":
2166                         return handleNightMode();
2167                     case "car":
2168                         return handleCarMode();
2169                     case "time":
2170                         return handleCustomTime();
2171                     default:
2172                         return handleDefaultCommands(cmd);
2173                 }
2174             } catch (RemoteException e) {
2175                 final PrintWriter err = getErrPrintWriter();
2176                 err.println("Remote exception: " + e);
2177             }
2178             return -1;
2179         }
2180 
handleCustomTime()2181         private int handleCustomTime() throws RemoteException {
2182             final String modeStr = getNextArg();
2183             if (modeStr == null) {
2184                 printCustomTime();
2185                 return 0;
2186             }
2187             switch (modeStr) {
2188                 case "start":
2189                     final String start = getNextArg();
2190                     mInterface.setCustomNightModeStart(toMilliSeconds(LocalTime.parse(start)));
2191                     return 0;
2192                 case "end":
2193                     final String end = getNextArg();
2194                     mInterface.setCustomNightModeEnd(toMilliSeconds(LocalTime.parse(end)));
2195                     return 0;
2196                 default:
2197                     getErrPrintWriter().println("command must be in [start|end]");
2198                     return -1;
2199             }
2200         }
2201 
printCustomTime()2202         private void printCustomTime() throws RemoteException {
2203             getOutPrintWriter().println("start " + fromMilliseconds(
2204                     mInterface.getCustomNightModeStart()).toString());
2205             getOutPrintWriter().println("end " + fromMilliseconds(
2206                     mInterface.getCustomNightModeEnd()).toString());
2207         }
2208 
handleNightMode()2209         private int handleNightMode() throws RemoteException {
2210             final PrintWriter err = getErrPrintWriter();
2211             final String modeStr = getNextArg();
2212             if (modeStr == null) {
2213                 printCurrentNightMode();
2214                 return 0;
2215             }
2216 
2217             final int mode = strToNightMode(modeStr);
2218             final int customType = strToNightModeCustomType(modeStr);
2219             if (mode >= 0) {
2220                 mInterface.setNightMode(mode);
2221                 if (mode == UiModeManager.MODE_NIGHT_CUSTOM) {
2222                     mInterface.setNightModeCustomType(customType);
2223                 }
2224                 printCurrentNightMode();
2225                 return 0;
2226             } else {
2227                 err.println("Error: mode must be '" + NIGHT_MODE_STR_YES + "', '"
2228                         + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO
2229                         +  "', or '" + NIGHT_MODE_STR_CUSTOM_SCHEDULE + "', or '"
2230                         + NIGHT_MODE_STR_CUSTOM_BEDTIME + "'");
2231                 return -1;
2232             }
2233         }
2234 
printCurrentNightMode()2235         private void printCurrentNightMode() throws RemoteException {
2236             final PrintWriter pw = getOutPrintWriter();
2237             final int currMode = mInterface.getNightMode();
2238             final int customType = mInterface.getNightModeCustomType();
2239             final String currModeStr = nightModeToStr(currMode, customType);
2240             pw.println("Night mode: " + currModeStr);
2241         }
2242 
nightModeToStr(int mode, int customType)2243         private static String nightModeToStr(int mode, int customType) {
2244             switch (mode) {
2245                 case UiModeManager.MODE_NIGHT_YES:
2246                     return NIGHT_MODE_STR_YES;
2247                 case UiModeManager.MODE_NIGHT_NO:
2248                     return NIGHT_MODE_STR_NO;
2249                 case UiModeManager.MODE_NIGHT_AUTO:
2250                     return NIGHT_MODE_STR_AUTO;
2251                 case MODE_NIGHT_CUSTOM:
2252                     if (customType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE) {
2253                         return NIGHT_MODE_STR_CUSTOM_SCHEDULE;
2254                     }
2255                     if (customType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
2256                         return NIGHT_MODE_STR_CUSTOM_BEDTIME;
2257                     }
2258                 default:
2259                     return NIGHT_MODE_STR_UNKNOWN;
2260             }
2261         }
2262 
strToNightMode(String modeStr)2263         private static int strToNightMode(String modeStr) {
2264             switch (modeStr) {
2265                 case NIGHT_MODE_STR_YES:
2266                     return UiModeManager.MODE_NIGHT_YES;
2267                 case NIGHT_MODE_STR_NO:
2268                     return UiModeManager.MODE_NIGHT_NO;
2269                 case NIGHT_MODE_STR_AUTO:
2270                     return UiModeManager.MODE_NIGHT_AUTO;
2271                 case NIGHT_MODE_STR_CUSTOM_SCHEDULE:
2272                 case NIGHT_MODE_STR_CUSTOM_BEDTIME:
2273                     return UiModeManager.MODE_NIGHT_CUSTOM;
2274                 default:
2275                     return -1;
2276             }
2277         }
2278 
strToNightModeCustomType(String customTypeStr)2279         private static int strToNightModeCustomType(String customTypeStr) {
2280             switch (customTypeStr) {
2281                 case NIGHT_MODE_STR_CUSTOM_BEDTIME:
2282                     return UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
2283                 case NIGHT_MODE_STR_CUSTOM_SCHEDULE:
2284                     return UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE;
2285                 default:
2286                     return -1;
2287             }
2288         }
2289 
handleCarMode()2290         private int handleCarMode() throws RemoteException {
2291             final PrintWriter err = getErrPrintWriter();
2292             final String modeStr = getNextArg();
2293             if (modeStr == null) {
2294                 printCurrentCarMode();
2295                 return 0;
2296             }
2297 
2298             if (modeStr.equals("yes")) {
2299                 mInterface.enableCarMode(0 /* flags */, DEFAULT_PRIORITY, "" /* package */);
2300                 printCurrentCarMode();
2301                 return 0;
2302             } else if (modeStr.equals("no")) {
2303                 mInterface.disableCarMode(0 /* flags */);
2304                 printCurrentCarMode();
2305                 return 0;
2306             } else {
2307                 err.println("Error: mode must be 'yes', or 'no'");
2308                 return -1;
2309             }
2310         }
2311 
printCurrentCarMode()2312         private void printCurrentCarMode() throws RemoteException {
2313             final PrintWriter pw = getOutPrintWriter();
2314             final int currMode = mInterface.getCurrentModeType();
2315             pw.println("Car mode: " + (currMode == Configuration.UI_MODE_TYPE_CAR ? "yes" : "no"));
2316         }
2317     }
2318 
2319     public final class LocalService extends UiModeManagerInternal {
2320 
2321         @Override
isNightMode()2322         public boolean isNightMode() {
2323             synchronized (mLock) {
2324                 final boolean isIt = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_YES) != 0;
2325                 if (LOG) {
2326                     Slog.d(TAG,
2327                         "LocalService.isNightMode(): mNightMode=" + mNightMode
2328                         + "; mComputedNightMode=" + mComputedNightMode
2329                         + "; uiMode=" + mConfiguration.uiMode
2330                         + "; isIt=" + isIt);
2331                 }
2332                 return isIt;
2333             }
2334         }
2335     }
2336 
2337     @VisibleForTesting
2338     public static class Injector {
getCallingUid()2339         public int getCallingUid() {
2340             return Binder.getCallingUid();
2341         }
2342 
startDreamWhenDockedIfAppropriate(Context context)2343         public void startDreamWhenDockedIfAppropriate(Context context) {
2344             Sandman.startDreamWhenDockedIfAppropriate(context);
2345         }
2346     }
2347 
2348     /**
2349      * Interface to contain the value for system night mode. We make the night mode accessible
2350      * through this class to ensure that the reassignment of this value invalidates the cache.
2351      */
2352     private interface NightMode {
get()2353         int get();
set(int mode)2354         void set(int mode);
2355     }
2356 }
2357