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