1/*
2 * Copyright (C) 2008 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;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.ActivityManagerNative;
22import android.app.IUiModeManager;
23import android.app.Notification;
24import android.app.NotificationManager;
25import android.app.PendingIntent;
26import android.app.StatusBarManager;
27import android.app.UiModeManager;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.os.BatteryManager;
36import android.os.Binder;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.provider.Settings;
43import android.service.dreams.Sandman;
44import android.util.Slog;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48
49import com.android.internal.R;
50import com.android.internal.app.DisableCarModeActivity;
51import com.android.server.twilight.TwilightListener;
52import com.android.server.twilight.TwilightManager;
53import com.android.server.twilight.TwilightState;
54
55final class UiModeManagerService extends SystemService {
56    private static final String TAG = UiModeManager.class.getSimpleName();
57    private static final boolean LOG = false;
58
59    // Enable launching of applications when entering the dock.
60    private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
61    private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
62
63    final Object mLock = new Object();
64    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
65
66    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
67    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
68
69    private boolean mCarModeEnabled = false;
70    private boolean mCharging = false;
71    private int mDefaultUiModeType;
72    private boolean mCarModeKeepsScreenOn;
73    private boolean mDeskModeKeepsScreenOn;
74    private boolean mTelevision;
75    private boolean mWatch;
76    private boolean mComputedNightMode;
77    private int mCarModeEnableFlags;
78
79    int mCurUiMode = 0;
80    private int mSetUiMode = 0;
81    private boolean mHoldingConfiguration = false;
82
83    private Configuration mConfiguration = new Configuration();
84    boolean mSystemReady;
85
86    private final Handler mHandler = new Handler();
87
88    private TwilightManager mTwilightManager;
89    private NotificationManager mNotificationManager;
90    private StatusBarManager mStatusBarManager;
91
92    private PowerManager.WakeLock mWakeLock;
93
94    public UiModeManagerService(Context context) {
95        super(context);
96    }
97
98    private static Intent buildHomeIntent(String category) {
99        Intent intent = new Intent(Intent.ACTION_MAIN);
100        intent.addCategory(category);
101        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
102                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
103        return intent;
104    }
105
106    // The broadcast receiver which receives the result of the ordered broadcast sent when
107    // the dock state changes. The original ordered broadcast is sent with an initial result
108    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
109    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
110    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
111        @Override
112        public void onReceive(Context context, Intent intent) {
113            if (getResultCode() != Activity.RESULT_OK) {
114                if (LOG) {
115                    Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
116                            + ": canceled: " + getResultCode());
117                }
118                return;
119            }
120
121            final int enableFlags = intent.getIntExtra("enableFlags", 0);
122            final int disableFlags = intent.getIntExtra("disableFlags", 0);
123            synchronized (mLock) {
124                updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
125            }
126        }
127    };
128
129    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
130        @Override
131        public void onReceive(Context context, Intent intent) {
132            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
133                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
134            updateDockState(state);
135        }
136    };
137
138    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
139        @Override
140        public void onReceive(Context context, Intent intent) {
141            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
142            synchronized (mLock) {
143                if (mSystemReady) {
144                    updateLocked(0, 0);
145                }
146            }
147        }
148    };
149
150    private final TwilightListener mTwilightListener = new TwilightListener() {
151        @Override
152        public void onTwilightStateChanged() {
153            updateTwilight();
154        }
155    };
156
157    @Override
158    public void onStart() {
159        final Context context = getContext();
160
161        final PowerManager powerManager =
162                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
163        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
164
165        context.registerReceiver(mDockModeReceiver,
166                new IntentFilter(Intent.ACTION_DOCK_EVENT));
167        context.registerReceiver(mBatteryReceiver,
168                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
169
170        mConfiguration.setToDefaults();
171
172        final Resources res = context.getResources();
173        mDefaultUiModeType = res.getInteger(
174                com.android.internal.R.integer.config_defaultUiModeType);
175        mCarModeKeepsScreenOn = (res.getInteger(
176                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
177        mDeskModeKeepsScreenOn = (res.getInteger(
178                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
179
180        final PackageManager pm = context.getPackageManager();
181        mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
182                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
183        mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
184
185        final int defaultNightMode = res.getInteger(
186                com.android.internal.R.integer.config_defaultNightMode);
187        mNightMode = Settings.Secure.getInt(context.getContentResolver(),
188                Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
189
190        // Update the initial, static configurations.
191        synchronized (this) {
192            updateConfigurationLocked();
193            sendConfigurationLocked();
194        }
195
196        publishBinderService(Context.UI_MODE_SERVICE, mService);
197    }
198
199    private final IBinder mService = new IUiModeManager.Stub() {
200        @Override
201        public void enableCarMode(int flags) {
202            final long ident = Binder.clearCallingIdentity();
203            try {
204                synchronized (mLock) {
205                    setCarModeLocked(true, flags);
206                    if (mSystemReady) {
207                        updateLocked(flags, 0);
208                    }
209                }
210            } finally {
211                Binder.restoreCallingIdentity(ident);
212            }
213        }
214
215        @Override
216        public void disableCarMode(int flags) {
217            final long ident = Binder.clearCallingIdentity();
218            try {
219                synchronized (mLock) {
220                    setCarModeLocked(false, 0);
221                    if (mSystemReady) {
222                        updateLocked(0, flags);
223                    }
224                }
225            } finally {
226                Binder.restoreCallingIdentity(ident);
227            }
228        }
229
230        @Override
231        public int getCurrentModeType() {
232            final long ident = Binder.clearCallingIdentity();
233            try {
234                synchronized (mLock) {
235                    return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
236                }
237            } finally {
238                Binder.restoreCallingIdentity(ident);
239            }
240        }
241
242        @Override
243        public void setNightMode(int mode) {
244            switch (mode) {
245                case UiModeManager.MODE_NIGHT_NO:
246                case UiModeManager.MODE_NIGHT_YES:
247                case UiModeManager.MODE_NIGHT_AUTO:
248                    break;
249                default:
250                    throw new IllegalArgumentException("Unknown mode: " + mode);
251            }
252
253            final long ident = Binder.clearCallingIdentity();
254            try {
255                synchronized (mLock) {
256                    if (mNightMode != mode) {
257                        Settings.Secure.putInt(getContext().getContentResolver(),
258                                Settings.Secure.UI_NIGHT_MODE, mode);
259                        mNightMode = mode;
260                        updateLocked(0, 0);
261                    }
262                }
263            } finally {
264                Binder.restoreCallingIdentity(ident);
265            }
266        }
267
268        @Override
269        public int getNightMode() {
270            synchronized (mLock) {
271                return mNightMode;
272            }
273        }
274
275        @Override
276        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
277            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
278                    != PackageManager.PERMISSION_GRANTED) {
279
280                pw.println("Permission Denial: can't dump uimode service from from pid="
281                        + Binder.getCallingPid()
282                        + ", uid=" + Binder.getCallingUid());
283                return;
284            }
285
286            dumpImpl(pw);
287        }
288    };
289
290    void dumpImpl(PrintWriter pw) {
291        synchronized (mLock) {
292            pw.println("Current UI Mode Service state:");
293            pw.print("  mDockState="); pw.print(mDockState);
294                    pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
295            pw.print("  mNightMode="); pw.print(mNightMode);
296                    pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
297                    pw.print(" mComputedNightMode="); pw.print(mComputedNightMode);
298                    pw.print(" mCarModeEnableFlags="); pw.println(mCarModeEnableFlags);
299            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
300                    pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
301            pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
302                    pw.print(" mSystemReady="); pw.println(mSystemReady);
303            if (mTwilightManager != null) {
304                // We may not have a TwilightManager.
305                pw.print("  mTwilightService.getCurrentState()=");
306                pw.println(mTwilightManager.getCurrentState());
307            }
308        }
309    }
310
311    @Override
312    public void onBootPhase(int phase) {
313        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
314            synchronized (mLock) {
315                mTwilightManager = getLocalService(TwilightManager.class);
316                if (mTwilightManager != null) {
317                    mTwilightManager.registerListener(mTwilightListener, mHandler);
318                }
319                mSystemReady = true;
320                mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
321                updateComputedNightModeLocked();
322                updateLocked(0, 0);
323            }
324        }
325    }
326
327    void setCarModeLocked(boolean enabled, int flags) {
328        if (mCarModeEnabled != enabled) {
329            mCarModeEnabled = enabled;
330        }
331        mCarModeEnableFlags = flags;
332    }
333
334    private void updateDockState(int newState) {
335        synchronized (mLock) {
336            if (newState != mDockState) {
337                mDockState = newState;
338                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR, 0);
339                if (mSystemReady) {
340                    updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
341                }
342            }
343        }
344    }
345
346    private static boolean isDeskDockState(int state) {
347        switch (state) {
348            case Intent.EXTRA_DOCK_STATE_DESK:
349            case Intent.EXTRA_DOCK_STATE_LE_DESK:
350            case Intent.EXTRA_DOCK_STATE_HE_DESK:
351                return true;
352            default:
353                return false;
354        }
355    }
356
357    private void updateConfigurationLocked() {
358        int uiMode = mDefaultUiModeType;
359        if (mTelevision) {
360            uiMode = Configuration.UI_MODE_TYPE_TELEVISION;
361        } else if (mWatch) {
362            uiMode = Configuration.UI_MODE_TYPE_WATCH;
363        } else if (mCarModeEnabled) {
364            uiMode = Configuration.UI_MODE_TYPE_CAR;
365        } else if (isDeskDockState(mDockState)) {
366            uiMode = Configuration.UI_MODE_TYPE_DESK;
367        }
368
369        if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
370            updateComputedNightModeLocked();
371            uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
372                    : Configuration.UI_MODE_NIGHT_NO;
373        } else {
374            uiMode |= mNightMode << 4;
375        }
376
377        if (LOG) {
378            Slog.d(TAG,
379                "updateConfigurationLocked: mDockState=" + mDockState
380                + "; mCarMode=" + mCarModeEnabled
381                + "; mNightMode=" + mNightMode
382                + "; uiMode=" + uiMode);
383        }
384
385        mCurUiMode = uiMode;
386        if (!mHoldingConfiguration) {
387            mConfiguration.uiMode = uiMode;
388        }
389    }
390
391    private void sendConfigurationLocked() {
392        if (mSetUiMode != mConfiguration.uiMode) {
393            mSetUiMode = mConfiguration.uiMode;
394
395            try {
396                ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
397            } catch (RemoteException e) {
398                Slog.w(TAG, "Failure communicating with activity manager", e);
399            }
400        }
401    }
402
403    void updateLocked(int enableFlags, int disableFlags) {
404        String action = null;
405        String oldAction = null;
406        if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
407            adjustStatusBarCarModeLocked();
408            oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
409        } else if (isDeskDockState(mLastBroadcastState)) {
410            oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
411        }
412
413        if (mCarModeEnabled) {
414            if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
415                adjustStatusBarCarModeLocked();
416
417                if (oldAction != null) {
418                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
419                }
420                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
421                action = UiModeManager.ACTION_ENTER_CAR_MODE;
422            }
423        } else if (isDeskDockState(mDockState)) {
424            if (!isDeskDockState(mLastBroadcastState)) {
425                if (oldAction != null) {
426                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
427                }
428                mLastBroadcastState = mDockState;
429                action = UiModeManager.ACTION_ENTER_DESK_MODE;
430            }
431        } else {
432            mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
433            action = oldAction;
434        }
435
436        if (action != null) {
437            if (LOG) {
438                Slog.v(TAG, String.format(
439                    "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
440                    action, enableFlags, disableFlags));
441            }
442
443            // Send the ordered broadcast; the result receiver will receive after all
444            // broadcasts have been sent. If any broadcast receiver changes the result
445            // code from the initial value of RESULT_OK, then the result receiver will
446            // not launch the corresponding dock application. This gives apps a chance
447            // to override the behavior and stay in their app even when the device is
448            // placed into a dock.
449            Intent intent = new Intent(action);
450            intent.putExtra("enableFlags", enableFlags);
451            intent.putExtra("disableFlags", disableFlags);
452            getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
453                    mResultReceiver, null, Activity.RESULT_OK, null, null);
454
455            // Attempting to make this transition a little more clean, we are going
456            // to hold off on doing a configuration change until we have finished
457            // the broadcast and started the home activity.
458            mHoldingConfiguration = true;
459            updateConfigurationLocked();
460        } else {
461            String category = null;
462            if (mCarModeEnabled) {
463                if (ENABLE_LAUNCH_CAR_DOCK_APP
464                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
465                    category = Intent.CATEGORY_CAR_DOCK;
466                }
467            } else if (isDeskDockState(mDockState)) {
468                if (ENABLE_LAUNCH_DESK_DOCK_APP
469                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
470                    category = Intent.CATEGORY_DESK_DOCK;
471                }
472            } else {
473                if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
474                    category = Intent.CATEGORY_HOME;
475                }
476            }
477
478            if (LOG) {
479                Slog.v(TAG, "updateLocked: null action, mDockState="
480                        + mDockState +", category=" + category);
481            }
482
483            sendConfigurationAndStartDreamOrDockAppLocked(category);
484        }
485
486        // keep screen on when charging and in car mode
487        boolean keepScreenOn = mCharging &&
488                ((mCarModeEnabled && mCarModeKeepsScreenOn &&
489                  (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) ||
490                 (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
491        if (keepScreenOn != mWakeLock.isHeld()) {
492            if (keepScreenOn) {
493                mWakeLock.acquire();
494            } else {
495                mWakeLock.release();
496            }
497        }
498    }
499
500    private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
501        // Launch a dock activity
502        String category = null;
503        if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
504            // Only launch car home when car mode is enabled and the caller
505            // has asked us to switch to it.
506            if (ENABLE_LAUNCH_CAR_DOCK_APP
507                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
508                category = Intent.CATEGORY_CAR_DOCK;
509            }
510        } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
511            // Only launch car home when desk mode is enabled and the caller
512            // has asked us to switch to it.  Currently re-using the car
513            // mode flag since we don't have a formal API for "desk mode".
514            if (ENABLE_LAUNCH_DESK_DOCK_APP
515                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
516                category = Intent.CATEGORY_DESK_DOCK;
517            }
518        } else {
519            // Launch the standard home app if requested.
520            if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
521                category = Intent.CATEGORY_HOME;
522            }
523        }
524
525        if (LOG) {
526            Slog.v(TAG, String.format(
527                "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
528                    + "category=%s",
529                action, enableFlags, disableFlags, category));
530        }
531
532        sendConfigurationAndStartDreamOrDockAppLocked(category);
533    }
534
535    private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
536        // Update the configuration but don't send it yet.
537        mHoldingConfiguration = false;
538        updateConfigurationLocked();
539
540        // Start the dock app, if there is one.
541        boolean dockAppStarted = false;
542        if (category != null) {
543            // Now we are going to be careful about switching the
544            // configuration and starting the activity -- we need to
545            // do this in a specific order under control of the
546            // activity manager, to do it cleanly.  So compute the
547            // new config, but don't set it yet, and let the
548            // activity manager take care of both the start and config
549            // change.
550            Intent homeIntent = buildHomeIntent(category);
551            if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
552                try {
553                    int result = ActivityManagerNative.getDefault().startActivityWithConfig(
554                            null, null, homeIntent, null, null, null, 0, 0,
555                            mConfiguration, null, UserHandle.USER_CURRENT);
556                    if (result >= ActivityManager.START_SUCCESS) {
557                        dockAppStarted = true;
558                    } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
559                        Slog.e(TAG, "Could not start dock app: " + homeIntent
560                                + ", startActivityWithConfig result " + result);
561                    }
562                } catch (RemoteException ex) {
563                    Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
564                }
565            }
566        }
567
568        // Send the new configuration.
569        sendConfigurationLocked();
570
571        // If we did not start a dock app, then start dreaming if supported.
572        if (category != null && !dockAppStarted) {
573            Sandman.startDreamWhenDockedIfAppropriate(getContext());
574        }
575    }
576
577    private void adjustStatusBarCarModeLocked() {
578        final Context context = getContext();
579        if (mStatusBarManager == null) {
580            mStatusBarManager = (StatusBarManager)
581                    context.getSystemService(Context.STATUS_BAR_SERVICE);
582        }
583
584        // Fear not: StatusBarManagerService manages a list of requests to disable
585        // features of the status bar; these are ORed together to form the
586        // active disabled list. So if (for example) the device is locked and
587        // the status bar should be totally disabled, the calls below will
588        // have no effect until the device is unlocked.
589        if (mStatusBarManager != null) {
590            mStatusBarManager.disable(mCarModeEnabled
591                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
592                : StatusBarManager.DISABLE_NONE);
593        }
594
595        if (mNotificationManager == null) {
596            mNotificationManager = (NotificationManager)
597                    context.getSystemService(Context.NOTIFICATION_SERVICE);
598        }
599
600        if (mNotificationManager != null) {
601            if (mCarModeEnabled) {
602                Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
603
604                Notification.Builder n = new Notification.Builder(context)
605                        .setSmallIcon(R.drawable.stat_notify_car_mode)
606                        .setDefaults(Notification.DEFAULT_LIGHTS)
607                        .setOngoing(true)
608                        .setWhen(0)
609                        .setColor(context.getColor(
610                                com.android.internal.R.color.system_notification_accent_color))
611                        .setContentTitle(
612                                context.getString(R.string.car_mode_disable_notification_title))
613                        .setContentText(
614                                context.getString(R.string.car_mode_disable_notification_message))
615                        .setContentIntent(
616                                PendingIntent.getActivityAsUser(context, 0, carModeOffIntent, 0,
617                                        null, UserHandle.CURRENT));
618                mNotificationManager.notifyAsUser(null,
619                        R.string.car_mode_disable_notification_title, n.build(), UserHandle.ALL);
620            } else {
621                mNotificationManager.cancelAsUser(null,
622                        R.string.car_mode_disable_notification_title, UserHandle.ALL);
623            }
624        }
625    }
626
627    void updateTwilight() {
628        synchronized (mLock) {
629            if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
630                updateComputedNightModeLocked();
631                updateLocked(0, 0);
632            }
633        }
634    }
635
636    private void updateComputedNightModeLocked() {
637        if (mTwilightManager != null) {
638            TwilightState state = mTwilightManager.getCurrentState();
639            if (state != null) {
640                mComputedNightMode = state.isNight();
641            }
642        }
643    }
644
645
646}
647