12416e09649eb6ab767eba458796e126196c77a34Jeff Brown/*
2908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen * Copyright (C) 2016 The Android Open Source Project
32416e09649eb6ab767eba458796e126196c77a34Jeff Brown *
42416e09649eb6ab767eba458796e126196c77a34Jeff Brown * Licensed under the Apache License, Version 2.0 (the "License");
52416e09649eb6ab767eba458796e126196c77a34Jeff Brown * you may not use this file except in compliance with the License.
62416e09649eb6ab767eba458796e126196c77a34Jeff Brown * You may obtain a copy of the License at
72416e09649eb6ab767eba458796e126196c77a34Jeff Brown *
82416e09649eb6ab767eba458796e126196c77a34Jeff Brown *      http://www.apache.org/licenses/LICENSE-2.0
92416e09649eb6ab767eba458796e126196c77a34Jeff Brown *
102416e09649eb6ab767eba458796e126196c77a34Jeff Brown * Unless required by applicable law or agreed to in writing, software
112416e09649eb6ab767eba458796e126196c77a34Jeff Brown * distributed under the License is distributed on an "AS IS" BASIS,
122416e09649eb6ab767eba458796e126196c77a34Jeff Brown * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132416e09649eb6ab767eba458796e126196c77a34Jeff Brown * See the License for the specific language governing permissions and
142416e09649eb6ab767eba458796e126196c77a34Jeff Brown * limitations under the License.
152416e09649eb6ab767eba458796e126196c77a34Jeff Brown */
162416e09649eb6ab767eba458796e126196c77a34Jeff Brown
17182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinskipackage com.android.server.twilight;
18182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski
19908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenimport android.annotation.NonNull;
202416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.app.AlarmManager;
212416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.content.BroadcastReceiver;
222416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.content.Context;
232416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.content.Intent;
242416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.content.IntentFilter;
25908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenimport android.icu.impl.CalendarAstronomer;
26908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenimport android.icu.util.Calendar;
272416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.location.Location;
282416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.location.LocationListener;
292416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.location.LocationManager;
302416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.os.Bundle;
312416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.os.Handler;
32908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenimport android.os.Looper;
332416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.os.Message;
34908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenimport android.util.ArrayMap;
352416e09649eb6ab767eba458796e126196c77a34Jeff Brownimport android.util.Slog;
362416e09649eb6ab767eba458796e126196c77a34Jeff Brown
376384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassenimport com.android.internal.annotations.GuardedBy;
386384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassenimport com.android.server.SystemService;
396384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassen
406384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassenimport java.util.Objects;
412416e09649eb6ab767eba458796e126196c77a34Jeff Brown
422416e09649eb6ab767eba458796e126196c77a34Jeff Brown/**
432416e09649eb6ab767eba458796e126196c77a34Jeff Brown * Figures out whether it's twilight time based on the user's location.
446384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassen * <p>
452416e09649eb6ab767eba458796e126196c77a34Jeff Brown * Used by the UI mode manager and other components to adjust night mode
462416e09649eb6ab767eba458796e126196c77a34Jeff Brown * effects based on sunrise and sunset.
472416e09649eb6ab767eba458796e126196c77a34Jeff Brown */
48908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassenpublic final class TwilightService extends SystemService
49908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener {
502416e09649eb6ab767eba458796e126196c77a34Jeff Brown
516384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassen    private static final String TAG = "TwilightService";
526384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassen    private static final boolean DEBUG = false;
535dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
54908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private static final int MSG_START_LISTENING = 1;
55908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private static final int MSG_STOP_LISTENING = 2;
565dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
57908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @GuardedBy("mListeners")
58908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>();
592416e09649eb6ab767eba458796e126196c77a34Jeff Brown
60908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private final Handler mHandler;
612416e09649eb6ab767eba458796e126196c77a34Jeff Brown
62cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks    protected AlarmManager mAlarmManager;
636384878d69d3deb4a7c314f611ee62a634f21411Justin Klaassen    private LocationManager mLocationManager;
642416e09649eb6ab767eba458796e126196c77a34Jeff Brown
65908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private boolean mBootCompleted;
66908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private boolean mHasListeners;
67908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
68908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private BroadcastReceiver mTimeChangedReceiver;
69cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks    protected Location mLastLocation;
70908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
71908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @GuardedBy("mListeners")
72cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks    protected TwilightState mLastTwilightState;
735dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
74b880d880c6cd989eacc28c365fc9a41d31900da1Jeff Brown    public TwilightService(Context context) {
75b880d880c6cd989eacc28c365fc9a41d31900da1Jeff Brown        super(context);
76908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        mHandler = new Handler(Looper.getMainLooper(), this);
77b880d880c6cd989eacc28c365fc9a41d31900da1Jeff Brown    }
78b880d880c6cd989eacc28c365fc9a41d31900da1Jeff Brown
79182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski    @Override
80182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski    public void onStart() {
81908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        publishLocalService(TwilightManager.class, new TwilightManager() {
82908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            @Override
83908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            public void registerListener(@NonNull TwilightListener listener,
84908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    @NonNull Handler handler) {
85908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                synchronized (mListeners) {
86908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    final boolean wasEmpty = mListeners.isEmpty();
87908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    mListeners.put(listener, handler);
88908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
89908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    if (wasEmpty && !mListeners.isEmpty()) {
90908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        mHandler.sendEmptyMessage(MSG_START_LISTENING);
91908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    }
925dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk                }
93908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            }
945dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
95908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            @Override
96908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            public void unregisterListener(@NonNull TwilightListener listener) {
97908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                synchronized (mListeners) {
98908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    final boolean wasEmpty = mListeners.isEmpty();
99908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    mListeners.remove(listener);
1005dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
101908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    if (!wasEmpty && mListeners.isEmpty()) {
102908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        mHandler.sendEmptyMessage(MSG_STOP_LISTENING);
103908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    }
1045dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk                }
1055dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk            }
1062416e09649eb6ab767eba458796e126196c77a34Jeff Brown
107908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            @Override
108908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            public TwilightState getLastTwilightState() {
109908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                synchronized (mListeners) {
110908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    return mLastTwilightState;
111908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                }
112182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski            }
113908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        });
114908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
1152416e09649eb6ab767eba458796e126196c77a34Jeff Brown
116908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
117908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onBootPhase(int phase) {
118908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (phase == PHASE_BOOT_COMPLETED) {
119908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            final Context c = getContext();
120908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
121908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
122182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski
123908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mBootCompleted = true;
124908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            if (mHasListeners) {
125908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                startListening();
1262416e09649eb6ab767eba458796e126196c77a34Jeff Brown            }
1272416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
128908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
1292416e09649eb6ab767eba458796e126196c77a34Jeff Brown
130908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
131908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public boolean handleMessage(Message msg) {
132908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        switch (msg.what) {
133908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            case MSG_START_LISTENING:
134908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                if (!mHasListeners) {
135908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    mHasListeners = true;
136908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    if (mBootCompleted) {
137908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        startListening();
1385dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk                    }
1392416e09649eb6ab767eba458796e126196c77a34Jeff Brown                }
140908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                return true;
141908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            case MSG_STOP_LISTENING:
142908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                if (mHasListeners) {
143908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    mHasListeners = false;
144908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    if (mBootCompleted) {
145908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        stopListening();
146908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    }
1472416e09649eb6ab767eba458796e126196c77a34Jeff Brown                }
148908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                return true;
1492416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
150908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        return false;
151908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
1522416e09649eb6ab767eba458796e126196c77a34Jeff Brown
153908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private void startListening() {
154ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen        Slog.d(TAG, "startListening");
155908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
156908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Start listening for location updates (default: low power, max 1h, min 10m).
157908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        mLocationManager.requestLocationUpdates(
158908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                null /* default */, this, Looper.getMainLooper());
159908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
160908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Request the device's location immediately if a previous location isn't available.
161908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (mLocationManager.getLastLocation() == null) {
162908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
163908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                mLocationManager.requestSingleUpdate(
164908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
165908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
166908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                mLocationManager.requestSingleUpdate(
167908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
168908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            }
1692416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
1702416e09649eb6ab767eba458796e126196c77a34Jeff Brown
171908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Update whenever the system clock is changed.
172908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (mTimeChangedReceiver == null) {
173908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mTimeChangedReceiver = new BroadcastReceiver() {
174908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                @Override
175908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                public void onReceive(Context context, Intent intent) {
176ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen                    Slog.d(TAG, "onReceive: " + intent);
177908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    updateTwilightState();
178908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                }
179908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            };
1802416e09649eb6ab767eba458796e126196c77a34Jeff Brown
181908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
182908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
183908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
1842416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
1852416e09649eb6ab767eba458796e126196c77a34Jeff Brown
186908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Force an update now that we have listeners registered.
187908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        updateTwilightState();
1882416e09649eb6ab767eba458796e126196c77a34Jeff Brown    }
1892416e09649eb6ab767eba458796e126196c77a34Jeff Brown
190908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private void stopListening() {
191ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen        Slog.d(TAG, "stopListening");
1922416e09649eb6ab767eba458796e126196c77a34Jeff Brown
193908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (mTimeChangedReceiver != null) {
194908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            getContext().unregisterReceiver(mTimeChangedReceiver);
195908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mTimeChangedReceiver = null;
1962416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
1972416e09649eb6ab767eba458796e126196c77a34Jeff Brown
198908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (mLastTwilightState != null) {
199908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mAlarmManager.cancel(this);
2005dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk        }
2015dbd4aad809e6fec51df62280bcc1bfe05cc7df5Jason Monk
202908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        mLocationManager.removeUpdates(this);
203908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        mLastLocation = null;
204908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2052416e09649eb6ab767eba458796e126196c77a34Jeff Brown
206908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private void updateTwilightState() {
207908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Calculate the twilight state based on the current time and location.
208908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        final long currentTimeMillis = System.currentTimeMillis();
209908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        final Location location = mLastLocation != null ? mLastLocation
210908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                : mLocationManager.getLastLocation();
211908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        final TwilightState state = calculateTwilightState(location, currentTimeMillis);
212908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (DEBUG) {
213908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            Slog.d(TAG, "updateTwilightState: " + state);
2142416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
2152416e09649eb6ab767eba458796e126196c77a34Jeff Brown
216908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Notify listeners if the state has changed.
217908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        synchronized (mListeners) {
218908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            if (!Objects.equals(mLastTwilightState, state)) {
219908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                mLastTwilightState = state;
220908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
221908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                for (int i = mListeners.size() - 1; i >= 0; --i) {
222908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    final TwilightListener listener = mListeners.keyAt(i);
223908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    final Handler handler = mListeners.valueAt(i);
224908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    handler.post(new Runnable() {
225908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        @Override
226908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                        public void run() {
227908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                            listener.onTwilightStateChanged(state);
2282416e09649eb6ab767eba458796e126196c77a34Jeff Brown                        }
229908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    });
2302416e09649eb6ab767eba458796e126196c77a34Jeff Brown                }
2312416e09649eb6ab767eba458796e126196c77a34Jeff Brown            }
2322416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
2332416e09649eb6ab767eba458796e126196c77a34Jeff Brown
234908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        // Schedule an alarm to update the state at the next sunrise or sunset.
235908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (state != null) {
236908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            final long triggerAtMillis = state.isNight()
237908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                    ? state.sunriseTimeMillis() : state.sunsetTimeMillis();
238908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            mAlarmManager.setExact(AlarmManager.RTC, triggerAtMillis, TAG, this, mHandler);
2392416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
240182f73fc4da13a6417e5086ec9ecce80eb8423caAdam Lesinski    }
2412416e09649eb6ab767eba458796e126196c77a34Jeff Brown
242908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
243908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onAlarm() {
244ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen        Slog.d(TAG, "onAlarm");
245908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        updateTwilightState();
246908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2472416e09649eb6ab767eba458796e126196c77a34Jeff Brown
248908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
249908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onLocationChanged(Location location) {
250cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks        // Location providers may erroneously return (0.0, 0.0) when they fail to determine the
251cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks        // device's location. These location updates can be safely ignored since the chance of a
252cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks        // user actually being at these coordinates is quite low.
253cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks        if (location != null
254cc2801296dd1829196fcaa7b4018e372121e36e5Christine Franks                && !(location.getLongitude() == 0.0 && location.getLatitude() == 0.0)) {
255ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen            Slog.d(TAG, "onLocationChanged:"
256ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen                    + " provider=" + location.getProvider()
257ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen                    + " accuracy=" + location.getAccuracy()
258ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen                    + " time=" + location.getTime());
259ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen            mLastLocation = location;
260ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen            updateTwilightState();
261ec8837ae2670fbca115c566f8871e66bfa72917bJustin Klaassen        }
262908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2632416e09649eb6ab767eba458796e126196c77a34Jeff Brown
264908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
265908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onStatusChanged(String provider, int status, Bundle extras) {
266908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2672416e09649eb6ab767eba458796e126196c77a34Jeff Brown
268908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
269908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onProviderEnabled(String provider) {
270908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2712416e09649eb6ab767eba458796e126196c77a34Jeff Brown
272908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    @Override
273908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    public void onProviderDisabled(String provider) {
274908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
2752416e09649eb6ab767eba458796e126196c77a34Jeff Brown
276908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    /**
277908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     * Calculates the twilight state for a specific location and time.
278908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     *
279908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     * @param location the location to use
280908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     * @param timeMillis the reference time to use
281908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     * @return the calculated {@link TwilightState}, or {@code null} if location is {@code null}
282908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen     */
283908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    private static TwilightState calculateTwilightState(Location location, long timeMillis) {
284908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (location == null) {
285908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            return null;
2862416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
2872416e09649eb6ab767eba458796e126196c77a34Jeff Brown
288908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        final CalendarAstronomer ca = new CalendarAstronomer(
289908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen                location.getLongitude(), location.getLatitude());
290908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
291908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        final Calendar noon = Calendar.getInstance();
292908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        noon.setTimeInMillis(timeMillis);
293908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        noon.set(Calendar.HOUR_OF_DAY, 12);
294908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        noon.set(Calendar.MINUTE, 0);
295908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        noon.set(Calendar.SECOND, 0);
296908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        noon.set(Calendar.MILLISECOND, 0);
297908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        ca.setTime(noon.getTimeInMillis());
298908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
299908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        long sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
300908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        long sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
301908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen
302908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        if (sunsetTimeMillis < timeMillis) {
303908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            noon.add(Calendar.DATE, 1);
304908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            ca.setTime(noon.getTimeInMillis());
305908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
306908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        } else if (sunriseTimeMillis > timeMillis) {
307908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            noon.add(Calendar.DATE, -1);
308908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            ca.setTime(noon.getTimeInMillis());
309908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen            sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
3102416e09649eb6ab767eba458796e126196c77a34Jeff Brown        }
3112416e09649eb6ab767eba458796e126196c77a34Jeff Brown
312908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen        return new TwilightState(sunriseTimeMillis, sunsetTimeMillis);
313908b86c796443ba4ec55c669e8a0297fc80574a6Justin Klaassen    }
3142416e09649eb6ab767eba458796e126196c77a34Jeff Brown}
315