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