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