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