/* * 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 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.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.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.IDozeHardware; import android.service.dreams.IDreamManager; import android.text.TextUtils; import android.util.Slog; 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 PowerManager.WakeLock mDozeWakeLock; private final McuHal mMcuHal; // synchronized on self private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; private int mCurrentDreamUserId; private boolean mCurrentDreamIsTest; private boolean mCurrentDreamCanDoze; private boolean mCurrentDreamIsDozing; private DozeHardwareWrapper mCurrentDreamDozeHardware; 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); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG); mMcuHal = McuHal.open(); if (mMcuHal != null) { mMcuHal.reset(); } } @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) { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { stopDreamLocked(); } } }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); } } private void dumpInternal(PrintWriter pw) { pw.println("DREAM MANAGER (dumpsys dreams)"); pw.println(); pw.println("mMcuHal=" + mMcuHal); 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("mCurrentDreamDozeHardware=" + mCurrentDreamDozeHardware); pw.println(); DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { @Override public void dump(PrintWriter pw) { mController.dump(pw); } }, pw, 200); } private boolean isDreamingInternal() { synchronized (mLock) { return mCurrentDreamToken != null && !mCurrentDreamIsTest; } } 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(); } private void finishSelfInternal(IBinder token) { if (DEBUG) { Slog.d(TAG, "Dream finished: " + token); } // 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(); } } } 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 = doze ? getDozeComponent() : chooseDreamForUser(userId); if (dream != null) { synchronized (mLock) { startDreamLocked(dream, false /*isTest*/, doze, userId); } } } private void stopDreamInternal() { synchronized (mLock) { stopDreamLocked(); } } private void startDozingInternal(IBinder token) { if (DEBUG) { Slog.d(TAG, "Dream requested to start dozing: " + token); } synchronized (mLock) { if (mCurrentDreamToken == token && mCurrentDreamCanDoze && !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(); } } } private IDozeHardware getDozeHardwareInternal(IBinder token) { synchronized (mLock) { if (mCurrentDreamToken == token && mCurrentDreamCanDoze && mCurrentDreamDozeHardware == null && mMcuHal != null) { mCurrentDreamDozeHardware = new DozeHardwareWrapper(); return mCurrentDreamDozeHardware; } return null; } } private ComponentName chooseDreamForUser(int userId) { ComponentName[] dreams = getDreamComponentsForUser(userId); return dreams != null && dreams.length != 0 ? dreams[0] : null; } 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 validComponents = new ArrayList(); if (components != null) { for (ComponentName component : components) { if (serviceExists(component)) { validComponents.add(component); } else { Slog.w(TAG, "Dream " + component + " does not exist"); } } } // 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() { // 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); } return TextUtils.isEmpty(name) ? null : ComponentName.unflattenFromString(name); } private boolean serviceExists(ComponentName name) { try { return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null; } catch (NameNotFoundException e) { return false; } } 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(); if (DEBUG) 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() { if (mCurrentDreamToken != null) { if (DEBUG) Slog.i(TAG, "Leaving dreamland."); cleanupDreamLocked(); mHandler.post(new Runnable() { @Override public void run() { mController.stopDream(); } }); } } private void cleanupDreamLocked() { mCurrentDreamToken = null; mCurrentDreamName = null; mCurrentDreamIsTest = false; mCurrentDreamCanDoze = false; mCurrentDreamUserId = 0; if (mCurrentDreamIsDozing) { mCurrentDreamIsDozing = false; mDozeWakeLock.release(); } if (mCurrentDreamDozeHardware != null) { mCurrentDreamDozeHardware.release(); mCurrentDreamDozeHardware = null; } } 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) { // 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); } finally { Binder.restoreCallingIdentity(ident); } } @Override // Binder call public void startDozing(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 { startDozingInternal(token); } 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); } } @Override // Binder call public IDozeHardware getDozeHardware(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 { return getDozeHardwareInternal(token); } finally { Binder.restoreCallingIdentity(ident); } } } private final class LocalService extends DreamManagerInternal { @Override public void startDream(boolean doze) { startDreamInternal(doze); } @Override public void stopDream() { stopDreamInternal(); } @Override public boolean isDreaming() { return isDreamingInternal(); } } private final class DozeHardwareWrapper extends IDozeHardware.Stub { private boolean mReleased; public void release() { synchronized (mMcuHal) { if (!mReleased) { mReleased = true; mMcuHal.reset(); } } } @Override // Binder call public byte[] sendMessage(String msg, byte[] arg) { if (msg == null) { throw new IllegalArgumentException("msg must not be null"); } final long ident = Binder.clearCallingIdentity(); try { synchronized (mMcuHal) { if (mReleased) { Slog.w(TAG, "Ignoring message to MCU HAL because the dream " + "has already ended: " + msg); return null; } return mMcuHal.sendMessage(msg, arg); } } finally { Binder.restoreCallingIdentity(ident); } } } }