Home | History | Annotate | Download | only in server
      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 android.annotation.Nullable;
     20 import android.app.Activity;
     21 import android.app.ActivityManager;
     22 import android.app.IUiModeManager;
     23 import android.app.Notification;
     24 import android.app.NotificationManager;
     25 import android.app.PendingIntent;
     26 import android.app.StatusBarManager;
     27 import android.app.UiModeManager;
     28 import android.content.BroadcastReceiver;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.content.pm.PackageManager;
     33 import android.content.res.Configuration;
     34 import android.content.res.Resources;
     35 import android.os.BatteryManager;
     36 import android.os.Binder;
     37 import android.os.Handler;
     38 import android.os.PowerManager;
     39 import android.os.RemoteException;
     40 import android.os.ResultReceiver;
     41 import android.os.ServiceManager;
     42 import android.os.ShellCallback;
     43 import android.os.ShellCommand;
     44 import android.os.SystemProperties;
     45 import android.os.UserHandle;
     46 import android.provider.Settings;
     47 import android.service.dreams.Sandman;
     48 import android.service.vr.IVrManager;
     49 import android.service.vr.IVrStateCallbacks;
     50 import android.text.TextUtils;
     51 import android.util.Slog;
     52 
     53 import java.io.File;
     54 import java.io.FileDescriptor;
     55 import java.io.PrintWriter;
     56 import java.util.ArrayList;
     57 import java.util.Collections;
     58 
     59 import com.android.internal.R;
     60 import com.android.internal.app.DisableCarModeActivity;
     61 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
     62 import com.android.internal.notification.SystemNotificationChannels;
     63 import com.android.internal.util.DumpUtils;
     64 import com.android.server.power.ShutdownThread;
     65 import com.android.server.twilight.TwilightListener;
     66 import com.android.server.twilight.TwilightManager;
     67 import com.android.server.twilight.TwilightState;
     68 
     69 final 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