/* * 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); } } }