Home | History | Annotate | Download | only in dreams
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.dreams;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 
     21 import android.app.ActivityManager;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.PackageManager.NameNotFoundException;
     29 import android.os.Binder;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Looper;
     33 import android.os.PowerManager;
     34 import android.os.SystemClock;
     35 import android.os.UserHandle;
     36 import android.provider.Settings;
     37 import android.service.dreams.IDreamManager;
     38 import android.util.Slog;
     39 
     40 import java.io.FileDescriptor;
     41 import java.io.PrintWriter;
     42 import java.util.ArrayList;
     43 import java.util.List;
     44 
     45 import libcore.util.Objects;
     46 
     47 /**
     48  * Service api for managing dreams.
     49  *
     50  * @hide
     51  */
     52 public final class DreamManagerService extends IDreamManager.Stub {
     53     private static final boolean DEBUG = false;
     54     private static final String TAG = "DreamManagerService";
     55 
     56     private final Object mLock = new Object();
     57 
     58     private final Context mContext;
     59     private final DreamHandler mHandler;
     60     private final DreamController mController;
     61     private final PowerManager mPowerManager;
     62 
     63     private Binder mCurrentDreamToken;
     64     private ComponentName mCurrentDreamName;
     65     private int mCurrentDreamUserId;
     66     private boolean mCurrentDreamIsTest;
     67 
     68     public DreamManagerService(Context context, Handler mainHandler) {
     69         mContext = context;
     70         mHandler = new DreamHandler(mainHandler.getLooper());
     71         mController = new DreamController(context, mHandler, mControllerListener);
     72 
     73         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
     74     }
     75 
     76     public void systemReady() {
     77         mContext.registerReceiver(new BroadcastReceiver() {
     78             @Override
     79             public void onReceive(Context context, Intent intent) {
     80                 synchronized (mLock) {
     81                     stopDreamLocked();
     82                 }
     83             }
     84         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
     85     }
     86 
     87     @Override
     88     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     89         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
     90 
     91         pw.println("DREAM MANAGER (dumpsys dreams)");
     92         pw.println();
     93 
     94         pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
     95         pw.println("mCurrentDreamName=" + mCurrentDreamName);
     96         pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
     97         pw.println("mCurrentDreamIsTest=" + mCurrentDreamIsTest);
     98         pw.println();
     99 
    100         DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
    101             @Override
    102             public void dump(PrintWriter pw) {
    103                 mController.dump(pw);
    104             }
    105         }, pw, 200);
    106     }
    107 
    108     @Override // Binder call
    109     public ComponentName[] getDreamComponents() {
    110         checkPermission(android.Manifest.permission.READ_DREAM_STATE);
    111 
    112         final int userId = UserHandle.getCallingUserId();
    113         final long ident = Binder.clearCallingIdentity();
    114         try {
    115             return getDreamComponentsForUser(userId);
    116         } finally {
    117             Binder.restoreCallingIdentity(ident);
    118         }
    119     }
    120 
    121     @Override // Binder call
    122     public void setDreamComponents(ComponentName[] componentNames) {
    123         checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
    124 
    125         final int userId = UserHandle.getCallingUserId();
    126         final long ident = Binder.clearCallingIdentity();
    127         try {
    128             Settings.Secure.putStringForUser(mContext.getContentResolver(),
    129                     Settings.Secure.SCREENSAVER_COMPONENTS,
    130                     componentsToString(componentNames),
    131                     userId);
    132         } finally {
    133             Binder.restoreCallingIdentity(ident);
    134         }
    135     }
    136 
    137     @Override // Binder call
    138     public ComponentName getDefaultDreamComponent() {
    139         checkPermission(android.Manifest.permission.READ_DREAM_STATE);
    140 
    141         final int userId = UserHandle.getCallingUserId();
    142         final long ident = Binder.clearCallingIdentity();
    143         try {
    144             String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    145                     Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
    146                     userId);
    147             return name == null ? null : ComponentName.unflattenFromString(name);
    148         } finally {
    149             Binder.restoreCallingIdentity(ident);
    150         }
    151     }
    152 
    153     @Override // Binder call
    154     public boolean isDreaming() {
    155         checkPermission(android.Manifest.permission.READ_DREAM_STATE);
    156 
    157         synchronized (mLock) {
    158             return mCurrentDreamToken != null && !mCurrentDreamIsTest;
    159         }
    160     }
    161 
    162     @Override // Binder call
    163     public void dream() {
    164         checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
    165 
    166         final long ident = Binder.clearCallingIdentity();
    167         try {
    168             // Ask the power manager to nap.  It will eventually call back into
    169             // startDream() if/when it is appropriate to start dreaming.
    170             // Because napping could cause the screen to turn off immediately if the dream
    171             // cannot be started, we keep one eye open and gently poke user activity.
    172             long time = SystemClock.uptimeMillis();
    173             mPowerManager.userActivity(time, true /*noChangeLights*/);
    174             mPowerManager.nap(time);
    175         } finally {
    176             Binder.restoreCallingIdentity(ident);
    177         }
    178     }
    179 
    180     @Override // Binder call
    181     public void testDream(ComponentName dream) {
    182         checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
    183 
    184         if (dream == null) {
    185             throw new IllegalArgumentException("dream must not be null");
    186         }
    187 
    188         final int callingUserId = UserHandle.getCallingUserId();
    189         final int currentUserId = ActivityManager.getCurrentUser();
    190         if (callingUserId != currentUserId) {
    191             // This check is inherently prone to races but at least it's something.
    192             Slog.w(TAG, "Aborted attempt to start a test dream while a different "
    193                     + " user is active: callingUserId=" + callingUserId
    194                     + ", currentUserId=" + currentUserId);
    195             return;
    196         }
    197         final long ident = Binder.clearCallingIdentity();
    198         try {
    199             synchronized (mLock) {
    200                 startDreamLocked(dream, true /*isTest*/, callingUserId);
    201             }
    202         } finally {
    203             Binder.restoreCallingIdentity(ident);
    204         }
    205     }
    206 
    207     @Override // Binder call
    208     public void awaken() {
    209         checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
    210 
    211         final long ident = Binder.clearCallingIdentity();
    212         try {
    213             // Treat an explicit request to awaken as user activity so that the
    214             // device doesn't immediately go to sleep if the timeout expired,
    215             // for example when being undocked.
    216             long time = SystemClock.uptimeMillis();
    217             mPowerManager.userActivity(time, false /*noChangeLights*/);
    218             stopDream();
    219         } finally {
    220             Binder.restoreCallingIdentity(ident);
    221         }
    222     }
    223 
    224     @Override // Binder call
    225     public void finishSelf(IBinder token) {
    226         // Requires no permission, called by Dream from an arbitrary process.
    227         if (token == null) {
    228             throw new IllegalArgumentException("token must not be null");
    229         }
    230 
    231         final long ident = Binder.clearCallingIdentity();
    232         try {
    233             if (DEBUG) {
    234                 Slog.d(TAG, "Dream finished: " + token);
    235             }
    236 
    237             // Note that a dream finishing and self-terminating is not
    238             // itself considered user activity.  If the dream is ending because
    239             // the user interacted with the device then user activity will already
    240             // have been poked so the device will stay awake a bit longer.
    241             // If the dream is ending on its own for other reasons and no wake
    242             // locks are held and the user activity timeout has expired then the
    243             // device may simply go to sleep.
    244             synchronized (mLock) {
    245                 if (mCurrentDreamToken == token) {
    246                     stopDreamLocked();
    247                 }
    248             }
    249         } finally {
    250             Binder.restoreCallingIdentity(ident);
    251         }
    252     }
    253 
    254     /**
    255      * Called by the power manager to start a dream.
    256      */
    257     public void startDream() {
    258         int userId = ActivityManager.getCurrentUser();
    259         ComponentName dream = chooseDreamForUser(userId);
    260         if (dream != null) {
    261             synchronized (mLock) {
    262                 startDreamLocked(dream, false /*isTest*/, userId);
    263             }
    264         }
    265     }
    266 
    267     /**
    268      * Called by the power manager to stop a dream.
    269      */
    270     public void stopDream() {
    271         synchronized (mLock) {
    272             stopDreamLocked();
    273         }
    274     }
    275 
    276     private ComponentName chooseDreamForUser(int userId) {
    277         ComponentName[] dreams = getDreamComponentsForUser(userId);
    278         return dreams != null && dreams.length != 0 ? dreams[0] : null;
    279     }
    280 
    281     private ComponentName[] getDreamComponentsForUser(int userId) {
    282         String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    283                 Settings.Secure.SCREENSAVER_COMPONENTS,
    284                 userId);
    285         ComponentName[] components = componentsFromString(names);
    286 
    287         // first, ensure components point to valid services
    288         List<ComponentName> validComponents = new ArrayList<ComponentName>();
    289         if (components != null) {
    290             for (ComponentName component : components) {
    291                 if (serviceExists(component)) {
    292                     validComponents.add(component);
    293                 } else {
    294                     Slog.w(TAG, "Dream " + component + " does not exist");
    295                 }
    296             }
    297         }
    298 
    299         // fallback to the default dream component if necessary
    300         if (validComponents.isEmpty()) {
    301             ComponentName defaultDream = getDefaultDreamComponent();
    302             if (defaultDream != null) {
    303                 Slog.w(TAG, "Falling back to default dream " + defaultDream);
    304                 validComponents.add(defaultDream);
    305             }
    306         }
    307         return validComponents.toArray(new ComponentName[validComponents.size()]);
    308     }
    309 
    310     private boolean serviceExists(ComponentName name) {
    311         try {
    312             return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null;
    313         } catch (NameNotFoundException e) {
    314             return false;
    315         }
    316     }
    317 
    318     private void startDreamLocked(final ComponentName name,
    319             final boolean isTest, final int userId) {
    320         if (Objects.equal(mCurrentDreamName, name)
    321                 && mCurrentDreamIsTest == isTest
    322                 && mCurrentDreamUserId == userId) {
    323             return;
    324         }
    325 
    326         stopDreamLocked();
    327 
    328         if (DEBUG) Slog.i(TAG, "Entering dreamland.");
    329 
    330         final Binder newToken = new Binder();
    331         mCurrentDreamToken = newToken;
    332         mCurrentDreamName = name;
    333         mCurrentDreamIsTest = isTest;
    334         mCurrentDreamUserId = userId;
    335 
    336         mHandler.post(new Runnable() {
    337             @Override
    338             public void run() {
    339                 mController.startDream(newToken, name, isTest, userId);
    340             }
    341         });
    342     }
    343 
    344     private void stopDreamLocked() {
    345         if (mCurrentDreamToken != null) {
    346             if (DEBUG) Slog.i(TAG, "Leaving dreamland.");
    347 
    348             cleanupDreamLocked();
    349 
    350             mHandler.post(new Runnable() {
    351                 @Override
    352                 public void run() {
    353                     mController.stopDream();
    354                 }
    355             });
    356         }
    357     }
    358 
    359     private void cleanupDreamLocked() {
    360         mCurrentDreamToken = null;
    361         mCurrentDreamName = null;
    362         mCurrentDreamIsTest = false;
    363         mCurrentDreamUserId = 0;
    364     }
    365 
    366     private void checkPermission(String permission) {
    367         if (mContext.checkCallingOrSelfPermission(permission)
    368                 != PackageManager.PERMISSION_GRANTED) {
    369             throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
    370                     + ", must have permission " + permission);
    371         }
    372     }
    373 
    374     private static String componentsToString(ComponentName[] componentNames) {
    375         StringBuilder names = new StringBuilder();
    376         if (componentNames != null) {
    377             for (ComponentName componentName : componentNames) {
    378                 if (names.length() > 0) {
    379                     names.append(',');
    380                 }
    381                 names.append(componentName.flattenToString());
    382             }
    383         }
    384         return names.toString();
    385     }
    386 
    387     private static ComponentName[] componentsFromString(String names) {
    388         if (names == null) {
    389             return null;
    390         }
    391         String[] namesArray = names.split(",");
    392         ComponentName[] componentNames = new ComponentName[namesArray.length];
    393         for (int i = 0; i < namesArray.length; i++) {
    394             componentNames[i] = ComponentName.unflattenFromString(namesArray[i]);
    395         }
    396         return componentNames;
    397     }
    398 
    399     private final DreamController.Listener mControllerListener = new DreamController.Listener() {
    400         @Override
    401         public void onDreamStopped(Binder token) {
    402             synchronized (mLock) {
    403                 if (mCurrentDreamToken == token) {
    404                     cleanupDreamLocked();
    405                 }
    406             }
    407         }
    408     };
    409 
    410     /**
    411      * Handler for asynchronous operations performed by the dream manager.
    412      * Ensures operations to {@link DreamController} are single-threaded.
    413      */
    414     private final class DreamHandler extends Handler {
    415         public DreamHandler(Looper looper) {
    416             super(looper, null, true /*async*/);
    417         }
    418     }
    419 }
    420