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.app.Activity;
     20 import android.app.ActivityManagerNative;
     21 import android.app.AlarmManager;
     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.ActivityNotFoundException;
     29 import android.content.BroadcastReceiver;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.IntentFilter;
     33 import android.content.pm.PackageManager;
     34 import android.content.res.Configuration;
     35 import android.location.Criteria;
     36 import android.location.Location;
     37 import android.location.LocationListener;
     38 import android.location.LocationManager;
     39 import android.os.BatteryManager;
     40 import android.os.Binder;
     41 import android.os.Bundle;
     42 import android.os.Handler;
     43 import android.os.Message;
     44 import android.os.PowerManager;
     45 import android.os.RemoteException;
     46 import android.os.ServiceManager;
     47 import android.os.SystemClock;
     48 import android.provider.Settings;
     49 import android.text.format.DateUtils;
     50 import android.text.format.Time;
     51 import android.util.Slog;
     52 
     53 import java.io.FileDescriptor;
     54 import java.io.PrintWriter;
     55 import java.util.Iterator;
     56 
     57 import com.android.internal.R;
     58 import com.android.internal.app.DisableCarModeActivity;
     59 
     60 class UiModeManagerService extends IUiModeManager.Stub {
     61     private static final String TAG = UiModeManager.class.getSimpleName();
     62     private static final boolean LOG = false;
     63 
     64     private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
     65 
     66     // Enable launching of applications when entering the dock.
     67     private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
     68     private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
     69 
     70     private static final int MSG_UPDATE_TWILIGHT = 0;
     71     private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
     72     private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
     73 
     74     private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS;
     75     private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
     76     private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
     77     private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
     78     private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 15 * DateUtils.MINUTE_IN_MILLIS;
     79     private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
     80 
     81     private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
     82 
     83     private final Context mContext;
     84 
     85     final Object mLock = new Object();
     86 
     87     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     88     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     89 
     90     private int mNightMode = UiModeManager.MODE_NIGHT_NO;
     91     private boolean mCarModeEnabled = false;
     92     private boolean mCharging = false;
     93     private final boolean mCarModeKeepsScreenOn;
     94     private final boolean mDeskModeKeepsScreenOn;
     95 
     96     private boolean mComputedNightMode;
     97     private int mCurUiMode = 0;
     98     private int mSetUiMode = 0;
     99 
    100     private boolean mHoldingConfiguration = false;
    101     private Configuration mConfiguration = new Configuration();
    102 
    103     private boolean mSystemReady;
    104 
    105     private NotificationManager mNotificationManager;
    106 
    107     private AlarmManager mAlarmManager;
    108 
    109     private LocationManager mLocationManager;
    110     private Location mLocation;
    111     private StatusBarManager mStatusBarManager;
    112     private final PowerManager.WakeLock mWakeLock;
    113 
    114     static Intent buildHomeIntent(String category) {
    115         Intent intent = new Intent(Intent.ACTION_MAIN);
    116         intent.addCategory(category);
    117         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    118                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    119         return intent;
    120     }
    121 
    122     // The broadcast receiver which receives the result of the ordered broadcast sent when
    123     // the dock state changes. The original ordered broadcast is sent with an initial result
    124     // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
    125     // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
    126     private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
    127         @Override
    128         public void onReceive(Context context, Intent intent) {
    129             if (getResultCode() != Activity.RESULT_OK) {
    130                 if (LOG) {
    131                     Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
    132                             + ": canceled: " + getResultCode());
    133                 }
    134                 return;
    135             }
    136 
    137             final int  enableFlags = intent.getIntExtra("enableFlags", 0);
    138             final int  disableFlags = intent.getIntExtra("disableFlags", 0);
    139 
    140             synchronized (mLock) {
    141                 // Launch a dock activity
    142                 String category = null;
    143                 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
    144                     // Only launch car home when car mode is enabled and the caller
    145                     // has asked us to switch to it.
    146                     if (ENABLE_LAUNCH_CAR_DOCK_APP
    147                             && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
    148                         category = Intent.CATEGORY_CAR_DOCK;
    149                     }
    150                 } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) {
    151                     // Only launch car home when desk mode is enabled and the caller
    152                     // has asked us to switch to it.  Currently re-using the car
    153                     // mode flag since we don't have a formal API for "desk mode".
    154                     if (ENABLE_LAUNCH_DESK_DOCK_APP
    155                             && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
    156                         category = Intent.CATEGORY_DESK_DOCK;
    157                     }
    158                 } else {
    159                     // Launch the standard home app if requested.
    160                     if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
    161                         category = Intent.CATEGORY_HOME;
    162                     }
    163                 }
    164 
    165                 if (LOG) {
    166                     Slog.v(TAG, String.format(
    167                         "Handling broadcast result for action %s: enable=0x%08x disable=0x%08x category=%s",
    168                         intent.getAction(), enableFlags, disableFlags, category));
    169                 }
    170 
    171                 if (category != null) {
    172                     // This is the new activity that will serve as home while
    173                     // we are in care mode.
    174                     Intent homeIntent = buildHomeIntent(category);
    175 
    176                     // Now we are going to be careful about switching the
    177                     // configuration and starting the activity -- we need to
    178                     // do this in a specific order under control of the
    179                     // activity manager, to do it cleanly.  So compute the
    180                     // new config, but don't set it yet, and let the
    181                     // activity manager take care of both the start and config
    182                     // change.
    183                     Configuration newConfig = null;
    184                     if (mHoldingConfiguration) {
    185                         mHoldingConfiguration = false;
    186                         updateConfigurationLocked(false);
    187                         newConfig = mConfiguration;
    188                     }
    189                     try {
    190                         ActivityManagerNative.getDefault().startActivityWithConfig(
    191                                 null, homeIntent, null, null, 0, null, null, 0, false, false,
    192                                 newConfig);
    193                         mHoldingConfiguration = false;
    194                     } catch (RemoteException e) {
    195                         Slog.w(TAG, e.getCause());
    196                     }
    197                 }
    198 
    199                 if (mHoldingConfiguration) {
    200                     mHoldingConfiguration = false;
    201                     updateConfigurationLocked(true);
    202                 }
    203             }
    204         }
    205     };
    206 
    207     private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
    208         @Override
    209         public void onReceive(Context context, Intent intent) {
    210             if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
    211                 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
    212             }
    213         }
    214     };
    215 
    216     private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
    217         @Override
    218         public void onReceive(Context context, Intent intent) {
    219             int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
    220                     Intent.EXTRA_DOCK_STATE_UNDOCKED);
    221             updateDockState(state);
    222         }
    223     };
    224 
    225     private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
    226         @Override
    227         public void onReceive(Context context, Intent intent) {
    228             mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
    229             synchronized (mLock) {
    230                 if (mSystemReady) {
    231                     updateLocked(0, 0);
    232                 }
    233             }
    234         }
    235     };
    236 
    237     private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() {
    238         @Override
    239         public void onReceive(Context context, Intent intent) {
    240             if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
    241                 if (!intent.getBooleanExtra("state", false)) {
    242                     // Airplane mode is now off!
    243                     mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
    244                 }
    245             } else {
    246                 // Time zone has changed!
    247                 mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
    248             }
    249         }
    250     };
    251 
    252     // A LocationListener to initialize the network location provider. The location updates
    253     // are handled through the passive location provider.
    254     private final LocationListener mEmptyLocationListener =  new LocationListener() {
    255         public void onLocationChanged(Location location) {
    256         }
    257 
    258         public void onProviderDisabled(String provider) {
    259         }
    260 
    261         public void onProviderEnabled(String provider) {
    262         }
    263 
    264         public void onStatusChanged(String provider, int status, Bundle extras) {
    265         }
    266     };
    267 
    268     private final LocationListener mLocationListener = new LocationListener() {
    269 
    270         public void onLocationChanged(Location location) {
    271             final boolean hasMoved = hasMoved(location);
    272             final boolean hasBetterAccuracy = mLocation == null
    273                     || location.getAccuracy() < mLocation.getAccuracy();
    274             if (hasMoved || hasBetterAccuracy) {
    275                 synchronized (mLock) {
    276                     mLocation = location;
    277                     if (hasMoved && isDoingNightMode()
    278                             && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
    279                         mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
    280                     }
    281                 }
    282             }
    283         }
    284 
    285         public void onProviderDisabled(String provider) {
    286         }
    287 
    288         public void onProviderEnabled(String provider) {
    289         }
    290 
    291         public void onStatusChanged(String provider, int status, Bundle extras) {
    292         }
    293 
    294         /*
    295          * The user has moved if the accuracy circles of the two locations
    296          * don't overlap.
    297          */
    298         private boolean hasMoved(Location location) {
    299             if (location == null) {
    300                 return false;
    301             }
    302             if (mLocation == null) {
    303                 return true;
    304             }
    305 
    306             /* if new location is older than the current one, the devices hasn't
    307              * moved.
    308              */
    309             if (location.getTime() < mLocation.getTime()) {
    310                 return false;
    311             }
    312 
    313             /* Get the distance between the two points */
    314             float distance = mLocation.distanceTo(location);
    315 
    316             /* Get the total accuracy radius for both locations */
    317             float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
    318 
    319             /* If the distance is greater than the combined accuracy of the two
    320              * points then they can't overlap and hence the user has moved.
    321              */
    322             return distance >= totalAccuracy;
    323         }
    324     };
    325 
    326     public UiModeManagerService(Context context) {
    327         mContext = context;
    328 
    329         ServiceManager.addService(Context.UI_MODE_SERVICE, this);
    330 
    331         mAlarmManager =
    332             (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
    333         mLocationManager =
    334             (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
    335         mContext.registerReceiver(mTwilightUpdateReceiver,
    336                 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
    337         mContext.registerReceiver(mDockModeReceiver,
    338                 new IntentFilter(Intent.ACTION_DOCK_EVENT));
    339         mContext.registerReceiver(mBatteryReceiver,
    340                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    341         IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    342         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    343         mContext.registerReceiver(mUpdateLocationReceiver, filter);
    344 
    345         PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    346         mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
    347 
    348         mConfiguration.setToDefaults();
    349 
    350         mCarModeKeepsScreenOn = (context.getResources().getInteger(
    351                 com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
    352         mDeskModeKeepsScreenOn = (context.getResources().getInteger(
    353                 com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
    354 
    355         mNightMode = Settings.Secure.getInt(mContext.getContentResolver(),
    356                 Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
    357     }
    358 
    359     public void disableCarMode(int flags) {
    360         synchronized (mLock) {
    361             setCarModeLocked(false);
    362             if (mSystemReady) {
    363                 updateLocked(0, flags);
    364             }
    365         }
    366     }
    367 
    368     public void enableCarMode(int flags) {
    369         synchronized (mLock) {
    370             setCarModeLocked(true);
    371             if (mSystemReady) {
    372                 updateLocked(flags, 0);
    373             }
    374         }
    375     }
    376 
    377     public int getCurrentModeType() {
    378         synchronized (mLock) {
    379             return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
    380         }
    381     }
    382 
    383     public void setNightMode(int mode) throws RemoteException {
    384         synchronized (mLock) {
    385             switch (mode) {
    386                 case UiModeManager.MODE_NIGHT_NO:
    387                 case UiModeManager.MODE_NIGHT_YES:
    388                 case UiModeManager.MODE_NIGHT_AUTO:
    389                     break;
    390                 default:
    391                     throw new IllegalArgumentException("Unknown mode: " + mode);
    392             }
    393             if (!isDoingNightMode()) {
    394                 return;
    395             }
    396 
    397             if (mNightMode != mode) {
    398                 long ident = Binder.clearCallingIdentity();
    399                 Settings.Secure.putInt(mContext.getContentResolver(),
    400                         Settings.Secure.UI_NIGHT_MODE, mode);
    401                 Binder.restoreCallingIdentity(ident);
    402                 mNightMode = mode;
    403                 updateLocked(0, 0);
    404             }
    405         }
    406     }
    407 
    408     public int getNightMode() throws RemoteException {
    409         return mNightMode;
    410     }
    411 
    412     void systemReady() {
    413         synchronized (mLock) {
    414             mSystemReady = true;
    415             mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
    416             updateLocked(0, 0);
    417             mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
    418         }
    419     }
    420 
    421     boolean isDoingNightMode() {
    422         return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
    423     }
    424 
    425     void setCarModeLocked(boolean enabled) {
    426         if (mCarModeEnabled != enabled) {
    427             mCarModeEnabled = enabled;
    428         }
    429     }
    430 
    431     void updateDockState(int newState) {
    432         synchronized (mLock) {
    433             if (newState != mDockState) {
    434                 mDockState = newState;
    435                 setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
    436                 if (mSystemReady) {
    437                     updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
    438                 }
    439             }
    440         }
    441     }
    442 
    443     final static boolean isDeskDockState(int state) {
    444         switch (state) {
    445             case Intent.EXTRA_DOCK_STATE_DESK:
    446             case Intent.EXTRA_DOCK_STATE_LE_DESK:
    447             case Intent.EXTRA_DOCK_STATE_HE_DESK:
    448                 return true;
    449             default:
    450                 return false;
    451         }
    452     }
    453 
    454     final void updateConfigurationLocked(boolean sendIt) {
    455         int uiMode = Configuration.UI_MODE_TYPE_NORMAL;
    456         if (mCarModeEnabled) {
    457             uiMode = Configuration.UI_MODE_TYPE_CAR;
    458         } else if (isDeskDockState(mDockState)) {
    459             uiMode = Configuration.UI_MODE_TYPE_DESK;
    460         }
    461         if (mCarModeEnabled) {
    462             if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
    463                 updateTwilightLocked();
    464                 uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
    465                         : Configuration.UI_MODE_NIGHT_NO;
    466             } else {
    467                 uiMode |= mNightMode << 4;
    468             }
    469         } else {
    470             // Disabling the car mode clears the night mode.
    471             uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
    472         }
    473 
    474         if (LOG) {
    475             Slog.d(TAG,
    476                 "updateConfigurationLocked: mDockState=" + mDockState
    477                 + "; mCarMode=" + mCarModeEnabled
    478                 + "; mNightMode=" + mNightMode
    479                 + "; uiMode=" + uiMode);
    480         }
    481 
    482         mCurUiMode = uiMode;
    483 
    484         if (!mHoldingConfiguration && uiMode != mSetUiMode) {
    485             mSetUiMode = uiMode;
    486             mConfiguration.uiMode = uiMode;
    487 
    488             if (sendIt) {
    489                 try {
    490                     ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
    491                 } catch (RemoteException e) {
    492                     Slog.w(TAG, "Failure communicating with activity manager", e);
    493                 }
    494             }
    495         }
    496     }
    497 
    498     final void updateLocked(int enableFlags, int disableFlags) {
    499         long ident = Binder.clearCallingIdentity();
    500 
    501         try {
    502             String action = null;
    503             String oldAction = null;
    504             if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
    505                 adjustStatusBarCarModeLocked();
    506                 oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
    507             } else if (isDeskDockState(mLastBroadcastState)) {
    508                 oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
    509             }
    510 
    511             if (mCarModeEnabled) {
    512                 if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
    513                     adjustStatusBarCarModeLocked();
    514 
    515                     if (oldAction != null) {
    516                         mContext.sendBroadcast(new Intent(oldAction));
    517                     }
    518                     mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
    519                     action = UiModeManager.ACTION_ENTER_CAR_MODE;
    520                 }
    521             } else if (isDeskDockState(mDockState)) {
    522                 if (!isDeskDockState(mLastBroadcastState)) {
    523                     if (oldAction != null) {
    524                         mContext.sendBroadcast(new Intent(oldAction));
    525                     }
    526                     mLastBroadcastState = mDockState;
    527                     action = UiModeManager.ACTION_ENTER_DESK_MODE;
    528                 }
    529             } else {
    530                 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
    531                 action = oldAction;
    532             }
    533 
    534             if (action != null) {
    535                 if (LOG) {
    536                     Slog.v(TAG, String.format(
    537                         "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
    538                         action, enableFlags, disableFlags));
    539                 }
    540 
    541                 // Send the ordered broadcast; the result receiver will receive after all
    542                 // broadcasts have been sent. If any broadcast receiver changes the result
    543                 // code from the initial value of RESULT_OK, then the result receiver will
    544                 // not launch the corresponding dock application. This gives apps a chance
    545                 // to override the behavior and stay in their app even when the device is
    546                 // placed into a dock.
    547                 Intent intent = new Intent(action);
    548                 intent.putExtra("enableFlags", enableFlags);
    549                 intent.putExtra("disableFlags", disableFlags);
    550                 mContext.sendOrderedBroadcast(intent, null,
    551                         mResultReceiver, null, Activity.RESULT_OK, null, null);
    552                 // Attempting to make this transition a little more clean, we are going
    553                 // to hold off on doing a configuration change until we have finished
    554                 // the broadcast and started the home activity.
    555                 mHoldingConfiguration = true;
    556             } else {
    557                 Intent homeIntent = null;
    558                 if (mCarModeEnabled) {
    559                     if (ENABLE_LAUNCH_CAR_DOCK_APP
    560                             && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
    561                         homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK);
    562                     }
    563                 } else if (isDeskDockState(mDockState)) {
    564                     if (ENABLE_LAUNCH_DESK_DOCK_APP
    565                             && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
    566                         homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK);
    567                     }
    568                 } else {
    569                     if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
    570                         homeIntent = buildHomeIntent(Intent.CATEGORY_HOME);
    571                     }
    572                 }
    573 
    574                 if (LOG) {
    575                     Slog.v(TAG, "updateLocked: null action, mDockState="
    576                             + mDockState +", firing homeIntent: " + homeIntent);
    577                 }
    578 
    579                 if (homeIntent != null) {
    580                     try {
    581                         mContext.startActivity(homeIntent);
    582                     } catch (ActivityNotFoundException e) {
    583                     }
    584                 }
    585             }
    586 
    587             updateConfigurationLocked(true);
    588 
    589             // keep screen on when charging and in car mode
    590             boolean keepScreenOn = mCharging &&
    591                     ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
    592                      (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
    593             if (keepScreenOn != mWakeLock.isHeld()) {
    594                 if (keepScreenOn) {
    595                     mWakeLock.acquire();
    596                 } else {
    597                     mWakeLock.release();
    598                 }
    599             }
    600         } finally {
    601             Binder.restoreCallingIdentity(ident);
    602         }
    603     }
    604 
    605     private void adjustStatusBarCarModeLocked() {
    606         if (mStatusBarManager == null) {
    607             mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
    608         }
    609 
    610         // Fear not: StatusBarManagerService manages a list of requests to disable
    611         // features of the status bar; these are ORed together to form the
    612         // active disabled list. So if (for example) the device is locked and
    613         // the status bar should be totally disabled, the calls below will
    614         // have no effect until the device is unlocked.
    615         if (mStatusBarManager != null) {
    616             mStatusBarManager.disable(mCarModeEnabled
    617                 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
    618                 : StatusBarManager.DISABLE_NONE);
    619         }
    620 
    621         if (mNotificationManager == null) {
    622             mNotificationManager = (NotificationManager)
    623                     mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    624         }
    625 
    626         if (mNotificationManager != null) {
    627             if (mCarModeEnabled) {
    628                 Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
    629 
    630                 Notification n = new Notification();
    631                 n.icon = R.drawable.stat_notify_car_mode;
    632                 n.defaults = Notification.DEFAULT_LIGHTS;
    633                 n.flags = Notification.FLAG_ONGOING_EVENT;
    634                 n.when = 0;
    635                 n.setLatestEventInfo(
    636                         mContext,
    637                         mContext.getString(R.string.car_mode_disable_notification_title),
    638                         mContext.getString(R.string.car_mode_disable_notification_message),
    639                         PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
    640                 mNotificationManager.notify(0, n);
    641             } else {
    642                 mNotificationManager.cancel(0);
    643             }
    644         }
    645     }
    646 
    647     private final Handler mHandler = new Handler() {
    648 
    649         boolean mPassiveListenerEnabled;
    650         boolean mNetworkListenerEnabled;
    651         boolean mDidFirstInit;
    652         long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS;
    653 
    654         @Override
    655         public void handleMessage(Message msg) {
    656             switch (msg.what) {
    657                 case MSG_UPDATE_TWILIGHT:
    658                     synchronized (mLock) {
    659                         if (isDoingNightMode() && mLocation != null
    660                                 && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
    661                             updateTwilightLocked();
    662                             updateLocked(0, 0);
    663                         }
    664                     }
    665                     break;
    666                 case MSG_GET_NEW_LOCATION_UPDATE:
    667                     if (!mNetworkListenerEnabled) {
    668                         // Don't do anything -- we are still trying to get a
    669                         // location.
    670                         return;
    671                     }
    672                     if ((mLastNetworkRegisterTime+MIN_LOCATION_UPDATE_MS)
    673                             >= SystemClock.elapsedRealtime()) {
    674                         // Don't do anything -- it hasn't been long enough
    675                         // since we last requested an update.
    676                         return;
    677                     }
    678 
    679                     // Unregister the current location monitor, so we can
    680                     // register a new one for it to get an immediate update.
    681                     mNetworkListenerEnabled = false;
    682                     mLocationManager.removeUpdates(mEmptyLocationListener);
    683 
    684                     // Fall through to re-register listener.
    685                 case MSG_ENABLE_LOCATION_UPDATES:
    686                     // enable network provider to receive at least location updates for a given
    687                     // distance.
    688                     boolean networkLocationEnabled;
    689                     try {
    690                         networkLocationEnabled =
    691                             mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    692                     } catch (Exception e) {
    693                         // we may get IllegalArgumentException if network location provider
    694                         // does not exist or is not yet installed.
    695                         networkLocationEnabled = false;
    696                     }
    697                     if (!mNetworkListenerEnabled && networkLocationEnabled) {
    698                         mNetworkListenerEnabled = true;
    699                         mLastNetworkRegisterTime = SystemClock.elapsedRealtime();
    700                         mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
    701                                 LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
    702 
    703                         if (!mDidFirstInit) {
    704                             mDidFirstInit = true;
    705                             if (mLocation == null) {
    706                                 retrieveLocation();
    707                             }
    708                             synchronized (mLock) {
    709                                 if (isDoingNightMode() && mLocation != null
    710                                         && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
    711                                     updateTwilightLocked();
    712                                     updateLocked(0, 0);
    713                                 }
    714                             }
    715                         }
    716                     }
    717                    // enable passive provider to receive updates from location fixes (gps
    718                    // and network).
    719                    boolean passiveLocationEnabled;
    720                     try {
    721                         passiveLocationEnabled =
    722                             mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER);
    723                     } catch (Exception e) {
    724                         // we may get IllegalArgumentException if passive location provider
    725                         // does not exist or is not yet installed.
    726                         passiveLocationEnabled = false;
    727                     }
    728                     if (!mPassiveListenerEnabled && passiveLocationEnabled) {
    729                         mPassiveListenerEnabled = true;
    730                         mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
    731                                 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener);
    732                     }
    733                     if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
    734                         long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
    735                         interval *= 1.5;
    736                         if (interval == 0) {
    737                             interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
    738                         } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
    739                             interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
    740                         }
    741                         Bundle bundle = new Bundle();
    742                         bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
    743                         Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
    744                         newMsg.setData(bundle);
    745                         mHandler.sendMessageDelayed(newMsg, interval);
    746                     }
    747                     break;
    748             }
    749         }
    750 
    751         private void retrieveLocation() {
    752             Location location = null;
    753             final Iterator<String> providers =
    754                     mLocationManager.getProviders(new Criteria(), true).iterator();
    755             while (providers.hasNext()) {
    756                 final Location lastKnownLocation =
    757                         mLocationManager.getLastKnownLocation(providers.next());
    758                 // pick the most recent location
    759                 if (location == null || (lastKnownLocation != null &&
    760                         location.getTime() < lastKnownLocation.getTime())) {
    761                     location = lastKnownLocation;
    762                 }
    763             }
    764             // In the case there is no location available (e.g. GPS fix or network location
    765             // is not available yet), the longitude of the location is estimated using the timezone,
    766             // latitude and accuracy are set to get a good average.
    767             if (location == null) {
    768                 Time currentTime = new Time();
    769                 currentTime.set(System.currentTimeMillis());
    770                 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
    771                         (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
    772                 location = new Location("fake");
    773                 location.setLongitude(lngOffset);
    774                 location.setLatitude(0);
    775                 location.setAccuracy(417000.0f);
    776                 location.setTime(System.currentTimeMillis());
    777             }
    778             synchronized (mLock) {
    779                 mLocation = location;
    780             }
    781         }
    782     };
    783 
    784     void updateTwilightLocked() {
    785         if (mLocation == null) {
    786             return;
    787         }
    788         final long currentTime = System.currentTimeMillis();
    789         boolean nightMode;
    790         // calculate current twilight
    791         TwilightCalculator tw = new TwilightCalculator();
    792         tw.calculateTwilight(currentTime,
    793                 mLocation.getLatitude(), mLocation.getLongitude());
    794         if (tw.mState == TwilightCalculator.DAY) {
    795             nightMode = false;
    796         } else {
    797             nightMode = true;
    798         }
    799 
    800         // schedule next update
    801         long nextUpdate = 0;
    802         if (tw.mSunrise == -1 || tw.mSunset == -1) {
    803             // In the case the day or night never ends the update is scheduled 12 hours later.
    804             nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
    805         } else {
    806             final int mLastTwilightState = tw.mState;
    807             // add some extra time to be on the save side.
    808             nextUpdate += DateUtils.MINUTE_IN_MILLIS;
    809             if (currentTime > tw.mSunset) {
    810                 // next update should be on the following day
    811                 tw.calculateTwilight(currentTime
    812                         + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
    813                         mLocation.getLongitude());
    814             }
    815 
    816             if (mLastTwilightState == TwilightCalculator.NIGHT) {
    817                 nextUpdate += tw.mSunrise;
    818             } else {
    819                 nextUpdate += tw.mSunset;
    820             }
    821         }
    822 
    823         Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
    824         PendingIntent pendingIntent =
    825                 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
    826         mAlarmManager.cancel(pendingIntent);
    827         mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
    828 
    829         mComputedNightMode = nightMode;
    830     }
    831 
    832     @Override
    833     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    834         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    835                 != PackageManager.PERMISSION_GRANTED) {
    836 
    837             pw.println("Permission Denial: can't dump uimode service from from pid="
    838                     + Binder.getCallingPid()
    839                     + ", uid=" + Binder.getCallingUid());
    840             return;
    841         }
    842 
    843         synchronized (mLock) {
    844             pw.println("Current UI Mode Service state:");
    845             pw.print("  mDockState="); pw.print(mDockState);
    846                     pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
    847             pw.print("  mNightMode="); pw.print(mNightMode);
    848                     pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
    849                     pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
    850             pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
    851                     pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
    852             pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
    853                     pw.print(" mSystemReady="); pw.println(mSystemReady);
    854             if (mLocation != null) {
    855                 pw.print("  mLocation="); pw.println(mLocation);
    856             }
    857         }
    858     }
    859 }
    860