diff --git a/api/current.txt b/api/current.txt index ba74e388cf77c..0d09aa4579941 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20289,6 +20289,8 @@ package android.service.dreams { method public void setContentView(android.view.View); method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); method public void setInteractive(boolean); + field public static final java.lang.String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED"; + field public static final java.lang.String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED"; field public static final java.lang.String METADATA_NAME_CONFIG_ACTIVITY = "android.service.dreams.config_activity"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.dreams.Dream"; } diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java index ba2ac671866b2..4a23d396df69b 100644 --- a/core/java/android/service/dreams/Dream.java +++ b/core/java/android/service/dreams/Dream.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; import android.view.ActionMode; @@ -40,12 +39,16 @@ import android.view.accessibility.AccessibilityEvent; import com.android.internal.policy.PolicyManager; /** - * Extend this class to implement a custom screensaver. + * Extend this class to implement a custom Dream. + * + *

Dreams are interactive screensavers launched when a charging device is idle, or docked in a + * desk dock. Dreams provide another modality for apps to express themselves, tailored for + * an exhibition/lean-back experience.

*/ public class Dream extends Service implements Window.Callback { private final static boolean DEBUG = true; - private final static String TAG = "Dream"; - + private final String TAG = Dream.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the @@ -60,28 +63,43 @@ public class Dream extends Service implements Window.Callback { public static final String METADATA_NAME_CONFIG_ACTIVITY = "android.service.dreams.config_activity"; - private Window mWindow; + /** + * Broadcast Action: Sent after the system starts dreaming. + * + *

This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED"; + /** + * Broadcast Action: Sent after the system stops dreaming. + * + *

This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.

+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED"; + + private final Handler mHandler = new Handler(); + private IBinder mWindowToken; + private Window mWindow; private WindowManager mWindowManager; private IDreamManager mSandman; - private boolean mInteractive; - - final Handler mHandler = new Handler(); - - boolean mFinished = false; + private boolean mFinished; // begin Window.Callback methods @Override public boolean dispatchKeyEvent(KeyEvent event) { // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK if (!mInteractive) { - if (DEBUG) Slog.v(TAG, "finishing on keyEvent"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on keyEvent"); + safelyFinish(); return true; } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - if (DEBUG) Slog.v(TAG, "finishing on back key"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on back key"); + safelyFinish(); return true; } return mWindow.superDispatchKeyEvent(event); @@ -90,8 +108,8 @@ public class Dream extends Service implements Window.Callback { @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (!mInteractive) { - if (DEBUG) Slog.v(TAG, "finishing on keyShortcutEvent"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on keyShortcutEvent"); + safelyFinish(); return true; } return mWindow.superDispatchKeyShortcutEvent(event); @@ -102,8 +120,8 @@ public class Dream extends Service implements Window.Callback { // TODO: create more flexible version of mInteractive that allows clicks // but finish()es on any other kind of activity if (!mInteractive) { - if (DEBUG) Slog.v(TAG, "finishing on touchEvent"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on touchEvent"); + safelyFinish(); return true; } return mWindow.superDispatchTouchEvent(event); @@ -112,8 +130,8 @@ public class Dream extends Service implements Window.Callback { @Override public boolean dispatchTrackballEvent(MotionEvent event) { if (!mInteractive) { - if (DEBUG) Slog.v(TAG, "finishing on trackballEvent"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on trackballEvent"); + safelyFinish(); return true; } return mWindow.superDispatchTrackballEvent(event); @@ -122,8 +140,8 @@ public class Dream extends Service implements Window.Callback { @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { if (!mInteractive) { - if (DEBUG) Slog.v(TAG, "finishing on genericMotionEvent"); - finish(); + if (DEBUG) Slog.v(TAG, "Finishing on genericMotionEvent"); + safelyFinish(); return true; } return mWindow.superDispatchGenericMotionEvent(event); @@ -212,30 +230,9 @@ public class Dream extends Service implements Window.Callback { public Window getWindow() { return mWindow; } - - /** - * Called when this Dream is constructed. Place your initialization here. - * - * Subclasses must call through to the superclass implementation. - */ - @Override - public void onCreate() { - super.onCreate(); - - if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId()); - - mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); - } - - /** - * Called when this Dream is started. - */ - public void onStart() { - // hook for subclasses - } /** - * Inflate a layout resource and set it to be the content view for this Dream. + * Inflates a layout resource and set it to be the content view for this Dream. * Behaves similarly to {@link android.app.Activity#setContentView(int)}. * * @param layoutResID Resource ID to be inflated. @@ -248,7 +245,7 @@ public class Dream extends Service implements Window.Callback { } /** - * Set a view to be the content view for this Dream. + * Sets a view to be the content view for this Dream. * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)}, * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view. * @@ -262,7 +259,7 @@ public class Dream extends Service implements Window.Callback { } /** - * Set a view to be the content view for this Dream. + * Sets a view to be the content view for this Dream. * Behaves similarly to * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}. * @@ -277,7 +274,7 @@ public class Dream extends Service implements Window.Callback { } /** - * Add a view to the Dream's window, leaving other content views in place. + * Adds a view to the Dream's window, leaving other content views in place. * * @param view The desired content to display. * @param params Layout parameters for the view. @@ -285,21 +282,27 @@ public class Dream extends Service implements Window.Callback { public void addContentView(View view, ViewGroup.LayoutParams params) { getWindow().addContentView(view, params); } - + /** - * @param mInteractive the mInteractive to set + * Marks this dream as interactive to receive input events. + * + *

Non-interactive dreams (default) will dismiss on the first input event.

+ * + *

Interactive dreams should call {@link #finish()} to dismiss themselves.

+ * + * @param interactive True if this dream will handle input events. */ - public void setInteractive(boolean mInteractive) { - this.mInteractive = mInteractive; + public void setInteractive(boolean interactive) { + mInteractive = interactive; } /** - * @return the mInteractive + * Returns whether or not this dream is interactive. */ public boolean isInteractive() { return mInteractive; } - + /** Convenience method for setting View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. */ protected void lightsOut() { // turn the lights down low @@ -319,14 +322,29 @@ public class Dream extends Service implements Window.Callback { public View findViewById(int id) { return getWindow().findViewById(id); } - + /** - * Called when this Dream is being removed from the screen and stopped. + * Called when this Dream is constructed. Place your initialization here. + * + * Subclasses must call through to the superclass implementation. */ @Override - public void onDestroy() { - super.onDestroy(); - mWindowManager.removeView(mWindow.getDecorView()); + public void onCreate() { + if (DEBUG) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId()); + super.onCreate(); + loadSandman(); + } + + /** + * Called when this Dream is started. + */ + public void onStart() { + // hook for subclasses + Slog.v(TAG, "called Dream.onStart()"); + } + + private void loadSandman() { + mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); } /** @@ -335,16 +353,21 @@ public class Dream extends Service implements Window.Callback { * @param windowToken Binder to attach to the window to allow access to the correct window type. * @hide */ - final /*package*/ void attach(IBinder windowToken) { - if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId()); - + private final void attach(IBinder windowToken) { + if (DEBUG) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId()); + + if (mSandman == null) { + Slog.w(TAG, "No dream manager found, super.onCreate may not have been called"); + loadSandman(); + } + mWindowToken = windowToken; mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(this); mWindow.requestFeature(Window.FEATURE_NO_TITLE); mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000)); - if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken - + " to window of type " + WindowManager.LayoutParams.TYPE_DREAM); + if (DEBUG) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s", + windowToken, WindowManager.LayoutParams.TYPE_DREAM)); WindowManager.LayoutParams lp = mWindow.getAttributes(); lp.type = WindowManager.LayoutParams.TYPE_DREAM; @@ -355,58 +378,105 @@ public class Dream extends Service implements Window.Callback { | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON ); mWindow.setAttributes(lp); - - //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp); - - if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow); + + if (DEBUG) Slog.v(TAG, "Created and attached window: " + mWindow); mWindow.setWindowManager(null, windowToken, "dream", true); mWindowManager = mWindow.getWindowManager(); - - // now make it visible + + // now make it visible (on the ui thread) mHandler.post(new Runnable(){ @Override public void run() { - if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId()); + if (DEBUG) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId()); - getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); + try { + getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); + } catch (Throwable t) { + Slog.w("Crashed adding window view", t); + safelyFinish(); + return; + } // start it up - onStart(); + try { + onStart(); + } catch (Throwable t) { + Slog.w("Crashed in onStart()", t); + safelyFinish(); + } }}); } - - /** - * Stop the dream and wake up. - * - * After this method is called, the service will be stopped. - */ - public void finish() { - if (mFinished) return; + + private void safelyFinish() { + if (DEBUG) Slog.v(TAG, "safelyFinish()"); try { - mSandman.awaken(); // assuming we were started by the DreamManager - stopSelf(); // if launched via any other means - mFinished = true; - } catch (RemoteException ex) { - // sigh + finish(); + } catch (Throwable t) { + Slog.w(TAG, "Crashed in safelyFinish()", t); + finishInternal(); + return; + } + + if (!mFinished) { + Slog.w(TAG, "Bad dream, did not call super.finish()"); + finishInternal(); } } - class IDreamServiceWrapper extends IDreamService.Stub { - public IDreamServiceWrapper() { - } + /** + * Stops the dream, detaches from the window, and wakes up. + * + * Subclasses must call through to the superclass implementation. + * + *

After this method is called, the service will be stopped.

+ */ + public void finish() { + if (DEBUG) Slog.v(TAG, "finish()"); + finishInternal(); + } + private void finishInternal() { + if (DEBUG) Slog.v(TAG, "finishInternal() mFinished = " + mFinished); + if (mFinished) return; + try { + mFinished = true; + + if (mSandman != null) { + mSandman.awakenSelf(mWindowToken); + } else { + Slog.w(TAG, "No dream manager found"); + } + stopSelf(); // if launched via any other means + + } catch (Throwable t) { + Slog.w(TAG, "Crashed in finishInternal()", t); + } + } + + @Override + public void onDestroy() { + if (DEBUG) Slog.v(TAG, "onDestroy()"); + super.onDestroy(); + + if (DEBUG) Slog.v(TAG, "Removing window"); + try { + mWindowManager.removeView(mWindow.getDecorView()); + } catch (Throwable t) { + Slog.w(TAG, "Crashed removing window view", t); + } + } + + @Override + public final IBinder onBind(Intent intent) { + if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); + return new DreamServiceWrapper(); + } + + private class DreamServiceWrapper extends IDreamService.Stub { public void attach(IBinder windowToken) { Dream.this.attach(windowToken); } } - /** - * Implement to return the implementation of the internal accessibility - * service interface. Subclasses should not override. - */ - @Override - public final IBinder onBind(Intent intent) { - return new IDreamServiceWrapper(); - } } diff --git a/core/java/android/service/dreams/DreamManagerService.java b/core/java/android/service/dreams/DreamManagerService.java deleted file mode 100644 index 4aa1cbb5e99ef..0000000000000 --- a/core/java/android/service/dreams/DreamManagerService.java +++ /dev/null @@ -1,247 +0,0 @@ -package android.service.dreams; - -import static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS; -import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT; -import java.io.FileDescriptor; -import java.io.PrintWriter; - -import android.app.ActivityManagerNative; -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.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.Slog; -import android.view.IWindowManager; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; - -/** - * - * @hide - * - */ - -public class DreamManagerService - extends IDreamManager.Stub - implements ServiceConnection -{ - private static final boolean DEBUG = true; - private static final String TAG = "DreamManagerService"; - - final Object mLock = new Object[0]; - - private Context mContext; - private IWindowManager mIWindowManager; - - private ComponentName mCurrentDreamComponent; - private IDreamService mCurrentDream; - private Binder mCurrentDreamToken; - private int mCurrentUserId; - - public DreamManagerService(Context context) { - if (DEBUG) Slog.v(TAG, "DreamManagerService startup"); - mContext = context; - mIWindowManager = WindowManagerGlobal.getWindowManagerService(); - } - - 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); - } - } - - // IDreamManager method - @Override - public void dream() { - ComponentName[] dreams = getDreamComponentsForUser(mCurrentUserId); - ComponentName name = dreams != null && dreams.length > 0 ? dreams[0] : null; - if (name != null) { - synchronized (mLock) { - final long ident = Binder.clearCallingIdentity(); - try { - bindDreamComponentL(name, false); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - // IDreamManager method - @Override - public void setDreamComponents(ComponentName[] componentNames) { - Settings.Secure.putStringForUser(mContext.getContentResolver(), - SCREENSAVER_COMPONENTS, - componentsToString(componentNames), - UserHandle.getCallingUserId()); - } - - private static String componentsToString(ComponentName[] componentNames) { - StringBuilder names = new StringBuilder(); - if (componentNames != null) { - for (ComponentName componentName : componentNames) { - if (names.length() > 0) - names.append(','); - names.append(componentName.flattenToString()); - } - } - return names.toString(); - } - - private static ComponentName[] componentsFromString(String names) { - String[] namesArray = names.split(","); - ComponentName[] componentNames = new ComponentName[namesArray.length]; - for (int i = 0; i < namesArray.length; i++) - componentNames[i] = ComponentName.unflattenFromString(namesArray[i]); - return componentNames; - } - - // IDreamManager method - @Override - public ComponentName[] getDreamComponents() { - return getDreamComponentsForUser(UserHandle.getCallingUserId()); - } - - private ComponentName[] getDreamComponentsForUser(int userId) { - String names = Settings.Secure.getStringForUser(mContext.getContentResolver(), - SCREENSAVER_COMPONENTS, - userId); - return names == null ? null : componentsFromString(names); - } - - // IDreamManager method - @Override - public ComponentName getDefaultDreamComponent() { - String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), - SCREENSAVER_DEFAULT_COMPONENT, - UserHandle.getCallingUserId()); - return name == null ? null : ComponentName.unflattenFromString(name); - } - - // IDreamManager method - @Override - public void testDream(ComponentName name) { - if (DEBUG) Slog.v(TAG, "startDream name=" + name - + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); -// checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); - synchronized (mLock) { - final long ident = Binder.clearCallingIdentity(); - try { - bindDreamComponentL(name, true); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - // IDreamManager method - @Override - public void awaken() { - if (DEBUG) Slog.v(TAG, "awaken()"); - synchronized (mLock) { - if (mCurrentDream != null) { - if (DEBUG) Slog.v(TAG, "disconnecting: " + mCurrentDreamComponent + " service: " + mCurrentDream); - mContext.unbindService(this); - mCurrentDream = null; - mCurrentDreamToken = null; - } - } - } - - // IDreamManager method - @Override - public boolean isDreaming() { - synchronized (mLock) { - return mCurrentDreamToken != null; - } - } - - public void bindDreamComponentL(ComponentName componentName, boolean test) { - if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName - + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); - - Intent intent = new Intent(Intent.ACTION_MAIN) - .setComponent(componentName) - .addFlags( - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - ) - .putExtra("android.dreams.TEST", test); - - mCurrentDreamComponent = componentName; - mCurrentDreamToken = new Binder(); - 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. Proceed at your own risk."); - } - - if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) { - Slog.w(TAG, "unable to bind service: " + componentName); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId()); - - mCurrentDream = IDreamService.Stub.asInterface(service); - try { - if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken); - mCurrentDream.attach(mCurrentDreamToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Unable to send window token to dream:" + ex); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream); - // Only happens in exceptional circumstances - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - - pw.println("Dreamland:"); - pw.print(" component="); pw.println(mCurrentDreamComponent); - pw.print(" token="); pw.println(mCurrentDreamToken); - pw.print(" dream="); pw.println(mCurrentDream); - } - - public void systemReady() { - - // dream settings are kept per user, so keep track of current user - try { - mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id; - } catch (RemoteException e) { - Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); - } - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); - if (DEBUG) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house"); - } - }}, filter); - - if (DEBUG) Slog.v(TAG, "ready to dream!"); - } - -} diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index b6fcdf00bb878..bd1c5245ab5eb 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -19,6 +19,7 @@ package android.service.dreams; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.content.ComponentName; +import android.os.IBinder; /** @hide */ interface IDreamManager { @@ -29,4 +30,5 @@ interface IDreamManager { ComponentName getDefaultDreamComponent(); void testDream(in ComponentName componentName); boolean isDreaming(); + void awakenSelf(in IBinder token); } \ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 89d78b682a022..661b70c4f518d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -140,6 +140,9 @@ + + + @@ -1929,6 +1932,18 @@ android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="signature|system" /> + + + + + + + + + + 0 ? dreams[0] : null; + if (firstDream != null) { + mHandler.requestStart(firstDream, false /*isTest*/); + synchronized (mLock) { + setDreamingLocked(true, false /*isTest*/); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void testDream(ComponentName dream) { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + 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*/); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + + } + + @Override + 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(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void awakenSelf(IBinder token) { + // requires no permission, called by Dream from an arbitrary process + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Wake up from dream: " + token); + if (token != null) { + mHandler.requestStopSelf(token); + } + } 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(); + } + // 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); + } + } + + 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[] getDreamComponentsForUser(int userId) { + String names = Settings.Secure.getStringForUser(mContext.getContentResolver(), + SCREENSAVER_COMPONENTS, + userId); + return names == null ? null : componentsFromString(names); + } + + private static String componentsToString(ComponentName[] componentNames) { + StringBuilder names = new StringBuilder(); + if (componentNames != null) { + for (ComponentName componentName : componentNames) { + if (names.length() > 0) { + names.append(','); + } + names.append(componentName.flattenToString()); + } + } + return names.toString(); + } + + private static ComponentName[] componentsFromString(String names) { + String[] namesArray = names.split(","); + ComponentName[] componentNames = new ComponentName[namesArray.length]; + for (int i = 0; i < namesArray.length; i++) { + componentNames[i] = ComponentName.unflattenFromString(namesArray[i]); + } + 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; + } + } 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); + }}); + } + + 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/SystemServer.java b/services/java/com/android/server/SystemServer.java index 90783b740ca1e..c0b46c76a10bc 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -38,7 +38,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.server.search.SearchManagerService; -import android.service.dreams.DreamManagerService; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index fda619c2a7d4d..7052ed57c99fa 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -50,6 +50,7 @@ import android.os.ServiceManager; import android.os.SystemClock; 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; @@ -98,6 +99,8 @@ 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. @@ -364,6 +367,10 @@ public final class PowerManagerService extends IPowerManager.Stub filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(new DockReceiver(), filter); + filter = new IntentFilter(); + filter.addAction(Dream.ACTION_DREAMING_STOPPED); + mContext.registerReceiver(new DreamReceiver(), filter); + // Register for settings changes. final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( @@ -1146,8 +1153,12 @@ public final class PowerManagerService extends IPowerManager.Stub * Determines whether to post a message to the sandman to update the dream state. */ private void updateDreamLocked(int dirty) { - if ((dirty & (DIRTY_WAKEFULNESS | DIRTY_SETTINGS - | DIRTY_IS_POWERED | DIRTY_STAY_ON | DIRTY_BATTERY_STATE)) != 0) { + if ((dirty & (DIRTY_WAKEFULNESS + | DIRTY_SETTINGS + | DIRTY_IS_POWERED + | DIRTY_STAY_ON + | DIRTY_BATTERY_STATE + | DIRTY_DREAM_ENDED)) != 0) { scheduleSandmanLocked(); } } @@ -1230,15 +1241,15 @@ public final class PowerManagerService extends IPowerManager.Stub handleDreamFinishedLocked(); } - // Allow the sandman to detect when the dream has ended. - // FIXME: The DreamManagerService should tell us explicitly. + // 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, 1000); + mHandler.sendMessageDelayed(msg, 5000); } } } @@ -1472,6 +1483,11 @@ public final class PowerManagerService extends IPowerManager.Stub // 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. @@ -1937,6 +1953,15 @@ public final class PowerManagerService extends IPowerManager.Stub } } + private final class DreamReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleDreamEndedLocked(); + } + } + } + private final class SettingsObserver extends ContentObserver { public SettingsObserver(Handler handler) { super(handler);