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
17package com.android.server;
18
19import android.annotation.Nullable;
20import android.app.Activity;
21import android.app.ActivityManager;
22import android.app.IUiModeManager;
23import android.app.Notification;
24import android.app.NotificationManager;
25import android.app.PendingIntent;
26import android.app.StatusBarManager;
27import android.app.UiModeManager;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.os.BatteryManager;
36import android.os.Binder;
37import android.os.Handler;
38import android.os.PowerManager;
39import android.os.RemoteException;
40import android.os.ResultReceiver;
41import android.os.ServiceManager;
42import android.os.ShellCallback;
43import android.os.ShellCommand;
44import android.os.SystemProperties;
45import android.os.UserHandle;
46import android.provider.Settings;
47import android.service.dreams.Sandman;
48import android.service.vr.IVrManager;
49import android.service.vr.IVrStateCallbacks;
50import android.text.TextUtils;
51import android.util.Slog;
52
53import java.io.File;
54import java.io.FileDescriptor;
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.Collections;
58
59import com.android.internal.R;
60import com.android.internal.app.DisableCarModeActivity;
61import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
62import com.android.internal.notification.SystemNotificationChannels;
63import com.android.internal.util.DumpUtils;
64import com.android.server.power.ShutdownThread;
65import com.android.server.twilight.TwilightListener;
66import com.android.server.twilight.TwilightManager;
67import com.android.server.twilight.TwilightState;
68
69final class UiModeManagerService extends SystemService {
70    private static final String TAG = UiModeManager.class.getSimpleName();
71    private static final boolean LOG = false;
72
73    // Enable launching of applications when entering the dock.
74    private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
75
76    final Object mLock = new Object();
77    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
78
79    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
80    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
81
82    private boolean mCarModeEnabled = false;
83    private boolean mCharging = false;
84    private int mDefaultUiModeType;
85    private boolean mCarModeKeepsScreenOn;
86    private boolean mDeskModeKeepsScreenOn;
87    private boolean mTelevision;
88    private boolean mWatch;
89    private boolean mVrHeadset;
90    private boolean mComputedNightMode;
91    private int mCarModeEnableFlags;
92
93    // flag set by resource, whether to enable Car dock launch when starting car mode.
94    private boolean mEnableCarDockLaunch = true;
95    // flag set by resource, whether to lock UI mode to the default one or not.
96    private boolean mUiModeLocked = false;
97    // flag set by resource, whether to night mode change for normal all or not.
98    private boolean mNightModeLocked = false;
99
100    int mCurUiMode = 0;
101    private int mSetUiMode = 0;
102    private boolean mHoldingConfiguration = false;
103
104    private Configuration mConfiguration = new Configuration();
105    boolean mSystemReady;
106
107    private final Handler mHandler = new Handler();
108
109    private TwilightManager mTwilightManager;
110    private NotificationManager mNotificationManager;
111    private StatusBarManager mStatusBarManager;
112
113    private PowerManager.WakeLock mWakeLock;
114
115    public UiModeManagerService(Context context) {
116        super(context);
117    }
118
119    private static Intent buildHomeIntent(String category) {
120        Intent intent = new Intent(Intent.ACTION_MAIN);
121        intent.addCategory(category);
122        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
123                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
124        return intent;
125    }
126
127    // The broadcast receiver which receives the result of the ordered broadcast sent when
128    // the dock state changes. The original ordered broadcast is sent with an initial result
129    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
130    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
131    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
132        @Override
133        public void onReceive(Context context, Intent intent) {
134            if (getResultCode() != Activity.RESULT_OK) {
135                if (LOG) {
136                    Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
137                            + ": canceled: " + getResultCode());
138                }
139                return;
140            }
141
142            final int enableFlags = intent.getIntExtra("enableFlags", 0);
143            final int disableFlags = intent.getIntExtra("disableFlags", 0);
144            synchronized (mLock) {
145                updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
146            }
147        }
148    };
149
150    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
151        @Override
152        public void onReceive(Context context, Intent intent) {
153            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
154                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
155            updateDockState(state);
156        }
157    };
158
159    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
160        @Override
161        public void onReceive(Context context, Intent intent) {
162            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
163            synchronized (mLock) {
164                if (mSystemReady) {
165                    updateLocked(0, 0);
166                }
167            }
168        }
169    };
170
171    private final TwilightListener mTwilightListener = new TwilightListener() {
172        @Override
173        public void onTwilightStateChanged(@Nullable TwilightState state) {
174            synchronized (mLock) {
175                if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
176                    updateComputedNightModeLocked();
177                    updateLocked(0, 0);
178                }
179            }
180        }
181    };
182
183    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
184        @Override
185        public void onVrStateChanged(boolean enabled) {
186            synchronized (mLock) {
187                mVrHeadset = enabled;
188                if (mSystemReady) {
189                    updateLocked(0, 0);
190                }
191            }
192        }
193    };
194
195    @Override
196    public void onStart() {
197        final Context context = getContext();
198
199        final PowerManager powerManager =
200                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
201        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
202
203        context.registerReceiver(mDockModeReceiver,
204                new IntentFilter(Intent.ACTION_DOCK_EVENT));
205        context.registerReceiver(mBatteryReceiver,
206                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
207
208        mConfiguration.setToDefaults();
209
210        final Resources res = context.getResources();
211        mDefaultUiModeType = res.getInteger(
212                com.android.internal.R.integer.config_defaultUiModeType);
213        mCarModeKeepsScreenOn = (res.getInteger(
214                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
215        mDeskModeKeepsScreenOn = (res.getInteger(
216                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
217        mEnableCarDockLaunch = res.getBoolean(
218                com.android.internal.R.bool.config_enableCarDockHomeLaunch);
219        mUiModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockUiMode);
220        mNightModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockDayNightMode);
221
222        final PackageManager pm = context.getPackageManager();
223        mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
224                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
225        mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
226
227        final int defaultNightMode = res.getInteger(
228                com.android.internal.R.integer.config_defaultNightMode);
229        mNightMode = Settings.Secure.getInt(context.getContentResolver(),
230                Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
231
232        // Update the initial, static configurations.
233        SystemServerInitThreadPool.get().submit(() -> {
234            synchronized (mLock) {
235                updateConfigurationLocked();
236                sendConfigurationLocked();
237            }
238
239        }, TAG + ".onStart");
240        publishBinderService(Context.UI_MODE_SERVICE, mService);
241    }
242
243    private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
244        @Override
245        public void enableCarMode(int flags) {
246            if (isUiModeLocked()) {
247                Slog.e(TAG, "enableCarMode while UI mode is locked");
248                return;
249            }
250            final long ident = Binder.clearCallingIdentity();
251            try {
252                synchronized (mLock) {
253                    setCarModeLocked(true, flags);
254                    if (mSystemReady) {
255                        updateLocked(flags, 0);
256                    }
257                }
258            } finally {
259                Binder.restoreCallingIdentity(ident);
260            }
261        }
262
263        @Override
264        public void disableCarMode(int flags) {
265            if (isUiModeLocked()) {
266                Slog.e(TAG, "disableCarMode while UI mode is locked");
267                return;
268            }
269            final long ident = Binder.clearCallingIdentity();
270            try {
271                synchronized (mLock) {
272                    setCarModeLocked(false, 0);
273                    if (mSystemReady) {
274                        updateLocked(0, flags);
275                    }
276                }
277            } finally {
278                Binder.restoreCallingIdentity(ident);
279            }
280        }
281
282        @Override
283        public int getCurrentModeType() {
284            final long ident = Binder.clearCallingIdentity();
285            try {
286                synchronized (mLock) {
287                    return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
288                }
289            } finally {
290                Binder.restoreCallingIdentity(ident);
291            }
292        }
293
294        @Override
295        public void setNightMode(int mode) {
296            if (isNightModeLocked() &&  (getContext().checkCallingOrSelfPermission(
297                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
298                    != PackageManager.PERMISSION_GRANTED)) {
299                Slog.e(TAG,
300                        "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
301                return;
302            }
303            switch (mode) {
304                case UiModeManager.MODE_NIGHT_NO:
305                case UiModeManager.MODE_NIGHT_YES:
306                case UiModeManager.MODE_NIGHT_AUTO:
307                    break;
308                default:
309                    throw new IllegalArgumentException("Unknown mode: " + mode);
310            }
311
312            final long ident = Binder.clearCallingIdentity();
313            try {
314                synchronized (mLock) {
315                    if (mNightMode != mode) {
316                        Settings.Secure.putInt(getContext().getContentResolver(),
317                                Settings.Secure.UI_NIGHT_MODE, mode);
318                        mNightMode = mode;
319                        updateLocked(0, 0);
320                    }
321                }
322            } finally {
323                Binder.restoreCallingIdentity(ident);
324            }
325        }
326
327        @Override
328        public int getNightMode() {
329            synchronized (mLock) {
330                return mNightMode;
331            }
332        }
333
334        @Override
335        public boolean isUiModeLocked() {
336            synchronized (mLock) {
337                return mUiModeLocked;
338            }
339        }
340
341        @Override
342        public boolean isNightModeLocked() {
343            synchronized (mLock) {
344                return mNightModeLocked;
345            }
346        }
347
348        @Override
349        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
350                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
351            new Shell(mService).exec(mService, in, out, err, args, callback, resultReceiver);
352        }
353
354        @Override
355        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
356            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
357            dumpImpl(pw);
358        }
359    };
360
361    void dumpImpl(PrintWriter pw) {
362        synchronized (mLock) {
363            pw.println("Current UI Mode Service state:");
364            pw.print("  mDockState="); pw.print(mDockState);
365                    pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
366            pw.print("  mNightMode="); pw.print(mNightMode);
367                    pw.print(" mNightModeLocked="); pw.print(mNightModeLocked);
368                    pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
369                    pw.print(" mComputedNightMode="); pw.print(mComputedNightMode);
370                    pw.print(" mCarModeEnableFlags="); pw.print(mCarModeEnableFlags);
371                    pw.print(" mEnableCarDockLaunch="); pw.println(mEnableCarDockLaunch);
372            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
373                    pw.print(" mUiModeLocked="); pw.print(mUiModeLocked);
374                    pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
375            pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
376                    pw.print(" mSystemReady="); pw.println(mSystemReady);
377            if (mTwilightManager != null) {
378                // We may not have a TwilightManager.
379                pw.print("  mTwilightService.getLastTwilightState()=");
380                pw.println(mTwilightManager.getLastTwilightState());
381            }
382        }
383    }
384
385    @Override
386    public void onBootPhase(int phase) {
387        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
388            synchronized (mLock) {
389                mTwilightManager = getLocalService(TwilightManager.class);
390                mSystemReady = true;
391                mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
392                updateComputedNightModeLocked();
393                registerVrStateListener();
394                updateLocked(0, 0);
395            }
396        }
397    }
398
399    void setCarModeLocked(boolean enabled, int flags) {
400        if (mCarModeEnabled != enabled) {
401            mCarModeEnabled = enabled;
402        }
403        mCarModeEnableFlags = flags;
404    }
405
406    private void updateDockState(int newState) {
407        synchronized (mLock) {
408            if (newState != mDockState) {
409                mDockState = newState;
410                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR, 0);
411                if (mSystemReady) {
412                    updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
413                }
414            }
415        }
416    }
417
418    private static boolean isDeskDockState(int state) {
419        switch (state) {
420            case Intent.EXTRA_DOCK_STATE_DESK:
421            case Intent.EXTRA_DOCK_STATE_LE_DESK:
422            case Intent.EXTRA_DOCK_STATE_HE_DESK:
423                return true;
424            default:
425                return false;
426        }
427    }
428
429    private void updateConfigurationLocked() {
430        int uiMode = mDefaultUiModeType;
431        if (mUiModeLocked) {
432            // no-op, keeps default one
433        } else if (mTelevision) {
434            uiMode = Configuration.UI_MODE_TYPE_TELEVISION;
435        } else if (mWatch) {
436            uiMode = Configuration.UI_MODE_TYPE_WATCH;
437        } else if (mCarModeEnabled) {
438            uiMode = Configuration.UI_MODE_TYPE_CAR;
439        } else if (isDeskDockState(mDockState)) {
440            uiMode = Configuration.UI_MODE_TYPE_DESK;
441        } else if (mVrHeadset) {
442            uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
443        }
444
445        if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
446            if (mTwilightManager != null) {
447                mTwilightManager.registerListener(mTwilightListener, mHandler);
448            }
449            updateComputedNightModeLocked();
450            uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
451                    : Configuration.UI_MODE_NIGHT_NO;
452        } else {
453            if (mTwilightManager != null) {
454                mTwilightManager.unregisterListener(mTwilightListener);
455            }
456            uiMode |= mNightMode << 4;
457        }
458
459        if (LOG) {
460            Slog.d(TAG,
461                "updateConfigurationLocked: mDockState=" + mDockState
462                + "; mCarMode=" + mCarModeEnabled
463                + "; mNightMode=" + mNightMode
464                + "; uiMode=" + uiMode);
465        }
466
467        mCurUiMode = uiMode;
468        if (!mHoldingConfiguration) {
469            mConfiguration.uiMode = uiMode;
470        }
471    }
472
473    private void sendConfigurationLocked() {
474        if (mSetUiMode != mConfiguration.uiMode) {
475            mSetUiMode = mConfiguration.uiMode;
476
477            try {
478                ActivityManager.getService().updateConfiguration(mConfiguration);
479            } catch (RemoteException e) {
480                Slog.w(TAG, "Failure communicating with activity manager", e);
481            }
482        }
483    }
484
485    void updateLocked(int enableFlags, int disableFlags) {
486        String action = null;
487        String oldAction = null;
488        if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
489            adjustStatusBarCarModeLocked();
490            oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
491        } else if (isDeskDockState(mLastBroadcastState)) {
492            oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
493        }
494
495        if (mCarModeEnabled) {
496            if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
497                adjustStatusBarCarModeLocked();
498                if (oldAction != null) {
499                    sendForegroundBroadcastToAllUsers(oldAction);
500                }
501                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
502                action = UiModeManager.ACTION_ENTER_CAR_MODE;
503            }
504        } else if (isDeskDockState(mDockState)) {
505            if (!isDeskDockState(mLastBroadcastState)) {
506                if (oldAction != null) {
507                    sendForegroundBroadcastToAllUsers(oldAction);
508                }
509                mLastBroadcastState = mDockState;
510                action = UiModeManager.ACTION_ENTER_DESK_MODE;
511            }
512        } else {
513            mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
514            action = oldAction;
515        }
516
517        if (action != null) {
518            if (LOG) {
519                Slog.v(TAG, String.format(
520                    "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
521                    action, enableFlags, disableFlags));
522            }
523
524            // Send the ordered broadcast; the result receiver will receive after all
525            // broadcasts have been sent. If any broadcast receiver changes the result
526            // code from the initial value of RESULT_OK, then the result receiver will
527            // not launch the corresponding dock application. This gives apps a chance
528            // to override the behavior and stay in their app even when the device is
529            // placed into a dock.
530            Intent intent = new Intent(action);
531            intent.putExtra("enableFlags", enableFlags);
532            intent.putExtra("disableFlags", disableFlags);
533            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
534            getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
535                    mResultReceiver, null, Activity.RESULT_OK, null, null);
536
537            // Attempting to make this transition a little more clean, we are going
538            // to hold off on doing a configuration change until we have finished
539            // the broadcast and started the home activity.
540            mHoldingConfiguration = true;
541            updateConfigurationLocked();
542        } else {
543            String category = null;
544            if (mCarModeEnabled) {
545                if (mEnableCarDockLaunch
546                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
547                    category = Intent.CATEGORY_CAR_DOCK;
548                }
549            } else if (isDeskDockState(mDockState)) {
550                if (ENABLE_LAUNCH_DESK_DOCK_APP
551                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
552                    category = Intent.CATEGORY_DESK_DOCK;
553                }
554            } else {
555                if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
556                    category = Intent.CATEGORY_HOME;
557                }
558            }
559
560            if (LOG) {
561                Slog.v(TAG, "updateLocked: null action, mDockState="
562                        + mDockState +", category=" + category);
563            }
564
565            sendConfigurationAndStartDreamOrDockAppLocked(category);
566        }
567
568        // keep screen on when charging and in car mode
569        boolean keepScreenOn = mCharging &&
570                ((mCarModeEnabled && mCarModeKeepsScreenOn &&
571                  (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
572                 (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
573        if (keepScreenOn != mWakeLock.isHeld()) {
574            if (keepScreenOn) {
575                mWakeLock.acquire();
576            } else {
577                mWakeLock.release();
578            }
579        }
580    }
581
582    private void sendForegroundBroadcastToAllUsers(String action) {
583        getContext().sendBroadcastAsUser(new Intent(action)
584                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), UserHandle.ALL);
585    }
586
587    private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
588        // Launch a dock activity
589        String category = null;
590        if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
591            // Only launch car home when car mode is enabled and the caller
592            // has asked us to switch to it.
593            if (mEnableCarDockLaunch
594                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
595                category = Intent.CATEGORY_CAR_DOCK;
596            }
597        } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
598            // Only launch car home when desk mode is enabled and the caller
599            // has asked us to switch to it.  Currently re-using the car
600            // mode flag since we don't have a formal API for "desk mode".
601            if (ENABLE_LAUNCH_DESK_DOCK_APP
602                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
603                category = Intent.CATEGORY_DESK_DOCK;
604            }
605        } else {
606            // Launch the standard home app if requested.
607            if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
608                category = Intent.CATEGORY_HOME;
609            }
610        }
611
612        if (LOG) {
613            Slog.v(TAG, String.format(
614                "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
615                    + "category=%s",
616                action, enableFlags, disableFlags, category));
617        }
618
619        sendConfigurationAndStartDreamOrDockAppLocked(category);
620    }
621
622    private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
623        // Update the configuration but don't send it yet.
624        mHoldingConfiguration = false;
625        updateConfigurationLocked();
626
627        // Start the dock app, if there is one.
628        boolean dockAppStarted = false;
629        if (category != null) {
630            // Now we are going to be careful about switching the
631            // configuration and starting the activity -- we need to
632            // do this in a specific order under control of the
633            // activity manager, to do it cleanly.  So compute the
634            // new config, but don't set it yet, and let the
635            // activity manager take care of both the start and config
636            // change.
637            Intent homeIntent = buildHomeIntent(category);
638            if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
639                try {
640                    int result = ActivityManager.getService().startActivityWithConfig(
641                            null, null, homeIntent, null, null, null, 0, 0,
642                            mConfiguration, null, UserHandle.USER_CURRENT);
643                    if (ActivityManager.isStartResultSuccessful(result)) {
644                        dockAppStarted = true;
645                    } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
646                        Slog.e(TAG, "Could not start dock app: " + homeIntent
647                                + ", startActivityWithConfig result " + result);
648                    }
649                } catch (RemoteException ex) {
650                    Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
651                }
652            }
653        }
654
655        // Send the new configuration.
656        sendConfigurationLocked();
657
658        // If we did not start a dock app, then start dreaming if supported.
659        if (category != null && !dockAppStarted) {
660            Sandman.startDreamWhenDockedIfAppropriate(getContext());
661        }
662    }
663
664    private void adjustStatusBarCarModeLocked() {
665        final Context context = getContext();
666        if (mStatusBarManager == null) {
667            mStatusBarManager = (StatusBarManager)
668                    context.getSystemService(Context.STATUS_BAR_SERVICE);
669        }
670
671        // Fear not: StatusBarManagerService manages a list of requests to disable
672        // features of the status bar; these are ORed together to form the
673        // active disabled list. So if (for example) the device is locked and
674        // the status bar should be totally disabled, the calls below will
675        // have no effect until the device is unlocked.
676        if (mStatusBarManager != null) {
677            mStatusBarManager.disable(mCarModeEnabled
678                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
679                : StatusBarManager.DISABLE_NONE);
680        }
681
682        if (mNotificationManager == null) {
683            mNotificationManager = (NotificationManager)
684                    context.getSystemService(Context.NOTIFICATION_SERVICE);
685        }
686
687        if (mNotificationManager != null) {
688            if (mCarModeEnabled) {
689                Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
690
691                Notification.Builder n =
692                        new Notification.Builder(context, SystemNotificationChannels.CAR_MODE)
693                        .setSmallIcon(R.drawable.stat_notify_car_mode)
694                        .setDefaults(Notification.DEFAULT_LIGHTS)
695                        .setOngoing(true)
696                        .setWhen(0)
697                        .setColor(context.getColor(
698                                com.android.internal.R.color.system_notification_accent_color))
699                        .setContentTitle(
700                                context.getString(R.string.car_mode_disable_notification_title))
701                        .setContentText(
702                                context.getString(R.string.car_mode_disable_notification_message))
703                        .setContentIntent(
704                                PendingIntent.getActivityAsUser(context, 0, carModeOffIntent, 0,
705                                        null, UserHandle.CURRENT));
706                mNotificationManager.notifyAsUser(null,
707                        SystemMessage.NOTE_CAR_MODE_DISABLE, n.build(), UserHandle.ALL);
708            } else {
709                mNotificationManager.cancelAsUser(null,
710                        SystemMessage.NOTE_CAR_MODE_DISABLE, UserHandle.ALL);
711            }
712        }
713    }
714
715    private void updateComputedNightModeLocked() {
716        if (mTwilightManager != null) {
717            TwilightState state = mTwilightManager.getLastTwilightState();
718            if (state != null) {
719                mComputedNightMode = state.isNight();
720            }
721        }
722    }
723
724    private void registerVrStateListener() {
725        IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
726                Context.VR_SERVICE));
727        try {
728            if (vrManager != null) {
729                vrManager.registerListener(mVrStateCallbacks);
730            }
731        } catch (RemoteException e) {
732            Slog.e(TAG, "Failed to register VR mode state listener: " + e);
733        }
734    }
735
736    /**
737     * Handles "adb shell" commands.
738     */
739    private static class Shell extends ShellCommand {
740        public static final String NIGHT_MODE_STR_YES = "yes";
741        public static final String NIGHT_MODE_STR_NO = "no";
742        public static final String NIGHT_MODE_STR_AUTO = "auto";
743        public static final String NIGHT_MODE_STR_UNKNOWN = "unknown";
744        private final IUiModeManager mInterface;
745
746        Shell(IUiModeManager iface) {
747            mInterface = iface;
748        }
749
750        @Override
751        public void onHelp() {
752            final PrintWriter pw = getOutPrintWriter();
753            pw.println("UiModeManager service (uimode) commands:");
754            pw.println("  help");
755            pw.println("    Print this help text.");
756            pw.println("  night [yes|no|auto]");
757            pw.println("    Set or read night mode.");
758        }
759
760        @Override
761        public int onCommand(String cmd) {
762            if (cmd == null) {
763                return handleDefaultCommands(cmd);
764            }
765
766            try {
767                switch (cmd) {
768                    case "night":
769                        return handleNightMode();
770                    default:
771                        return handleDefaultCommands(cmd);
772                }
773            } catch (RemoteException e) {
774                final PrintWriter err = getErrPrintWriter();
775                err.println("Remote exception: " + e);
776            }
777            return -1;
778        }
779
780        private int handleNightMode() throws RemoteException {
781            final PrintWriter err = getErrPrintWriter();
782            final String modeStr = getNextArg();
783            if (modeStr == null) {
784                printCurrentNightMode();
785                return 0;
786            }
787
788            final int mode = strToNightMode(modeStr);
789            if (mode >= 0) {
790                mInterface.setNightMode(mode);
791                printCurrentNightMode();
792                return 0;
793            } else {
794                err.println("Error: mode must be '" + NIGHT_MODE_STR_YES + "', '"
795                        + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO + "'");
796                return -1;
797            }
798        }
799
800        private void printCurrentNightMode() throws RemoteException {
801            final PrintWriter pw = getOutPrintWriter();
802            final int currMode = mInterface.getNightMode();
803            final String currModeStr = nightModeToStr(currMode);
804            pw.println("Night mode: " + currModeStr);
805        }
806
807        private static String nightModeToStr(int mode) {
808            switch (mode) {
809                case UiModeManager.MODE_NIGHT_YES:
810                    return NIGHT_MODE_STR_YES;
811                case UiModeManager.MODE_NIGHT_NO:
812                    return NIGHT_MODE_STR_NO;
813                case UiModeManager.MODE_NIGHT_AUTO:
814                    return NIGHT_MODE_STR_AUTO;
815                default:
816                    return NIGHT_MODE_STR_UNKNOWN;
817            }
818        }
819
820        private static int strToNightMode(String modeStr) {
821            switch (modeStr) {
822                case NIGHT_MODE_STR_YES:
823                    return UiModeManager.MODE_NIGHT_YES;
824                case NIGHT_MODE_STR_NO:
825                    return UiModeManager.MODE_NIGHT_NO;
826                case NIGHT_MODE_STR_AUTO:
827                    return UiModeManager.MODE_NIGHT_AUTO;
828                default:
829                    return -1;
830            }
831        }
832    }
833}
834