diff options
Diffstat (limited to 'services/core/java/com/android/server/dreams/DreamManagerService.java')
-rw-r--r-- | services/core/java/com/android/server/dreams/DreamManagerService.java | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java new file mode 100644 index 0000000..ffb113c --- /dev/null +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -0,0 +1,494 @@ +/* + * 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.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.DreamManagerInternal; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +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 Binder mCurrentDreamToken; + private ComponentName mCurrentDreamName; + private int mCurrentDreamUserId; + private boolean mCurrentDreamIsTest; + + 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); + } + + @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("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); + } + + 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*/, userId); + } + } + + private void startDreamInternal() { + int userId = ActivityManager.getCurrentUser(); + ComponentName dream = chooseDreamForUser(userId); + if (dream != null) { + synchronized (mLock) { + startDreamLocked(dream, false /*isTest*/, userId); + } + } + } + + private void stopDreamInternal() { + 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); + 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 (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 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 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) { + 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); + } + } + } + + private final class LocalService extends DreamManagerInternal { + @Override + public void startDream() { + startDreamInternal(); + } + + @Override + public void stopDream() { + stopDreamInternal(); + } + + @Override + public boolean isDreaming() { + return isDreamingInternal(); + } + } +} |