/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.IUiModeManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Slog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Iterator;

import com.android.internal.R;
import com.android.internal.app.DisableCarModeActivity;

class UiModeManagerService extends IUiModeManager.Stub {
    private static final String TAG = UiModeManager.class.getSimpleName();
    private static final boolean LOG = false;

    private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";

    private static final int MSG_UPDATE_TWILIGHT = 0;
    private static final int MSG_ENABLE_LOCATION_UPDATES = 1;

    private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
    private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
    private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
    private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
    private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;

    private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";

    private final Context mContext;

    final Object mLock = new Object();

    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;

    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
    private boolean mCarModeEnabled = false;
    private boolean mCharging = false;
    private final boolean mCarModeKeepsScreenOn;
    private final boolean mDeskModeKeepsScreenOn;

    private boolean mComputedNightMode;
    private int mCurUiMode = 0;
    private int mSetUiMode = 0;

    private boolean mHoldingConfiguration = false;
    private Configuration mConfiguration = new Configuration();

    private boolean mSystemReady;

    private NotificationManager mNotificationManager;

    private AlarmManager mAlarmManager;

    private LocationManager mLocationManager;
    private Location mLocation;
    private StatusBarManager mStatusBarManager;
    private final PowerManager.WakeLock mWakeLock;

    static Intent buildHomeIntent(String category) {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(category);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        return intent;
    }
    
    // The broadcast receiver which receives the result of the ordered broadcast sent when
    // the dock state changes. The original ordered broadcast is sent with an initial result
    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (getResultCode() != Activity.RESULT_OK) {
                return;
            }

            final int  enableFlags = intent.getIntExtra("enableFlags", 0);
            final int  disableFlags = intent.getIntExtra("disableFlags", 0);
            
