blob: b02ea7f768fd2c4b6b96a83385cdda16f1aa2c04 [file] [log] [blame]
/*
* 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);
}
}
}