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