TwilightService v2.0
- Switched to using CalendarAstronomer for more accurate sunrise/sunset
times.
- Exposed sunrise/sunset times via TwilightState so that clients can
track the current twilight period and perform their own
interpolations.
- Adopted LocationRequest API for fused location updates:
(low power, min 1h, max 10m).
- TwilightService is now only activated when a listener is registered,
minimizing impact to system health on platforms / configurations
where twilight state is not needed.
Bug: 28588307
Bug: 30190450
Bug: 30282370
Bug: 30650316
Change-Id: Ic5c94d8608e8bb3a3d895e623676a1468d4abdcd
diff --git a/services/core/java/com/android/server/TwilightCalculator.java b/services/core/java/com/android/server/TwilightCalculator.java
deleted file mode 100644
index 5839b16..0000000
--- a/services/core/java/com/android/server/TwilightCalculator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2010 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.text.format.DateUtils;
-
-/** @hide */
-public class TwilightCalculator {
-
- /** Value of {@link #mState} if it is currently day */
- public static final int DAY = 0;
-
- /** Value of {@link #mState} if it is currently night */
- public static final int NIGHT = 1;
-
- private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f);
-
- // element for calculating solar transit.
- private static final float J0 = 0.0009f;
-
- // correction for civil twilight
- private static final float ALTIDUTE_CORRECTION_CIVIL_TWILIGHT = -0.104719755f;
-
- // coefficients for calculating Equation of Center.
- private static final float C1 = 0.0334196f;
- private static final float C2 = 0.000349066f;
- private static final float C3 = 0.000005236f;
-
- private static final float OBLIQUITY = 0.40927971f;
-
- // Java time on Jan 1, 2000 12:00 UTC.
- private static final long UTC_2000 = 946728000000L;
-
- /**
- * Time of sunset (civil twilight) in milliseconds or -1 in the case the day
- * or night never ends.
- */
- public long mSunset;
-
- /**
- * Time of sunrise (civil twilight) in milliseconds or -1 in the case the
- * day or night never ends.
- */
- public long mSunrise;
-
- /** Current state */
- public int mState;
-
- /**
- * calculates the civil twilight bases on time and geo-coordinates.
- *
- * @param time time in milliseconds.
- * @param latiude latitude in degrees.
- * @param longitude latitude in degrees.
- */
- public void calculateTwilight(long time, double latiude, double longitude) {
- final float daysSince2000 = (float) (time - UTC_2000) / DateUtils.DAY_IN_MILLIS;
-
- // mean anomaly
- final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f;
-
- // true anomaly
- final double trueAnomaly = meanAnomaly + C1 * Math.sin(meanAnomaly) + C2
- * Math.sin(2 * meanAnomaly) + C3 * Math.sin(3 * meanAnomaly);
-
- // ecliptic longitude
- final double solarLng = trueAnomaly + 1.796593063d + Math.PI;
-
- // solar transit in days since 2000
- final double arcLongitude = -longitude / 360;
- float n = Math.round(daysSince2000 - J0 - arcLongitude);
- double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053d * Math.sin(meanAnomaly)
- + -0.0069d * Math.sin(2 * solarLng);
-
- // declination of sun
- double solarDec = Math.asin(Math.sin(solarLng) * Math.sin(OBLIQUITY));
-
- final double latRad = latiude * DEGREES_TO_RADIANS;
-
- double cosHourAngle = (Math.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
- * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
- // The day or night never ends for the given date and location, if this value is out of
- // range.
- if (cosHourAngle >= 1) {
- mState = NIGHT;
- mSunset = -1;
- mSunrise = -1;
- return;
- } else if (cosHourAngle <= -1) {
- mState = DAY;
- mSunset = -1;
- mSunrise = -1;
- return;
- }
-
- float hourAngle = (float) (Math.acos(cosHourAngle) / (2 * Math.PI));
-
- mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
- mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
-
- if (mSunrise < time && mSunset > time) {
- mState = DAY;
- } else {
- mState = NIGHT;
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 6f713cd..bb5f62b 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -155,8 +156,13 @@
private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
- public void onTwilightStateChanged() {
- updateTwilight();
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
+ synchronized (mLock) {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ updateComputedNightModeLocked();
+ updateLocked(0, 0);
+ }
+ }
}
};
@@ -344,8 +350,8 @@
pw.print(" mSystemReady="); pw.println(mSystemReady);
if (mTwilightManager != null) {
// We may not have a TwilightManager.
- pw.print(" mTwilightService.getCurrentState()=");
- pw.println(mTwilightManager.getCurrentState());
+ pw.print(" mTwilightService.getLastTwilightState()=");
+ pw.println(mTwilightManager.getLastTwilightState());
}
}
}
@@ -355,9 +361,6 @@
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
mTwilightManager = getLocalService(TwilightManager.class);
- if (mTwilightManager != null) {
- mTwilightManager.registerListener(mTwilightListener, mHandler);
- }
mSystemReady = true;
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
updateComputedNightModeLocked();
@@ -411,10 +414,16 @@
}
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ if (mTwilightManager != null) {
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+ }
updateComputedNightModeLocked();
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
} else {
+ if (mTwilightManager != null) {
+ mTwilightManager.unregisterListener(mTwilightListener);
+ }
uiMode |= mNightMode << 4;
}
@@ -668,18 +677,9 @@
}
}
- void updateTwilight() {
- synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- updateComputedNightModeLocked();
- updateLocked(0, 0);
- }
- }
- }
-
private void updateComputedNightModeLocked() {
if (mTwilightManager != null) {
- TwilightState state = mTwilightManager.getCurrentState();
+ TwilightState state = mTwilightManager.getLastTwilightState();
if (state != null) {
mComputedNightMode = state.isNight();
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index c16dac2..15ae846 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -22,6 +22,7 @@
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import android.annotation.Nullable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -268,7 +269,7 @@
pw.println();
pw.println("Automatic Brightness Controller State:");
pw.println(" mLightSensor=" + mLightSensor);
- pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
+ pw.println(" mTwilight.getLastTwilightState()=" + mTwilight.getLastTwilightState());
pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
pw.println(" mAmbientLux=" + mAmbientLux);
@@ -495,12 +496,14 @@
}
if (mUseTwilight) {
- TwilightState state = mTwilight.getCurrentState();
+ TwilightState state = mTwilight.getLastTwilightState();
if (state != null && state.isNight()) {
- final long now = System.currentTimeMillis();
- gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
+ final long duration = state.sunriseTimeMillis() - state.sunsetTimeMillis();
+ final long progress = System.currentTimeMillis() - state.sunsetTimeMillis();
+ final float amount = (float) Math.pow(2.0 * progress / duration - 1.0, 2.0);
+ gamma *= 1 + amount * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
+ Slog.d(TAG, "updateAutoBrightness: twilight amount=" + amount);
}
}
}
@@ -621,7 +624,7 @@
private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
- public void onTwilightStateChanged() {
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
updateAutoBrightness(true /*sendUpdate*/);
}
};
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 39498a6..d9af7cb 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -217,12 +218,12 @@
if (mIsActivated == null || mIsActivated != activated) {
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
- mIsActivated = activated;
-
if (mAutoMode != null) {
mAutoMode.onActivated(activated);
}
+ mIsActivated = activated;
+
// Cancel the old animator if still running.
if (mColorMatrixAnimator != null) {
mColorMatrixAnimator.cancel();
@@ -395,7 +396,9 @@
@Override
public void onActivated(boolean activated) {
- mLastActivatedTime = Calendar.getInstance();
+ if (mIsActivated != null) {
+ mLastActivatedTime = Calendar.getInstance();
+ }
updateNextAlarm();
}
@@ -424,22 +427,30 @@
private final TwilightManager mTwilightManager;
- private boolean mIsNight;
+ private Calendar mLastActivatedTime;
public TwilightAutoMode() {
mTwilightManager = getLocalService(TwilightManager.class);
}
- private void updateActivated() {
- final TwilightState state = mTwilightManager.getCurrentState();
+ private void updateActivated(TwilightState state) {
final boolean isNight = state != null && state.isNight();
- if (mIsNight != isNight) {
- mIsNight = isNight;
-
- if (mIsActivated == null || mIsActivated != isNight) {
- mController.setActivated(isNight);
+ boolean setActivated = mIsActivated == null || mIsActivated != isNight;
+ if (setActivated && state != null && mLastActivatedTime != null) {
+ final Calendar sunrise = state.sunrise();
+ final Calendar sunset = state.sunset();
+ if (sunrise.before(sunset)) {
+ setActivated = mLastActivatedTime.before(sunrise)
+ || mLastActivatedTime.after(sunset);
+ } else {
+ setActivated = mLastActivatedTime.before(sunset)
+ || mLastActivatedTime.after(sunrise);
}
}
+
+ if (setActivated) {
+ mController.setActivated(isNight);
+ }
}
@Override
@@ -447,18 +458,26 @@
mTwilightManager.registerListener(this, mHandler);
// Force an update to initialize state.
- updateActivated();
+ updateActivated(mTwilightManager.getLastTwilightState());
}
@Override
public void onStop() {
mTwilightManager.unregisterListener(this);
+ mLastActivatedTime = null;
}
@Override
- public void onTwilightStateChanged() {
+ public void onActivated(boolean activated) {
+ if (mIsActivated != null) {
+ mLastActivatedTime = Calendar.getInstance();
+ }
+ }
+
+ @Override
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
- updateActivated();
+ updateActivated(state);
}
}
diff --git a/services/core/java/com/android/server/twilight/TwilightListener.java b/services/core/java/com/android/server/twilight/TwilightListener.java
index 29ead44..58dcef6 100644
--- a/services/core/java/com/android/server/twilight/TwilightListener.java
+++ b/services/core/java/com/android/server/twilight/TwilightListener.java
@@ -16,6 +16,14 @@
package com.android.server.twilight;
+import android.annotation.Nullable;
+
+/**
+ * Callback for when the twilight state has changed.
+ */
public interface TwilightListener {
- void onTwilightStateChanged();
+ /**
+ * Called when the twilight state has changed.
+ */
+ void onTwilightStateChanged(@Nullable TwilightState state);
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/twilight/TwilightManager.java b/services/core/java/com/android/server/twilight/TwilightManager.java
index 56137a4..5ef9417 100644
--- a/services/core/java/com/android/server/twilight/TwilightManager.java
+++ b/services/core/java/com/android/server/twilight/TwilightManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,10 +16,30 @@
package com.android.server.twilight;
+import android.annotation.NonNull;
import android.os.Handler;
+/**
+ * This class provides sunrise/sunset information based on the device's current location.
+ */
public interface TwilightManager {
- void registerListener(TwilightListener listener, Handler handler);
- void unregisterListener(TwilightListener listener);
- TwilightState getCurrentState();
+ /**
+ * Register a listener to be notified whenever the twilight state changes.
+ *
+ * @param listener the {@link TwilightListener} to be notified
+ * @param handler the {@link Handler} to use to notify the listener
+ */
+ void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler);
+
+ /**
+ * Unregisters a previously registered listener.
+ *
+ * @param listener the {@link TwilightListener} to be unregistered
+ */
+ void unregisterListener(@NonNull TwilightListener listener);
+
+ /**
+ * Returns the last {@link TwilightState}, or {@code null} if not available.
+ */
+ TwilightState getLastTwilightState();
}
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index ee7a4a0..acd6587 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,31 +16,27 @@
package com.android.server.twilight;
+import android.annotation.NonNull;
import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.location.Criteria;
+import android.icu.impl.CalendarAstronomer;
+import android.icu.util.Calendar;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
-import android.os.SystemClock;
-import android.text.format.DateUtils;
-import android.text.format.Time;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
-import com.android.server.TwilightCalculator;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
import java.util.Objects;
/**
@@ -49,476 +45,261 @@
* Used by the UI mode manager and other components to adjust night mode
* effects based on sunrise and sunset.
*/
-public final class TwilightService extends SystemService {
+public final class TwilightService extends SystemService
+ implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener {
private static final String TAG = "TwilightService";
private static final boolean DEBUG = false;
- private static final String ACTION_UPDATE_TWILIGHT_STATE =
- "com.android.server.action.UPDATE_TWILIGHT_STATE";
+ private static final int MSG_START_LISTENING = 1;
+ private static final int MSG_STOP_LISTENING = 2;
- /**
- * The amount of time after or before sunrise over which to start adjusting twilight affected
- * things. We want the change to happen gradually so that it is below the threshold of
- * perceptibility and so that the adjustment has and so that the adjustment has
- * maximum effect well after dusk.
- */
- private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+ @GuardedBy("mListeners")
+ private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>();
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private final List<TwilightListenerRecord> mListeners = new ArrayList<>();
+ private final Handler mHandler;
private AlarmManager mAlarmManager;
private LocationManager mLocationManager;
- private LocationHandler mLocationHandler;
- @GuardedBy("mLock")
- private TwilightState mTwilightState;
+ private boolean mBootCompleted;
+ private boolean mHasListeners;
+
+ private BroadcastReceiver mTimeChangedReceiver;
+ private Location mLastLocation;
+
+ @GuardedBy("mListeners")
+ private TwilightState mLastTwilightState;
public TwilightService(Context context) {
super(context);
+ mHandler = new Handler(Looper.getMainLooper(), this);
}
@Override
public void onStart() {
- mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
- mLocationManager = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- mLocationHandler = new LocationHandler();
+ publishLocalService(TwilightManager.class, new TwilightManager() {
+ @Override
+ public void registerListener(@NonNull TwilightListener listener,
+ @NonNull Handler handler) {
+ synchronized (mListeners) {
+ final boolean wasEmpty = mListeners.isEmpty();
+ mListeners.put(listener, handler);
- IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiver(mReceiver, filter);
+ if (wasEmpty && !mListeners.isEmpty()) {
+ mHandler.sendEmptyMessage(MSG_START_LISTENING);
+ }
+ }
+ }
- publishLocalService(TwilightManager.class, mService);
+ @Override
+ public void unregisterListener(@NonNull TwilightListener listener) {
+ synchronized (mListeners) {
+ final boolean wasEmpty = mListeners.isEmpty();
+ mListeners.remove(listener);
+
+ if (!wasEmpty && mListeners.isEmpty()) {
+ mHandler.sendEmptyMessage(MSG_STOP_LISTENING);
+ }
+ }
+ }
+
+ @Override
+ public TwilightState getLastTwilightState() {
+ synchronized (mListeners) {
+ return mLastTwilightState;
+ }
+ }
+ });
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
- // Initialize the current twilight state.
- mLocationHandler.requestTwilightUpdate();
- }
- }
+ final Context c = getContext();
+ mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
+ mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
- private void setTwilightState(TwilightState state) {
- synchronized (mLock) {
- if (!Objects.equals(mTwilightState, state)) {
- if (DEBUG) {
- Slog.d(TAG, "Twilight state changed: " + state);
- }
-
- mTwilightState = state;
-
- for (TwilightListenerRecord mListener : mListeners) {
- mListener.postUpdate();
- }
+ mBootCompleted = true;
+ if (mHasListeners) {
+ startListening();
}
}
}
- private static class TwilightListenerRecord implements Runnable {
-
- private final TwilightListener mListener;
- private final Handler mHandler;
-
- public TwilightListenerRecord(TwilightListener listener, Handler handler) {
- mListener = listener;
- mHandler = handler;
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LISTENING:
+ if (!mHasListeners) {
+ mHasListeners = true;
+ if (mBootCompleted) {
+ startListening();
+ }
+ }
+ return true;
+ case MSG_STOP_LISTENING:
+ if (mHasListeners) {
+ mHasListeners = false;
+ if (mBootCompleted) {
+ stopListening();
+ }
+ }
+ return true;
}
-
- public void postUpdate() {
- mHandler.post(this);
- }
-
- @Override
- public void run() {
- mListener.onTwilightStateChanged();
- }
+ return false;
}
- private final TwilightManager mService = new TwilightManager() {
- @Override
- public TwilightState getCurrentState() {
- synchronized (mLock) {
- return mTwilightState;
+ private void startListening() {
+ if (DEBUG) Slog.d(TAG, "startListening");
+
+ // Start listening for location updates (default: low power, max 1h, min 10m).
+ mLocationManager.requestLocationUpdates(
+ null /* default */, this, Looper.getMainLooper());
+
+ // Request the device's location immediately if a previous location isn't available.
+ if (mLocationManager.getLastLocation() == null) {
+ if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+ mLocationManager.requestSingleUpdate(
+ LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
+ } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ mLocationManager.requestSingleUpdate(
+ LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
}
}
- @Override
- public void registerListener(TwilightListener listener, Handler handler) {
- synchronized (mLock) {
- mListeners.add(new TwilightListenerRecord(listener, handler));
-
- if (mListeners.size() == 1) {
- mLocationHandler.enableLocationUpdates();
- }
- }
- }
-
- @Override
- public void unregisterListener(TwilightListener listener) {
- synchronized (mLock) {
- for (int i = 0; i < mListeners.size(); i++) {
- if (mListeners.get(i).mListener == listener) {
- mListeners.remove(i);
- }
- }
-
- if (mListeners.size() == 0) {
- mLocationHandler.disableLocationUpdates();
- }
- }
- }
- };
-
- // The user has moved if the accuracy circles of the two locations don't overlap.
- private static boolean hasMoved(Location from, Location to) {
- if (to == null) {
- return false;
- }
-
- if (from == null) {
- return true;
- }
-
- // if new location is older than the current one, the device hasn't moved.
- if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) {
- return false;
- }
-
- // Get the distance between the two points.
- float distance = from.distanceTo(to);
-
- // Get the total accuracy radius for both locations.
- float totalAccuracy = from.getAccuracy() + to.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;
- }
-
- private final class LocationHandler extends Handler {
-
- private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
- private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
- private static final int MSG_PROCESS_NEW_LOCATION = 3;
- private static final int MSG_DO_TWILIGHT_UPDATE = 4;
- private static final int MSG_DISABLE_LOCATION_UPDATES = 5;
-
- private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS;
- private static final long MIN_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 =
- 15 * DateUtils.MINUTE_IN_MILLIS;
- private static final double FACTOR_GMT_OFFSET_LONGITUDE =
- 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
-
- private final TwilightCalculator mTwilightCalculator = new TwilightCalculator();
-
- private boolean mPassiveListenerEnabled;
- private boolean mNetworkListenerEnabled;
- private boolean mDidFirstInit;
- private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS;
- private long mLastUpdateInterval;
- private Location mLocation;
-
- public void processNewLocation(Location location) {
- Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location);
- sendMessage(msg);
- }
-
- public void enableLocationUpdates() {
- sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
- }
-
- public void disableLocationUpdates() {
- sendEmptyMessage(MSG_DISABLE_LOCATION_UPDATES);
- }
-
- public void requestLocationUpdate() {
- sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
- }
-
- public void requestTwilightUpdate() {
- sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_PROCESS_NEW_LOCATION: {
- final Location location = (Location) msg.obj;
- final boolean hasMoved = hasMoved(mLocation, location);
- final boolean hasBetterAccuracy = mLocation == null
- || location.getAccuracy() < mLocation.getAccuracy();
- if (DEBUG) {
- Slog.d(TAG, "Processing new location: " + location
- + ", hasMoved=" + hasMoved
- + ", hasBetterAccuracy=" + hasBetterAccuracy);
- }
- if (hasMoved || hasBetterAccuracy) {
- setLocation(location);
- }
- break;
- }
-
- case MSG_GET_NEW_LOCATION_UPDATE:
- if (!mNetworkListenerEnabled) {
- // Don't do anything -- we are still trying to get a
- // location.
- return;
- }
- if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >=
- SystemClock.elapsedRealtime()) {
- // Don't do anything -- it hasn't been long enough
- // since we last requested an update.
- return;
- }
-
- // Unregister the current location monitor, so we can
- // register a new one for it to get an immediate update.
- mNetworkListenerEnabled = false;
- mLocationManager.removeUpdates(mEmptyLocationListener);
-
- // Fall through to re-register listener.
- 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;
- mLastNetworkRegisterTime = SystemClock.elapsedRealtime();
- mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
- LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
-
- if (!mDidFirstInit) {
- mDidFirstInit = true;
- if (mLocation == null) {
- retrieveLocation();
- }
- }
- }
-
- // 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)) {
- mLastUpdateInterval *= 1.5;
- if (mLastUpdateInterval == 0) {
- mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
- } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
- mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
- }
- sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval);
- }
- break;
-
- case MSG_DISABLE_LOCATION_UPDATES:
- mLocationManager.removeUpdates(mLocationListener);
- removeMessages(MSG_ENABLE_LOCATION_UPDATES);
- break;
-
- case MSG_DO_TWILIGHT_UPDATE:
+ // Update whenever the system clock is changed.
+ if (mTimeChangedReceiver == null) {
+ mTimeChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive: " + intent);
updateTwilightState();
- break;
+ }
+ };
+
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
+ intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
+ }
+
+ // Force an update now that we have listeners registered.
+ updateTwilightState();
+ }
+
+ private void stopListening() {
+ if (DEBUG) Slog.d(TAG, "stopListening");
+
+ if (mTimeChangedReceiver != null) {
+ getContext().unregisterReceiver(mTimeChangedReceiver);
+ mTimeChangedReceiver = null;
+ }
+
+ if (mLastTwilightState != null) {
+ mAlarmManager.cancel(this);
+ }
+
+ mLocationManager.removeUpdates(this);
+ mLastLocation = null;
+ }
+
+ private void updateTwilightState() {
+ // Calculate the twilight state based on the current time and location.
+ final long currentTimeMillis = System.currentTimeMillis();
+ final Location location = mLastLocation != null ? mLastLocation
+ : mLocationManager.getLastLocation();
+ final TwilightState state = calculateTwilightState(location, currentTimeMillis);
+ if (DEBUG) {
+ Slog.d(TAG, "updateTwilightState: " + state);
+ }
+
+ // Notify listeners if the state has changed.
+ synchronized (mListeners) {
+ if (!Objects.equals(mLastTwilightState, state)) {
+ mLastTwilightState = state;
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final TwilightListener listener = mListeners.keyAt(i);
+ final Handler handler = mListeners.valueAt(i);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onTwilightStateChanged(state);
+ }
+ });
+ }
}
}
- 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.getElapsedRealtimeNanos() <
- lastKnownLocation.getElapsedRealtimeNanos())) {
- 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());
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
- if (DEBUG) {
- Slog.d(TAG, "Estimated location from timezone: " + location);
- }
- }
-
- setLocation(location);
- }
-
- private void setLocation(Location location) {
- mLocation = location;
- updateTwilightState();
- }
-
- private void updateTwilightState() {
- if (mLocation == null) {
- setTwilightState(null);
- return;
- }
-
- final long now = System.currentTimeMillis();
-
- // calculate today's twilight
- mTwilightCalculator.calculateTwilight(now,
- mLocation.getLatitude(), mLocation.getLongitude());
- final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT);
- final long todaySunrise = mTwilightCalculator.mSunrise;
- final long todaySunset = mTwilightCalculator.mSunset;
-
- // calculate tomorrow's twilight
- mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS,
- mLocation.getLatitude(), mLocation.getLongitude());
- final long tomorrowSunrise = mTwilightCalculator.mSunrise;
-
- float amount = 0;
- if (isNight) {
- if (todaySunrise == -1 || todaySunset == -1) {
- amount = 1;
- } else if (now > todaySunset) {
- amount = Math.min(1, (now - todaySunset) / (float) TWILIGHT_ADJUSTMENT_TIME);
- } else {
- amount = Math.max(0, 1
- - (todaySunrise - now) / (float) TWILIGHT_ADJUSTMENT_TIME);
- }
- }
- // set twilight state
- TwilightState state = new TwilightState(isNight, amount);
- if (DEBUG) {
- Slog.d(TAG, "Updating twilight state: " + state);
- }
- setTwilightState(state);
-
- // schedule next update
- long nextUpdate = 0;
- if (todaySunrise == -1 || todaySunset == -1) {
- // In the case the day or night never ends the update is scheduled 12 hours later.
- nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS;
- } else {
- // add some extra time to be on the safe side.
- nextUpdate += DateUtils.MINUTE_IN_MILLIS;
-
- if (amount == 1 || amount == 0) {
- if (now > todaySunset) {
- nextUpdate += tomorrowSunrise;
- } else if (now > todaySunrise) {
- nextUpdate += todaySunset;
- } else {
- nextUpdate += todaySunrise;
- }
- } else {
- // This is the update rate while transitioning.
- // Leave at 10 min for now (one from above).
- nextUpdate += 9 * DateUtils.MINUTE_IN_MILLIS;
- }
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms");
- }
-
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- getContext(), 0, new Intent(ACTION_UPDATE_TWILIGHT_STATE), 0);
- mAlarmManager.cancel(pendingIntent);
- mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
+ // Schedule an alarm to update the state at the next sunrise or sunset.
+ if (state != null) {
+ final long triggerAtMillis = state.isNight()
+ ? state.sunriseTimeMillis() : state.sunsetTimeMillis();
+ mAlarmManager.setExact(AlarmManager.RTC, triggerAtMillis, TAG, this, mHandler);
}
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())
- && !intent.getBooleanExtra("state", false)) {
- // Airplane mode is now off!
- mLocationHandler.requestLocationUpdate();
- return;
- }
- // Time zone has changed or alarm expired.
- mLocationHandler.requestTwilightUpdate();
- }
- };
+ @Override
+ public void onAlarm() {
+ if (DEBUG) Slog.d(TAG, "onAlarm");
+ updateTwilightState();
+ }
- // A LocationListener to initialize the network location provider. The location updates
- // are handled through the passive location provider.
- private final LocationListener mEmptyLocationListener = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
+ @Override
+ public void onLocationChanged(Location location) {
+ if (DEBUG) Slog.d(TAG, "onLocationChanged: " + location);
+ mLastLocation = location;
+ updateTwilightState();
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+
+ /**
+ * Calculates the twilight state for a specific location and time.
+ *
+ * @param location the location to use
+ * @param timeMillis the reference time to use
+ * @return the calculated {@link TwilightState}, or {@code null} if location is {@code null}
+ */
+ private static TwilightState calculateTwilightState(Location location, long timeMillis) {
+ if (location == null) {
+ return null;
}
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
+ final CalendarAstronomer ca = new CalendarAstronomer(
+ location.getLongitude(), location.getLatitude());
+
+ final Calendar noon = Calendar.getInstance();
+ noon.setTimeInMillis(timeMillis);
+ noon.set(Calendar.HOUR_OF_DAY, 12);
+ noon.set(Calendar.MINUTE, 0);
+ noon.set(Calendar.SECOND, 0);
+ noon.set(Calendar.MILLISECOND, 0);
+ ca.setTime(noon.getTimeInMillis());
+
+ long sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
+ long sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
+
+ if (sunsetTimeMillis < timeMillis) {
+ noon.add(Calendar.DATE, 1);
+ ca.setTime(noon.getTimeInMillis());
+ sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
+ } else if (sunriseTimeMillis > timeMillis) {
+ noon.add(Calendar.DATE, -1);
+ ca.setTime(noon.getTimeInMillis());
+ sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
}
- @Override
- public void onProviderEnabled(String provider) {
- }
-
- @Override
- public void onProviderDisabled(String provider) {
- }
- };
-
- private final LocationListener mLocationListener = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
- mLocationHandler.processNewLocation(location);
- }
-
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
- }
-
- @Override
- public void onProviderEnabled(String provider) {
- }
-
- @Override
- public void onProviderDisabled(String provider) {
- }
- };
+ return new TwilightState(sunriseTimeMillis, sunsetTimeMillis);
+ }
}
diff --git a/services/core/java/com/android/server/twilight/TwilightState.java b/services/core/java/com/android/server/twilight/TwilightState.java
index dec053b..a12965d 100644
--- a/services/core/java/com/android/server/twilight/TwilightState.java
+++ b/services/core/java/com/android/server/twilight/TwilightState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,59 +16,89 @@
package com.android.server.twilight;
-import java.text.DateFormat;
-import java.util.Date;
+import android.text.format.DateFormat;
+
+import java.util.Calendar;
/**
- * Describes whether it is day or night.
- * This object is immutable.
+ * The twilight state, consisting of the sunrise and sunset times (in millis) for the current
+ * period.
+ * <p/>
+ * Note: This object is immutable.
*/
-public class TwilightState {
+public final class TwilightState {
- private final boolean mIsNight;
- private final float mAmount;
+ private final long mSunriseTimeMillis;
+ private final long mSunsetTimeMillis;
- TwilightState(boolean isNight, float amount) {
- mIsNight = isNight;
- mAmount = amount;
+ TwilightState(long sunriseTimeMillis, long sunsetTimeMillis) {
+ mSunriseTimeMillis = sunriseTimeMillis;
+ mSunsetTimeMillis = sunsetTimeMillis;
}
/**
- * Returns true if it is currently night time.
+ * Returns the time (in UTC milliseconds from epoch) of the upcoming or previous sunrise if
+ * it's night or day respectively.
+ */
+ public long sunriseTimeMillis() {
+ return mSunriseTimeMillis;
+ }
+
+ /**
+ * Returns a new {@link Calendar} instance initialized to {@link #sunriseTimeMillis()}.
+ */
+ public Calendar sunrise() {
+ final Calendar sunrise = Calendar.getInstance();
+ sunrise.setTimeInMillis(mSunriseTimeMillis);
+ return sunrise;
+ }
+
+ /**
+ * Returns the time (in UTC milliseconds from epoch) of the upcoming or previous sunset if
+ * it's day or night respectively.
+ */
+ public long sunsetTimeMillis() {
+ return mSunsetTimeMillis;
+ }
+
+ /**
+ * Returns a new {@link Calendar} instance initialized to {@link #sunsetTimeMillis()}.
+ */
+ public Calendar sunset() {
+ final Calendar sunset = Calendar.getInstance();
+ sunset.setTimeInMillis(mSunsetTimeMillis);
+ return sunset;
+ }
+
+ /**
+ * Returns {@code true} if it is currently night time.
*/
public boolean isNight() {
- return mIsNight;
- }
-
- /**
- * For twilight affects that change gradually over time, this is the amount they
- * should currently be in effect.
- */
- public float getAmount() {
- return mAmount;
+ final long now = System.currentTimeMillis();
+ return now >= mSunsetTimeMillis && now < mSunriseTimeMillis;
}
@Override
public boolean equals(Object o) {
- return o instanceof TwilightState && equals((TwilightState)o);
+ return o instanceof TwilightState && equals((TwilightState) o);
}
public boolean equals(TwilightState other) {
return other != null
- && mIsNight == other.mIsNight
- && mAmount == other.mAmount;
+ && mSunriseTimeMillis == other.mSunriseTimeMillis
+ && mSunsetTimeMillis == other.mSunsetTimeMillis;
}
@Override
public int hashCode() {
- return 0; // don't care
+ return Long.hashCode(mSunriseTimeMillis) ^ Long.hashCode(mSunsetTimeMillis);
}
@Override
public String toString() {
- DateFormat f = DateFormat.getDateTimeInstance();
- return "{TwilightState: isNight=" + mIsNight
- + ", mAmount=" + mAmount
- + "}";
+ return "TwilightState {"
+ + " sunrise=" + DateFormat.format("MM-dd HH:mm", mSunriseTimeMillis)
+ + " sunset="+ DateFormat.format("MM-dd HH:mm", mSunsetTimeMillis)
+ + " }";
}
}