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
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.server;
|
package com.android.server;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.ActivityManagerNative;
|
import android.app.ActivityManagerNative;
|
||||||
@@ -155,8 +156,13 @@ final class UiModeManagerService extends SystemService {
|
|||||||
|
|
||||||
private final TwilightListener mTwilightListener = new TwilightListener() {
|
private final TwilightListener mTwilightListener = new TwilightListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTwilightStateChanged() {
|
public void onTwilightStateChanged(@Nullable TwilightState state) {
|
||||||
updateTwilight();
|
synchronized (mLock) {
|
||||||
|
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
|
||||||
|
updateComputedNightModeLocked();
|
||||||
|
updateLocked(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -344,8 +350,8 @@ final class UiModeManagerService extends SystemService {
|
|||||||
pw.print(" mSystemReady="); pw.println(mSystemReady);
|
pw.print(" mSystemReady="); pw.println(mSystemReady);
|
||||||
if (mTwilightManager != null) {
|
if (mTwilightManager != null) {
|
||||||
// We may not have a TwilightManager.
|
// We may not have a TwilightManager.
|
||||||
pw.print(" mTwilightService.getCurrentState()=");
|
pw.print(" mTwilightService.getLastTwilightState()=");
|
||||||
pw.println(mTwilightManager.getCurrentState());
|
pw.println(mTwilightManager.getLastTwilightState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,9 +361,6 @@ final class UiModeManagerService extends SystemService {
|
|||||||
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
|
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mTwilightManager = getLocalService(TwilightManager.class);
|
mTwilightManager = getLocalService(TwilightManager.class);
|
||||||
if (mTwilightManager != null) {
|
|
||||||
mTwilightManager.registerListener(mTwilightListener, mHandler);
|
|
||||||
}
|
|
||||||
mSystemReady = true;
|
mSystemReady = true;
|
||||||
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
|
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
|
||||||
updateComputedNightModeLocked();
|
updateComputedNightModeLocked();
|
||||||
@@ -411,10 +414,16 @@ final class UiModeManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
|
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
|
||||||
|
if (mTwilightManager != null) {
|
||||||
|
mTwilightManager.registerListener(mTwilightListener, mHandler);
|
||||||
|
}
|
||||||
updateComputedNightModeLocked();
|
updateComputedNightModeLocked();
|
||||||
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
|
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
|
||||||
: Configuration.UI_MODE_NIGHT_NO;
|
: Configuration.UI_MODE_NIGHT_NO;
|
||||||
} else {
|
} else {
|
||||||
|
if (mTwilightManager != null) {
|
||||||
|
mTwilightManager.unregisterListener(mTwilightListener);
|
||||||
|
}
|
||||||
uiMode |= mNightMode << 4;
|
uiMode |= mNightMode << 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,18 +677,9 @@ final class UiModeManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTwilight() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
|
|
||||||
updateComputedNightModeLocked();
|
|
||||||
updateLocked(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateComputedNightModeLocked() {
|
private void updateComputedNightModeLocked() {
|
||||||
if (mTwilightManager != null) {
|
if (mTwilightManager != null) {
|
||||||
TwilightState state = mTwilightManager.getCurrentState();
|
TwilightState state = mTwilightManager.getLastTwilightState();
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
mComputedNightMode = state.isNight();
|
mComputedNightMode = state.isNight();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.android.server.twilight.TwilightListener;
|
|||||||
import com.android.server.twilight.TwilightManager;
|
import com.android.server.twilight.TwilightManager;
|
||||||
import com.android.server.twilight.TwilightState;
|
import com.android.server.twilight.TwilightState;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.hardware.Sensor;
|
import android.hardware.Sensor;
|
||||||
import android.hardware.SensorEvent;
|
import android.hardware.SensorEvent;
|
||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
@@ -268,7 +269,7 @@ class AutomaticBrightnessController {
|
|||||||
pw.println();
|
pw.println();
|
||||||
pw.println("Automatic Brightness Controller State:");
|
pw.println("Automatic Brightness Controller State:");
|
||||||
pw.println(" mLightSensor=" + mLightSensor);
|
pw.println(" mLightSensor=" + mLightSensor);
|
||||||
pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
|
pw.println(" mTwilight.getLastTwilightState()=" + mTwilight.getLastTwilightState());
|
||||||
pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
|
pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
|
||||||
pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
|
pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
|
||||||
pw.println(" mAmbientLux=" + mAmbientLux);
|
pw.println(" mAmbientLux=" + mAmbientLux);
|
||||||
@@ -495,12 +496,14 @@ class AutomaticBrightnessController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mUseTwilight) {
|
if (mUseTwilight) {
|
||||||
TwilightState state = mTwilight.getCurrentState();
|
TwilightState state = mTwilight.getLastTwilightState();
|
||||||
if (state != null && state.isNight()) {
|
if (state != null && state.isNight()) {
|
||||||
final long now = System.currentTimeMillis();
|
final long duration = state.sunriseTimeMillis() - state.sunsetTimeMillis();
|
||||||
gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
|
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) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
|
Slog.d(TAG, "updateAutoBrightness: twilight amount=" + amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,7 +624,7 @@ class AutomaticBrightnessController {
|
|||||||
|
|
||||||
private final TwilightListener mTwilightListener = new TwilightListener() {
|
private final TwilightListener mTwilightListener = new TwilightListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTwilightStateChanged() {
|
public void onTwilightStateChanged(@Nullable TwilightState state) {
|
||||||
updateAutoBrightness(true /*sendUpdate*/);
|
updateAutoBrightness(true /*sendUpdate*/);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.animation.Animator;
|
|||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.TypeEvaluator;
|
import android.animation.TypeEvaluator;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -217,12 +218,12 @@ public final class NightDisplayService extends SystemService
|
|||||||
if (mIsActivated == null || mIsActivated != activated) {
|
if (mIsActivated == null || mIsActivated != activated) {
|
||||||
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
|
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
|
||||||
|
|
||||||
mIsActivated = activated;
|
|
||||||
|
|
||||||
if (mAutoMode != null) {
|
if (mAutoMode != null) {
|
||||||
mAutoMode.onActivated(activated);
|
mAutoMode.onActivated(activated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mIsActivated = activated;
|
||||||
|
|
||||||
// Cancel the old animator if still running.
|
// Cancel the old animator if still running.
|
||||||
if (mColorMatrixAnimator != null) {
|
if (mColorMatrixAnimator != null) {
|
||||||
mColorMatrixAnimator.cancel();
|
mColorMatrixAnimator.cancel();
|
||||||
@@ -395,7 +396,9 @@ public final class NightDisplayService extends SystemService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivated(boolean activated) {
|
public void onActivated(boolean activated) {
|
||||||
|
if (mIsActivated != null) {
|
||||||
mLastActivatedTime = Calendar.getInstance();
|
mLastActivatedTime = Calendar.getInstance();
|
||||||
|
}
|
||||||
updateNextAlarm();
|
updateNextAlarm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,22 +427,30 @@ public final class NightDisplayService extends SystemService
|
|||||||
|
|
||||||
private final TwilightManager mTwilightManager;
|
private final TwilightManager mTwilightManager;
|
||||||
|
|
||||||
private boolean mIsNight;
|
private Calendar mLastActivatedTime;
|
||||||
|
|
||||||
public TwilightAutoMode() {
|
public TwilightAutoMode() {
|
||||||
mTwilightManager = getLocalService(TwilightManager.class);
|
mTwilightManager = getLocalService(TwilightManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateActivated() {
|
private void updateActivated(TwilightState state) {
|
||||||
final TwilightState state = mTwilightManager.getCurrentState();
|
|
||||||
final boolean isNight = state != null && state.isNight();
|
final boolean isNight = state != null && state.isNight();
|
||||||
if (mIsNight != isNight) {
|
boolean setActivated = mIsActivated == null || mIsActivated != isNight;
|
||||||
mIsNight = isNight;
|
if (setActivated && state != null && mLastActivatedTime != null) {
|
||||||
|
final Calendar sunrise = state.sunrise();
|
||||||
if (mIsActivated == null || mIsActivated != isNight) {
|
final Calendar sunset = state.sunset();
|
||||||
mController.setActivated(isNight);
|
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
|
@Override
|
||||||
@@ -447,18 +458,26 @@ public final class NightDisplayService extends SystemService
|
|||||||
mTwilightManager.registerListener(this, mHandler);
|
mTwilightManager.registerListener(this, mHandler);
|
||||||
|
|
||||||
// Force an update to initialize state.
|
// Force an update to initialize state.
|
||||||
updateActivated();
|
updateActivated(mTwilightManager.getLastTwilightState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
mTwilightManager.unregisterListener(this);
|
mTwilightManager.unregisterListener(this);
|
||||||
|
mLastActivatedTime = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
|
||||||
updateActivated();
|
updateActivated(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,14 @@
|
|||||||
|
|
||||||
package com.android.server.twilight;
|
package com.android.server.twilight;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the twilight state has changed.
|
||||||
|
*/
|
||||||
public interface TwilightListener {
|
public interface TwilightListener {
|
||||||
void onTwilightStateChanged();
|
/**
|
||||||
|
* Called when the twilight state has changed.
|
||||||
|
*/
|
||||||
|
void onTwilightStateChanged(@Nullable TwilightState state);
|
||||||
}
|
}
|
||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,10 +16,30 @@
|
|||||||
|
|
||||||
package com.android.server.twilight;
|
package com.android.server.twilight;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides sunrise/sunset information based on the device's current location.
|
||||||
|
*/
|
||||||
public interface TwilightManager {
|
public interface TwilightManager {
|
||||||
void registerListener(TwilightListener listener, Handler handler);
|
/**
|
||||||
void unregisterListener(TwilightListener listener);
|
* Register a listener to be notified whenever the twilight state changes.
|
||||||
TwilightState getCurrentState();
|
*
|
||||||
|
* @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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,31 +16,27 @@
|
|||||||
|
|
||||||
package com.android.server.twilight;
|
package com.android.server.twilight;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
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.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.SystemClock;
|
import android.util.ArrayMap;
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.text.format.Time;
|
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.server.SystemService;
|
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;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,445 +45,211 @@ import java.util.Objects;
|
|||||||
* Used by the UI mode manager and other components to adjust night mode
|
* Used by the UI mode manager and other components to adjust night mode
|
||||||
* effects based on sunrise and sunset.
|
* 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 String TAG = "TwilightService";
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
private static final String ACTION_UPDATE_TWILIGHT_STATE =
|
private static final int MSG_START_LISTENING = 1;
|
||||||
"com.android.server.action.UPDATE_TWILIGHT_STATE";
|
private static final int MSG_STOP_LISTENING = 2;
|
||||||
|
|
||||||
/**
|
@GuardedBy("mListeners")
|
||||||
* The amount of time after or before sunrise over which to start adjusting twilight affected
|
private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>();
|
||||||
* 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;
|
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Handler mHandler;
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
|
||||||
private final List<TwilightListenerRecord> mListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
private AlarmManager mAlarmManager;
|
private AlarmManager mAlarmManager;
|
||||||
private LocationManager mLocationManager;
|
private LocationManager mLocationManager;
|
||||||
private LocationHandler mLocationHandler;
|
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
private boolean mBootCompleted;
|
||||||
private TwilightState mTwilightState;
|
private boolean mHasListeners;
|
||||||
|
|
||||||
|
private BroadcastReceiver mTimeChangedReceiver;
|
||||||
|
private Location mLastLocation;
|
||||||
|
|
||||||
|
@GuardedBy("mListeners")
|
||||||
|
private TwilightState mLastTwilightState;
|
||||||
|
|
||||||
public TwilightService(Context context) {
|
public TwilightService(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
mHandler = new Handler(Looper.getMainLooper(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
|
publishLocalService(TwilightManager.class, new TwilightManager() {
|
||||||
mLocationManager = (LocationManager) getContext().getSystemService(
|
@Override
|
||||||
Context.LOCATION_SERVICE);
|
public void registerListener(@NonNull TwilightListener listener,
|
||||||
mLocationHandler = new LocationHandler();
|
@NonNull Handler handler) {
|
||||||
|
synchronized (mListeners) {
|
||||||
|
final boolean wasEmpty = mListeners.isEmpty();
|
||||||
|
mListeners.put(listener, handler);
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
if (wasEmpty && !mListeners.isEmpty()) {
|
||||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
mHandler.sendEmptyMessage(MSG_START_LISTENING);
|
||||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
}
|
||||||
filter.addAction(Intent.ACTION_USER_SWITCHED);
|
}
|
||||||
getContext().registerReceiver(mReceiver, filter);
|
}
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public void onBootPhase(int phase) {
|
public void onBootPhase(int phase) {
|
||||||
if (phase == PHASE_BOOT_COMPLETED) {
|
if (phase == PHASE_BOOT_COMPLETED) {
|
||||||
// Initialize the current twilight state.
|
final Context c = getContext();
|
||||||
mLocationHandler.requestTwilightUpdate();
|
mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
|
||||||
}
|
mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
|
||||||
}
|
|
||||||
|
|
||||||
private void setTwilightState(TwilightState state) {
|
mBootCompleted = true;
|
||||||
synchronized (mLock) {
|
if (mHasListeners) {
|
||||||
if (!Objects.equals(mTwilightState, state)) {
|
startListening();
|
||||||
if (DEBUG) {
|
|
||||||
Slog.d(TAG, "Twilight state changed: " + state);
|
|
||||||
}
|
|
||||||
|
|
||||||
mTwilightState = state;
|
|
||||||
|
|
||||||
for (TwilightListenerRecord mListener : mListeners) {
|
|
||||||
mListener.postUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TwilightListenerRecord implements Runnable {
|
|
||||||
|
|
||||||
private final TwilightListener mListener;
|
|
||||||
private final Handler mHandler;
|
|
||||||
|
|
||||||
public TwilightListenerRecord(TwilightListener listener, Handler handler) {
|
|
||||||
mListener = listener;
|
|
||||||
mHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void postUpdate() {
|
|
||||||
mHandler.post(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mListener.onTwilightStateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TwilightManager mService = new TwilightManager() {
|
|
||||||
@Override
|
|
||||||
public TwilightState getCurrentState() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
return mTwilightState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerListener(TwilightListener listener, Handler handler) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mListeners.add(new TwilightListenerRecord(listener, handler));
|
|
||||||
|
|
||||||
if (mListeners.size() == 1) {
|
|
||||||
mLocationHandler.enableLocationUpdates();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unregisterListener(TwilightListener listener) {
|
public boolean handleMessage(Message msg) {
|
||||||
synchronized (mLock) {
|
switch (msg.what) {
|
||||||
for (int i = 0; i < mListeners.size(); i++) {
|
case MSG_START_LISTENING:
|
||||||
if (mListeners.get(i).mListener == listener) {
|
if (!mHasListeners) {
|
||||||
mListeners.remove(i);
|
mHasListeners = true;
|
||||||
|
if (mBootCompleted) {
|
||||||
|
startListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MSG_STOP_LISTENING:
|
||||||
|
if (mHasListeners) {
|
||||||
|
mHasListeners = false;
|
||||||
|
if (mBootCompleted) {
|
||||||
|
stopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mListeners.size() == 0) {
|
// Update whenever the system clock is changed.
|
||||||
mLocationHandler.disableLocationUpdates();
|
if (mTimeChangedReceiver == null) {
|
||||||
}
|
mTimeChangedReceiver = new BroadcastReceiver() {
|
||||||
}
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (DEBUG) Slog.d(TAG, "onReceive: " + intent);
|
||||||
|
updateTwilightState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The user has moved if the accuracy circles of the two locations don't overlap.
|
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
|
||||||
private static boolean hasMoved(Location from, Location to) {
|
intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
||||||
if (to == null) {
|
getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from == null) {
|
// Force an update now that we have listeners registered.
|
||||||
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:
|
|
||||||
updateTwilightState();
|
updateTwilightState();
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveLocation() {
|
private void stopListening() {
|
||||||
Location location = null;
|
if (DEBUG) Slog.d(TAG, "stopListening");
|
||||||
final Iterator<String> providers =
|
|
||||||
mLocationManager.getProviders(new Criteria(), true).iterator();
|
if (mTimeChangedReceiver != null) {
|
||||||
while (providers.hasNext()) {
|
getContext().unregisterReceiver(mTimeChangedReceiver);
|
||||||
final Location lastKnownLocation =
|
mTimeChangedReceiver = null;
|
||||||
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
|
if (mLastTwilightState != null) {
|
||||||
// is not available yet), the longitude of the location is estimated using the
|
mAlarmManager.cancel(this);
|
||||||
// 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);
|
mLocationManager.removeUpdates(this);
|
||||||
}
|
mLastLocation = null;
|
||||||
|
|
||||||
private void setLocation(Location location) {
|
|
||||||
mLocation = location;
|
|
||||||
updateTwilightState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTwilightState() {
|
private void updateTwilightState() {
|
||||||
if (mLocation == null) {
|
// Calculate the twilight state based on the current time and location.
|
||||||
setTwilightState(null);
|
final long currentTimeMillis = System.currentTimeMillis();
|
||||||
return;
|
final Location location = mLastLocation != null ? mLastLocation
|
||||||
}
|
: mLocationManager.getLastLocation();
|
||||||
|
final TwilightState state = calculateTwilightState(location, currentTimeMillis);
|
||||||
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) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Updating twilight state: " + state);
|
Slog.d(TAG, "updateTwilightState: " + 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) {
|
// Notify listeners if the state has changed.
|
||||||
Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms");
|
synchronized (mListeners) {
|
||||||
}
|
if (!Objects.equals(mLastTwilightState, state)) {
|
||||||
|
mLastTwilightState = state;
|
||||||
|
|
||||||
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
for (int i = mListeners.size() - 1; i >= 0; --i) {
|
||||||
getContext(), 0, new Intent(ACTION_UPDATE_TWILIGHT_STATE), 0);
|
final TwilightListener listener = mListeners.keyAt(i);
|
||||||
mAlarmManager.cancel(pendingIntent);
|
final Handler handler = mListeners.valueAt(i);
|
||||||
mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
|
handler.post(new Runnable() {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void run() {
|
||||||
if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())
|
listener.onTwilightStateChanged(state);
|
||||||
&& !intent.getBooleanExtra("state", false)) {
|
}
|
||||||
// Airplane mode is now off!
|
});
|
||||||
mLocationHandler.requestLocationUpdate();
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Time zone has changed or alarm expired.
|
|
||||||
mLocationHandler.requestTwilightUpdate();
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// A LocationListener to initialize the network location provider. The location updates
|
// Schedule an alarm to update the state at the next sunrise or sunset.
|
||||||
// are handled through the passive location provider.
|
if (state != null) {
|
||||||
private final LocationListener mEmptyLocationListener = new LocationListener() {
|
final long triggerAtMillis = state.isNight()
|
||||||
|
? state.sunriseTimeMillis() : state.sunsetTimeMillis();
|
||||||
|
mAlarmManager.setExact(AlarmManager.RTC, triggerAtMillis, TAG, this, mHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAlarm() {
|
||||||
|
if (DEBUG) Slog.d(TAG, "onAlarm");
|
||||||
|
updateTwilightState();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
|
if (DEBUG) Slog.d(TAG, "onLocationChanged: " + location);
|
||||||
|
mLastLocation = location;
|
||||||
|
updateTwilightState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -501,24 +263,43 @@ public final class TwilightService extends SystemService {
|
|||||||
@Override
|
@Override
|
||||||
public void onProviderDisabled(String provider) {
|
public void onProviderDisabled(String provider) {
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private final LocationListener mLocationListener = new LocationListener() {
|
/**
|
||||||
@Override
|
* Calculates the twilight state for a specific location and time.
|
||||||
public void onLocationChanged(Location location) {
|
*
|
||||||
mLocationHandler.processNewLocation(location);
|
* @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
|
final CalendarAstronomer ca = new CalendarAstronomer(
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
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
|
return new TwilightState(sunriseTimeMillis, sunsetTimeMillis);
|
||||||
public void onProviderEnabled(String provider) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderDisabled(String provider) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,36 +16,66 @@
|
|||||||
|
|
||||||
package com.android.server.twilight;
|
package com.android.server.twilight;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import java.util.Date;
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes whether it is day or night.
|
* The twilight state, consisting of the sunrise and sunset times (in millis) for the current
|
||||||
* This object is immutable.
|
* period.
|
||||||
|
* <p/>
|
||||||
|
* Note: This object is immutable.
|
||||||
*/
|
*/
|
||||||
public class TwilightState {
|
public final class TwilightState {
|
||||||
|
|
||||||
private final boolean mIsNight;
|
private final long mSunriseTimeMillis;
|
||||||
private final float mAmount;
|
private final long mSunsetTimeMillis;
|
||||||
|
|
||||||
TwilightState(boolean isNight, float amount) {
|
TwilightState(long sunriseTimeMillis, long sunsetTimeMillis) {
|
||||||
mIsNight = isNight;
|
mSunriseTimeMillis = sunriseTimeMillis;
|
||||||
mAmount = amount;
|
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() {
|
public boolean isNight() {
|
||||||
return mIsNight;
|
final long now = System.currentTimeMillis();
|
||||||
}
|
return now >= mSunsetTimeMillis && now < mSunriseTimeMillis;
|
||||||
|
|
||||||
/**
|
|
||||||
* For twilight affects that change gradually over time, this is the amount they
|
|
||||||
* should currently be in effect.
|
|
||||||
*/
|
|
||||||
public float getAmount() {
|
|
||||||
return mAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,20 +85,20 @@ public class TwilightState {
|
|||||||
|
|
||||||
public boolean equals(TwilightState other) {
|
public boolean equals(TwilightState other) {
|
||||||
return other != null
|
return other != null
|
||||||
&& mIsNight == other.mIsNight
|
&& mSunriseTimeMillis == other.mSunriseTimeMillis
|
||||||
&& mAmount == other.mAmount;
|
&& mSunsetTimeMillis == other.mSunsetTimeMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return 0; // don't care
|
return Long.hashCode(mSunriseTimeMillis) ^ Long.hashCode(mSunsetTimeMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
DateFormat f = DateFormat.getDateTimeInstance();
|
return "TwilightState {"
|
||||||
return "{TwilightState: isNight=" + mIsNight
|
+ " sunrise=" + DateFormat.format("MM-dd HH:mm", mSunriseTimeMillis)
|
||||||
+ ", mAmount=" + mAmount
|
+ " sunset="+ DateFormat.format("MM-dd HH:mm", mSunsetTimeMillis)
|
||||||
+ " }";
|
+ " }";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user