/* * 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 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.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.IDreamManager; import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; import libcore.util.Objects; /** * Service api for managing dreams. * * @hide */ public final class DreamManagerService extends IDreamManager.Stub { 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 Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; private int mCurrentDreamUserId; private boolean mCurrentDreamIsTest; public DreamManagerService(Context context, Handler mainHandler) { mContext = context; mHandler = new DreamHandler(mainHandler.getLooper()); mController = new DreamController(context, mHandler, mControllerListener); mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); } public void systemReady() { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { stopDreamLocked(); } } }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 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(); DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { @Override public void dump(PrintWriter pw) { mController.dump(pw); } }, pw, 200); } @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 { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.SCREENSAVER_COMPONENTS, componentsToString(componentNames), userId); } 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 { String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, userId); return name == null ? null : ComponentName.unflattenFromString(name); } finally { Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean isDreaming() { checkPermission(android.Manifest.permission.READ_DREAM_STATE); synchronized (mLock) { return mCurrentDreamToken != null && !mCurrentDreamIsTest; } } @Override // Binder call public void dream() { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); final long ident = Binder.clearCallingIdentity(); try { // 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); } finally { Binder.restoreCallingIdentity(ident); } } @Override // Binder call public void testDream(ComponentName dream) { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); if (dream == null) { throw new IllegalArgumentException("dream must not be null"); } 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 { synchronized (mLock) { startDreamLocked(dream, true /*isTest*/, callingUserId); } } finally { Binder.restoreCallingIdentity(ident); } } @Override // Binder call public void awaken() { checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); final long ident = Binder.clearCallingIdentity(); try { // 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*/); stopDream(); } 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 { 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(); } } } finally { Binder.restoreCallingIdentity(ident); } } /** * Called by the power manager to start a dream. */ public void startDream() { int userId = ActivityManager.getCurrentUser(); ComponentName dream = chooseDreamForUser(userId); if (dream != null) { synchronized (mLock) { startDreamLocked(dream, false /*isTest*/, userId); } } } /** * Called by the power manager to stop a dream. */ public void stopDream() { synchronized (mLock) { stopDreamLocked(); } } 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); return names == null ? null : componentsFromString(names); } private void startDreamLocked(final ComponentName name, final boolean isTest, final int userId) { if (Objects.equal(mCurrentDreamName, name) && mCurrentDreamIsTest == isTest && mCurrentDreamUserId == userId) { return; } stopDreamLocked(); if (DEBUG) Slog.i(TAG, "Entering dreamland."); final Binder newToken = new Binder(); mCurrentDreamToken = newToken; mCurrentDreamName = name; mCurrentDreamIsTest = isTest; mCurrentDreamUserId = userId; mHandler.post(new Runnable() { @Override public void run() { mController.startDream(newToken, name, isTest, 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; mCurrentDreamUserId = 0; } 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) { 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*/); } } }