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