summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/UiModeManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/UiModeManagerService.java')
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java618
1 files changed, 618 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
new file mode 100644
index 0000000..de912dc
--- /dev/null
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IUiModeManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.dreams.Sandman;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.R;
+import com.android.internal.app.DisableCarModeActivity;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+final class UiModeManagerService extends SystemService {
+ private static final String TAG = UiModeManager.class.getSimpleName();
+ private static final boolean LOG = false;
+
+ // Enable launching of applications when entering the dock.
+ private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
+ private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
+
+ final Object mLock = new Object();
+ private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+ private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+ private boolean mCarModeEnabled = false;
+ private boolean mCharging = false;
+ private int mDefaultUiModeType;
+ private boolean mCarModeKeepsScreenOn;
+ private boolean mDeskModeKeepsScreenOn;
+ private boolean mTelevision;
+ private boolean mComputedNightMode;
+
+ int mCurUiMode = 0;
+ private int mSetUiMode = 0;
+ private boolean mHoldingConfiguration = false;
+
+ private Configuration mConfiguration = new Configuration();
+ boolean mSystemReady;
+
+ private final Handler mHandler = new Handler();
+
+ private TwilightManager mTwilightManager;
+ private NotificationManager mNotificationManager;
+ private StatusBarManager mStatusBarManager;
+
+ private PowerManager.WakeLock mWakeLock;
+
+ private static Intent buildHomeIntent(String category) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ return intent;
+ }
+
+ // The broadcast receiver which receives the result of the ordered broadcast sent when
+ // the dock state changes. The original ordered broadcast is sent with an initial result
+ // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
+ // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
+ private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getResultCode() != Activity.RESULT_OK) {
+ if (LOG) {
+ Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
+ + ": canceled: " + getResultCode());
+ }
+ return;
+ }
+
+ final int enableFlags = intent.getIntExtra("enableFlags", 0);
+ final int disableFlags = intent.getIntExtra("disableFlags", 0);
+ synchronized (mLock) {
+ updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ updateDockState(state);
+ }
+ };
+
+ private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
+ synchronized (mLock) {
+ if (mSystemReady) {
+ updateLocked(0, 0);
+ }
+ }
+ }
+ };
+
+ private final TwilightListener mTwilightListener = new TwilightListener() {
+ @Override
+ public void onTwilightStateChanged() {
+ updateTwilight();
+ }
+ };
+
+ @Override
+ public void onStart() {
+ final Context context = getContext();
+ mTwilightManager = getLocalService(TwilightManager.class);
+ final PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+
+ context.registerReceiver(mDockModeReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ context.registerReceiver(mBatteryReceiver,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+ mConfiguration.setToDefaults();
+
+ mDefaultUiModeType = context.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultUiModeType);
+ mCarModeKeepsScreenOn = (context.getResources().getInteger(
+ com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
+ mDeskModeKeepsScreenOn = (context.getResources().getInteger(
+ com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
+ mTelevision = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEVISION);
+
+ mNightMode = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
+
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+
+ publishBinderService(Context.UI_MODE_SERVICE, mService);
+ }
+
+ private final IBinder mService = new IUiModeManager.Stub() {
+ @Override
+ public void enableCarMode(int flags) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setCarModeLocked(true);
+ if (mSystemReady) {
+ updateLocked(flags, 0);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void disableCarMode(int flags) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setCarModeLocked(false);
+ if (mSystemReady) {
+ updateLocked(0, flags);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public int getCurrentModeType() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public void setNightMode(int mode) {
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_YES:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (isDoingNightModeLocked() && mNightMode != mode) {
+ Settings.Secure.putInt(getContext().getContentResolver(),
+ Settings.Secure.UI_NIGHT_MODE, mode);
+ mNightMode = mode;
+ updateLocked(0, 0);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public int getNightMode() {
+ synchronized (mLock) {
+ return mNightMode;
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ pw.println("Permission Denial: can't dump uimode service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ dumpImpl(pw);
+ }
+ };
+
+ void dumpImpl(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("Current UI Mode Service state:");
+ pw.print(" mDockState="); pw.print(mDockState);
+ pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
+ pw.print(" mNightMode="); pw.print(mNightMode);
+ pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
+ pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
+ pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+ pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
+ pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration);
+ pw.print(" mSystemReady="); pw.println(mSystemReady);
+ pw.print(" mTwilightService.getCurrentState()=");
+ pw.println(mTwilightManager.getCurrentState());
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ synchronized (mLock) {
+ mSystemReady = true;
+ mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
+ updateComputedNightModeLocked();
+ updateLocked(0, 0);
+ }
+ }
+ }
+
+ boolean isDoingNightModeLocked() {
+ return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+
+ void setCarModeLocked(boolean enabled) {
+ if (mCarModeEnabled != enabled) {
+ mCarModeEnabled = enabled;
+ }
+ }
+
+ private void updateDockState(int newState) {
+ synchronized (mLock) {
+ if (newState != mDockState) {
+ mDockState = newState;
+ setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
+ if (mSystemReady) {
+ updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
+ }
+ }
+ }
+ }
+
+ private static boolean isDeskDockState(int state) {
+ switch (state) {
+ case Intent.EXTRA_DOCK_STATE_DESK:
+ case Intent.EXTRA_DOCK_STATE_LE_DESK:
+ case Intent.EXTRA_DOCK_STATE_HE_DESK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void updateConfigurationLocked() {
+ int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION : mDefaultUiModeType;
+ if (mCarModeEnabled) {
+ uiMode = Configuration.UI_MODE_TYPE_CAR;
+ } else if (isDeskDockState(mDockState)) {
+ uiMode = Configuration.UI_MODE_TYPE_DESK;
+ }
+ if (mCarModeEnabled) {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ updateComputedNightModeLocked();
+ uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
+ : Configuration.UI_MODE_NIGHT_NO;
+ } else {
+ uiMode |= mNightMode << 4;
+ }
+ } else {
+ // Disabling the car mode clears the night mode.
+ uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
+ }
+
+ if (LOG) {
+ Slog.d(TAG,
+ "updateConfigurationLocked: mDockState=" + mDockState
+ + "; mCarMode=" + mCarModeEnabled
+ + "; mNightMode=" + mNightMode
+ + "; uiMode=" + uiMode);
+ }
+
+ mCurUiMode = uiMode;
+ if (!mHoldingConfiguration) {
+ mConfiguration.uiMode = uiMode;
+ }
+ }
+
+ private void sendConfigurationLocked() {
+ if (mSetUiMode != mConfiguration.uiMode) {
+ mSetUiMode = mConfiguration.uiMode;
+
+ try {
+ ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failure communicating with activity manager", e);
+ }
+ }
+ }
+
+ void updateLocked(int enableFlags, int disableFlags) {
+ String action = null;
+ String oldAction = null;
+ if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
+ adjustStatusBarCarModeLocked();
+ oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
+ } else if (isDeskDockState(mLastBroadcastState)) {
+ oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
+ }
+
+ if (mCarModeEnabled) {
+ if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
+ adjustStatusBarCarModeLocked();
+
+ if (oldAction != null) {
+ getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ }
+ mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
+ action = UiModeManager.ACTION_ENTER_CAR_MODE;
+ }
+ } else if (isDeskDockState(mDockState)) {
+ if (!isDeskDockState(mLastBroadcastState)) {
+ if (oldAction != null) {
+ getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ }
+ mLastBroadcastState = mDockState;
+ action = UiModeManager.ACTION_ENTER_DESK_MODE;
+ }
+ } else {
+ mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ action = oldAction;
+ }
+
+ if (action != null) {
+ if (LOG) {
+ Slog.v(TAG, String.format(
+ "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
+ action, enableFlags, disableFlags));
+ }
+
+ // Send the ordered broadcast; the result receiver will receive after all
+ // broadcasts have been sent. If any broadcast receiver changes the result
+ // code from the initial value of RESULT_OK, then the result receiver will
+ // not launch the corresponding dock application. This gives apps a chance
+ // to override the behavior and stay in their app even when the device is
+ // placed into a dock.
+ Intent intent = new Intent(action);
+ intent.putExtra("enableFlags", enableFlags);
+ intent.putExtra("disableFlags", disableFlags);
+ getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ mResultReceiver, null, Activity.RESULT_OK, null, null);
+
+ // Attempting to make this transition a little more clean, we are going
+ // to hold off on doing a configuration change until we have finished
+ // the broadcast and started the home activity.
+ mHoldingConfiguration = true;
+ updateConfigurationLocked();
+ } else {
+ String category = null;
+ if (mCarModeEnabled) {
+ if (ENABLE_LAUNCH_CAR_DOCK_APP
+ && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+ category = Intent.CATEGORY_CAR_DOCK;
+ }
+ } else if (isDeskDockState(mDockState)) {
+ if (ENABLE_LAUNCH_DESK_DOCK_APP
+ && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+ category = Intent.CATEGORY_DESK_DOCK;
+ }
+ } else {
+ if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
+ category = Intent.CATEGORY_HOME;
+ }
+ }
+
+ if (LOG) {
+ Slog.v(TAG, "updateLocked: null action, mDockState="
+ + mDockState +", category=" + category);
+ }
+
+ sendConfigurationAndStartDreamOrDockAppLocked(category);
+ }
+
+ // keep screen on when charging and in car mode
+ boolean keepScreenOn = mCharging &&
+ ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
+ (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+ if (keepScreenOn != mWakeLock.isHeld()) {
+ if (keepScreenOn) {
+ mWakeLock.acquire();
+ } else {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
+ // Launch a dock activity
+ String category = null;
+ if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
+ // Only launch car home when car mode is enabled and the caller
+ // has asked us to switch to it.
+ if (ENABLE_LAUNCH_CAR_DOCK_APP
+ && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+ category = Intent.CATEGORY_CAR_DOCK;
+ }
+ } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
+ // Only launch car home when desk mode is enabled and the caller
+ // has asked us to switch to it. Currently re-using the car
+ // mode flag since we don't have a formal API for "desk mode".
+ if (ENABLE_LAUNCH_DESK_DOCK_APP
+ && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+ category = Intent.CATEGORY_DESK_DOCK;
+ }
+ } else {
+ // Launch the standard home app if requested.
+ if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
+ category = Intent.CATEGORY_HOME;
+ }
+ }
+
+ if (LOG) {
+ Slog.v(TAG, String.format(
+ "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
+ + "category=%s",
+ action, enableFlags, disableFlags, category));
+ }
+
+ sendConfigurationAndStartDreamOrDockAppLocked(category);
+ }
+
+ private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
+ // Update the configuration but don't send it yet.
+ mHoldingConfiguration = false;
+ updateConfigurationLocked();
+
+ // Start the dock app, if there is one.
+ boolean dockAppStarted = false;
+ if (category != null) {
+ // Now we are going to be careful about switching the
+ // configuration and starting the activity -- we need to
+ // do this in a specific order under control of the
+ // activity manager, to do it cleanly. So compute the
+ // new config, but don't set it yet, and let the
+ // activity manager take care of both the start and config
+ // change.
+ Intent homeIntent = buildHomeIntent(category);
+ if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
+ try {
+ int result = ActivityManagerNative.getDefault().startActivityWithConfig(
+ null, null, homeIntent, null, null, null, 0, 0,
+ mConfiguration, null, UserHandle.USER_CURRENT);
+ if (result >= ActivityManager.START_SUCCESS) {
+ dockAppStarted = true;
+ } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
+ Slog.e(TAG, "Could not start dock app: " + homeIntent
+ + ", startActivityWithConfig result " + result);
+ }
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
+ }
+ }
+ }
+
+ // Send the new configuration.
+ sendConfigurationLocked();
+
+ // If we did not start a dock app, then start dreaming if supported.
+ if (category != null && !dockAppStarted) {
+ Sandman.startDreamWhenDockedIfAppropriate(getContext());
+ }
+ }
+
+ private void adjustStatusBarCarModeLocked() {
+ final Context context = getContext();
+ if (mStatusBarManager == null) {
+ mStatusBarManager = (StatusBarManager)
+ context.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+
+ // Fear not: StatusBarManagerService manages a list of requests to disable
+ // features of the status bar; these are ORed together to form the
+ // active disabled list. So if (for example) the device is locked and
+ // the status bar should be totally disabled, the calls below will
+ // have no effect until the device is unlocked.
+ if (mStatusBarManager != null) {
+ mStatusBarManager.disable(mCarModeEnabled
+ ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+ : StatusBarManager.DISABLE_NONE);
+ }
+
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ if (mNotificationManager != null) {
+ if (mCarModeEnabled) {
+ Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
+
+ Notification n = new Notification();
+ n.icon = R.drawable.stat_notify_car_mode;
+ n.defaults = Notification.DEFAULT_LIGHTS;
+ n.flags = Notification.FLAG_ONGOING_EVENT;
+ n.when = 0;
+ n.setLatestEventInfo(
+ context,
+ context.getString(R.string.car_mode_disable_notification_title),
+ context.getString(R.string.car_mode_disable_notification_message),
+ PendingIntent.getActivityAsUser(context, 0, carModeOffIntent, 0,
+ null, UserHandle.CURRENT));
+ mNotificationManager.notifyAsUser(null,
+ R.string.car_mode_disable_notification_title, n, UserHandle.ALL);
+ } else {
+ mNotificationManager.cancelAsUser(null,
+ R.string.car_mode_disable_notification_title, UserHandle.ALL);
+ }
+ }
+ }
+
+ void updateTwilight() {
+ synchronized (mLock) {
+ if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ updateComputedNightModeLocked();
+ updateLocked(0, 0);
+ }
+ }
+ }
+
+ private void updateComputedNightModeLocked() {
+ TwilightState state = mTwilightManager.getCurrentState();
+ if (state != null) {
+ mComputedNightMode = state.isNight();
+ }
+ }
+
+
+}