From 62c82e4d92cc0b856059f905d81885f7808a0e7d Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 26 Sep 2012 01:30:41 -0700 Subject: Make DreamManagerService more robust. Clearly isolated the DreamManagerService and DreamController responsibilities. DreamManagerService contains just enough logic to manage the global synchronous behaviors. All of the asynchronous behaviors are in DreamController. Added a new PowerManager function called nap() to request the device to start napping. If it is a good time to nap, then the PowerManagerService will call startDream() on the DreamManagerService to start dreaming. Fixed a possible multi-user issue by explicitly tracking for which user a dream service is being started and stopping dreams when the current user changes. The user id is also passed to bindService() to ensure that the dream has the right environment. Fix interactions with docks and the UI mode manager. It is important that we always send the ACTION_DOCK_EVENT broadcast to the system so that it can configure audio routing and the like. When docked, the UI mode manager starts a dock app if there is one, otherwise it starts a dream. This change resolves issues with dreams started for reasons other than a user activity timeout. Bug: 7204211 Change-Id: I3193cc8190982c0836319176fa2e9c4dcad9c01f --- core/java/android/os/IPowerManager.aidl | 1 + core/java/android/os/PowerManager.java | 34 +- core/java/android/service/dreams/Dream.java | 10 +- .../java/android/service/dreams/IDreamManager.aidl | 2 +- .../src/com/android/systemui/Somnambulator.java | 3 +- .../systemui/statusbar/phone/PhoneStatusBar.java | 3 +- services/java/com/android/server/DockObserver.java | 89 ++--- services/java/com/android/server/SystemServer.java | 7 +- .../com/android/server/UiModeManagerService.java | 220 +++++++----- .../com/android/server/dreams/DreamController.java | 280 ++++++++------- .../android/server/dreams/DreamManagerService.java | 393 ++++++++++----------- .../android/server/power/PowerManagerService.java | 145 ++++---- .../bridge/android/BridgePowerManager.java | 5 + 13 files changed, 615 insertions(+), 577 deletions(-) diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 7aee644..eec19cb 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -34,6 +34,7 @@ interface IPowerManager void userActivity(long time, int event, int flags); void wakeUp(long time); void goToSleep(long time, int reason); + void nap(long time); boolean isScreenOn(); void reboot(String reason); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index cc2c002..58372f4 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -426,7 +426,7 @@ public final class PowerManager { *

* * @param when The time of the user activity, in the {@link SystemClock#uptimeMillis()} - * time base. This timestamp is used to correctly order the user activity with + * time base. This timestamp is used to correctly order the user activity request with * other power management functions. It should be set * to the timestamp of the input event that caused the user activity. * @param noChangeLights If true, does not cause the keyboard backlight to turn on @@ -457,7 +457,7 @@ public final class PowerManager { * * @param time The time when the request to go to sleep was issued, in the * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly - * order the user activity with other power management functions. It should be set + * order the go to sleep request with other power management functions. It should be set * to the timestamp of the input event that caused the request to go to sleep. * * @see #userActivity @@ -481,7 +481,7 @@ public final class PowerManager { * * @param time The time when the request to wake up was issued, in the * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly - * order the user activity with other power management functions. It should be set + * order the wake up request with other power management functions. It should be set * to the timestamp of the input event that caused the request to wake up. * * @see #userActivity @@ -495,6 +495,34 @@ public final class PowerManager { } /** + * Forces the device to start napping. + *

+ * If the device is currently awake, starts dreaming, otherwise does nothing. + * When the dream ends or if the dream cannot be started, the device will + * either wake up or go to sleep depending on whether there has been recent + * user activity. + *

+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + *

+ * + * @param time The time when the request to nap was issued, in the + * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly + * order the nap request with other power management functions. It should be set + * to the timestamp of the input event that caused the request to nap. + * + * @see #wakeUp + * @see #goToSleep + * + * @hide + */ + public void nap(long time) { + try { + mService.nap(time); + } catch (RemoteException e) { + } + } + + /** * Sets the brightness of the backlights (screen, keyboard, button). *

* Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java index 473414c..590acfa 100644 --- a/core/java/android/service/dreams/Dream.java +++ b/core/java/android/service/dreams/Dream.java @@ -72,6 +72,12 @@ public class Dream extends Service implements Window.Callback { private final String TAG = Dream.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; /** + * The name of the dream manager service. + * @hide + */ + public static final String DREAM_SERVICE = "dreams"; + + /** * Used with {@link Intent#ACTION_MAIN} to declare the necessary intent-filter for a dream. * * @see Dream @@ -499,7 +505,7 @@ public class Dream extends Service implements Window.Callback { // end public api private void loadSandman() { - mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); + mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE)); } private final void attach(IBinder windowToken) { @@ -584,7 +590,7 @@ public class Dream extends Service implements Window.Callback { mFinished = true; if (mSandman != null) { - mSandman.awakenSelf(mWindowToken); + mSandman.finishSelf(mWindowToken); } else { Slog.w(TAG, "No dream manager found"); } diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index bd1c524..1c1b390 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -30,5 +30,5 @@ interface IDreamManager { ComponentName getDefaultDreamComponent(); void testDream(in ComponentName componentName); boolean isDreaming(); - void awakenSelf(in IBinder token); + void finishSelf(in IBinder token); } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/Somnambulator.java b/packages/SystemUI/src/com/android/systemui/Somnambulator.java index 89d4ef7..bd87238 100644 --- a/packages/SystemUI/src/com/android/systemui/Somnambulator.java +++ b/packages/SystemUI/src/com/android/systemui/Somnambulator.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.content.Intent; import android.os.RemoteException; import android.os.ServiceManager; +import android.service.dreams.Dream; import android.service.dreams.IDreamManager; import android.util.Slog; @@ -45,7 +46,7 @@ public class Somnambulator extends Activity { setResult(RESULT_OK, resultIntent); } else { IDreamManager somnambulist = IDreamManager.Stub.asInterface( - ServiceManager.checkService("dreams")); + ServiceManager.checkService(Dream.DREAM_SERVICE)); if (somnambulist != null) { try { Slog.v("Somnambulator", "Dreaming by user request."); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index e6aa632..d72632f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -45,6 +45,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.service.dreams.Dream; import android.service.dreams.IDreamManager; import android.util.DisplayMetrics; import android.util.Log; @@ -262,7 +263,7 @@ public class PhoneStatusBar extends BaseStatusBar { .getDefaultDisplay(); mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.checkService("dreams")); + ServiceManager.checkService(Dream.DREAM_SERVICE)); super.start(); // calls createAndAddWindows() diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index 8ad5a91..4a8bf72 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -16,9 +16,6 @@ package com.android.server; -import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK; -import static android.provider.Settings.Secure.SCREENSAVER_ENABLED; - import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -27,16 +24,12 @@ import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Handler; -import android.os.Looper; import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.PowerManager; import android.os.SystemClock; import android.os.UEventObserver; import android.os.UserHandle; import android.provider.Settings; -import android.service.dreams.IDreamManager; import android.util.Log; import android.util.Slog; @@ -48,14 +41,10 @@ import java.io.FileReader; */ final class DockObserver extends UEventObserver { private static final String TAG = DockObserver.class.getSimpleName(); - private static final boolean LOG = false; private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; - private static final int DEFAULT_SCREENSAVER_ENABLED = 1; - private static final int DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK = 1; - private static final int MSG_DOCK_STATE_CHANGED = 0; private final Object mLock = new Object(); @@ -66,11 +55,16 @@ final class DockObserver extends UEventObserver { private boolean mSystemReady; private final Context mContext; + private final PowerManager mPowerManager; + private final PowerManager.WakeLock mWakeLock; public DockObserver(Context context) { mContext = context; - init(); // set initial status + mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + + init(); // set initial status startObserving(DOCK_UEVENT_MATCH); } @@ -87,17 +81,9 @@ final class DockObserver extends UEventObserver { mPreviousDockState = mDockState; mDockState = newState; if (mSystemReady) { - // Don't force screen on when undocking from the desk dock. - // The change in power state will do this anyway. - // FIXME - we should be configurable. - if ((mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK - && mPreviousDockState != Intent.EXTRA_DOCK_STATE_LE_DESK - && mPreviousDockState != Intent.EXTRA_DOCK_STATE_HE_DESK) || - mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - PowerManager pm = - (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - pm.wakeUp(SystemClock.uptimeMillis()); - } + // Wake up immediately when docked or undocked. + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + updateLocked(); } } @@ -138,6 +124,7 @@ final class DockObserver extends UEventObserver { } private void updateLocked() { + mWakeLock.acquire(); mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); } @@ -145,8 +132,8 @@ final class DockObserver extends UEventObserver { synchronized (mLock) { Slog.i(TAG, "Dock state changed: " + mDockState); + // Skip the dock intent if not yet provisioned. final ContentResolver cr = mContext.getContentResolver(); - if (Settings.Global.getInt(cr, Settings.Global.DEVICE_PROVISIONED, 0) == 0) { Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); @@ -158,16 +145,8 @@ final class DockObserver extends UEventObserver { intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); - // Check if this is Bluetooth Dock - // TODO(BT): Get Dock address. - // String address = null; - // if (address != null) { - // intent.putExtra(BluetoothDevice.EXTRA_DEVICE, - // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)); - // } - - // User feedback to confirm dock connection. Particularly - // useful for flaky contact pins... + // Play a sound to provide feedback to confirm dock connection. + // Particularly useful for flaky contact pins... if (Settings.Global.getInt(cr, Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) { String whichSound = null; @@ -204,42 +183,14 @@ final class DockObserver extends UEventObserver { } } - IDreamManager mgr = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); - if (mgr != null) { - // dreams feature enabled - boolean undocked = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED; - if (undocked) { - try { - if (mgr.isDreaming()) { - mgr.awaken(); - } - } catch (RemoteException e) { - Slog.w(TAG, "Unable to awaken!", e); - } - } else { - if (isScreenSaverEnabled(mContext) && isScreenSaverActivatedOnDock(mContext)) { - try { - mgr.dream(); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to dream!", e); - } - } - } - } else { - // dreams feature not enabled, send legacy intent - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - } - } + // Send the dock event intent. + // There are many components in the system watching for this so as to + // adjust audio routing, screen orientation, etc. + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - private static boolean isScreenSaverEnabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - SCREENSAVER_ENABLED, DEFAULT_SCREENSAVER_ENABLED) != 0; - } - - private static boolean isScreenSaverActivatedOnDock(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK) != 0; + // Release the wake lock that was acquired when the message was posted. + mWakeLock.release(); + } } private final Handler mHandler = new Handler(true /*async*/) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 10f6961..738e19b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -38,6 +38,7 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.server.search.SearchManagerService; +import android.service.dreams.Dream; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -739,8 +740,8 @@ class ServerThread extends Thread { try { Slog.i(TAG, "Dreams Service"); // Dreams (interactive idle-time views, a/k/a screen savers) - dreamy = new DreamManagerService(context); - ServiceManager.addService("dreams", dreamy); + dreamy = new DreamManagerService(context, wmHandler); + ServiceManager.addService(Dream.DREAM_SERVICE, dreamy); } catch (Throwable e) { reportWtf("starting DreamManagerService", e); } @@ -811,7 +812,7 @@ class ServerThread extends Thread { context.getResources().updateConfiguration(config, metrics); try { - power.systemReady(twilight); + power.systemReady(twilight, dreamy); } catch (Throwable e) { reportWtf("making Power Manager Service ready", e); } diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index 85a6e41..07e8f18 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.Activity; +import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IUiModeManager; import android.app.Notification; @@ -39,6 +40,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.service.dreams.Dream; +import android.service.dreams.IDreamManager; import android.util.Slog; import java.io.FileDescriptor; @@ -56,6 +59,9 @@ class UiModeManagerService extends IUiModeManager.Stub { private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; + private static final int DEFAULT_SCREENSAVER_ENABLED = 1; + private static final int DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK = 1; + private final Context mContext; private final TwilightService mTwilightService; private final Handler mHandler = new Handler(); @@ -110,72 +116,10 @@ class UiModeManagerService extends IUiModeManager.Stub { return; } - final int enableFlags = intent.getIntExtra("enableFlags", 0); - final int disableFlags = intent.getIntExtra("disableFlags", 0); - + final int enableFlags = intent.getIntExtra("enableFlags", 0); + final int disableFlags = intent.getIntExtra("disableFlags", 0); synchronized (mLock) { - // Launch a dock activity - String category = null; - if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { - // 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(intent.getAction())) { - // 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", - intent.getAction(), enableFlags, disableFlags, category)); - } - - if (category != null) { - // This is the new activity that will serve as home while - // we are in care mode. - Intent homeIntent = buildHomeIntent(category); - - // 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. - Configuration newConfig = null; - if (mHoldingConfiguration) { - mHoldingConfiguration = false; - updateConfigurationLocked(false); - newConfig = mConfiguration; - } - try { - ActivityManagerNative.getDefault().startActivityWithConfig( - null, homeIntent, null, null, null, 0, 0, - newConfig, null, UserHandle.USER_CURRENT); - mHoldingConfiguration = false; - } catch (RemoteException e) { - Slog.w(TAG, e.getCause()); - } - } - - if (mHoldingConfiguration) { - mHoldingConfiguration = false; - updateConfigurationLocked(true); - } + updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags); } } }; @@ -335,9 +279,8 @@ class UiModeManagerService extends IUiModeManager.Stub { } } - final void updateConfigurationLocked(boolean sendIt) { - int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION - : mDefaultUiModeType; + final void updateConfigurationLocked() { + int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION : mDefaultUiModeType; if (mCarModeEnabled) { uiMode = Configuration.UI_MODE_TYPE_CAR; } else if (isDeskDockState(mDockState)) { @@ -365,17 +308,19 @@ class UiModeManagerService extends IUiModeManager.Stub { } mCurUiMode = uiMode; - - if (!mHoldingConfiguration && uiMode != mSetUiMode) { - mSetUiMode = uiMode; + if (!mHoldingConfiguration) { mConfiguration.uiMode = uiMode; + } + } - if (sendIt) { - try { - ActivityManagerNative.getDefault().updateConfiguration(mConfiguration); - } catch (RemoteException e) { - Slog.w(TAG, "Failure communicating with activity manager", e); - } + final 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); } } } @@ -434,43 +379,38 @@ class UiModeManagerService extends IUiModeManager.Stub { intent.putExtra("disableFlags", disableFlags); mContext.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 { - Intent homeIntent = null; + String category = null; if (mCarModeEnabled) { if (ENABLE_LAUNCH_CAR_DOCK_APP - && (enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { - homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK); + && (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) { - homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK); + && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { + category = Intent.CATEGORY_DESK_DOCK; } } else { - if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { - homeIntent = buildHomeIntent(Intent.CATEGORY_HOME); + if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { + category = Intent.CATEGORY_HOME; } } if (LOG) { Slog.v(TAG, "updateLocked: null action, mDockState=" - + mDockState +", firing homeIntent: " + homeIntent); + + mDockState +", category=" + category); } - if (homeIntent != null) { - try { - mContext.startActivityAsUser(homeIntent, UserHandle.CURRENT); - } catch (ActivityNotFoundException e) { - } - } + sendConfigurationAndStartDreamOrDockAppLocked(category); } - updateConfigurationLocked(true); - // keep screen on when charging and in car mode boolean keepScreenOn = mCharging && ((mCarModeEnabled && mCarModeKeepsScreenOn) || @@ -487,6 +427,100 @@ class UiModeManagerService extends IUiModeManager.Stub { } } + 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); + try { + int result = ActivityManagerNative.getDefault().startActivityWithConfig( + 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 (!dockAppStarted && isScreenSaverEnabled() && isScreenSaverActivatedOnDock()) { + Slog.i(TAG, "Activating dream while docked."); + try { + IDreamManager dreamManagerService = IDreamManager.Stub.asInterface( + ServiceManager.getService(Dream.DREAM_SERVICE)); + dreamManagerService.dream(); + } catch (RemoteException ex) { + Slog.e(TAG, "Could not start dream when docked.", ex); + } + } + } + + private boolean isScreenSaverEnabled() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, DEFAULT_SCREENSAVER_ENABLED, + UserHandle.USER_CURRENT) != 0; + } + + private boolean isScreenSaverActivatedOnDock() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK, UserHandle.USER_CURRENT) != 0; + } + private void adjustStatusBarCarModeLocked() { if (mStatusBarManager == null) { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); diff --git a/services/java/com/android/server/dreams/DreamController.java b/services/java/com/android/server/dreams/DreamController.java index a6ed5ab..81c80187 100644 --- a/services/java/com/android/server/dreams/DreamController.java +++ b/services/java/com/android/server/dreams/DreamController.java @@ -25,193 +25,219 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.IBinder.DeathRecipient; +import android.service.dreams.Dream; import android.service.dreams.IDreamService; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import com.android.internal.util.DumpUtils; - import java.io.PrintWriter; import java.util.NoSuchElementException; /** * Internal controller for starting and stopping the current dream and managing related state. * - * Assumes all operations (except {@link #dump}) are called from a single thread. + * Assumes all operations are called from the dream handler thread. */ final class DreamController { - private static final boolean DEBUG = true; - private static final String TAG = DreamController.class.getSimpleName(); - - public interface Listener { - void onDreamStopped(boolean wasTest); - } + private static final String TAG = "DreamController"; private final Context mContext; - private final IWindowManager mIWindowManager; - private final DeathRecipient mDeathRecipient; - private final ServiceConnection mServiceConnection; + private final Handler mHandler; private final Listener mListener; + private final IWindowManager mIWindowManager; - private Handler mHandler; + private final Intent mDreamingStartedIntent = new Intent(Dream.ACTION_DREAMING_STARTED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + private final Intent mDreamingStoppedIntent = new Intent(Dream.ACTION_DREAMING_STOPPED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - private ComponentName mCurrentDreamComponent; - private IDreamService mCurrentDream; - private Binder mCurrentDreamToken; - private boolean mCurrentDreamIsTest; + private DreamRecord mCurrentDream; - public DreamController(Context context, DeathRecipient deathRecipient, - ServiceConnection serviceConnection, Listener listener) { + public DreamController(Context context, Handler handler, Listener listener) { mContext = context; - mDeathRecipient = deathRecipient; - mServiceConnection = serviceConnection; + mHandler = handler; mListener = listener; mIWindowManager = WindowManagerGlobal.getWindowManagerService(); } - public void setHandler(Handler handler) { - mHandler = handler; - } - public void dump(PrintWriter pw) { - if (mHandler== null || pw == null) { - return; + pw.println("Dreamland:"); + if (mCurrentDream != null) { + pw.println(" mCurrentDream:"); + pw.println(" mToken=" + mCurrentDream.mToken); + pw.println(" mName=" + mCurrentDream.mName); + pw.println(" mIsTest=" + mCurrentDream.mIsTest); + pw.println(" mUserId=" + mCurrentDream.mUserId); + pw.println(" mBound=" + mCurrentDream.mBound); + pw.println(" mService=" + mCurrentDream.mService); + pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); + } else { + pw.println(" mCurrentDream: null"); } - DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { - @Override - public void dump(PrintWriter pw) { - pw.print(" component="); pw.println(mCurrentDreamComponent); - pw.print(" token="); pw.println(mCurrentDreamToken); - pw.print(" dream="); pw.println(mCurrentDream); - } - }, pw, 200); } - public void start(ComponentName dream, boolean isTest) { - if (DEBUG) Slog.v(TAG, String.format("start(%s,%s)", dream, isTest)); + public void startDream(Binder token, ComponentName name, boolean isTest, int userId) { + stopDream(); - if (mCurrentDreamComponent != null ) { - if (dream.equals(mCurrentDreamComponent) && isTest == mCurrentDreamIsTest) { - if (DEBUG) Slog.v(TAG, "Dream is already started: " + dream); - return; - } - // stop the current dream before starting a new one - stop(); - } + Slog.i(TAG, "Starting dream: name=" + name + ", isTest=" + isTest + ", userId=" + userId); - mCurrentDreamComponent = dream; - mCurrentDreamIsTest = isTest; - mCurrentDreamToken = new Binder(); + mCurrentDream = new DreamRecord(token, name, isTest, userId); try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken - + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM); - mIWindowManager.addWindowToken(mCurrentDreamToken, - WindowManager.LayoutParams.TYPE_DREAM); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to add window token."); - stop(); + mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM); + } catch (RemoteException ex) { + Slog.e(TAG, "Unable to add window token for dream.", ex); + stopDream(); return; } - Intent intent = new Intent(Intent.ACTION_MAIN) - .setComponent(mCurrentDreamComponent) - .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .putExtra("android.dreams.TEST", mCurrentDreamIsTest); - - if (!mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)) { - Slog.w(TAG, "Unable to bind service"); - stop(); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Dream.CATEGORY_DREAM); + intent.setComponent(name); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + try { + if (!mContext.bindService(intent, mCurrentDream, + Context.BIND_AUTO_CREATE, userId)) { + Slog.e(TAG, "Unable to bind dream service: " + intent); + stopDream(); + return; + } + } catch (SecurityException ex) { + Slog.e(TAG, "Unable to bind dream service: " + intent, ex); + stopDream(); return; } - if (DEBUG) Slog.v(TAG, "Bound service"); - } - public void attach(ComponentName name, IBinder dream) { - if (DEBUG) Slog.v(TAG, String.format("attach(%s,%s)", name, dream)); - mCurrentDream = IDreamService.Stub.asInterface(dream); + mCurrentDream.mBound = true; + } - boolean linked = linkDeathRecipient(dream); - if (!linked) { - stop(); + public void stopDream() { + if (mCurrentDream == null) { return; } - try { - if (DEBUG) Slog.v(TAG, "Attaching with token:" + mCurrentDreamToken); - mCurrentDream.attach(mCurrentDreamToken); - } catch (Throwable ex) { - Slog.w(TAG, "Unable to send window token to dream:" + ex); - stop(); + final DreamRecord oldDream = mCurrentDream; + mCurrentDream = null; + Slog.i(TAG, "Stopping dream: name=" + oldDream.mName + + ", isTest=" + oldDream.mIsTest + ", userId=" + oldDream.mUserId); + + if (oldDream.mSentStartBroadcast) { + mContext.sendBroadcast(mDreamingStoppedIntent); } - } - public void stop() { - if (DEBUG) Slog.v(TAG, "stop()"); + if (oldDream.mService != null) { + // TODO: It would be nice to tell the dream that it's being stopped so that + // it can shut down nicely before we yank its window token out from under it. + try { + oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); + } catch (NoSuchElementException ex) { + // don't care + } + oldDream.mService = null; + } - if (mCurrentDream != null) { - unlinkDeathRecipient(mCurrentDream.asBinder()); + if (oldDream.mBound) { + mContext.unbindService(oldDream); + } - if (DEBUG) Slog.v(TAG, "Unbinding: " + mCurrentDreamComponent + " service: " + mCurrentDream); - mContext.unbindService(mServiceConnection); + try { + mIWindowManager.removeWindowToken(oldDream.mToken); + } catch (RemoteException ex) { + Slog.w(TAG, "Error removing window token for dream.", ex); } - if (mCurrentDreamToken != null) { - removeWindowToken(mCurrentDreamToken); + + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDreamStopped(oldDream.mToken); + } + }); + } + + private void attach(IDreamService service) { + try { + service.asBinder().linkToDeath(mCurrentDream, 0); + service.attach(mCurrentDream.mToken); + } catch (RemoteException ex) { + Slog.e(TAG, "The dream service died unexpectedly.", ex); + stopDream(); + return; } - final boolean wasTest = mCurrentDreamIsTest; - mCurrentDream = null; - mCurrentDreamToken = null; - mCurrentDreamComponent = null; - mCurrentDreamIsTest = false; + mCurrentDream.mService = service; - if (mListener != null && mHandler != null) { - mHandler.post(new Runnable(){ - @Override - public void run() { - mListener.onDreamStopped(wasTest); - }}); + if (!mCurrentDream.mIsTest) { + mContext.sendBroadcast(mDreamingStartedIntent); + mCurrentDream.mSentStartBroadcast = true; } } - public void stopSelf(IBinder token) { - if (DEBUG) Slog.v(TAG, String.format("stopSelf(%s)", token)); - if (token == null || token != mCurrentDreamToken) { - Slog.w(TAG, "Stop requested for non-current dream token: " + token); - } else { - stop(); - } + /** + * Callback interface to be implemented by the {@link DreamManagerService}. + */ + public interface Listener { + void onDreamStopped(Binder token); } - private void removeWindowToken(IBinder token) { - if (DEBUG) Slog.v(TAG, "Removing window token: " + token); - try { - mIWindowManager.removeWindowToken(token); - } catch (Throwable e) { - Slog.w(TAG, "Error removing window token", e); + private final class DreamRecord implements DeathRecipient, ServiceConnection { + public final Binder mToken; + public final ComponentName mName; + public final boolean mIsTest; + public final int mUserId; + + public boolean mBound; + public IDreamService mService; + public boolean mSentStartBroadcast; + + public DreamRecord(Binder token, ComponentName name, + boolean isTest, int userId) { + mToken = token; + mName = name; + mIsTest = isTest; + mUserId = userId; } - } - private boolean linkDeathRecipient(IBinder dream) { - if (DEBUG) Slog.v(TAG, "Linking death recipient"); - try { - dream.linkToDeath(mDeathRecipient, 0); - return true; - } catch (RemoteException e) { - Slog.w(TAG, "Unable to link death recipient", e); - return false; + // May be called on any thread. + @Override + public void binderDied() { + mHandler.post(new Runnable() { + @Override + public void run() { + mService = null; + if (mCurrentDream == DreamRecord.this) { + stopDream(); + } + } + }); } - } - private void unlinkDeathRecipient(IBinder dream) { - if (DEBUG) Slog.v(TAG, "Unlinking death recipient"); - try { - dream.unlinkToDeath(mDeathRecipient, 0); - } catch (NoSuchElementException e) { - // we tried + // May be called on any thread. + @Override + public void onServiceConnected(ComponentName name, final IBinder service) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mCurrentDream == DreamRecord.this && mService == null) { + attach(IDreamService.Stub.asInterface(service)); + } + } + }); } - } + // May be called on any thread. + @Override + public void onServiceDisconnected(ComponentName name) { + mHandler.post(new Runnable() { + @Override + public void run() { + mService = null; + if (mCurrentDream == DreamRecord.this) { + stopDream(); + } + } + }); + } + } } \ No newline at end of file diff --git a/services/java/com/android/server/dreams/DreamManagerService.java b/services/java/com/android/server/dreams/DreamManagerService.java index 71df247..1f40176 100644 --- a/services/java/com/android/server/dreams/DreamManagerService.java +++ b/services/java/com/android/server/dreams/DreamManagerService.java @@ -16,100 +16,97 @@ package com.android.server.dreams; -import static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS; -import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT; +import com.android.internal.util.DumpUtils; -import android.app.ActivityManagerNative; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; +import android.os.Looper; +import android.os.PowerManager; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; -import android.service.dreams.Dream; import android.service.dreams.IDreamManager; import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; +import libcore.util.Objects; + /** * Service api for managing dreams. * * @hide */ -public final class DreamManagerService - extends IDreamManager.Stub - implements ServiceConnection { +public final class DreamManagerService extends IDreamManager.Stub { private static final boolean DEBUG = true; - private static final String TAG = DreamManagerService.class.getSimpleName(); - - private static final Intent mDreamingStartedIntent = new Intent(Dream.ACTION_DREAMING_STARTED) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - private static final Intent mDreamingStoppedIntent = new Intent(Dream.ACTION_DREAMING_STOPPED) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + private static final String TAG = "DreamManagerService"; private final Object mLock = new Object(); - private final DreamController mController; - private final DreamControllerHandler mHandler; - private final Context mContext; - private final CurrentUserManager mCurrentUserManager = new CurrentUserManager(); - - private final DeathRecipient mAwakenOnBinderDeath = new DeathRecipient() { - @Override - public void binderDied() { - if (DEBUG) Slog.v(TAG, "binderDied()"); - awaken(); - } - }; - - private final DreamController.Listener mControllerListener = new DreamController.Listener() { - @Override - public void onDreamStopped(boolean wasTest) { - synchronized(mLock) { - setDreamingLocked(false, wasTest); - } - }}; + private final Context mContext; + private final DreamHandler mHandler; + private final DreamController mController; + private final PowerManager mPowerManager; - private boolean mIsDreaming; + private Binder mCurrentDreamToken; + private ComponentName mCurrentDreamName; + private int mCurrentDreamUserId; + private boolean mCurrentDreamIsTest; - public DreamManagerService(Context context) { - if (DEBUG) Slog.v(TAG, "DreamManagerService startup"); + public DreamManagerService(Context context, Handler mainHandler) { mContext = context; - mController = new DreamController(context, mAwakenOnBinderDeath, this, mControllerListener); - mHandler = new DreamControllerHandler(mController); - mController.setHandler(mHandler); + mHandler = new DreamHandler(mainHandler.getLooper()); + mController = new DreamController(context, mHandler, mControllerListener); + + mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); } public void systemReady() { - mCurrentUserManager.init(mContext); - - if (DEBUG) Slog.v(TAG, "Ready to dream!"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + stopDreamLocked(); + } + } + }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - pw.println("Dreamland:"); - mController.dump(pw); - mCurrentUserManager.dump(pw); + pw.println("DREAM MANAGER (dumpsys dreams)"); + pw.println(); + + pw.println("mCurrentDreamToken=" + mCurrentDreamToken); + pw.println("mCurrentDreamName=" + mCurrentDreamName); + pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId); + pw.println("mCurrentDreamIsTest=" + mCurrentDreamIsTest); + pw.println(); + + DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { + @Override + public void dump(PrintWriter pw) { + mController.dump(pw); + } + }, pw, 200); } - // begin IDreamManager api - @Override + @Override // Binder call public ComponentName[] getDreamComponents() { checkPermission(android.Manifest.permission.READ_DREAM_STATE); - int userId = UserHandle.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { return getDreamComponentsForUser(userId); @@ -118,15 +115,15 @@ public final class DreamManagerService } } - @Override + @Override // Binder call public void setDreamComponents(ComponentName[] componentNames) { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); - int userId = UserHandle.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { Settings.Secure.putStringForUser(mContext.getContentResolver(), - SCREENSAVER_COMPONENTS, + Settings.Secure.SCREENSAVER_COMPONENTS, componentsToString(componentNames), userId); } finally { @@ -134,142 +131,213 @@ public final class DreamManagerService } } - @Override + @Override // Binder call public ComponentName getDefaultDreamComponent() { checkPermission(android.Manifest.permission.READ_DREAM_STATE); - int userId = UserHandle.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), - SCREENSAVER_DEFAULT_COMPONENT, + Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, userId); return name == null ? null : ComponentName.unflattenFromString(name); } finally { Binder.restoreCallingIdentity(ident); } - } - @Override + @Override // Binder call public boolean isDreaming() { checkPermission(android.Manifest.permission.READ_DREAM_STATE); - return mIsDreaming; + synchronized (mLock) { + return mCurrentDreamToken != null && !mCurrentDreamIsTest; + } } - @Override + @Override // Binder call public void dream() { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); final long ident = Binder.clearCallingIdentity(); try { - if (DEBUG) Slog.v(TAG, "Dream now"); - ComponentName[] dreams = getDreamComponentsForUser(mCurrentUserManager.getCurrentUserId()); - ComponentName firstDream = dreams != null && dreams.length > 0 ? dreams[0] : null; - if (firstDream != null) { - mHandler.requestStart(firstDream, false /*isTest*/); - synchronized (mLock) { - setDreamingLocked(true, false /*isTest*/); - } - } + // Ask the power manager to nap. It will eventually call back into + // startDream() if/when it is appropriate to start dreaming. + // Because napping could cause the screen to turn off immediately if the dream + // cannot be started, we keep one eye open and gently poke user activity. + long time = SystemClock.uptimeMillis(); + mPowerManager.userActivity(time, true /*noChangeLights*/); + mPowerManager.nap(time); } finally { Binder.restoreCallingIdentity(ident); } } - @Override + @Override // Binder call public void testDream(ComponentName dream) { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + if (dream == null) { + throw new IllegalArgumentException("dream must not be null"); + } + + final int callingUserId = UserHandle.getCallingUserId(); + final int currentUserId = ActivityManager.getCurrentUser(); + if (callingUserId != currentUserId) { + // This check is inherently prone to races but at least it's something. + Slog.w(TAG, "Aborted attempt to start a test dream while a different " + + " user is active: callingUserId=" + callingUserId + + ", currentUserId=" + currentUserId); + return; + } final long ident = Binder.clearCallingIdentity(); try { - if (DEBUG) Slog.v(TAG, "Test dream name=" + dream); - if (dream != null) { - mHandler.requestStart(dream, true /*isTest*/); - synchronized (mLock) { - setDreamingLocked(true, true /*isTest*/); - } + synchronized (mLock) { + startDreamLocked(dream, true /*isTest*/, callingUserId); } } finally { Binder.restoreCallingIdentity(ident); } - } - @Override + @Override // Binder call public void awaken() { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); final long ident = Binder.clearCallingIdentity(); try { - if (DEBUG) Slog.v(TAG, "Wake up"); - mHandler.requestStop(); + // Treat an explicit request to awaken as user activity so that the + // device doesn't immediately go to sleep if the timeout expired, + // for example when being undocked. + long time = SystemClock.uptimeMillis(); + mPowerManager.userActivity(time, false /*noChangeLights*/); + stopDream(); } finally { Binder.restoreCallingIdentity(ident); } } - @Override - public void awakenSelf(IBinder token) { - // requires no permission, called by Dream from an arbitrary process + @Override // Binder call + public void finishSelf(IBinder token) { + // Requires no permission, called by Dream from an arbitrary process. + if (token == null) { + throw new IllegalArgumentException("token must not be null"); + } final long ident = Binder.clearCallingIdentity(); try { - if (DEBUG) Slog.v(TAG, "Wake up from dream: " + token); - if (token != null) { - mHandler.requestStopSelf(token); + if (DEBUG) { + Slog.d(TAG, "Dream finished: " + token); + } + + // Note that a dream finishing and self-terminating is not + // itself considered user activity. If the dream is ending because + // the user interacted with the device then user activity will already + // have been poked so the device will stay awake a bit longer. + // If the dream is ending on its own for other reasons and no wake + // locks are held and the user activity timeout has expired then the + // device may simply go to sleep. + synchronized (mLock) { + if (mCurrentDreamToken == token) { + stopDreamLocked(); + } } } finally { Binder.restoreCallingIdentity(ident); } } - // end IDreamManager api - - // begin ServiceConnection - @Override - public void onServiceConnected(ComponentName name, IBinder dream) { - if (DEBUG) Slog.v(TAG, "Service connected: " + name + " binder=" + - dream + " thread=" + Thread.currentThread().getId()); - mHandler.requestAttach(name, dream); - } - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Slog.v(TAG, "Service disconnected: " + name); - // Only happens in exceptional circumstances, awaken just to be safe - awaken(); + /** + * Called by the power manager to start a dream. + */ + public void startDream() { + int userId = ActivityManager.getCurrentUser(); + ComponentName dream = chooseDreamForUser(userId); + if (dream != null) { + synchronized (mLock) { + startDreamLocked(dream, false /*isTest*/, userId); + } + } } - // end ServiceConnection - private void checkPermission(String permission) { - if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) { - throw new SecurityException("Access denied to process: " + Binder.getCallingPid() - + ", must have permission " + permission); + /** + * Called by the power manager to stop a dream. + */ + public void stopDream() { + synchronized (mLock) { + stopDreamLocked(); } } - private void setDreamingLocked(boolean isDreaming, boolean isTest) { - boolean wasDreaming = mIsDreaming; - if (!isTest) { - if (!wasDreaming && isDreaming) { - if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STARTED"); - mContext.sendBroadcast(mDreamingStartedIntent); - } else if (wasDreaming && !isDreaming) { - if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STOPPED"); - mContext.sendBroadcast(mDreamingStoppedIntent); - } - } - mIsDreaming = isDreaming; + private ComponentName chooseDreamForUser(int userId) { + ComponentName[] dreams = getDreamComponentsForUser(userId); + return dreams != null && dreams.length != 0 ? dreams[0] : null; } private ComponentName[] getDreamComponentsForUser(int userId) { String names = Settings.Secure.getStringForUser(mContext.getContentResolver(), - SCREENSAVER_COMPONENTS, + Settings.Secure.SCREENSAVER_COMPONENTS, userId); return names == null ? null : componentsFromString(names); } + private void startDreamLocked(final ComponentName name, + final boolean isTest, final int userId) { + if (Objects.equal(mCurrentDreamName, name) + && mCurrentDreamIsTest == isTest + && mCurrentDreamUserId == userId) { + return; + } + + stopDreamLocked(); + + Slog.i(TAG, "Entering dreamland."); + + final Binder newToken = new Binder(); + mCurrentDreamToken = newToken; + mCurrentDreamName = name; + mCurrentDreamIsTest = isTest; + mCurrentDreamUserId = userId; + + mHandler.post(new Runnable() { + @Override + public void run() { + mController.startDream(newToken, name, isTest, userId); + } + }); + } + + private void stopDreamLocked() { + if (mCurrentDreamToken != null) { + Slog.i(TAG, "Leaving dreamland."); + + cleanupDreamLocked(); + + mHandler.post(new Runnable() { + @Override + public void run() { + mController.stopDream(); + } + }); + } + } + + private void cleanupDreamLocked() { + mCurrentDreamToken = null; + mCurrentDreamName = null; + mCurrentDreamIsTest = false; + mCurrentDreamUserId = 0; + } + + private void checkPermission(String permission) { + if (mContext.checkCallingOrSelfPermission(permission) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + permission); + } + } + private static String componentsToString(ComponentName[] componentNames) { StringBuilder names = new StringBuilder(); if (componentNames != null) { @@ -292,93 +360,24 @@ public final class DreamManagerService return componentNames; } - /** - * Keeps track of the current user, since dream() uses the current user's configuration. - */ - private static class CurrentUserManager { - private final Object mLock = new Object(); - private int mCurrentUserId; - - public void init(Context context) { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - synchronized(mLock) { - mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); - if (DEBUG) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house"); - } - } - }}, filter); - try { - synchronized (mLock) { - mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id; + private final DreamController.Listener mControllerListener = new DreamController.Listener() { + @Override + public void onDreamStopped(Binder token) { + synchronized (mLock) { + if (mCurrentDreamToken == token) { + cleanupDreamLocked(); } - } catch (RemoteException e) { - Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); } } - - public void dump(PrintWriter pw) { - pw.print(" user="); pw.println(getCurrentUserId()); - } - - public int getCurrentUserId() { - synchronized(mLock) { - return mCurrentUserId; - } - } - } + }; /** * Handler for asynchronous operations performed by the dream manager. - * * Ensures operations to {@link DreamController} are single-threaded. */ - private static final class DreamControllerHandler extends Handler { - private final DreamController mController; - private final Runnable mStopRunnable = new Runnable() { - @Override - public void run() { - mController.stop(); - }}; - - public DreamControllerHandler(DreamController controller) { - super(true /*async*/); - mController = controller; - } - - public void requestStart(final ComponentName name, final boolean isTest) { - post(new Runnable(){ - @Override - public void run() { - mController.start(name, isTest); - }}); - } - - public void requestAttach(final ComponentName name, final IBinder dream) { - post(new Runnable(){ - @Override - public void run() { - mController.attach(name, dream); - }}); + private final class DreamHandler extends Handler { + public DreamHandler(Looper looper) { + super(looper, null, true /*async*/); } - - public void requestStopSelf(final IBinder token) { - post(new Runnable(){ - @Override - public void run() { - mController.stopSelf(token); - }}); - } - - public void requestStop() { - post(mStopRunnable); - } - } - } diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 030eb5e..ad138e8 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -24,6 +24,7 @@ import com.android.server.TwilightService; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService; import com.android.server.display.DisplayManagerService; +import com.android.server.dreams.DreamManagerService; import android.Manifest; import android.content.BroadcastReceiver; @@ -46,13 +47,11 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.service.dreams.Dream; -import android.service.dreams.IDreamManager; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -100,14 +99,12 @@ public final class PowerManagerService extends IPowerManager.Stub private static final int DIRTY_STAY_ON = 1 << 7; // Dirty bit: battery state changed private static final int DIRTY_BATTERY_STATE = 1 << 8; - // Dirty bit: dream ended - private static final int DIRTY_DREAM_ENDED = 1 << 9; // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp(). // The screen should be off or in the process of being turned off by the display controller. private static final int WAKEFULNESS_ASLEEP = 0; // Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). - // When the user activity timeout expires, the device may start napping. + // When the user activity timeout expires, the device may start napping or go to sleep. private static final int WAKEFULNESS_AWAKE = 1; // Wakefulness: The device is napping. It is deciding whether to dream or go to sleep // but hasn't gotten around to it yet. It can be awoken by a call to wakeUp(), which @@ -149,7 +146,7 @@ public final class PowerManagerService extends IPowerManager.Stub private Notifier mNotifier; private DisplayPowerController mDisplayPowerController; private SettingsObserver mSettingsObserver; - private IDreamManager mDreamManager; + private DreamManagerService mDreamManager; private LightsService.Light mAttentionLight; private final Object mLock = new Object(); @@ -335,9 +332,10 @@ public final class PowerManagerService extends IPowerManager.Stub } } - public void systemReady(TwilightService twilight) { + public void systemReady(TwilightService twilight, DreamManagerService dreamManager) { synchronized (mLock) { mSystemReady = true; + mDreamManager = dreamManager; PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); @@ -365,10 +363,7 @@ public final class PowerManagerService extends IPowerManager.Stub mContext.registerReceiver(new BootCompletedReceiver(), filter, null, mHandler); filter = new IntentFilter(); - filter.addAction(Intent.ACTION_DOCK_EVENT); - mContext.registerReceiver(new DockReceiver(), filter, null, mHandler); - - filter = new IntentFilter(); + filter.addAction(Dream.ACTION_DREAMING_STARTED); filter.addAction(Dream.ACTION_DREAMING_STOPPED); mContext.registerReceiver(new DreamReceiver(), filter, null, mHandler); @@ -887,6 +882,47 @@ public final class PowerManagerService extends IPowerManager.Stub return true; } + @Override // Binder call + public void nap(long eventTime) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + napInternal(eventTime); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void napInternal(long eventTime) { + synchronized (mLock) { + if (napNoUpdateLocked(eventTime)) { + updatePowerStateLocked(); + } + } + } + + private boolean napNoUpdateLocked(long eventTime) { + if (DEBUG_SPEW) { + Slog.d(TAG, "napNoUpdateLocked: eventTime=" + eventTime); + } + + if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE + || !mBootCompleted || !mSystemReady) { + return false; + } + + Slog.i(TAG, "Nap time..."); + + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_NAPPING; + return true; + } + /** * Updates the global power state based on dirty bits recorded in mDirty. * @@ -1143,11 +1179,15 @@ public final class PowerManagerService extends IPowerManager.Stub | DIRTY_WAKEFULNESS | DIRTY_STAY_ON)) != 0) { if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { if (DEBUG_SPEW) { - Slog.d(TAG, "updateWakefulnessLocked: Nap time..."); + Slog.d(TAG, "updateWakefulnessLocked: Bed time..."); + } + final long time = SystemClock.uptimeMillis(); + if (mDreamsActivateOnSleepSetting) { + changed = napNoUpdateLocked(time); + } else { + changed = goToSleepNoUpdateLocked(time, + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); } - mWakefulness = WAKEFULNESS_NAPPING; - mDirty |= DIRTY_WAKEFULNESS; - changed = true; } } return changed; @@ -1172,8 +1212,7 @@ public final class PowerManagerService extends IPowerManager.Stub | DIRTY_SETTINGS | DIRTY_IS_POWERED | DIRTY_STAY_ON - | DIRTY_BATTERY_STATE - | DIRTY_DREAM_ENDED)) != 0) { + | DIRTY_BATTERY_STATE)) != 0) { scheduleSandmanLocked(); } } @@ -1210,32 +1249,15 @@ public final class PowerManagerService extends IPowerManager.Stub } } - // Get the dream manager, if needed. - if (startDreaming && mDreamManager == null) { - mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.checkService("dreams")); - if (mDreamManager == null) { - Slog.w(TAG, "Unable to find IDreamManager."); - } - } - // Start dreaming if needed. // We only control the dream on the handler thread, so we don't need to worry about // concurrent attempts to start or stop the dream. boolean isDreaming = false; if (mDreamManager != null) { - try { - isDreaming = mDreamManager.isDreaming(); - if (startDreaming && !isDreaming) { - Slog.i(TAG, "Entering dreamland."); - mDreamManager.dream(); - isDreaming = mDreamManager.isDreaming(); - if (!isDreaming) { - Slog.i(TAG, "Could not enter dreamland. Sleep will be dreamless."); - } - } - } catch (RemoteException ex) { + if (startDreaming) { + mDreamManager.startDream(); } + isDreaming = mDreamManager.isDreaming(); } // Update dream state. @@ -1255,18 +1277,6 @@ public final class PowerManagerService extends IPowerManager.Stub if (!continueDreaming) { handleDreamFinishedLocked(); } - - // In addition to listening for the intent, poll the sandman periodically to detect - // when the dream has ended (as a watchdog only, ensuring our state is always correct). - if (mWakefulness == WAKEFULNESS_DREAMING - || mWakefulness == WAKEFULNESS_NAPPING) { - if (!mSandmanScheduled) { - mSandmanScheduled = true; - Message msg = mHandler.obtainMessage(MSG_SANDMAN); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, 5000); - } - } } // Stop dreaming if needed. @@ -1274,26 +1284,22 @@ public final class PowerManagerService extends IPowerManager.Stub // If so, then the power manager will have posted another message to the handler // to take care of it later. if (mDreamManager != null) { - try { - if (!continueDreaming && isDreaming) { - Slog.i(TAG, "Leaving dreamland."); - mDreamManager.awaken(); - } - } catch (RemoteException ex) { + if (!continueDreaming) { + mDreamManager.stopDream(); } } } /** * Returns true if the device is allowed to dream in its current state, - * assuming there has been no recent user activity and no wake locks are held. + * assuming that there was either an explicit request to nap or the user activity + * timeout expired and no wake locks are held. */ private boolean canDreamLocked() { return mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting - && mDreamsActivateOnSleepSetting - && !mBatteryService.isBatteryLow(); + && mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF; } /** @@ -1313,7 +1319,6 @@ public final class PowerManagerService extends IPowerManager.Stub } } - /** * Updates the display power state asynchronously. * When the update is finished, mDisplayReady will be set to true. The display @@ -1494,15 +1499,6 @@ public final class PowerManagerService extends IPowerManager.Stub updatePowerStateLocked(); } - private void handleDockStateChangedLocked(int dockState) { - // TODO - } - - private void handleDreamEndedLocked() { - mDirty |= DIRTY_DREAM_ENDED; - updatePowerStateLocked(); - } - /** * Reboot the device immediately, passing 'reason' (may be null) * to the underlying __reboot system call. Should not return. @@ -1957,22 +1953,11 @@ public final class PowerManagerService extends IPowerManager.Stub } } - private final class DockReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - handleDockStateChangedLocked(dockState); - } - } - } - private final class DreamReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { - handleDreamEndedLocked(); + scheduleSandmanLocked(); } } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java index 0c85204..0cf0f21 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java @@ -60,6 +60,11 @@ public class BridgePowerManager implements IPowerManager { } @Override + public void nap(long arg0) throws RemoteException { + // pass for now. + } + + @Override public void preventScreenOn(boolean arg0) throws RemoteException { // pass for now. } -- cgit v1.1