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