DreamManagerService.java revision 9da8301e0abbc940b28a524e36e83f04884ce4cb
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;
21import com.android.server.SystemService;
22
23import android.Manifest;
24import android.app.ActivityManager;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.os.Binder;
33import android.os.Build;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.PowerManager;
38import android.os.SystemClock;
39import android.os.SystemProperties;
40import android.os.UserHandle;
41import android.provider.Settings;
42import android.service.dreams.DreamManagerInternal;
43import android.service.dreams.DreamService;
44import android.service.dreams.IDozeHardware;
45import android.service.dreams.IDreamManager;
46import android.text.TextUtils;
47import android.util.Slog;
48
49import java.io.FileDescriptor;
50import java.io.PrintWriter;
51import java.util.ArrayList;
52import java.util.List;
53
54import libcore.util.Objects;
55
56/**
57 * Service api for managing dreams.
58 *
59 * @hide
60 */
61public final class DreamManagerService extends SystemService {
62    private static final boolean DEBUG = false;
63    private static final String TAG = "DreamManagerService";
64
65    private final Object mLock = new Object();
66
67    private final Context mContext;
68    private final DreamHandler mHandler;
69    private final DreamController mController;
70    private final PowerManager mPowerManager;
71    private final PowerManager.WakeLock mDozeWakeLock;
72    private final McuHal mMcuHal; // synchronized on self
73
74    private Binder mCurrentDreamToken;
75    private ComponentName mCurrentDreamName;
76    private int mCurrentDreamUserId;
77    private boolean mCurrentDreamIsTest;
78    private boolean mCurrentDreamCanDoze;
79    private boolean mCurrentDreamIsDozing;
80    private DozeHardwareWrapper mCurrentDreamDozeHardware;
81
82    public DreamManagerService(Context context) {
83        super(context);
84        mContext = context;
85        mHandler = new DreamHandler(FgThread.get().getLooper());
86        mController = new DreamController(context, mHandler, mControllerListener);
87
88        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
89        mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
90
91        mMcuHal = McuHal.open();
92        if (mMcuHal != null) {
93            mMcuHal.reset();
94        }
95    }
96
97    @Override
98    public void onStart() {
99        publishBinderService(DreamService.DREAM_SERVICE, new BinderService());
100        publishLocalService(DreamManagerInternal.class, new LocalService());
101    }
102
103    @Override
104    public void onBootPhase(int phase) {
105        if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
106            mContext.registerReceiver(new BroadcastReceiver() {
107                @Override
108                public void onReceive(Context context, Intent intent) {
109                    synchronized (mLock) {
110                        stopDreamLocked();
111                    }
112                }
113            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
114        }
115    }
116
117    private void dumpInternal(PrintWriter pw) {
118        pw.println("DREAM MANAGER (dumpsys dreams)");
119        pw.println();
120
121        pw.println("mMcuHal=" + mMcuHal);
122        pw.println();
123        pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
124        pw.println("mCurrentDreamName=" + mCurrentDreamName);
125        pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
126        pw.println("mCurrentDreamIsTest=" + mCurrentDreamIsTest);
127        pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
128        pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
129        pw.println("mCurrentDreamDozeHardware=" + mCurrentDreamDozeHardware);
130        pw.println();
131
132        DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
133            @Override
134            public void dump(PrintWriter pw) {
135                mController.dump(pw);
136            }
137        }, pw, 200);
138    }
139
140    private boolean isDreamingInternal() {
141        synchronized (mLock) {
142            return mCurrentDreamToken != null && !mCurrentDreamIsTest;
143        }
144    }
145
146    private void requestDreamInternal() {
147        // Ask the power manager to nap.  It will eventually call back into
148        // startDream() if/when it is appropriate to start dreaming.
149        // Because napping could cause the screen to turn off immediately if the dream
150        // cannot be started, we keep one eye open and gently poke user activity.
151        long time = SystemClock.uptimeMillis();
152        mPowerManager.userActivity(time, true /*noChangeLights*/);
153        mPowerManager.nap(time);
154    }
155
156    private void requestAwakenInternal() {
157        // Treat an explicit request to awaken as user activity so that the
158        // device doesn't immediately go to sleep if the timeout expired,
159        // for example when being undocked.
160        long time = SystemClock.uptimeMillis();
161        mPowerManager.userActivity(time, false /*noChangeLights*/);
162        stopDreamInternal();
163    }
164
165    private void finishSelfInternal(IBinder token) {
166        if (DEBUG) {
167            Slog.d(TAG, "Dream finished: " + token);
168        }
169
170        // Note that a dream finishing and self-terminating is not
171        // itself considered user activity.  If the dream is ending because
172        // the user interacted with the device then user activity will already
173        // have been poked so the device will stay awake a bit longer.
174        // If the dream is ending on its own for other reasons and no wake
175        // locks are held and the user activity timeout has expired then the
176        // device may simply go to sleep.
177        synchronized (mLock) {
178            if (mCurrentDreamToken == token) {
179                stopDreamLocked();
180            }
181        }
182    }
183
184    private void testDreamInternal(ComponentName dream, int userId) {
185        synchronized (mLock) {
186            startDreamLocked(dream, true /*isTest*/, false /*canDoze*/, userId);
187        }
188    }
189
190    private void startDreamInternal(boolean doze) {
191        final int userId = ActivityManager.getCurrentUser();
192        final ComponentName dream = doze ? getDozeComponent() : chooseDreamForUser(userId);
193        if (dream != null) {
194            synchronized (mLock) {
195                startDreamLocked(dream, false /*isTest*/, doze, userId);
196            }
197        }
198    }
199
200    private void stopDreamInternal() {
201        synchronized (mLock) {
202            stopDreamLocked();
203        }
204    }
205
206    private void startDozingInternal(IBinder token) {
207        if (DEBUG) {
208            Slog.d(TAG, "Dream requested to start dozing: " + token);
209        }
210
211        synchronized (mLock) {
212            if (mCurrentDreamToken == token && mCurrentDreamCanDoze
213                    && !mCurrentDreamIsDozing) {
214                mCurrentDreamIsDozing = true;
215                mDozeWakeLock.acquire();
216            }
217        }
218    }
219
220    private void stopDozingInternal(IBinder token) {
221        if (DEBUG) {
222            Slog.d(TAG, "Dream requested to stop dozing: " + token);
223        }
224
225        synchronized (mLock) {
226            if (mCurrentDreamToken == token && mCurrentDreamIsDozing) {
227                mCurrentDreamIsDozing = false;
228                mDozeWakeLock.release();
229            }
230        }
231    }
232
233    private IDozeHardware getDozeHardwareInternal(IBinder token) {
234        synchronized (mLock) {
235            if (mCurrentDreamToken == token && mCurrentDreamCanDoze
236                    && mCurrentDreamDozeHardware == null && mMcuHal != null) {
237                mCurrentDreamDozeHardware = new DozeHardwareWrapper();
238                return mCurrentDreamDozeHardware;
239            }
240            return null;
241        }
242    }
243
244    private ComponentName chooseDreamForUser(int userId) {
245        ComponentName[] dreams = getDreamComponentsForUser(userId);
246        return dreams != null && dreams.length != 0 ? dreams[0] : null;
247    }
248
249    private ComponentName[] getDreamComponentsForUser(int userId) {
250        String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
251                Settings.Secure.SCREENSAVER_COMPONENTS,
252                userId);
253        ComponentName[] components = componentsFromString(names);
254
255        // first, ensure components point to valid services
256        List<ComponentName> validComponents = new ArrayList<ComponentName>();
257        if (components != null) {
258            for (ComponentName component : components) {
259                if (serviceExists(component)) {
260                    validComponents.add(component);
261                } else {
262                    Slog.w(TAG, "Dream " + component + " does not exist");
263                }
264            }
265        }
266
267        // fallback to the default dream component if necessary
268        if (validComponents.isEmpty()) {
269            ComponentName defaultDream = getDefaultDreamComponentForUser(userId);
270            if (defaultDream != null) {
271                Slog.w(TAG, "Falling back to default dream " + defaultDream);
272                validComponents.add(defaultDream);
273            }
274        }
275        return validComponents.toArray(new ComponentName[validComponents.size()]);
276    }
277
278    private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
279        Settings.Secure.putStringForUser(mContext.getContentResolver(),
280                Settings.Secure.SCREENSAVER_COMPONENTS,
281                componentsToString(componentNames),
282                userId);
283    }
284
285    private ComponentName getDefaultDreamComponentForUser(int userId) {
286        String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
287                Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
288                userId);
289        return name == null ? null : ComponentName.unflattenFromString(name);
290    }
291
292    private ComponentName getDozeComponent() {
293        // Read the component from a system property to facilitate debugging.
294        // Note that for production devices, the dream should actually be declared in
295        // a config.xml resource.
296        String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null;
297        if (TextUtils.isEmpty(name)) {
298            // Read the component from a config.xml resource.
299            // The value should be specified in a resource overlay for the product.
300            name = mContext.getResources().getString(
301                    com.android.internal.R.string.config_dozeComponent);
302        }
303        return TextUtils.isEmpty(name) ? null : ComponentName.unflattenFromString(name);
304    }
305
306    private boolean serviceExists(ComponentName name) {
307        try {
308            return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null;
309        } catch (NameNotFoundException e) {
310            return false;
311        }
312    }
313
314    private void startDreamLocked(final ComponentName name,
315            final boolean isTest, final boolean canDoze, final int userId) {
316        if (Objects.equal(mCurrentDreamName, name)
317                && mCurrentDreamIsTest == isTest
318                && mCurrentDreamCanDoze == canDoze
319                && mCurrentDreamUserId == userId) {
320            return;
321        }
322
323        stopDreamLocked();
324
325        if (DEBUG) Slog.i(TAG, "Entering dreamland.");
326
327        final Binder newToken = new Binder();
328        mCurrentDreamToken = newToken;
329        mCurrentDreamName = name;
330        mCurrentDreamIsTest = isTest;
331        mCurrentDreamCanDoze = canDoze;
332        mCurrentDreamUserId = userId;
333
334        mHandler.post(new Runnable() {
335            @Override
336            public void run() {
337                mController.startDream(newToken, name, isTest, canDoze, userId);
338            }
339        });
340    }
341
342    private void stopDreamLocked() {
343        if (mCurrentDreamToken != null) {
344            if (DEBUG) Slog.i(TAG, "Leaving dreamland.");
345
346            cleanupDreamLocked();
347
348            mHandler.post(new Runnable() {
349                @Override
350                public void run() {
351                    mController.stopDream();
352                }
353            });
354        }
355    }
356
357    private void cleanupDreamLocked() {
358        mCurrentDreamToken = null;
359        mCurrentDreamName = null;
360        mCurrentDreamIsTest = false;
361        mCurrentDreamCanDoze = false;
362        mCurrentDreamUserId = 0;
363        if (mCurrentDreamIsDozing) {
364            mCurrentDreamIsDozing = false;
365            mDozeWakeLock.release();
366        }
367        if (mCurrentDreamDozeHardware != null) {
368            mCurrentDreamDozeHardware.release();
369            mCurrentDreamDozeHardware = null;
370        }
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    private final class BinderService extends IDreamManager.Stub {
428        @Override // Binder call
429        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
430            if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
431                    != PackageManager.PERMISSION_GRANTED) {
432                pw.println("Permission Denial: can't dump DreamManager from from pid="
433                        + Binder.getCallingPid()
434                        + ", uid=" + Binder.getCallingUid());
435                return;
436            }
437
438            final long ident = Binder.clearCallingIdentity();
439            try {
440                dumpInternal(pw);
441            } finally {
442                Binder.restoreCallingIdentity(ident);
443            }
444        }
445
446        @Override // Binder call
447        public ComponentName[] getDreamComponents() {
448            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
449
450            final int userId = UserHandle.getCallingUserId();
451            final long ident = Binder.clearCallingIdentity();
452            try {
453                return getDreamComponentsForUser(userId);
454            } finally {
455                Binder.restoreCallingIdentity(ident);
456            }
457        }
458
459        @Override // Binder call
460        public void setDreamComponents(ComponentName[] componentNames) {
461            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
462
463            final int userId = UserHandle.getCallingUserId();
464            final long ident = Binder.clearCallingIdentity();
465            try {
466                setDreamComponentsForUser(userId, componentNames);
467            } finally {
468                Binder.restoreCallingIdentity(ident);
469            }
470        }
471
472        @Override // Binder call
473        public ComponentName getDefaultDreamComponent() {
474            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
475
476            final int userId = UserHandle.getCallingUserId();
477            final long ident = Binder.clearCallingIdentity();
478            try {
479                return getDefaultDreamComponentForUser(userId);
480            } finally {
481                Binder.restoreCallingIdentity(ident);
482            }
483        }
484
485        @Override // Binder call
486        public boolean isDreaming() {
487            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
488
489            final long ident = Binder.clearCallingIdentity();
490            try {
491                return isDreamingInternal();
492            } finally {
493                Binder.restoreCallingIdentity(ident);
494            }
495        }
496
497        @Override // Binder call
498        public void dream() {
499            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
500
501            final long ident = Binder.clearCallingIdentity();
502            try {
503                requestDreamInternal();
504            } finally {
505                Binder.restoreCallingIdentity(ident);
506            }
507        }
508
509        @Override // Binder call
510        public void testDream(ComponentName dream) {
511            if (dream == null) {
512                throw new IllegalArgumentException("dream must not be null");
513            }
514            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
515
516            final int callingUserId = UserHandle.getCallingUserId();
517            final int currentUserId = ActivityManager.getCurrentUser();
518            if (callingUserId != currentUserId) {
519                // This check is inherently prone to races but at least it's something.
520                Slog.w(TAG, "Aborted attempt to start a test dream while a different "
521                        + " user is active: callingUserId=" + callingUserId
522                        + ", currentUserId=" + currentUserId);
523                return;
524            }
525            final long ident = Binder.clearCallingIdentity();
526            try {
527                testDreamInternal(dream, callingUserId);
528            } finally {
529                Binder.restoreCallingIdentity(ident);
530            }
531        }
532
533        @Override // Binder call
534        public void awaken() {
535            checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
536
537            final long ident = Binder.clearCallingIdentity();
538            try {
539                requestAwakenInternal();
540            } finally {
541                Binder.restoreCallingIdentity(ident);
542            }
543        }
544
545        @Override // Binder call
546        public void finishSelf(IBinder token) {
547            // Requires no permission, called by Dream from an arbitrary process.
548            if (token == null) {
549                throw new IllegalArgumentException("token must not be null");
550            }
551
552            final long ident = Binder.clearCallingIdentity();
553            try {
554                finishSelfInternal(token);
555            } finally {
556                Binder.restoreCallingIdentity(ident);
557            }
558        }
559
560        @Override // Binder call
561        public void startDozing(IBinder token) {
562            // Requires no permission, called by Dream from an arbitrary process.
563            if (token == null) {
564                throw new IllegalArgumentException("token must not be null");
565            }
566
567            final long ident = Binder.clearCallingIdentity();
568            try {
569                startDozingInternal(token);
570            } finally {
571                Binder.restoreCallingIdentity(ident);
572            }
573        }
574
575        @Override // Binder call
576        public void stopDozing(IBinder token) {
577            // Requires no permission, called by Dream from an arbitrary process.
578            if (token == null) {
579                throw new IllegalArgumentException("token must not be null");
580            }
581
582            final long ident = Binder.clearCallingIdentity();
583            try {
584                stopDozingInternal(token);
585            } finally {
586                Binder.restoreCallingIdentity(ident);
587            }
588        }
589
590        @Override // Binder call
591        public IDozeHardware getDozeHardware(IBinder token) {
592            // Requires no permission, called by Dream from an arbitrary process.
593            if (token == null) {
594                throw new IllegalArgumentException("token must not be null");
595            }
596
597            final long ident = Binder.clearCallingIdentity();
598            try {
599                return getDozeHardwareInternal(token);
600            } finally {
601                Binder.restoreCallingIdentity(ident);
602            }
603        }
604    }
605
606    private final class LocalService extends DreamManagerInternal {
607        @Override
608        public void startDream(boolean doze) {
609            startDreamInternal(doze);
610        }
611
612        @Override
613        public void stopDream() {
614            stopDreamInternal();
615        }
616
617        @Override
618        public boolean isDreaming() {
619            return isDreamingInternal();
620        }
621    }
622
623    private final class DozeHardwareWrapper extends IDozeHardware.Stub {
624        private boolean mReleased;
625
626        public void release() {
627            synchronized (mMcuHal) {
628                if (!mReleased) {
629                    mReleased = true;
630                    mMcuHal.reset();
631                }
632            }
633        }
634
635        @Override // Binder call
636        public byte[] sendMessage(String msg, byte[] arg) {
637            if (msg == null) {
638                throw new IllegalArgumentException("msg must not be null");
639            }
640
641            final long ident = Binder.clearCallingIdentity();
642            try {
643                synchronized (mMcuHal) {
644                    if (mReleased) {
645                        Slog.w(TAG, "Ignoring message to MCU HAL because the dream "
646                                + "has already ended: " + msg);
647                        return null;
648                    }
649                    return mMcuHal.sendMessage(msg, arg);
650                }
651            } finally {
652                Binder.restoreCallingIdentity(ident);
653            }
654        }
655    }
656}
657