| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server; |
| |
| import android.app.Activity; |
| import android.app.ActivityManagerNative; |
| import android.app.AlarmManager; |
| import android.app.IUiModeManager; |
| import android.app.Notification; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.StatusBarManager; |
| import android.app.UiModeManager; |
| import android.content.ActivityNotFoundException; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.location.Criteria; |
| import android.location.Location; |
| import android.location.LocationListener; |
| import android.location.LocationManager; |
| import android.os.BatteryManager; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.provider.Settings; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.util.Slog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.Iterator; |
| |
| import com.android.internal.R; |
| import com.android.internal.app.DisableCarModeActivity; |
| |
| class UiModeManagerService extends IUiModeManager.Stub { |
| private static final String TAG = UiModeManager.class.getSimpleName(); |
| private static final boolean LOG = false; |
| |
| private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL"; |
| |
| private static final int MSG_UPDATE_TWILIGHT = 0; |
| private static final int MSG_ENABLE_LOCATION_UPDATES = 1; |
| private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; |
| |
| private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; |
| private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; |
| private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; |
| private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; |
| private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 15 * DateUtils.MINUTE_IN_MILLIS; |
| private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; |
| |
| private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE"; |
| |
| private final Context mContext; |
| |
| final Object mLock = new Object(); |
| |
| private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; |
| private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; |
| |
| private int mNightMode = UiModeManager.MODE_NIGHT_NO; |
| private boolean mCarModeEnabled = false; |
| private boolean mCharging = false; |
| private final boolean mCarModeKeepsScreenOn; |
| private final boolean mDeskModeKeepsScreenOn; |
| |
| private boolean mComputedNightMode; |
| private int mCurUiMode = 0; |
| private int mSetUiMode = 0; |
| |
| private boolean mHoldingConfiguration = false; |
| private Configuration mConfiguration = new Configuration(); |
| |
| private boolean mSystemReady; |
| |
| private NotificationManager mNotificationManager; |
| |
| private AlarmManager mAlarmManager; |
| |
| private LocationManager mLocationManager; |
| private Location mLocation; |
| private StatusBarManager mStatusBarManager; |
| private final PowerManager.WakeLock mWakeLock; |
| |
| static Intent buildHomeIntent(String category) { |
| Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.addCategory(category); |
| intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); |
| return intent; |
| } |
| |
| // The broadcast receiver which receives the result of the ordered broadcast sent when |
| // the dock state changes. The original ordered broadcast is sent with an initial result |
| // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., |
| // to RESULT_CANCELED, then the intent to start a dock app will not be sent. |
| private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (getResultCode() != Activity.RESULT_OK) { |
| return; |
| } |
| |
| final int enableFlags = intent.getIntExtra("enableFlags", 0); |
| final int disableFlags = intent.getIntExtra("disableFlags", 0); |
| |
| synchronized (mLock) { |
| // Launch a dock activity |
| String category = null; |
| if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { |
| // Only launch car home when car mode is enabled and the caller |
| // has asked us to switch to it. |
| if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { |
| category = Intent.CATEGORY_CAR_DOCK; |
| } |
| } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) { |
| // Only launch car home when desk mode is enabled and the caller |
| // has asked us to switch to it. Currently re-using the car |
| // mode flag since we don't have a formal API for "desk mode". |
| if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { |
| category = Intent.CATEGORY_DESK_DOCK; |
| } |
| } else { |
| // Launch the standard home app if requested. |
| if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { |
| category = Intent.CATEGORY_HOME; |
| } |
| } |
| |
| if (category != null) { |
| // This is the new activity that will serve as home while |
| // we are in care mode. |
| Intent homeIntent = buildHomeIntent(category); |
| |
| // Now we are going to be careful about switching the |
| // configuration and starting the activity -- we need to |
| // do this in a specific order under control of the |
| // activity manager, to do it cleanly. So compute the |
| // new config, but don't set it yet, and let the |
| // activity manager take care of both the start and config |
| // change. |
| Configuration newConfig = null; |
| if (mHoldingConfiguration) { |
| mHoldingConfiguration = false; |
| updateConfigurationLocked(false); |
| newConfig = mConfiguration; |
| } |
| try { |
| ActivityManagerNative.getDefault().startActivityWithConfig( |
| null, homeIntent, null, null, 0, null, null, 0, false, false, |
| newConfig); |
| mHoldingConfiguration = false; |
| } catch (RemoteException e) { |
| Slog.w(TAG, e.getCause()); |
| } |
| } |
| |
| if (mHoldingConfiguration) { |
| mHoldingConfiguration = false; |
| updateConfigurationLocked(true); |
| } |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { |
| mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, |
| Intent.EXTRA_DOCK_STATE_UNDOCKED); |
| updateDockState(state); |
| } |
| }; |
| |
| private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); |
| synchronized (mLock) { |
| if (mSystemReady) { |
| updateLocked(0, 0); |
| } |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) { |
| if (!intent.getBooleanExtra("state", false)) { |
| // Airplane mode is now off! |
| mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); |
| } |
| } else { |
| // Time zone has changed! |
| mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); |
| } |
| } |
| }; |
| |
| // A LocationListener to initialize the network location provider. The location updates |
| // are handled through the passive location provider. |
| private final LocationListener mEmptyLocationListener = new LocationListener() { |
| public void onLocationChanged(Location location) { |
| } |
| |
| public void onProviderDisabled(String provider) { |
| } |
| |
| public void onProviderEnabled(String provider) { |
| } |
| |
| public void onStatusChanged(String provider, int status, Bundle extras) { |
| } |
| }; |
| |
| private final LocationListener mLocationListener = new LocationListener() { |
| |
| public void onLocationChanged(Location location) { |
| final boolean hasMoved = hasMoved(location); |
| final boolean hasBetterAccuracy = mLocation == null |
| || location.getAccuracy() < mLocation.getAccuracy(); |
| if (hasMoved || hasBetterAccuracy) { |
| synchronized (mLock) { |
| mLocation = location; |
| if (hasMoved && isDoingNightMode() |
| && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { |
| mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); |
| } |
| } |
| } |
| } |
| |
| public void onProviderDisabled(String provider) { |
| } |
| |
| public void onProviderEnabled(String provider) { |
| } |
| |
| public void onStatusChanged(String provider, int status, Bundle extras) { |
| } |
| |
| /* |
| * The user has moved if the accuracy circles of the two locations |
| * don't overlap. |
| */ |
| private boolean hasMoved(Location location) { |
| if (location == null) { |
| return false; |
| } |
| if (mLocation == null) { |
| return true; |
| } |
| |
| /* if new location is older than the current one, the devices hasn't |
| * moved. |
| */ |
| if (location.getTime() < mLocation.getTime()) { |
| return false; |
| } |
| |
| /* Get the distance between the two points */ |
| float distance = mLocation.distanceTo(location); |
| |
| /* Get the total accuracy radius for both locations */ |
| float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy(); |
| |
| /* If the distance is greater than the combined accuracy of the two |
| * points then they can't overlap and hence the user has moved. |
| */ |
| return distance >= totalAccuracy; |
| } |
| }; |
| |
| public UiModeManagerService(Context context) { |
| mContext = context; |
| |
| ServiceManager.addService(Context.UI_MODE_SERVICE, this); |
| |
| mAlarmManager = |
| (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); |
| mLocationManager = |
| (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); |
| mContext.registerReceiver(mTwilightUpdateReceiver, |
| new IntentFilter(ACTION_UPDATE_NIGHT_MODE)); |
| mContext.registerReceiver(mDockModeReceiver, |
| new IntentFilter(Intent.ACTION_DOCK_EVENT)); |
| mContext.registerReceiver(mBatteryReceiver, |
| new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); |
| IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); |
| mContext.registerReceiver(mUpdateLocationReceiver, filter); |
| |
| PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); |
| mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); |
| |
| mConfiguration.setToDefaults(); |
| |
| mCarModeKeepsScreenOn = (context.getResources().getInteger( |
| com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1); |
| mDeskModeKeepsScreenOn = (context.getResources().getInteger( |
| com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1); |
| |
| mNightMode = Settings.Secure.getInt(mContext.getContentResolver(), |
| Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO); |
| } |
| |
| public void disableCarMode(int flags) { |
| synchronized (mLock) { |
| setCarModeLocked(false); |
| if (mSystemReady) { |
| updateLocked(0, flags); |
| } |
| } |
| } |
| |
| public void enableCarMode(int flags) { |
| synchronized (mLock) { |
| setCarModeLocked(true); |
| if (mSystemReady) { |
| updateLocked(flags, 0); |
| } |
| } |
| } |
| |
| public int getCurrentModeType() { |
| synchronized (mLock) { |
| return mCurUiMode & Configuration.UI_MODE_TYPE_MASK; |
| } |
| } |
| |
| public void setNightMode(int mode) throws RemoteException { |
| synchronized (mLock) { |
| switch (mode) { |
| case UiModeManager.MODE_NIGHT_NO: |
| case UiModeManager.MODE_NIGHT_YES: |
| case UiModeManager.MODE_NIGHT_AUTO: |
| break; |
| default: |
| throw new IllegalArgumentException("Unknown mode: " + mode); |
| } |
| if (!isDoingNightMode()) { |
| return; |
| } |
| |
| if (mNightMode != mode) { |
| long ident = Binder.clearCallingIdentity(); |
| Settings.Secure.putInt(mContext.getContentResolver(), |
| Settings.Secure.UI_NIGHT_MODE, mode); |
| Binder.restoreCallingIdentity(ident); |
| mNightMode = mode; |
| updateLocked(0, 0); |
| } |
| } |
| } |
| |
| public int getNightMode() throws RemoteException { |
| return mNightMode; |
| } |
| |
| void systemReady() { |
| synchronized (mLock) { |
| mSystemReady = true; |
| mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; |
| updateLocked(0, 0); |
| mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); |
| } |
| } |
| |
| boolean isDoingNightMode() { |
| return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED; |
| } |
| |
| void setCarModeLocked(boolean enabled) { |
| if (mCarModeEnabled != enabled) { |
| mCarModeEnabled = enabled; |
| } |
| } |
| |
| void updateDockState(int newState) { |
| synchronized (mLock) { |
| if (newState != mDockState) { |
| mDockState = newState; |
| setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR); |
| if (mSystemReady) { |
| updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0); |
| } |
| } |
| } |
| } |
| |
| final void updateConfigurationLocked(boolean sendIt) { |
| int uiMode = Configuration.UI_MODE_TYPE_NORMAL; |
| if (mCarModeEnabled) { |
| uiMode = Configuration.UI_MODE_TYPE_CAR; |
| } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { |
| uiMode = Configuration.UI_MODE_TYPE_DESK; |
| } |
| if (mCarModeEnabled) { |
| if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { |
| updateTwilightLocked(); |
| uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES |
| : Configuration.UI_MODE_NIGHT_NO; |
| } else { |
| uiMode |= mNightMode << 4; |
| } |
| } else { |
| // Disabling the car mode clears the night mode. |
| uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO; |
| } |
| |
| if (LOG) { |
| Slog.d(TAG, |
| "updateConfigurationLocked: mDockState=" + mDockState |
| + "; mCarMode=" + mCarModeEnabled |
| + "; mNightMode=" + mNightMode |
| + "; uiMode=" + uiMode); |
| } |
| |
| mCurUiMode = uiMode; |
| |
| if (!mHoldingConfiguration && uiMode != mSetUiMode) { |
| mSetUiMode = uiMode; |
| mConfiguration.uiMode = uiMode; |
| |
| if (sendIt) { |
| try { |
| ActivityManagerNative.getDefault().updateConfiguration(mConfiguration); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Failure communicating with activity manager", e); |
| } |
| } |
| } |
| } |
| |
| final void updateLocked(int enableFlags, int disableFlags) { |
| long ident = Binder.clearCallingIdentity(); |
| |
| try { |
| String action = null; |
| String oldAction = null; |
| if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { |
| adjustStatusBarCarModeLocked(); |
| oldAction = UiModeManager.ACTION_EXIT_CAR_MODE; |
| } else if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_DESK) { |
| oldAction = UiModeManager.ACTION_EXIT_DESK_MODE; |
| } |
| |
| if (mCarModeEnabled) { |
| if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) { |
| adjustStatusBarCarModeLocked(); |
| |
| if (oldAction != null) { |
| mContext.sendBroadcast(new Intent(oldAction)); |
| } |
| mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR; |
| action = UiModeManager.ACTION_ENTER_CAR_MODE; |
| } |
| } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { |
| if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_DESK) { |
| if (oldAction != null) { |
| mContext.sendBroadcast(new Intent(oldAction)); |
| } |
| mLastBroadcastState = Intent.EXTRA_DOCK_STATE_DESK; |
| action = UiModeManager.ACTION_ENTER_DESK_MODE; |
| } |
| } else { |
| mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; |
| action = oldAction; |
| } |
| |
| if (action != null) { |
| // Send the ordered broadcast; the result receiver will receive after all |
| // broadcasts have been sent. If any broadcast receiver changes the result |
| // code from the initial value of RESULT_OK, then the result receiver will |
| // not launch the corresponding dock application. This gives apps a chance |
| // to override the behavior and stay in their app even when the device is |
| // placed into a dock. |
| Intent intent = new Intent(action); |
| intent.putExtra("enableFlags", enableFlags); |
| intent.putExtra("disableFlags", disableFlags); |
| mContext.sendOrderedBroadcast(intent, null, |
| mResultReceiver, null, Activity.RESULT_OK, null, null); |
| // Attempting to make this transition a little more clean, we are going |
| // to hold off on doing a configuration change until we have finished |
| // the broadcast and started the home activity. |
| mHoldingConfiguration = true; |
| } else { |
| Intent homeIntent = null; |
| if (mCarModeEnabled) { |
| if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { |
| homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK); |
| } |
| } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { |
| if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { |
| homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK); |
| } |
| } else { |
| if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { |
| homeIntent = buildHomeIntent(Intent.CATEGORY_HOME); |
| } |
| } |
| if (homeIntent != null) { |
| try { |
| mContext.startActivity(homeIntent); |
| } catch (ActivityNotFoundException e) { |
| } |
| } |
| } |
| |
| updateConfigurationLocked(true); |
| |
| // keep screen on when charging and in car mode |
| boolean keepScreenOn = mCharging && |
| ((mCarModeEnabled && mCarModeKeepsScreenOn) || |
| (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn)); |
| if (keepScreenOn != mWakeLock.isHeld()) { |
| if (keepScreenOn) { |
| mWakeLock.acquire(); |
| } else { |
| mWakeLock.release(); |
| } |
| } |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private void adjustStatusBarCarModeLocked() { |
| if (mStatusBarManager == null) { |
| mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); |
| } |
| |
| // Fear not: StatusBarManagerService manages a list of requests to disable |
| // features of the status bar; these are ORed together to form the |
| // active disabled list. So if (for example) the device is locked and |
| // the status bar should be totally disabled, the calls below will |
| // have no effect until the device is unlocked. |
| if (mStatusBarManager != null) { |
| mStatusBarManager.disable(mCarModeEnabled |
| ? StatusBarManager.DISABLE_NOTIFICATION_TICKER |
| : StatusBarManager.DISABLE_NONE); |
| } |
| |
| if (mNotificationManager == null) { |
| mNotificationManager = (NotificationManager) |
| mContext.getSystemService(Context.NOTIFICATION_SERVICE); |
| } |
| |
| if (mNotificationManager != null) { |
| if (mCarModeEnabled) { |
| Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class); |
| |
| Notification n = new Notification(); |
| n.icon = R.drawable.stat_notify_car_mode; |
| n.defaults = Notification.DEFAULT_LIGHTS; |
| n.flags = Notification.FLAG_ONGOING_EVENT; |
| n.when = 0; |
| n.setLatestEventInfo( |
| mContext, |
| mContext.getString(R.string.car_mode_disable_notification_title), |
| mContext.getString(R.string.car_mode_disable_notification_message), |
| PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0)); |
| mNotificationManager.notify(0, n); |
| } else { |
| mNotificationManager.cancel(0); |
| } |
| } |
| } |
| |
| private final Handler mHandler = new Handler() { |
| |
| boolean mPassiveListenerEnabled; |
| boolean mNetworkListenerEnabled; |
| boolean mDidFirstInit; |
| long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_UPDATE_TWILIGHT: |
| synchronized (mLock) { |
| if (isDoingNightMode() && mLocation != null |
| && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { |
| updateTwilightLocked(); |
| updateLocked(0, 0); |
| } |
| } |
| break; |
| case MSG_GET_NEW_LOCATION_UPDATE: |
| if (!mNetworkListenerEnabled) { |
| // Don't do anything -- we are still trying to get a |
| // location. |
| return; |
| } |
| if ((mLastNetworkRegisterTime+MIN_LOCATION_UPDATE_MS) |
| >= SystemClock.elapsedRealtime()) { |
| // Don't do anything -- it hasn't been long enough |
| // since we last requested an update. |
| return; |
| } |
| |
| // Unregister the current location monitor, so we can |
| // register a new one for it to get an immediate update. |
| mNetworkListenerEnabled = false; |
| mLocationManager.removeUpdates(mEmptyLocationListener); |
| |
| // Fall through to re-register listener. |
| case MSG_ENABLE_LOCATION_UPDATES: |
| // enable network provider to receive at least location updates for a given |
| // distance. |
| boolean networkLocationEnabled; |
| try { |
| networkLocationEnabled = |
| mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); |
| } catch (Exception e) { |
| // we may get IllegalArgumentException if network location provider |
| // does not exist or is not yet installed. |
| networkLocationEnabled = false; |
| } |
| if (!mNetworkListenerEnabled && networkLocationEnabled) { |
| mNetworkListenerEnabled = true; |
| mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); |
| mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, |
| LOCATION_UPDATE_MS, 0, mEmptyLocationListener); |
| |
| if (!mDidFirstInit) { |
| mDidFirstInit = true; |
| if (mLocation == null) { |
| retrieveLocation(); |
| } |
| synchronized (mLock) { |
| if (isDoingNightMode() && mLocation != null |
| && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { |
| updateTwilightLocked(); |
| updateLocked(0, 0); |
| } |
| } |
| } |
| } |
| // enable passive provider to receive updates from location fixes (gps |
| // and network). |
| boolean passiveLocationEnabled; |
| try { |
| passiveLocationEnabled = |
| mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); |
| } catch (Exception e) { |
| // we may get IllegalArgumentException if passive location provider |
| // does not exist or is not yet installed. |
| passiveLocationEnabled = false; |
| } |
| if (!mPassiveListenerEnabled && passiveLocationEnabled) { |
| mPassiveListenerEnabled = true; |
| mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, |
| 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); |
| } |
| if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { |
| long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL); |
| interval *= 1.5; |
| if (interval == 0) { |
| interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; |
| } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { |
| interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; |
| } |
| Bundle bundle = new Bundle(); |
| bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval); |
| Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES); |
| newMsg.setData(bundle); |
| mHandler.sendMessageDelayed(newMsg, interval); |
| } |
| break; |
| } |
| } |
| |
| private void retrieveLocation() { |
| Location location = null; |
| final Iterator<String> providers = |
| mLocationManager.getProviders(new Criteria(), true).iterator(); |
| while (providers.hasNext()) { |
| final Location lastKnownLocation = |
| mLocationManager.getLastKnownLocation(providers.next()); |
| // pick the most recent location |
| if (location == null || (lastKnownLocation != null && |
| location.getTime() < lastKnownLocation.getTime())) { |
| location = lastKnownLocation; |
| } |
| } |
| // In the case there is no location available (e.g. GPS fix or network location |
| // is not available yet), the longitude of the location is estimated using the timezone, |
| // latitude and accuracy are set to get a good average. |
| if (location == null) { |
| Time currentTime = new Time(); |
| currentTime.set(System.currentTimeMillis()); |
| double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * |
| (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); |
| location = new Location("fake"); |
| location.setLongitude(lngOffset); |
| location.setLatitude(0); |
| location.setAccuracy(417000.0f); |
| location.setTime(System.currentTimeMillis()); |
| } |
| synchronized (mLock) { |
| mLocation = location; |
| } |
| } |
| }; |
| |
| void updateTwilightLocked() { |
| if (mLocation == null) { |
| return; |
| } |
| final long currentTime = System.currentTimeMillis(); |
| boolean nightMode; |
| // calculate current twilight |
| TwilightCalculator tw = new TwilightCalculator(); |
| tw.calculateTwilight(currentTime, |
| mLocation.getLatitude(), mLocation.getLongitude()); |
| if (tw.mState == TwilightCalculator.DAY) { |
| nightMode = false; |
| } else { |
| nightMode = true; |
| } |
| |
| // schedule next update |
| long nextUpdate = 0; |
| if (tw.mSunrise == -1 || tw.mSunset == -1) { |
| // In the case the day or night never ends the update is scheduled 12 hours later. |
| nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS; |
| } else { |
| final int mLastTwilightState = tw.mState; |
| // add some extra time to be on the save side. |
| nextUpdate += DateUtils.MINUTE_IN_MILLIS; |
| if (currentTime > tw.mSunset) { |
| // next update should be on the following day |
| tw.calculateTwilight(currentTime |
| + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(), |
| mLocation.getLongitude()); |
| } |
| |
| if (mLastTwilightState == TwilightCalculator.NIGHT) { |
| nextUpdate += tw.mSunrise; |
| } else { |
| nextUpdate += tw.mSunset; |
| } |
| } |
| |
| Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE); |
| PendingIntent pendingIntent = |
| PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); |
| mAlarmManager.cancel(pendingIntent); |
| mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); |
| |
| mComputedNightMode = nightMode; |
| } |
| |
| @Override |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) |
| != PackageManager.PERMISSION_GRANTED) { |
| |
| pw.println("Permission Denial: can't dump uimode service from from pid=" |
| + Binder.getCallingPid() |
| + ", uid=" + Binder.getCallingUid()); |
| return; |
| } |
| |
| synchronized (mLock) { |
| pw.println("Current UI Mode Service state:"); |
| pw.print(" mDockState="); pw.print(mDockState); |
| pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); |
| pw.print(" mNightMode="); pw.print(mNightMode); |
| pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); |
| pw.print(" mComputedNightMode="); pw.println(mComputedNightMode); |
| pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); |
| pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); |
| pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); |
| pw.print(" mSystemReady="); pw.println(mSystemReady); |
| if (mLocation != null) { |
| pw.print(" mLocation="); pw.println(mLocation); |
| } |
| } |
| } |
| } |