UiModeManagerService.java revision 2416e09649eb6ab767eba458796e126196c77a34
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 17package com.android.server; 18 19import android.app.Activity; 20import android.app.ActivityManagerNative; 21import android.app.IUiModeManager; 22import android.app.Notification; 23import android.app.NotificationManager; 24import android.app.PendingIntent; 25import android.app.StatusBarManager; 26import android.app.UiModeManager; 27import android.content.ActivityNotFoundException; 28import android.content.BroadcastReceiver; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.content.pm.PackageManager; 33import android.content.res.Configuration; 34import android.os.BatteryManager; 35import android.os.Binder; 36import android.os.Handler; 37import android.os.PowerManager; 38import android.os.RemoteException; 39import android.os.ServiceManager; 40import android.provider.Settings; 41import android.util.Slog; 42 43import java.io.FileDescriptor; 44import java.io.PrintWriter; 45 46import com.android.internal.R; 47import com.android.internal.app.DisableCarModeActivity; 48import com.android.server.TwilightService.TwilightState; 49 50class UiModeManagerService extends IUiModeManager.Stub { 51 private static final String TAG = UiModeManager.class.getSimpleName(); 52 private static final boolean LOG = false; 53 54 // Enable launching of applications when entering the dock. 55 private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; 56 private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; 57 58 private final Context mContext; 59 private final TwilightService mTwilightService; 60 private final Handler mHandler = new Handler(); 61 62 final Object mLock = new Object(); 63 64 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; 65 private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; 66 67 private int mNightMode = UiModeManager.MODE_NIGHT_NO; 68 private boolean mCarModeEnabled = false; 69 private boolean mCharging = false; 70 private final int mDefaultUiModeType; 71 private final boolean mCarModeKeepsScreenOn; 72 private final boolean mDeskModeKeepsScreenOn; 73 private final boolean mTelevision; 74 75 private boolean mComputedNightMode; 76 private int mCurUiMode = 0; 77 private int mSetUiMode = 0; 78 79 private boolean mHoldingConfiguration = false; 80 private Configuration mConfiguration = new Configuration(); 81 82 private boolean mSystemReady; 83 84 private NotificationManager mNotificationManager; 85 86 private StatusBarManager mStatusBarManager; 87 private final PowerManager.WakeLock mWakeLock; 88 89 static Intent buildHomeIntent(String category) { 90 Intent intent = new Intent(Intent.ACTION_MAIN); 91 intent.addCategory(category); 92 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 93 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 94 return intent; 95 } 96 97 // The broadcast receiver which receives the result of the ordered broadcast sent when 98 // the dock state changes. The original ordered broadcast is sent with an initial result 99 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., 100 // to RESULT_CANCELED, then the intent to start a dock app will not be sent. 101 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() { 102 @Override 103 public void onReceive(Context context, Intent intent) { 104 if (getResultCode() != Activity.RESULT_OK) { 105 if (LOG) { 106 Slog.v(TAG, "Handling broadcast result for action " + intent.getAction() 107 + ": canceled: " + getResultCode()); 108 } 109 return; 110 } 111 112 final int enableFlags = intent.getIntExtra("enableFlags", 0); 113 final int disableFlags = intent.getIntExtra("disableFlags", 0); 114 115 synchronized (mLock) { 116 // Launch a dock activity 117 String category = null; 118 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { 119 // Only launch car home when car mode is enabled and the caller 120 // has asked us to switch to it. 121 if (ENABLE_LAUNCH_CAR_DOCK_APP 122 && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { 123 category = Intent.CATEGORY_CAR_DOCK; 124 } 125 } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) { 126 // Only launch car home when desk mode is enabled and the caller 127 // has asked us to switch to it. Currently re-using the car 128 // mode flag since we don't have a formal API for "desk mode". 129 if (ENABLE_LAUNCH_DESK_DOCK_APP 130 && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { 131 category = Intent.CATEGORY_DESK_DOCK; 132 } 133 } else { 134 // Launch the standard home app if requested. 135 if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { 136 category = Intent.CATEGORY_HOME; 137 } 138 } 139 140 if (LOG) { 141 Slog.v(TAG, String.format( 142 "Handling broadcast result for action %s: enable=0x%08x disable=0x%08x category=%s", 143 intent.getAction(), enableFlags, disableFlags, category)); 144 } 145 146 if (category != null) { 147 // This is the new activity that will serve as home while 148 // we are in care mode. 149 Intent homeIntent = buildHomeIntent(category); 150 151 // Now we are going to be careful about switching the 152 // configuration and starting the activity -- we need to 153 // do this in a specific order under control of the 154 // activity manager, to do it cleanly. So compute the 155 // new config, but don't set it yet, and let the 156 // activity manager take care of both the start and config 157 // change. 158 Configuration newConfig = null; 159 if (mHoldingConfiguration) { 160 mHoldingConfiguration = false; 161 updateConfigurationLocked(false); 162 newConfig = mConfiguration; 163 } 164 try { 165 ActivityManagerNative.getDefault().startActivityWithConfig( 166 null, homeIntent, null, null, null, 0, 0, 167 newConfig, null); 168 mHoldingConfiguration = false; 169 } catch (RemoteException e) { 170 Slog.w(TAG, e.getCause()); 171 } 172 } 173 174 if (mHoldingConfiguration) { 175 mHoldingConfiguration = false; 176 updateConfigurationLocked(true); 177 } 178 } 179 } 180 }; 181 182 private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() { 183 @Override 184 public void onReceive(Context context, Intent intent) { 185 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, 186 Intent.EXTRA_DOCK_STATE_UNDOCKED); 187 updateDockState(state); 188 } 189 }; 190 191 private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { 192 @Override 193 public void onReceive(Context context, Intent intent) { 194 mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); 195 synchronized (mLock) { 196 if (mSystemReady) { 197 updateLocked(0, 0); 198 } 199 } 200 } 201 }; 202 203 private final TwilightService.TwilightListener mTwilightListener = 204 new TwilightService.TwilightListener() { 205 @Override 206 public void onTwilightStateChanged() { 207 updateTwilight(); 208 } 209 }; 210 211 public UiModeManagerService(Context context, TwilightService twilight) { 212 mContext = context; 213 mTwilightService = twilight; 214 215 ServiceManager.addService(Context.UI_MODE_SERVICE, this); 216 217 mContext.registerReceiver(mDockModeReceiver, 218 new IntentFilter(Intent.ACTION_DOCK_EVENT)); 219 mContext.registerReceiver(mBatteryReceiver, 220 new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 221 222 PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 223 mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); 224 225 mConfiguration.setToDefaults(); 226 227 mDefaultUiModeType = context.getResources().getInteger( 228 com.android.internal.R.integer.config_defaultUiModeType); 229 mCarModeKeepsScreenOn = (context.getResources().getInteger( 230 com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1); 231 mDeskModeKeepsScreenOn = (context.getResources().getInteger( 232 com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1); 233 mTelevision = context.getPackageManager().hasSystemFeature( 234 PackageManager.FEATURE_TELEVISION); 235 236 mNightMode = Settings.Secure.getInt(mContext.getContentResolver(), 237 Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO); 238 239 mTwilightService.registerListener(mTwilightListener, mHandler); 240 } 241 242 public void disableCarMode(int flags) { 243 synchronized (mLock) { 244 setCarModeLocked(false); 245 if (mSystemReady) { 246 updateLocked(0, flags); 247 } 248 } 249 } 250 251 public void enableCarMode(int flags) { 252 synchronized (mLock) { 253 setCarModeLocked(true); 254 if (mSystemReady) { 255 updateLocked(flags, 0); 256 } 257 } 258 } 259 260 public int getCurrentModeType() { 261 synchronized (mLock) { 262 return mCurUiMode & Configuration.UI_MODE_TYPE_MASK; 263 } 264 } 265 266 public void setNightMode(int mode) throws RemoteException { 267 synchronized (mLock) { 268 switch (mode) { 269 case UiModeManager.MODE_NIGHT_NO: 270 case UiModeManager.MODE_NIGHT_YES: 271 case UiModeManager.MODE_NIGHT_AUTO: 272 break; 273 default: 274 throw new IllegalArgumentException("Unknown mode: " + mode); 275 } 276 if (!isDoingNightMode()) { 277 return; 278 } 279 280 if (mNightMode != mode) { 281 long ident = Binder.clearCallingIdentity(); 282 Settings.Secure.putInt(mContext.getContentResolver(), 283 Settings.Secure.UI_NIGHT_MODE, mode); 284 Binder.restoreCallingIdentity(ident); 285 mNightMode = mode; 286 updateLocked(0, 0); 287 } 288 } 289 } 290 291 public int getNightMode() throws RemoteException { 292 return mNightMode; 293 } 294 295 void systemReady() { 296 synchronized (mLock) { 297 mSystemReady = true; 298 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; 299 updateComputedNightModeLocked(); 300 updateLocked(0, 0); 301 } 302 } 303 304 boolean isDoingNightMode() { 305 return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED; 306 } 307 308 void setCarModeLocked(boolean enabled) { 309 if (mCarModeEnabled != enabled) { 310 mCarModeEnabled = enabled; 311 } 312 } 313 314 void updateDockState(int newState) { 315 synchronized (mLock) { 316 if (newState != mDockState) { 317 mDockState = newState; 318 setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR); 319 if (mSystemReady) { 320 updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0); 321 } 322 } 323 } 324 } 325 326 final static boolean isDeskDockState(int state) { 327 switch (state) { 328 case Intent.EXTRA_DOCK_STATE_DESK: 329 case Intent.EXTRA_DOCK_STATE_LE_DESK: 330 case Intent.EXTRA_DOCK_STATE_HE_DESK: 331 return true; 332 default: 333 return false; 334 } 335 } 336 337 final void updateConfigurationLocked(boolean sendIt) { 338 int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION 339 : mDefaultUiModeType; 340 if (mCarModeEnabled) { 341 uiMode = Configuration.UI_MODE_TYPE_CAR; 342 } else if (isDeskDockState(mDockState)) { 343 uiMode = Configuration.UI_MODE_TYPE_DESK; 344 } 345 if (mCarModeEnabled) { 346 if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { 347 updateComputedNightModeLocked(); 348 uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES 349 : Configuration.UI_MODE_NIGHT_NO; 350 } else { 351 uiMode |= mNightMode << 4; 352 } 353 } else { 354 // Disabling the car mode clears the night mode. 355 uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO; 356 } 357 358 if (LOG) { 359 Slog.d(TAG, 360 "updateConfigurationLocked: mDockState=" + mDockState 361 + "; mCarMode=" + mCarModeEnabled 362 + "; mNightMode=" + mNightMode 363 + "; uiMode=" + uiMode); 364 } 365 366 mCurUiMode = uiMode; 367 368 if (!mHoldingConfiguration && uiMode != mSetUiMode) { 369 mSetUiMode = uiMode; 370 mConfiguration.uiMode = uiMode; 371 372 if (sendIt) { 373 try { 374 ActivityManagerNative.getDefault().updateConfiguration(mConfiguration); 375 } catch (RemoteException e) { 376 Slog.w(TAG, "Failure communicating with activity manager", e); 377 } 378 } 379 } 380 } 381 382 final void updateLocked(int enableFlags, int disableFlags) { 383 long ident = Binder.clearCallingIdentity(); 384 385 try { 386 String action = null; 387 String oldAction = null; 388 if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { 389 adjustStatusBarCarModeLocked(); 390 oldAction = UiModeManager.ACTION_EXIT_CAR_MODE; 391 } else if (isDeskDockState(mLastBroadcastState)) { 392 oldAction = UiModeManager.ACTION_EXIT_DESK_MODE; 393 } 394 395 if (mCarModeEnabled) { 396 if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) { 397 adjustStatusBarCarModeLocked(); 398 399 if (oldAction != null) { 400 mContext.sendBroadcast(new Intent(oldAction)); 401 } 402 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR; 403 action = UiModeManager.ACTION_ENTER_CAR_MODE; 404 } 405 } else if (isDeskDockState(mDockState)) { 406 if (!isDeskDockState(mLastBroadcastState)) { 407 if (oldAction != null) { 408 mContext.sendBroadcast(new Intent(oldAction)); 409 } 410 mLastBroadcastState = mDockState; 411 action = UiModeManager.ACTION_ENTER_DESK_MODE; 412 } 413 } else { 414 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; 415 action = oldAction; 416 } 417 418 if (action != null) { 419 if (LOG) { 420 Slog.v(TAG, String.format( 421 "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x", 422 action, enableFlags, disableFlags)); 423 } 424 425 // Send the ordered broadcast; the result receiver will receive after all 426 // broadcasts have been sent. If any broadcast receiver changes the result 427 // code from the initial value of RESULT_OK, then the result receiver will 428 // not launch the corresponding dock application. This gives apps a chance 429 // to override the behavior and stay in their app even when the device is 430 // placed into a dock. 431 Intent intent = new Intent(action); 432 intent.putExtra("enableFlags", enableFlags); 433 intent.putExtra("disableFlags", disableFlags); 434 mContext.sendOrderedBroadcast(intent, null, 435 mResultReceiver, null, Activity.RESULT_OK, null, null); 436 // Attempting to make this transition a little more clean, we are going 437 // to hold off on doing a configuration change until we have finished 438 // the broadcast and started the home activity. 439 mHoldingConfiguration = true; 440 } else { 441 Intent homeIntent = null; 442 if (mCarModeEnabled) { 443 if (ENABLE_LAUNCH_CAR_DOCK_APP 444 && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { 445 homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK); 446 } 447 } else if (isDeskDockState(mDockState)) { 448 if (ENABLE_LAUNCH_DESK_DOCK_APP 449 && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { 450 homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK); 451 } 452 } else { 453 if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { 454 homeIntent = buildHomeIntent(Intent.CATEGORY_HOME); 455 } 456 } 457 458 if (LOG) { 459 Slog.v(TAG, "updateLocked: null action, mDockState=" 460 + mDockState +", firing homeIntent: " + homeIntent); 461 } 462 463 if (homeIntent != null) { 464 try { 465 mContext.startActivity(homeIntent); 466 } catch (ActivityNotFoundException e) { 467 } 468 } 469 } 470 471 updateConfigurationLocked(true); 472 473 // keep screen on when charging and in car mode 474 boolean keepScreenOn = mCharging && 475 ((mCarModeEnabled && mCarModeKeepsScreenOn) || 476 (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn)); 477 if (keepScreenOn != mWakeLock.isHeld()) { 478 if (keepScreenOn) { 479 mWakeLock.acquire(); 480 } else { 481 mWakeLock.release(); 482 } 483 } 484 } finally { 485 Binder.restoreCallingIdentity(ident); 486 } 487 } 488 489 private void adjustStatusBarCarModeLocked() { 490 if (mStatusBarManager == null) { 491 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); 492 } 493 494 // Fear not: StatusBarManagerService manages a list of requests to disable 495 // features of the status bar; these are ORed together to form the 496 // active disabled list. So if (for example) the device is locked and 497 // the status bar should be totally disabled, the calls below will 498 // have no effect until the device is unlocked. 499 if (mStatusBarManager != null) { 500 mStatusBarManager.disable(mCarModeEnabled 501 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER 502 : StatusBarManager.DISABLE_NONE); 503 } 504 505 if (mNotificationManager == null) { 506 mNotificationManager = (NotificationManager) 507 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 508 } 509 510 if (mNotificationManager != null) { 511 if (mCarModeEnabled) { 512 Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class); 513 514 Notification n = new Notification(); 515 n.icon = R.drawable.stat_notify_car_mode; 516 n.defaults = Notification.DEFAULT_LIGHTS; 517 n.flags = Notification.FLAG_ONGOING_EVENT; 518 n.when = 0; 519 n.setLatestEventInfo( 520 mContext, 521 mContext.getString(R.string.car_mode_disable_notification_title), 522 mContext.getString(R.string.car_mode_disable_notification_message), 523 PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0)); 524 mNotificationManager.notify(0, n); 525 } else { 526 mNotificationManager.cancel(0); 527 } 528 } 529 } 530 531 private void updateTwilight() { 532 synchronized (mLock) { 533 if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { 534 updateComputedNightModeLocked(); 535 updateLocked(0, 0); 536 } 537 } 538 } 539 540 private void updateComputedNightModeLocked() { 541 TwilightState state = mTwilightService.getCurrentState(); 542 if (state != null) { 543 mComputedNightMode = state.isNight(); 544 } 545 } 546 547 @Override 548 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 549 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 550 != PackageManager.PERMISSION_GRANTED) { 551 552 pw.println("Permission Denial: can't dump uimode service from from pid=" 553 + Binder.getCallingPid() 554 + ", uid=" + Binder.getCallingUid()); 555 return; 556 } 557 558 synchronized (mLock) { 559 pw.println("Current UI Mode Service state:"); 560 pw.print(" mDockState="); pw.print(mDockState); 561 pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); 562 pw.print(" mNightMode="); pw.print(mNightMode); 563 pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); 564 pw.print(" mComputedNightMode="); pw.println(mComputedNightMode); 565 pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); 566 pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); 567 pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); 568 pw.print(" mSystemReady="); pw.println(mSystemReady); 569 pw.print(" mTwilightService.getCurrentState()="); 570 pw.println(mTwilightService.getCurrentState()); 571 } 572 } 573} 574