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