UiModeManagerService.java revision b8b11a0b1d82e24f7a79f2e1672e7f5cf1611a55
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.ActivityManagerNative;
21import android.app.AlarmManager;
22import android.app.IActivityManager;
23import android.app.KeyguardManager;
24import android.app.IUiModeManager;
25import android.app.Notification;
26import android.app.NotificationManager;
27import android.app.PendingIntent;
28import android.app.StatusBarManager;
29import android.app.UiModeManager;
30import android.content.ActivityNotFoundException;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.pm.PackageManager;
36import android.content.res.Configuration;
37import android.location.Criteria;
38import android.location.Location;
39import android.location.LocationListener;
40import android.location.LocationManager;
41import android.os.BatteryManager;
42import android.os.Binder;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Message;
46import android.os.PowerManager;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.text.format.DateUtils;
50import android.text.format.Time;
51import android.util.Slog;
52
53import java.io.FileDescriptor;
54import java.io.PrintWriter;
55
56import com.android.internal.R;
57import com.android.internal.app.DisableCarModeActivity;
58
59class UiModeManagerService extends IUiModeManager.Stub {
60    private static final String TAG = UiModeManager.class.getSimpleName();
61    private static final boolean LOG = false;
62
63    private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
64
65    private static final int MSG_UPDATE_TWILIGHT = 0;
66    private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
67
68    private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
69    private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
70    private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
71    private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
72    private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
73
74    private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
75
76    private final Context mContext;
77
78    final Object mLock = new Object();
79
80    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
81    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
82
83    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
84    private boolean mCarModeEnabled = false;
85    private boolean mCharging = false;
86    private final boolean mCarModeKeepsScreenOn;
87    private final boolean mDeskModeKeepsScreenOn;
88
89    private boolean mComputedNightMode;
90    private int mCurUiMode = 0;
91    private int mSetUiMode = 0;
92
93    private boolean mHoldingConfiguration = false;
94    private Configuration mConfiguration = new Configuration();
95
96    private boolean mSystemReady;
97
98    private NotificationManager mNotificationManager;
99
100    private AlarmManager mAlarmManager;
101
102    private LocationManager mLocationManager;
103    private Location mLocation;
104    private StatusBarManager mStatusBarManager;
105    private KeyguardManager.KeyguardLock mKeyguardLock;
106    private final PowerManager.WakeLock mWakeLock;
107
108    // The broadcast receiver which receives the result of the ordered broadcast sent when
109    // the dock state changes. The original ordered broadcast is sent with an initial result
110    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
111    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
112    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
113        @Override
114        public void onReceive(Context context, Intent intent) {
115            if (getResultCode() != Activity.RESULT_OK) {
116                return;
117            }
118
119            synchronized (mLock) {
120                // Launch a dock activity
121                String category;
122                if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
123                    // Only launch car home when car mode is enabled.
124                    category = Intent.CATEGORY_CAR_DOCK;
125                } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) {
126                    category = Intent.CATEGORY_DESK_DOCK;
127                } else {
128                    category = null;
129                }
130                if (category != null) {
131                    intent = new Intent(Intent.ACTION_MAIN);
132                    intent.addCategory(category);
133                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
134                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
135                    try {
136                        mContext.startActivity(intent);
137                    } catch (ActivityNotFoundException e) {
138                        Slog.w(TAG, e.getCause());
139                    }
140                }
141
142                if (mHoldingConfiguration) {
143                    mHoldingConfiguration = false;
144                    updateConfigurationLocked();
145                }
146            }
147        }
148    };
149
150    private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
151        @Override
152        public void onReceive(Context context, Intent intent) {
153            if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
154                mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
155            }
156        }
157    };
158
159    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
160        @Override
161        public void onReceive(Context context, Intent intent) {
162            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
163                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
164            updateDockState(state);
165        }
166    };
167
168    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
169        @Override
170        public void onReceive(Context context, Intent intent) {
171            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
172            synchronized (mLock) {
173                if (mSystemReady) {
174                    updateLocked();
175                }
176            }
177        }
178    };
179
180    // A LocationListener to initialize the network location provider. The location updates
181    // are handled through the passive location provider.
182    private final LocationListener mEmptyLocationListener =  new LocationListener() {
183        public void onLocationChanged(Location location) {
184        }
185
186        public void onProviderDisabled(String provider) {
187        }
188
189        public void onProviderEnabled(String provider) {
190        }
191
192        public void onStatusChanged(String provider, int status, Bundle extras) {
193        }
194    };
195
196    private final LocationListener mLocationListener = new LocationListener() {
197
198        public void onLocationChanged(Location location) {
199            final boolean hasMoved = hasMoved(location);
200            final boolean hasBetterAccuracy = mLocation == null
201                    || location.getAccuracy() < mLocation.getAccuracy();
202            if (hasMoved || hasBetterAccuracy) {
203                synchronized (mLock) {
204                    mLocation = location;
205                    if (hasMoved && isDoingNightMode()
206                            && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
207                        mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
208                    }
209                }
210            }
211        }
212
213        public void onProviderDisabled(String provider) {
214        }
215
216        public void onProviderEnabled(String provider) {
217        }
218
219        public void onStatusChanged(String provider, int status, Bundle extras) {
220        }
221
222        /*
223         * The user has moved if the accuracy circles of the two locations
224         * don't overlap.
225         */
226        private boolean hasMoved(Location location) {
227            if (location == null) {
228                return false;
229            }
230            if (mLocation == null) {
231                return true;
232            }
233
234            /* if new location is older than the current one, the devices hasn't
235             * moved.
236             */
237            if (location.getTime() < mLocation.getTime()) {
238                return false;
239            }
240
241            /* Get the distance between the two points */
242            float distance = mLocation.distanceTo(location);
243
244            /* Get the total accuracy radius for both locations */
245            float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
246
247            /* If the distance is greater than the combined accuracy of the two
248             * points then they can't overlap and hence the user has moved.
249             */
250            return distance >= totalAccuracy;
251        }
252    };
253
254    public UiModeManagerService(Context context) {
255        mContext = context;
256
257        ServiceManager.addService(Context.UI_MODE_SERVICE, this);
258
259        mAlarmManager =
260            (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
261        mLocationManager =
262            (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
263        mContext.registerReceiver(mTwilightUpdateReceiver,
264                new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
265        mContext.registerReceiver(mDockModeReceiver,
266                new IntentFilter(Intent.ACTION_DOCK_EVENT));
267        mContext.registerReceiver(mBatteryReceiver,
268                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
269
270        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
271        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
272
273        mConfiguration.setToDefaults();
274
275        mCarModeKeepsScreenOn = (context.getResources().getInteger(
276                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
277        mDeskModeKeepsScreenOn = (context.getResources().getInteger(
278                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
279    }
280
281    public void disableCarMode() {
282        synchronized (mLock) {
283            setCarModeLocked(false);
284            if (mSystemReady) {
285                updateLocked();
286            }
287        }
288    }
289
290    public void enableCarMode() {
291        mContext.enforceCallingOrSelfPermission(
292                android.Manifest.permission.ENABLE_CAR_MODE,
293                "Need ENABLE_CAR_MODE permission");
294        synchronized (mLock) {
295            setCarModeLocked(true);
296            if (mSystemReady) {
297                updateLocked();
298            }
299        }
300    }
301
302    public int getCurrentModeType() {
303        synchronized (mLock) {
304            return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
305        }
306    }
307
308    public void setNightMode(int mode) throws RemoteException {
309        synchronized (mLock) {
310            switch (mode) {
311                case UiModeManager.MODE_NIGHT_NO:
312                case UiModeManager.MODE_NIGHT_YES:
313                case UiModeManager.MODE_NIGHT_AUTO:
314                    break;
315                default:
316                    throw new IllegalArgumentException("Unknown mode: " + mode);
317            }
318            if (!isDoingNightMode()) {
319                return;
320            }
321
322            if (mNightMode != mode) {
323                mNightMode = mode;
324                updateLocked();
325            }
326        }
327    }
328
329    public int getNightMode() throws RemoteException {
330        return mNightMode;
331    }
332
333    void systemReady() {
334        synchronized (mLock) {
335            mSystemReady = true;
336            mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
337            updateLocked();
338            mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
339        }
340    }
341
342    boolean isDoingNightMode() {
343        return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
344    }
345
346    void setCarModeLocked(boolean enabled) {
347        if (mCarModeEnabled != enabled) {
348            mCarModeEnabled = enabled;
349
350            // Disable keyguard when in car mode
351            if (mKeyguardLock == null) {
352                KeyguardManager km =
353                        (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
354                if (km != null) {
355                    mKeyguardLock = km.newKeyguardLock(TAG);
356                }
357            }
358            if (mKeyguardLock != null) {
359                long ident = Binder.clearCallingIdentity();
360                if (enabled) {
361                    mKeyguardLock.disableKeyguard();
362                } else {
363                    mKeyguardLock.reenableKeyguard();
364                }
365                Binder.restoreCallingIdentity(ident);
366            }
367        }
368    }
369
370    void updateDockState(int newState) {
371        synchronized (mLock) {
372            if (newState != mDockState) {
373                mDockState = newState;
374                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
375                if (mSystemReady) {
376                    updateLocked();
377                }
378            }
379        }
380    }
381
382    final void updateConfigurationLocked() {
383        int uiMode = 0;
384        if (mCarModeEnabled) {
385            uiMode = Configuration.UI_MODE_TYPE_CAR;
386        } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
387            uiMode = Configuration.UI_MODE_TYPE_DESK;
388        }
389        if (uiMode != 0) {
390            if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
391                updateTwilightLocked();
392                uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
393                        : Configuration.UI_MODE_NIGHT_NO;
394            } else {
395                uiMode |= mNightMode << 4;
396            }
397        } else {
398            // Disabling the car mode clears the night mode.
399            uiMode = Configuration.UI_MODE_TYPE_NORMAL |
400                    Configuration.UI_MODE_NIGHT_NO;
401        }
402
403        mCurUiMode = uiMode;
404
405        if (!mHoldingConfiguration && uiMode != mSetUiMode) {
406            mSetUiMode = uiMode;
407
408            try {
409                final IActivityManager am = ActivityManagerNative.getDefault();
410                mConfiguration.uiMode = uiMode;
411                am.updateConfiguration(mConfiguration);
412            } catch (RemoteException e) {
413                Slog.w(TAG, "Failure communicating with activity manager", e);
414            }
415        }
416    }
417
418    final void updateLocked() {
419        long ident = Binder.clearCallingIdentity();
420
421        try {
422            String action = null;
423            String oldAction = null;
424            if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
425                oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
426            } else if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_DESK) {
427                oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
428            }
429
430            if (mCarModeEnabled) {
431                if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
432                    adjustStatusBarCarModeLocked();
433
434                    if (oldAction != null) {
435                        mContext.sendBroadcast(new Intent(oldAction));
436                    }
437                    mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
438                    action = UiModeManager.ACTION_ENTER_CAR_MODE;
439                }
440            } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
441                if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_DESK) {
442                    if (oldAction != null) {
443                        mContext.sendBroadcast(new Intent(oldAction));
444                    }
445                    mLastBroadcastState = Intent.EXTRA_DOCK_STATE_DESK;
446                    action = UiModeManager.ACTION_ENTER_DESK_MODE;
447                }
448            } else {
449                if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
450                    adjustStatusBarCarModeLocked();
451                }
452
453                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
454                action = oldAction;
455            }
456
457            if (action != null) {
458                // Send the ordered broadcast; the result receiver will receive after all
459                // broadcasts have been sent. If any broadcast receiver changes the result
460                // code from the initial value of RESULT_OK, then the result receiver will
461                // not launch the corresponding dock application. This gives apps a chance
462                // to override the behavior and stay in their app even when the device is
463                // placed into a dock.
464                mContext.sendOrderedBroadcast(new Intent(action), null,
465                        mResultReceiver, null, Activity.RESULT_OK, null, null);
466                // Attempting to make this transition a little more clean, we are going
467                // to hold off on doing a configuration change until we have finished
468                // the broacast and started the home activity.
469                mHoldingConfiguration = true;
470            }
471
472            updateConfigurationLocked();
473
474            // keep screen on when charging and in car mode
475            boolean keepScreenOn = mCharging &&
476                    ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
477                     (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
478            if (keepScreenOn != mWakeLock.isHeld()) {
479                if (keepScreenOn) {
480                    mWakeLock.acquire();
481                } else {
482                    mWakeLock.release();
483                }
484            }
485        } finally {
486            Binder.restoreCallingIdentity(ident);
487        }
488    }
489
490    private void adjustStatusBarCarModeLocked() {
491        if (mStatusBarManager == null) {
492            mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
493        }
494
495        // Fear not: StatusBarService manages a list of requests to disable
496        // features of the status bar; these are ORed together to form the
497        // active disabled list. So if (for example) the device is locked and
498        // the status bar should be totally disabled, the calls below will
499        // have no effect until the device is unlocked.
500        if (mStatusBarManager != null) {
501            mStatusBarManager.disable(mCarModeEnabled
502                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
503                : StatusBarManager.DISABLE_NONE);
504        }
505
506        if (mNotificationManager == null) {
507            mNotificationManager = (NotificationManager)
508                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
509        }
510
511        if (mNotificationManager != null) {
512            if (mCarModeEnabled) {
513                Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
514
515                Notification n = new Notification();
516                n.icon = R.drawable.stat_notify_car_mode;
517                n.defaults = Notification.DEFAULT_LIGHTS;
518                n.flags = Notification.FLAG_ONGOING_EVENT;
519                n.when = 0;
520                n.setLatestEventInfo(
521                        mContext,
522                        mContext.getString(R.string.car_mode_disable_notification_title),
523                        mContext.getString(R.string.car_mode_disable_notification_message),
524                        PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
525                mNotificationManager.notify(0, n);
526            } else {
527                mNotificationManager.cancel(0);
528            }
529        }
530    }
531
532    private final Handler mHandler = new Handler() {
533
534        boolean mPassiveListenerEnabled;
535        boolean mNetworkListenerEnabled;
536
537        @Override
538        public void handleMessage(Message msg) {
539            switch (msg.what) {
540                case MSG_UPDATE_TWILIGHT:
541                    synchronized (mLock) {
542                        if (isDoingNightMode() && mLocation != null
543                                && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
544                            updateTwilightLocked();
545                            updateLocked();
546                        }
547                    }
548                    break;
549                case MSG_ENABLE_LOCATION_UPDATES:
550                    // enable network provider to receive at least location updates for a given
551                    // distance.
552                    boolean networkLocationEnabled;
553                    try {
554                        networkLocationEnabled =
555                            mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
556                    } catch (Exception e) {
557                        // we may get IllegalArgumentException if network location provider
558                        // does not exist or is not yet installed.
559                        networkLocationEnabled = false;
560                    }
561                    if (!mNetworkListenerEnabled && networkLocationEnabled) {
562                        mNetworkListenerEnabled = true;
563                        mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
564                                LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
565
566                        if (mLocation == null) {
567                            retrieveLocation();
568                        }
569                        synchronized (mLock) {
570                            if (isDoingNightMode() && mLocation != null
571                                    && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
572                                updateTwilightLocked();
573                                updateLocked();
574                            }
575                        }
576                    }
577                   // enable passive provider to receive updates from location fixes (gps
578                   // and network).
579                   boolean passiveLocationEnabled;
580                    try {
581                        passiveLocationEnabled =
582                            mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER);
583                    } catch (Exception e) {
584                        // we may get IllegalArgumentException if passive location provider
585                        // does not exist or is not yet installed.
586                        passiveLocationEnabled = false;
587                    }
588                    if (!mPassiveListenerEnabled && passiveLocationEnabled) {
589                        mPassiveListenerEnabled = true;
590                        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
591                                0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener);
592                    }
593                    if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
594                        long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
595                        interval *= 1.5;
596                        if (interval == 0) {
597                            interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
598                        } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
599                            interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
600                        }
601                        Bundle bundle = new Bundle();
602                        bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
603                        Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
604                        newMsg.setData(bundle);
605                        mHandler.sendMessageDelayed(newMsg, interval);
606                    }
607                    break;
608            }
609        }
610
611        private void retrieveLocation() {
612            Location location;
613            Criteria criteria = new Criteria();
614            criteria.setSpeedRequired(false);
615            criteria.setAltitudeRequired(false);
616            criteria.setBearingRequired(false);
617            criteria.setAccuracy(Criteria.ACCURACY_FINE);
618            final String bestProvider = mLocationManager.getBestProvider(criteria, true);
619            location = mLocationManager.getLastKnownLocation(bestProvider);
620            // In the case there is no location available (e.g. GPS fix or network location
621            // is not available yet), the longitude of the location is estimated using the timezone,
622            // latitude and accuracy are set to get a good average.
623            if (location == null) {
624                Time currentTime = new Time();
625                currentTime.set(System.currentTimeMillis());
626                double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
627                        (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
628                location = new Location("fake");
629                location.setLongitude(lngOffset);
630                location.setLatitude(0);
631                location.setAccuracy(417000.0f);
632                location.setTime(System.currentTimeMillis());
633            }
634            synchronized (mLock) {
635                mLocation = location;
636            }
637        }
638    };
639
640    void updateTwilightLocked() {
641        if (mLocation == null) {
642            return;
643        }
644        final long currentTime = System.currentTimeMillis();
645        boolean nightMode;
646        // calculate current twilight
647        TwilightCalculator tw = new TwilightCalculator();
648        tw.calculateTwilight(currentTime,
649                mLocation.getLatitude(), mLocation.getLongitude());
650        if (tw.mState == TwilightCalculator.DAY) {
651            nightMode = false;
652        } else {
653            nightMode = true;
654        }
655
656        // schedule next update
657        long nextUpdate = 0;
658        if (tw.mSunrise == -1 || tw.mSunset == -1) {
659            // In the case the day or night never ends the update is scheduled 12 hours later.
660            nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
661        } else {
662            final int mLastTwilightState = tw.mState;
663            // add some extra time to be on the save side.
664            nextUpdate += DateUtils.MINUTE_IN_MILLIS;
665            if (currentTime > tw.mSunset) {
666                // next update should be on the following day
667                tw.calculateTwilight(currentTime
668                        + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
669                        mLocation.getLongitude());
670            }
671
672            if (mLastTwilightState == TwilightCalculator.NIGHT) {
673                nextUpdate += tw.mSunrise;
674            } else {
675                nextUpdate += tw.mSunset;
676            }
677        }
678
679        Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
680        PendingIntent pendingIntent =
681                PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
682        mAlarmManager.cancel(pendingIntent);
683        mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
684
685        mComputedNightMode = nightMode;
686    }
687
688    @Override
689    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
690        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
691                != PackageManager.PERMISSION_GRANTED) {
692
693            pw.println("Permission Denial: can't dump uimode service from from pid="
694                    + Binder.getCallingPid()
695                    + ", uid=" + Binder.getCallingUid());
696            return;
697        }
698
699        synchronized (mLock) {
700            pw.println("Current UI Mode Service state:");
701            pw.print("  mDockState="); pw.print(mDockState);
702                    pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
703            pw.print("  mNightMode="); pw.print(mNightMode);
704                    pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
705                    pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
706            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
707                    pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
708            pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
709                    pw.print(" mSystemReady="); pw.println(mSystemReady);
710            if (mLocation != null) {
711                pw.print("  mLocation="); pw.println(mLocation);
712            }
713        }
714    }
715}
716