diff options
author | John Spurlock <jspurlock@google.com> | 2012-09-20 05:45:53 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-09-20 05:45:54 -0700 |
commit | e0de5bfff2e74ee566ac2d053052de09aa25e54b (patch) | |
tree | 2fbae7d5235537036dd5ff47ffe5a42b4536d0b6 /services | |
parent | 8e356e089d4b9cf1cd0f31ceead666f8e75d4c28 (diff) | |
parent | f4f6b4c8b0fcf77d46567f13b409255948fe107b (diff) | |
download | frameworks_base-e0de5bfff2e74ee566ac2d053052de09aa25e54b.zip frameworks_base-e0de5bfff2e74ee566ac2d053052de09aa25e54b.tar.gz frameworks_base-e0de5bfff2e74ee566ac2d053052de09aa25e54b.tar.bz2 |
Merge "Fire "dreaming started" and "dreaming stopped" broadcasts." into jb-mr1-dev
Diffstat (limited to 'services')
4 files changed, 634 insertions, 6 deletions
diff --git a/services/java/com/android/server/DreamController.java b/services/java/com/android/server/DreamController.java new file mode 100644 index 0000000..498e581 --- /dev/null +++ b/services/java/com/android/server/DreamController.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.IBinder.DeathRecipient; +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. + */ +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 final Context mContext; + private final IWindowManager mIWindowManager; + private final DeathRecipient mDeathRecipient; + private final ServiceConnection mServiceConnection; + private final Listener mListener; + + private Handler mHandler; + + private ComponentName mCurrentDreamComponent; + private IDreamService mCurrentDream; + private Binder mCurrentDreamToken; + private boolean mCurrentDreamIsTest; + + public DreamController(Context context, DeathRecipient deathRecipient, + ServiceConnection serviceConnection, Listener listener) { + mContext = context; + mDeathRecipient = deathRecipient; + mServiceConnection = serviceConnection; + mListener = listener; + mIWindowManager = WindowManagerGlobal.getWindowManagerService(); + } + + public void setHandler(Handler handler) { + mHandler = handler; + } + + public void dump(PrintWriter pw) { + if (mHandler== null || pw == null) { + return; + } + 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)); + + 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(); + } + + mCurrentDreamComponent = dream; + mCurrentDreamIsTest = isTest; + 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."); + stop(); + 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(); + 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); + + boolean linked = linkDeathRecipient(dream); + if (!linked) { + stop(); + 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(); + } + } + + public void stop() { + if (DEBUG) Slog.v(TAG, "stop()"); + + if (mCurrentDream != null) { + unlinkDeathRecipient(mCurrentDream.asBinder()); + + if (DEBUG) Slog.v(TAG, "Unbinding: " + mCurrentDreamComponent + " service: " + mCurrentDream); + mContext.unbindService(mServiceConnection); + } + if (mCurrentDreamToken != null) { + removeWindowToken(mCurrentDreamToken); + } + + final boolean wasTest = mCurrentDreamIsTest; + mCurrentDream = null; + mCurrentDreamToken = null; + mCurrentDreamComponent = null; + mCurrentDreamIsTest = false; + + if (mListener != null && mHandler != null) { + mHandler.post(new Runnable(){ + @Override + public void run() { + mListener.onDreamStopped(wasTest); + }}); + } + } + + 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(); + } + } + + 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 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; + } + } + + private void unlinkDeathRecipient(IBinder dream) { + if (DEBUG) Slog.v(TAG, "Unlinking death recipient"); + try { + dream.unlinkToDeath(mDeathRecipient, 0); + } catch (NoSuchElementException e) { + // we tried + } + } + +}
\ No newline at end of file diff --git a/services/java/com/android/server/DreamManagerService.java b/services/java/com/android/server/DreamManagerService.java new file mode 100644 index 0000000..b02ea7f --- /dev/null +++ b/services/java/com/android/server/DreamManagerService.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS; +import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT; + +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.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.dreams.Dream; +import android.service.dreams.IDreamManager; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Service api for managing dreams. + * + * @hide + */ +public final class DreamManagerService + extends IDreamManager.Stub + implements ServiceConnection { + 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 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 boolean mIsDreaming; + + public DreamManagerService(Context context) { + if (DEBUG) Slog.v(TAG, "DreamManagerService startup"); + mContext = context; + mController = new DreamController(context, mAwakenOnBinderDeath, this, mControllerListener); + mHandler = new DreamControllerHandler(mController); + mController.setHandler(mHandler); + } + + public void systemReady() { + mCurrentUserManager.init(mContext); + + if (DEBUG) Slog.v(TAG, "Ready to dream!"); + } + + @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); + } + + // begin IDreamManager api + @Override + public ComponentName[] getDreamComponents() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + return getDreamComponentsForUser(userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void setDreamComponents(ComponentName[] componentNames) { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + SCREENSAVER_COMPONENTS, + componentsToString(componentNames), + userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public ComponentName getDefaultDreamComponent() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), + SCREENSAVER_DEFAULT_COMPONENT, + userId); + return name == null ? null : ComponentName.unflattenFromString(name); + } finally { + Binder.restoreCallingIdentity(ident); + } + + } + + @Override + public boolean isDreaming() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + + return mIsDreaming; + } + + @Override + 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*/); + } + } + } 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 1396d8b..2792704 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 fda619c..7052ed5 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); |