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