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