blob: bb4d67e0309f6ec2ba0276b8cfc5d68d2bff5a8c [file] [log] [blame]
Jeff Brown2416e092012-08-21 22:12:20 -07001/*
Justin Klaassen908b86c2016-08-08 09:18:42 -07002 * Copyright (C) 2016 The Android Open Source Project
Jeff Brown2416e092012-08-21 22:12:20 -07003 *
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
Adam Lesinski182f73f2013-12-05 16:48:06 -080017package com.android.server.twilight;
18
Justin Klaassen908b86c2016-08-08 09:18:42 -070019import android.annotation.NonNull;
Jeff Brown2416e092012-08-21 22:12:20 -070020import android.app.AlarmManager;
Jeff Brown2416e092012-08-21 22:12:20 -070021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
Justin Klaassen908b86c2016-08-08 09:18:42 -070025import android.icu.impl.CalendarAstronomer;
26import android.icu.util.Calendar;
Jeff Brown2416e092012-08-21 22:12:20 -070027import android.location.Location;
28import android.location.LocationListener;
29import android.location.LocationManager;
30import android.os.Bundle;
31import android.os.Handler;
Justin Klaassen908b86c2016-08-08 09:18:42 -070032import android.os.Looper;
Jeff Brown2416e092012-08-21 22:12:20 -070033import android.os.Message;
Justin Klaassen908b86c2016-08-08 09:18:42 -070034import android.util.ArrayMap;
Jeff Brown2416e092012-08-21 22:12:20 -070035import android.util.Slog;
36
Justin Klaassen63848782016-07-12 13:36:45 -070037import com.android.internal.annotations.GuardedBy;
38import com.android.server.SystemService;
Justin Klaassen63848782016-07-12 13:36:45 -070039
Justin Klaassen63848782016-07-12 13:36:45 -070040import java.util.Objects;
Jeff Brown2416e092012-08-21 22:12:20 -070041
42/**
43 * Figures out whether it's twilight time based on the user's location.
Justin Klaassen63848782016-07-12 13:36:45 -070044 * <p>
Jeff Brown2416e092012-08-21 22:12:20 -070045 * Used by the UI mode manager and other components to adjust night mode
46 * effects based on sunrise and sunset.
47 */
Justin Klaassen908b86c2016-08-08 09:18:42 -070048public final class TwilightService extends SystemService
49 implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener {
Justin Klaassen63848782016-07-12 13:36:45 -070050
51 private static final String TAG = "TwilightService";
52 private static final boolean DEBUG = false;
53
Justin Klaassen908b86c2016-08-08 09:18:42 -070054 private static final int MSG_START_LISTENING = 1;
55 private static final int MSG_STOP_LISTENING = 2;
Jeff Brown2416e092012-08-21 22:12:20 -070056
Justin Klaassen908b86c2016-08-08 09:18:42 -070057 @GuardedBy("mListeners")
58 private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>();
Jason Monk5dbd4aa2016-02-07 13:13:39 -050059
Justin Klaassen908b86c2016-08-08 09:18:42 -070060 private final Handler mHandler;
Jason Monk5dbd4aa2016-02-07 13:13:39 -050061
Christine Frankscc280122016-11-30 09:33:07 -080062 protected AlarmManager mAlarmManager;
Justin Klaassen63848782016-07-12 13:36:45 -070063 private LocationManager mLocationManager;
Jason Monk5dbd4aa2016-02-07 13:13:39 -050064
Justin Klaassen908b86c2016-08-08 09:18:42 -070065 private boolean mBootCompleted;
66 private boolean mHasListeners;
67
68 private BroadcastReceiver mTimeChangedReceiver;
Christine Frankscc280122016-11-30 09:33:07 -080069 protected Location mLastLocation;
Justin Klaassen908b86c2016-08-08 09:18:42 -070070
71 @GuardedBy("mListeners")
Christine Frankscc280122016-11-30 09:33:07 -080072 protected TwilightState mLastTwilightState;
Jason Monk5dbd4aa2016-02-07 13:13:39 -050073
Jeff Brownb880d882014-02-10 19:47:07 -080074 public TwilightService(Context context) {
75 super(context);
Justin Klaassen908b86c2016-08-08 09:18:42 -070076 mHandler = new Handler(Looper.getMainLooper(), this);
Jeff Brownb880d882014-02-10 19:47:07 -080077 }
78
Adam Lesinski182f73f2013-12-05 16:48:06 -080079 @Override
80 public void onStart() {
Justin Klaassen908b86c2016-08-08 09:18:42 -070081 publishLocalService(TwilightManager.class, new TwilightManager() {
82 @Override
83 public void registerListener(@NonNull TwilightListener listener,
84 @NonNull Handler handler) {
85 synchronized (mListeners) {
86 final boolean wasEmpty = mListeners.isEmpty();
87 mListeners.put(listener, handler);
Adam Lesinski182f73f2013-12-05 16:48:06 -080088
Justin Klaassen908b86c2016-08-08 09:18:42 -070089 if (wasEmpty && !mListeners.isEmpty()) {
90 mHandler.sendEmptyMessage(MSG_START_LISTENING);
91 }
92 }
93 }
Adam Lesinski182f73f2013-12-05 16:48:06 -080094
Justin Klaassen908b86c2016-08-08 09:18:42 -070095 @Override
96 public void unregisterListener(@NonNull TwilightListener listener) {
97 synchronized (mListeners) {
98 final boolean wasEmpty = mListeners.isEmpty();
99 mListeners.remove(listener);
100
101 if (!wasEmpty && mListeners.isEmpty()) {
102 mHandler.sendEmptyMessage(MSG_STOP_LISTENING);
103 }
104 }
105 }
106
107 @Override
108 public TwilightState getLastTwilightState() {
109 synchronized (mListeners) {
110 return mLastTwilightState;
111 }
112 }
113 });
Jason Monk5dbd4aa2016-02-07 13:13:39 -0500114 }
115
116 @Override
117 public void onBootPhase(int phase) {
118 if (phase == PHASE_BOOT_COMPLETED) {
Justin Klaassen908b86c2016-08-08 09:18:42 -0700119 final Context c = getContext();
120 mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
121 mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
Jason Monk5dbd4aa2016-02-07 13:13:39 -0500122
Justin Klaassen908b86c2016-08-08 09:18:42 -0700123 mBootCompleted = true;
124 if (mHasListeners) {
125 startListening();
Jason Monk5dbd4aa2016-02-07 13:13:39 -0500126 }
127 }
Jeff Brown2416e092012-08-21 22:12:20 -0700128 }
129
Justin Klaassen908b86c2016-08-08 09:18:42 -0700130 @Override
131 public boolean handleMessage(Message msg) {
132 switch (msg.what) {
133 case MSG_START_LISTENING:
134 if (!mHasListeners) {
135 mHasListeners = true;
136 if (mBootCompleted) {
137 startListening();
138 }
139 }
140 return true;
141 case MSG_STOP_LISTENING:
142 if (mHasListeners) {
143 mHasListeners = false;
144 if (mBootCompleted) {
145 stopListening();
146 }
147 }
148 return true;
Adam Lesinski182f73f2013-12-05 16:48:06 -0800149 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700150 return false;
Adam Lesinski182f73f2013-12-05 16:48:06 -0800151 }
152
Justin Klaassen908b86c2016-08-08 09:18:42 -0700153 private void startListening() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700154 Slog.d(TAG, "startListening");
Justin Klaassen908b86c2016-08-08 09:18:42 -0700155
156 // Start listening for location updates (default: low power, max 1h, min 10m).
157 mLocationManager.requestLocationUpdates(
158 null /* default */, this, Looper.getMainLooper());
159
160 // Request the device's location immediately if a previous location isn't available.
161 if (mLocationManager.getLastLocation() == null) {
162 if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
163 mLocationManager.requestSingleUpdate(
164 LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
165 } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
166 mLocationManager.requestSingleUpdate(
167 LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
Jeff Brown2416e092012-08-21 22:12:20 -0700168 }
169 }
Jeff Brown2416e092012-08-21 22:12:20 -0700170
Justin Klaassen908b86c2016-08-08 09:18:42 -0700171 // Update whenever the system clock is changed.
172 if (mTimeChangedReceiver == null) {
173 mTimeChangedReceiver = new BroadcastReceiver() {
174 @Override
175 public void onReceive(Context context, Intent intent) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700176 Slog.d(TAG, "onReceive: " + intent);
Jeff Brown2416e092012-08-21 22:12:20 -0700177 updateTwilightState();
Justin Klaassen908b86c2016-08-08 09:18:42 -0700178 }
179 };
180
181 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
182 intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
183 getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
184 }
185
186 // Force an update now that we have listeners registered.
187 updateTwilightState();
188 }
189
190 private void stopListening() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700191 Slog.d(TAG, "stopListening");
Justin Klaassen908b86c2016-08-08 09:18:42 -0700192
193 if (mTimeChangedReceiver != null) {
194 getContext().unregisterReceiver(mTimeChangedReceiver);
195 mTimeChangedReceiver = null;
196 }
197
198 if (mLastTwilightState != null) {
199 mAlarmManager.cancel(this);
200 }
201
202 mLocationManager.removeUpdates(this);
203 mLastLocation = null;
204 }
205
206 private void updateTwilightState() {
207 // Calculate the twilight state based on the current time and location.
208 final long currentTimeMillis = System.currentTimeMillis();
209 final Location location = mLastLocation != null ? mLastLocation
210 : mLocationManager.getLastLocation();
211 final TwilightState state = calculateTwilightState(location, currentTimeMillis);
212 if (DEBUG) {
213 Slog.d(TAG, "updateTwilightState: " + state);
214 }
215
216 // Notify listeners if the state has changed.
217 synchronized (mListeners) {
218 if (!Objects.equals(mLastTwilightState, state)) {
219 mLastTwilightState = state;
220
221 for (int i = mListeners.size() - 1; i >= 0; --i) {
222 final TwilightListener listener = mListeners.keyAt(i);
223 final Handler handler = mListeners.valueAt(i);
224 handler.post(new Runnable() {
225 @Override
226 public void run() {
227 listener.onTwilightStateChanged(state);
228 }
229 });
230 }
Jeff Brown2416e092012-08-21 22:12:20 -0700231 }
232 }
233
Justin Klaassen908b86c2016-08-08 09:18:42 -0700234 // Schedule an alarm to update the state at the next sunrise or sunset.
235 if (state != null) {
236 final long triggerAtMillis = state.isNight()
237 ? state.sunriseTimeMillis() : state.sunsetTimeMillis();
238 mAlarmManager.setExact(AlarmManager.RTC, triggerAtMillis, TAG, this, mHandler);
Jeff Brown2416e092012-08-21 22:12:20 -0700239 }
Adam Lesinski182f73f2013-12-05 16:48:06 -0800240 }
Jeff Brown2416e092012-08-21 22:12:20 -0700241
Justin Klaassen908b86c2016-08-08 09:18:42 -0700242 @Override
243 public void onAlarm() {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700244 Slog.d(TAG, "onAlarm");
Justin Klaassen908b86c2016-08-08 09:18:42 -0700245 updateTwilightState();
246 }
Jeff Brown2416e092012-08-21 22:12:20 -0700247
Justin Klaassen908b86c2016-08-08 09:18:42 -0700248 @Override
249 public void onLocationChanged(Location location) {
Christine Frankscc280122016-11-30 09:33:07 -0800250 // Location providers may erroneously return (0.0, 0.0) when they fail to determine the
251 // device's location. These location updates can be safely ignored since the chance of a
252 // user actually being at these coordinates is quite low.
253 if (location != null
254 && !(location.getLongitude() == 0.0 && location.getLatitude() == 0.0)) {
Justin Klaassenec8837a2016-08-23 12:04:42 -0700255 Slog.d(TAG, "onLocationChanged:"
256 + " provider=" + location.getProvider()
257 + " accuracy=" + location.getAccuracy()
258 + " time=" + location.getTime());
259 mLastLocation = location;
260 updateTwilightState();
261 }
Justin Klaassen908b86c2016-08-08 09:18:42 -0700262 }
263
264 @Override
265 public void onStatusChanged(String provider, int status, Bundle extras) {
266 }
267
268 @Override
269 public void onProviderEnabled(String provider) {
270 }
271
272 @Override
273 public void onProviderDisabled(String provider) {
274 }
275
276 /**
277 * Calculates the twilight state for a specific location and time.
278 *
279 * @param location the location to use
280 * @param timeMillis the reference time to use
281 * @return the calculated {@link TwilightState}, or {@code null} if location is {@code null}
282 */
283 private static TwilightState calculateTwilightState(Location location, long timeMillis) {
284 if (location == null) {
285 return null;
Jeff Brown2416e092012-08-21 22:12:20 -0700286 }
287
Justin Klaassen908b86c2016-08-08 09:18:42 -0700288 final CalendarAstronomer ca = new CalendarAstronomer(
289 location.getLongitude(), location.getLatitude());
290
291 final Calendar noon = Calendar.getInstance();
292 noon.setTimeInMillis(timeMillis);
293 noon.set(Calendar.HOUR_OF_DAY, 12);
294 noon.set(Calendar.MINUTE, 0);
295 noon.set(Calendar.SECOND, 0);
296 noon.set(Calendar.MILLISECOND, 0);
297 ca.setTime(noon.getTimeInMillis());
298
299 long sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
300 long sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
301
302 if (sunsetTimeMillis < timeMillis) {
303 noon.add(Calendar.DATE, 1);
304 ca.setTime(noon.getTimeInMillis());
305 sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
306 } else if (sunriseTimeMillis > timeMillis) {
307 noon.add(Calendar.DATE, -1);
308 ca.setTime(noon.getTimeInMillis());
309 sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
Jeff Brown2416e092012-08-21 22:12:20 -0700310 }
311
Justin Klaassen908b86c2016-08-08 09:18:42 -0700312 return new TwilightState(sunriseTimeMillis, sunsetTimeMillis);
313 }
Jeff Brown2416e092012-08-21 22:12:20 -0700314}