Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 1 | /* |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 2 | * Copyright (C) 2016 The Android Open Source Project |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 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 | |
Adam Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 17 | package com.android.server.twilight; |
| 18 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 19 | import android.annotation.NonNull; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 20 | import android.app.AlarmManager; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 21 | import android.content.BroadcastReceiver; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.IntentFilter; |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 25 | import android.icu.impl.CalendarAstronomer; |
| 26 | import android.icu.util.Calendar; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 27 | import android.location.Location; |
| 28 | import android.location.LocationListener; |
| 29 | import android.location.LocationManager; |
| 30 | import android.os.Bundle; |
| 31 | import android.os.Handler; |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 32 | import android.os.Looper; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 33 | import android.os.Message; |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 34 | import android.util.ArrayMap; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 35 | import android.util.Slog; |
| 36 | |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 37 | import com.android.internal.annotations.GuardedBy; |
| 38 | import com.android.server.SystemService; |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 39 | |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 40 | import java.util.Objects; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 41 | |
| 42 | /** |
| 43 | * Figures out whether it's twilight time based on the user's location. |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 44 | * <p> |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 45 | * Used by the UI mode manager and other components to adjust night mode |
| 46 | * effects based on sunrise and sunset. |
| 47 | */ |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 48 | public final class TwilightService extends SystemService |
| 49 | implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener { |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 50 | |
| 51 | private static final String TAG = "TwilightService"; |
| 52 | private static final boolean DEBUG = false; |
| 53 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 54 | private static final int MSG_START_LISTENING = 1; |
| 55 | private static final int MSG_STOP_LISTENING = 2; |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 56 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 57 | @GuardedBy("mListeners") |
| 58 | private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>(); |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 59 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 60 | private final Handler mHandler; |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 61 | |
Christine Franks | cc28012 | 2016-11-30 09:33:07 -0800 | [diff] [blame] | 62 | protected AlarmManager mAlarmManager; |
Justin Klaassen | 6384878 | 2016-07-12 13:36:45 -0700 | [diff] [blame] | 63 | private LocationManager mLocationManager; |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 64 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 65 | private boolean mBootCompleted; |
| 66 | private boolean mHasListeners; |
| 67 | |
| 68 | private BroadcastReceiver mTimeChangedReceiver; |
Christine Franks | cc28012 | 2016-11-30 09:33:07 -0800 | [diff] [blame] | 69 | protected Location mLastLocation; |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 70 | |
| 71 | @GuardedBy("mListeners") |
Christine Franks | cc28012 | 2016-11-30 09:33:07 -0800 | [diff] [blame] | 72 | protected TwilightState mLastTwilightState; |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 73 | |
Jeff Brown | b880d88 | 2014-02-10 19:47:07 -0800 | [diff] [blame] | 74 | public TwilightService(Context context) { |
| 75 | super(context); |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 76 | mHandler = new Handler(Looper.getMainLooper(), this); |
Jeff Brown | b880d88 | 2014-02-10 19:47:07 -0800 | [diff] [blame] | 77 | } |
| 78 | |
Adam Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 79 | @Override |
| 80 | public void onStart() { |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 81 | 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 Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 88 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 89 | if (wasEmpty && !mListeners.isEmpty()) { |
| 90 | mHandler.sendEmptyMessage(MSG_START_LISTENING); |
| 91 | } |
| 92 | } |
| 93 | } |
Adam Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 94 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 95 | @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 Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 114 | } |
| 115 | |
| 116 | @Override |
| 117 | public void onBootPhase(int phase) { |
| 118 | if (phase == PHASE_BOOT_COMPLETED) { |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 119 | final Context c = getContext(); |
| 120 | mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE); |
| 121 | mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE); |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 122 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 123 | mBootCompleted = true; |
| 124 | if (mHasListeners) { |
| 125 | startListening(); |
Jason Monk | 5dbd4aa | 2016-02-07 13:13:39 -0500 | [diff] [blame] | 126 | } |
| 127 | } |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 128 | } |
| 129 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 130 | @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 Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 149 | } |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 150 | return false; |
Adam Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 151 | } |
| 152 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 153 | private void startListening() { |
Justin Klaassen | ec8837a | 2016-08-23 12:04:42 -0700 | [diff] [blame] | 154 | Slog.d(TAG, "startListening"); |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 155 | |
| 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 Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 168 | } |
| 169 | } |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 170 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 171 | // 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 Klaassen | ec8837a | 2016-08-23 12:04:42 -0700 | [diff] [blame] | 176 | Slog.d(TAG, "onReceive: " + intent); |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 177 | updateTwilightState(); |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 178 | } |
| 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 Klaassen | ec8837a | 2016-08-23 12:04:42 -0700 | [diff] [blame] | 191 | Slog.d(TAG, "stopListening"); |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 192 | |
| 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 Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 231 | } |
| 232 | } |
| 233 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 234 | // 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 Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 239 | } |
Adam Lesinski | 182f73f | 2013-12-05 16:48:06 -0800 | [diff] [blame] | 240 | } |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 241 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 242 | @Override |
| 243 | public void onAlarm() { |
Justin Klaassen | ec8837a | 2016-08-23 12:04:42 -0700 | [diff] [blame] | 244 | Slog.d(TAG, "onAlarm"); |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 245 | updateTwilightState(); |
| 246 | } |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 247 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 248 | @Override |
| 249 | public void onLocationChanged(Location location) { |
Christine Franks | cc28012 | 2016-11-30 09:33:07 -0800 | [diff] [blame] | 250 | // 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 Klaassen | ec8837a | 2016-08-23 12:04:42 -0700 | [diff] [blame] | 255 | Slog.d(TAG, "onLocationChanged:" |
| 256 | + " provider=" + location.getProvider() |
| 257 | + " accuracy=" + location.getAccuracy() |
| 258 | + " time=" + location.getTime()); |
| 259 | mLastLocation = location; |
| 260 | updateTwilightState(); |
| 261 | } |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 262 | } |
| 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 Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 286 | } |
| 287 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 288 | 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 Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 310 | } |
| 311 | |
Justin Klaassen | 908b86c | 2016-08-08 09:18:42 -0700 | [diff] [blame] | 312 | return new TwilightState(sunriseTimeMillis, sunsetTimeMillis); |
| 313 | } |
Jeff Brown | 2416e09 | 2012-08-21 22:12:20 -0700 | [diff] [blame] | 314 | } |