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