/*
* 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.dreams;
import static android.Manifest.permission.BIND_DREAM_SERVICE;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
import android.Manifest;
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.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.text.TextUtils;
import android.util.Slog;
import android.view.Display;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import libcore.util.Objects;
/**
* Service api for managing dreams.
*
* @hide
*/
public final class DreamManagerService extends SystemService {
private static final boolean DEBUG = false;
private static final String TAG = "DreamManagerService";
private final Object mLock = new Object();
private final Context mContext;
private final DreamHandler mHandler;
private final DreamController mController;
private final PowerManager mPowerManager;
private final PowerManagerInternal mPowerManagerInternal;
private final PowerManager.WakeLock mDozeWakeLock;
private Binder mCurrentDreamToken;
private ComponentName mCurrentDreamName;
private int mCurrentDreamUserId;
private boolean mCurrentDreamIsTest;
private boolean mCurrentDreamCanDoze;
private boolean mCurrentDreamIsDozing;
private boolean mCurrentDreamIsWaking;
private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
public DreamManagerService(Context context) {
super(context);
mContext = context;
mHandler = new DreamHandler(FgThread.get().getLooper());
mController = new DreamController(context, mHandler, mControllerListener);
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
}
@Override
public void onStart() {
publishBinderService(DreamService.DREAM_SERVICE, new BinderService());
publishLocalService(DreamManagerInternal.class, new LocalService());
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
if (Build.IS_DEBUGGABLE) {
SystemProperties.addChangeCallback(mSystemPropertiesChanged);
}
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
stopDreamLocked(false /*immediate*/);
}
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
}
}
private void dumpInternal(PrintWriter 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("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
pw.println("mCurrentDreamDozeScreenState="
+ Display.stateToString(mCurrentDreamDozeScreenState));
pw.println("mCurrentDreamDozeScreenBrightness=" + mCurrentDreamDozeScreenBrightness);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
@Override
public void dump(PrintWriter pw, String prefix) {
mController.dump(pw);
}
}, pw, "", 200);
}
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDreamToken != null && !mCurrentDreamIsTest
&& !mCurrentDreamIsWaking;
}
}
private void requestDreamInternal() {
// 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);
}
private void requestAwakenInternal() {
// 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*/);
stopDreamInternal(false /*immediate*/);
}
private void finishSelfInternal(IBinder token, boolean immediate) {
if (DEBUG) {
Slog.d(TAG, "Dream finished: " + token + ", immediate=" + immediate);
}
// 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(immediate);
}
}
}
private void testDreamInternal(ComponentName dream, int userId) {
synchronized (mLock) {
startDreamLocked(dream, true /*isTest*/, false /*canDoze*/, userId);
}
}
private void startDreamInternal(boolean doze) {
final int userId = ActivityManager.getCurrentUser();
final ComponentName dream = chooseDreamForUser(doze, userId);
if (dream != null) {
synchronized (mLock) {
startDreamLocked(dream, false /*isTest*/, doze, userId);
}
}
}
private void stopDreamInternal(boolean immediate) {
synchronized (mLock) {
stopDreamLocked(immediate);
}
}
private void startDozingInternal(IBinder token, int screenState,
int screenBrightness) {
if (DEBUG) {
Slog.d(TAG, "Dream requested to start dozing: " + token
+ ", screenState=" + screenState
+ ", screenBrightness=" + screenBrightness);
}
synchronized (mLock) {
if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
mCurrentDreamDozeScreenState = screenState;
mCurrentDreamDozeScreenBrightness = screenBrightness;
mPowerManagerInternal.setDozeOverrideFromDreamManager(
screenState, screenBrightness);
if (!mCurrentDreamIsDozing) {
mCurrentDreamIsDozing = true;
mDozeWakeLock.acquire();
}
}
}
}
private void stopDozingInternal(IBinder token) {
if (DEBUG) {
Slog.d(TAG, "Dream requested to stop dozing: " + token);
}
synchronized (mLock) {
if (mCurrentDreamToken == token && mCurrentDreamIsDozing) {
mCurrentDreamIsDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT);
}
}
}
private ComponentName chooseDreamForUser(boolean doze, int userId) {
if (doze) {
ComponentName dozeComponent = getDozeComponent(userId);
return validateDream(dozeComponent) ? dozeComponent : null;
}
ComponentName[] dreams = getDreamComponentsForUser(userId);
return dreams != null && dreams.length != 0 ? dreams[0] : null;
}
private boolean validateDream(ComponentName component) {
if (component == null) return false;
final ServiceInfo serviceInfo = getServiceInfo(component);
if (serviceInfo == null) {
Slog.w(TAG, "Dream " + component + " does not exist");
return false;
} else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
&& !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
Slog.w(TAG, "Dream " + component
+ " is not available because its manifest is missing the " + BIND_DREAM_SERVICE
+ " permission on the dream service declaration.");
return false;
}
return true;
}
private ComponentName[] getDreamComponentsForUser(int userId) {
String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_COMPONENTS,
userId);
ComponentName[] components = componentsFromString(names);
// first, ensure components point to valid services
List<ComponentName> validComponents = new ArrayList<ComponentName>();
if (components != null) {
for (ComponentName component : components) {
if (validateDream(component)) {
validComponents.add(component);
}
}
}
// fallback to the default dream component if necessary
if (validComponents.isEmpty()) {
ComponentName defaultDream = getDefaultDreamComponentForUser(userId);
if (defaultDream != null) {
Slog.w(TAG, "Falling back to default dream " + defaultDream);
validComponents.add(defaultDream);
}
}
return validComponents.toArray(new ComponentName[validComponents.size()]);
}
private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_COMPONENTS,
componentsToString(componentNames),
userId);
}
private ComponentName getDefaultDreamComponentForUser(int userId) {
String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
userId);
return name == null ? null : ComponentName.unflattenFromString(name);
}
private ComponentName getDozeComponent() {
return getDozeComponent(ActivityManager.getCurrentUser());
}
private ComponentName getDozeComponent(int userId) {
// Read the component from a system property to facilitate debugging.
// Note that for production devices, the dream should actually be declared in
// a config.xml resource.
String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null;
if (TextUtils.isEmpty(name)) {
// Read the component from a config.xml resource.
// The value should be specified in a resource overlay for the product.
name = mContext.getResources().getString(
com.android.internal.R.string.config_dozeComponent);
}
boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.DOZE_ENABLED, 1, userId) != 0;
return TextUtils.isEmpty(name) || !enabled ? null : ComponentName.unflattenFromString(name);
}
private ServiceInfo getServiceInfo(ComponentName name) {
try {
return name != null ? mContext.getPackageManager().getServiceInfo(name, 0) : null;
} catch (NameNotFoundException e) {
return null;
}
}
private void startDreamLocked(final ComponentName name,
final boolean isTest, final boolean canDoze, final int userId) {
if (Objects.equal(mCurrentDreamName, name)
&& mCurrentDreamIsTest == isTest
&& mCurrentDreamCanDoze == canDoze
&& mCurrentDreamUserId == userId) {
return;
}
stopDreamLocked(true /*immediate*/);
Slog.i(TAG, "Entering dreamland.");
final Binder newToken = new Binder();
mCurrentDreamToken = newToken;
mCurrentDreamName = name;
mCurrentDreamIsTest = isTest;
mCurrentDreamCanDoze = canDoze;
mCurrentDreamUserId = userId;
mHandler.post(new Runnable() {
@Override
public void run() {
mController.startDream(newToken, name, isTest, canDoze, userId);
}
});
}
private void stopDreamLocked(final boolean immediate) {
if (mCurrentDreamToken != null) {
if (immediate) {
Slog.i(TAG, "Leaving dreamland.");
cleanupDreamLocked();
} else if (mCurrentDreamIsWaking) {
return; // already waking
} else {
Slog.i(TAG, "Gently waking up from dream.");
mCurrentDreamIsWaking = true;
}
mHandler.post(new Runnable() {
@Override
public void run() {
mController.stopDream(immediate);
}
});
}
}
private void cleanupDreamLocked() {
mCurrentDreamToken = null;
mCurrentDreamName = null;
mCurrentDreamIsTest = false;
mCurrentDreamCanDoze = false;
mCurrentDreamUserId = 0;
mCurrentDreamIsWaking = false;
if (mCurrentDreamIsDozing) {
mCurrentDreamIsDozing = false;
mDozeWakeLock.release();
}
mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
}
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) {
for (ComponentName componentName : componentNames) {
if (names.length() > 0) {
names.append(',');
}
names.append(componentName.flattenToString());
}
}
return names.toString();
}
private static ComponentName[] componentsFromString(String names) {
if (names == null) {
return null;
}
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;
}
private final DreamController.Listener mControllerListener = new DreamController.Listener() {
@Override
public void onDreamStopped(Binder token) {
synchronized (mLock) {
if (mCurrentDreamToken == token) {
cleanupDreamLocked();
}
}
}
};
/**
* Handler for asynchronous operations performed by the dream manager.
* Ensures operations to {@link DreamController} are single-threaded.
*/
private final class DreamHandler extends Handler {
public DreamHandler(Looper looper) {
super(looper, null, true /*async*/);
}
}
private final class BinderService extends IDreamManager.Stub {
@Override // Binder call
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump DreamManager from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}
final long ident = Binder.clearCallingIdentity();
try {
dumpInternal(pw);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public ComponentName[] getDreamComponents() {
checkPermission(android.Manifest.permission.READ_DREAM_STATE);
final int userId = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
return getDreamComponentsForUser(userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void setDreamComponents(ComponentName[] componentNames) {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
final int userId = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
setDreamComponentsForUser(userId, componentNames);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public ComponentName getDefaultDreamComponent() {
checkPermission(android.Manifest.permission.READ_DREAM_STATE);
final int userId = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
return getDefaultDreamComponentForUser(userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean isDreaming() {
checkPermission(android.Manifest.permission.READ_DREAM_STATE);
final long ident = Binder.clearCallingIdentity();
try {
return isDreamingInternal();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void dream() {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
final long ident = Binder.clearCallingIdentity();
try {
requestDreamInternal();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void testDream(ComponentName dream) {
if (dream == null) {
throw new IllegalArgumentException("dream must not be null");
}
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
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 {
testDreamInternal(dream, callingUserId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void awaken() {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
final long ident = Binder.clearCallingIdentity();
try {
requestAwakenInternal();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void finishSelf(IBinder token, boolean immediate) {
// 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 {
finishSelfInternal(token, immediate);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void startDozing(IBinder token, int screenState, int screenBrightness) {
// 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 {
startDozingInternal(token, screenState, screenBrightness);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public void stopDozing(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 {
stopDozingInternal(token);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
private final class LocalService extends DreamManagerInternal {
@Override
public void startDream(boolean doze) {
startDreamInternal(doze);
}
@Override
public void stopDream(boolean immediate) {
stopDreamInternal(immediate);
}
@Override
public boolean isDreaming() {
return isDreamingInternal();
}
}
private final Runnable mSystemPropertiesChanged = new Runnable() {
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "System properties changed");
synchronized (mLock) {
if (mCurrentDreamName != null && mCurrentDreamCanDoze
&& !mCurrentDreamName.equals(getDozeComponent())) {
// May have updated the doze component, wake up
mPowerManager.wakeUp(SystemClock.uptimeMillis(),
"android.server.dreams:SYSPROP");
}
}
}
};
}