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