            synchronized (mLock) {
                // Launch a dock activity
                String category = null;
                if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
                    // Only launch car home when car mode is enabled and the caller
                    // has asked us to switch to it.
                    if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
                        category = Intent.CATEGORY_CAR_DOCK;
                    }
                } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) {
                    // Only launch car home when desk mode is enabled and the caller
                    // has asked us to switch to it.  Currently re-using the car
                    // mode flag since we don't have a formal API for "desk mode".
                    if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
                        category = Intent.CATEGORY_DESK_DOCK;
                    }
                } else {
                    // Launch the standard home app if requested.
                    if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
                        category = Intent.CATEGORY_HOME;
                    }
                }
                
                if (category != null) {
                    // This is the new activity that will serve as home while
                    // we are in care mode.
                    Intent homeIntent = buildHomeIntent(category);
                    
                    // Now we are going to be careful about switching the
                    // configuration and starting the activity -- we need to
                    // do this in a specific order under control of the
                    // activity manager, to do it cleanly.  So compute the
                    // new config, but don't set it yet, and let the
                    // activity manager take care of both the start and config
                    // change.
                    Configuration newConfig = null;
                    if (mHoldingConfiguration) {
                        mHoldingConfiguration = false;
                        updateConfigurationLocked(false);
                        newConfig = mConfiguration;
                    }
                    try {
                        ActivityManagerNative.getDefault().startActivityWithConfig(
                                null, homeIntent, null, null, 0, null, null, 0, false, false,
                                newConfig);
                        mHoldingConfiguration = false;
                    } catch (RemoteException e) {
                        Slog.w(TAG, e.getCause());
                    }
                }

                if (mHoldingConfiguration) {
                    mHoldingConfiguration = false;
                    updateConfigurationLocked(true);
                }
            }
        }
    };

    private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
            }
        }
    };

    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
            updateDockState(state);
        }
    };

    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
            synchronized (mLock) {
                if (mSystemReady) {
                    updateLocked(0, 0);
                }
            }
        }
    };

    // A LocationListener to initialize the network location provider. The location updates
    // are handled through the passive location provider.
    private final LocationListener mEmptyLocationListener =  new LocationListener() {
        public void onLocationChanged(Location location) {
        }

        public void onProviderDisabled(String provider) {
        }

        public void onProviderEnabled(String provider) {
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {
        }
    };

    private final LocationListener mLocationListener = new LocationListener() {

        public void onLocationChanged(Location location) {
            final boolean hasMoved = hasMoved(location);
            final boolean hasBetterAccuracy = mLocation == null
                    || location.getAccuracy() < mLocation.getAccuracy();
            if (hasMoved || hasBetterAccuracy) {
                synchronized (mLock) {
                    mLocation = location;
                    if (hasMoved && isDoingNightMode()
                            && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                        mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
                    }
                }
            }
        }

        public void onProviderDisabled(String provider) {
        }

        public void onProviderEnabled(String provider) {
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        /*
         * The user has moved if the accuracy circles of the two locations
         * don't overlap.
         */
        private boolean hasMoved(Location location) {
            if (location == null) {
                return false;
            }
            if (mLocation == null) {
                return true;
            }

            /* if new location is older than the current one, the devices hasn't
             * moved.
             */
            if (location.getTime() < mLocation.getTime()) {
                return false;
            }

            /* Get the distance between the two points */
            float distance = mLocation.distanceTo(location);

            /* Get the total accuracy radius for both locations */
            float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();

            /* If the distance is greater than the combined accuracy of the two
             * points then they can't overlap and hence the user has moved.
             */
            return distance >= totalAccuracy;
        }
    };

    public UiModeManagerService(Context context) {
        mContext = context;

        ServiceManager.addService(Context.UI_MODE_SERVICE, this);

        mAlarmManager =
            (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        mLocationManager =
            (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
        mContext.registerReceiver(mTwilightUpdateReceiver,
                new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
        mContext.registerReceiver(mDockModeReceiver,
                new IntentFilter(Intent.ACTION_DOCK_EVENT));
        mContext.registerReceiver(mBatteryReceiver,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);

        mConfiguration.setToDefaults();

        mCarModeKeepsScreenOn = (context.getResources().getInteger(
                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
        mDeskModeKeepsScreenOn = (context.getResources().getInteger(
                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
        
        mNightMode = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
    }

    public void disableCarMode(int flags) {
        synchronized (mLock) {
            setCarModeLocked(false);
            if (mSystemReady) {
                updateLocked(0, flags);
            }
        }
    }

    public void enableCarMode(int flags) {
        synchronized (mLock) {
            setCarModeLocked(true);
            if (mSystemReady) {
                updateLocked(flags, 0);
            }
        }
    }

    public int getCurrentModeType() {
        synchronized (mLock) {
            return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
        }
    }

    public void setNightMode(int mode) throws RemoteException {
        synchronized (mLock) {
            switch (mode) {
                case UiModeManager.MODE_NIGHT_NO:
                case UiModeManager.MODE_NIGHT_YES:
                case UiModeManager.MODE_NIGHT_AUTO:
                    break;
                default:
                    throw new IllegalArgumentException("Unknown mode: " + mode);
            }
            if (!isDoingNightMode()) {
                return;
            }

            if (mNightMode != mode) {
                long ident = Binder.clearCallingIdentity();
                Settings.Secure.putInt(mContext.getContentResolver(),
                        Settings.Secure.UI_NIGHT_MODE, mode);
                Binder.restoreCallingIdentity(ident);
                mNightMode = mode;
                updateLocked(0, 0);
            }
        }
    }

    public int getNightMode() throws RemoteException {
        return mNightMode;
    }

    void systemReady() {
        synchronized (mLock) {
            mSystemReady = true;
            mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
            updateLocked(0, 0);
            mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
        }
    }

    boolean isDoingNightMode() {
        return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
    }

    void setCarModeLocked(boolean enabled) {
        if (mCarModeEnabled != enabled) {
            mCarModeEnabled = enabled;
        }
    }

    void updateDockState(int newState) {
        synchronized (mLock) {
            if (newState != mDockState) {
                mDockState = newState;
                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
                if (mSystemReady) {
                    updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
                }
            }
        }
    }

    final void updateConfigurationLocked(boolean sendIt) {
        int uiMode = Configuration.UI_MODE_TYPE_NORMAL;
        if (mCarModeEnabled) {
            uiMode = Configuration.UI_MODE_TYPE_CAR;
        } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
            uiMode = Configuration.UI_MODE_TYPE_DESK;
        }
        if (mCarModeEnabled) {
            if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                updateTwilightLocked();
                uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
                        : Configuration.UI_MODE_NIGHT_NO;
            } else {
                uiMode |= mNightMode << 4;
            }
        } else {
            // Disabling the car mode clears the night mode.
            uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
        }

        if (LOG) {
            Slog.d(TAG, 
                "updateConfigurationLocked: mDockState=" + mDockState 
                + "; mCarMode=" + mCarModeEnabled
                + "; mNightMode=" + mNightMode
                + "; uiMode=" + uiMode);
        }

        mCurUiMode = uiMode;

        if (!mHoldingConfiguration && uiMode != mSetUiMode) {
            mSetUiMode = uiMode;
            mConfiguration.uiMode = uiMode;

            if (sendIt) {
                try {
                    ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failure communicating with activity manager", e);
                }
            }
        }
    }

    final void updateLocked(int enableFlags, int disableFlags) {
        long ident = Binder.clearCallingIdentity();

        try {
            String action = null;
            String oldAction = null;
            if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
                adjustStatusBarCarModeLocked();
                oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
            } else if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_DESK) {
                oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
            }

            if (mCarModeEnabled) {
                if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
                    adjustStatusBarCarModeLocked();

                    if (oldAction != null) {
                        mContext.sendBroadcast(new Intent(oldAction));
                    }
                    mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
                    action = UiModeManager.ACTION_ENTER_CAR_MODE;
                }
            } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
                if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_DESK) {
                    if (oldAction != null) {
                        mContext.sendBroadcast(new Intent(oldAction));
                    }
                    mLastBroadcastState = Intent.EXTRA_DOCK_STATE_DESK;
                    action = UiModeManager.ACTION_ENTER_DESK_MODE;
                }
            } else {
                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
                action = oldAction;
            }

            if (action != null) {
                // Send the ordered broadcast; the result receiver will receive after all
                // broadcasts have been sent. If any broadcast receiver changes the result
                // code from the initial value of RESULT_OK, then the result receiver will
                // not launch the corresponding dock application. This gives apps a chance
                // to override the behavior and stay in their app even when the device is
                // placed into a dock.
                Intent intent = new Intent(action);
                intent.putExtra("enableFlags", enableFlags);
                intent.putExtra("disableFlags", disableFlags);
                mContext.sendOrderedBroadcast(intent, null,
                        mResultReceiver, null, Activity.RESULT_OK, null, null);
                // Attempting to make this transition a little more clean, we are going
                // to hold off on doing a configuration change until we have finished
                // the broadcast and started the home activity.
                mHoldingConfiguration = true;
            } else {
                Intent homeIntent = null;
                if (mCarModeEnabled) {
                    if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
                        homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK);
                    }
                } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
                    if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
                        homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK);
                    }
                } else {
                    if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
                        homeIntent = buildHomeIntent(Intent.CATEGORY_HOME);
                    }
                }
                if (homeIntent != null) {
                    try {
                        mContext.startActivity(homeIntent);
                    } catch (ActivityNotFoundException e) {
                    }
                }
            }

            updateConfigurationLocked(true);

            // keep screen on when charging and in car mode
            boolean keepScreenOn = mCharging &&
                    ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
                     (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
            if (keepScreenOn != mWakeLock.isHeld()) {
                if (keepScreenOn) {
                    mWakeLock.acquire();
                } else {
                    mWakeLock.release();
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void adjustStatusBarCarModeLocked() {
        if (mStatusBarManager == null) {
            mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
        }

        // Fear not: StatusBarService manages a list of requests to disable
        // features of the status bar; these are ORed together to form the
        // active disabled list. So if (for example) the device is locked and
        // the status bar should be totally disabled, the calls below will
        // have no effect until the device is unlocked.
        if (mStatusBarManager != null) {
            mStatusBarManager.disable(mCarModeEnabled
                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
                : StatusBarManager.DISABLE_NONE);
        }

        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager)
                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        if (mNotificationManager != null) {
            if (mCarModeEnabled) {
                Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);

                Notification n = new Notification();
                n.icon = R.drawable.stat_notify_car_mode;
                n.defaults = Notification.DEFAULT_LIGHTS;
                n.flags = Notification.FLAG_ONGOING_EVENT;
                n.when = 0;
                n.setLatestEventInfo(
                        mContext,
                        mContext.getString(R.string.car_mode_disable_notification_title),
                        mContext.getString(R.string.car_mode_disable_notification_message),
                        PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
                mNotificationManager.notify(0, n);
            } else {
                mNotificationManager.cancel(0);
            }
        }
    }

    private final Handler mHandler = new Handler() {

        boolean mPassiveListenerEnabled;
        boolean mNetworkListenerEnabled;

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_TWILIGHT:
                    synchronized (mLock) {
                        if (isDoingNightMode() && mLocation != null
                                && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                            updateTwilightLocked();
                            updateLocked(0, 0);
                        }
                    }
                    break;
                case MSG_ENABLE_LOCATION_UPDATES:
                    // enable network provider to receive at least location updates for a given
                    // distance.
                    boolean networkLocationEnabled;
                    try {
                        networkLocationEnabled =
                            mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
                    } catch (Exception e) {
                        // we may get IllegalArgumentException if network location provider
                        // does not exist or is not yet installed.
                        networkLocationEnabled = false;
                    }
                    if (!mNetworkListenerEnabled && networkLocationEnabled) {
                        mNetworkListenerEnabled = true;
                        mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                                LOCATION_UPDATE_MS, 0, mEmptyLocationListener);

                        if (mLocation == null) {
                            retrieveLocation();
                        }
                        synchronized (mLock) {
                            if (isDoingNightMode() && mLocation != null
                                    && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                                updateTwilightLocked();
                                updateLocked(0, 0);
                            }
                        }
                    }
                   // enable passive provider to receive updates from location fixes (gps
                   // and network).
                   boolean passiveLocationEnabled;
                    try {
                        passiveLocationEnabled =
                            mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER);
                    } catch (Exception e) {
                        // we may get IllegalArgumentException if passive location provider
                        // does not exist or is not yet installed.
                        passiveLocationEnabled = false;
                    }
                    if (!mPassiveListenerEnabled && passiveLocationEnabled) {
                        mPassiveListenerEnabled = true;
                        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
                                0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener);
                    }
                    if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
                        long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
                        interval *= 1.5;
                        if (interval == 0) {
                            interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
                        } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
                            interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
                        }
                        Bundle bundle = new Bundle();
                        bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
                        Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
                        newMsg.setData(bundle);
                        mHandler.sendMessageDelayed(newMsg, interval);
                    }
                    break;
            }
        }

        private void retrieveLocation() {
            Location location = null;
            final Iterator<String> providers =
                    mLocationManager.getProviders(new Criteria(), true).iterator();
            while (providers.hasNext()) {
                final Location lastKnownLocation =
                        mLocationManager.getLastKnownLocation(providers.next());
                // pick the most recent location
                if (location == null || (lastKnownLocation != null &&
                        location.getTime() < lastKnownLocation.getTime())) {
                    location = lastKnownLocation;
                }
            }
            // In the case there is no location available (e.g. GPS fix or network location
            // is not available yet), the longitude of the location is estimated using the timezone,
            // latitude and accuracy are set to get a good average.
            if (location == null) {
                Time currentTime = new Time();
                currentTime.set(System.currentTimeMillis());
                double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
                        (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
                location = new Location("fake");
                location.setLongitude(lngOffset);
                location.setLatitude(0);
                location.setAccuracy(417000.0f);
                location.setTime(System.currentTimeMillis());
            }
            synchronized (mLock) {
                mLocation = location;
            }
        }
    };

    void updateTwilightLocked() {
        if (mLocation == null) {
            return;
        }
        final long currentTime = System.currentTimeMillis();
        boolean nightMode;
        // calculate current twilight
        TwilightCalculator tw = new TwilightCalculator();
        tw.calculateTwilight(currentTime,
                mLocation.getLatitude(), mLocation.getLongitude());
        if (tw.mState == TwilightCalculator.DAY) {
            nightMode = false;
        } else {
            nightMode = true;
        }

        // schedule next update
        long nextUpdate = 0;
        if (tw.mSunrise == -1 || tw.mSunset == -1) {
            // In the case the day or night never ends the update is scheduled 12 hours later.
            nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
        } else {
            final int mLastTwilightState = tw.mState;
            // add some extra time to be on the save side.
            nextUpdate += DateUtils.MINUTE_IN_MILLIS;
            if (currentTime > tw.mSunset) {
                // next update should be on the following day
                tw.calculateTwilight(currentTime
                        + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
                        mLocation.getLongitude());
            }

            if (mLastTwilightState == TwilightCalculator.NIGHT) {
                nextUpdate += tw.mSunrise;
            } else {
                nextUpdate += tw.mSunset;
            }
        }

        Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
        PendingIntent pendingIntent =
                PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
        mAlarmManager.cancel(pendingIntent);
        mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);

        mComputedNightMode = nightMode;
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {

            pw.println("Permission Denial: can't dump uimode service from from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }

        synchronized (mLock) {
            pw.println("Current UI Mode Service state:");
            pw.print("  mDockState="); pw.print(mDockState);
                    pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
            pw.print("  mNightMode="); pw.print(mNightMode);
                    pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
                    pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
                    pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
            pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
                    pw.print(" mSystemReady="); pw.println(mSystemReady);
            if (mLocation != null) {
                pw.print("  mLocation="); pw.println(mLocation);
            }
        }
    }
}
