diff options
8 files changed, 345 insertions, 606 deletions
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 5839b1674d63..000000000000 --- 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 6f713cdfebbe..bb5f62bd4abf 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 @@ final class UiModeManagerService extends SystemService { 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 @@ final class UiModeManagerService extends SystemService { 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 @@ final class UiModeManagerService extends SystemService { 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 @@ final class UiModeManagerService extends SystemService { } 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 @@ final class UiModeManagerService extends SystemService { } } - 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 c16dac23d4c7..15ae846186fa 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.TwilightListener; 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 @@ class AutomaticBrightnessController { 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 @@ class AutomaticBrightnessController { } 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 @@ class AutomaticBrightnessController { 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 39498a62fdcd..d9af7cb913ee 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.Animator; 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 @@ public final class NightDisplayService extends SystemService 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 @@ public final class NightDisplayService extends SystemService @Override public void onActivated(boolean activated) { - mLastActivatedTime = Calendar.getInstance(); + if (mIsActivated != null) { + mLastActivatedTime = Calendar.getInstance(); + } updateNextAlarm(); } @@ -424,22 +427,30 @@ public final class NightDisplayService extends SystemService 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 @@ public final class NightDisplayService extends SystemService 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 29ead445892e..58dcef6cbba2 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 56137a406757..5ef941730cb7 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 ee7a4a0d5599..acd65875ffac 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 @@ import java.util.Objects; * 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(); - - 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); - - publishLocalService(TwilightManager.class, mService); - } - - @Override - public void onBootPhase(int phase) { - if (phase == PHASE_BOOT_COMPLETED) { - // Initialize the current twilight state. - mLocationHandler.requestTwilightUpdate(); - } - } - - private void setTwilightState(TwilightState state) { - synchronized (mLock) { - if (!Objects.equals(mTwilightState, state)) { - if (DEBUG) { - Slog.d(TAG, "Twilight state changed: " + state); + 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); + + if (wasEmpty && !mListeners.isEmpty()) { + mHandler.sendEmptyMessage(MSG_START_LISTENING); + } } + } - mTwilightState = state; + @Override + public void unregisterListener(@NonNull TwilightListener listener) { + synchronized (mListeners) { + final boolean wasEmpty = mListeners.isEmpty(); + mListeners.remove(listener); - for (TwilightListenerRecord mListener : mListeners) { - mListener.postUpdate(); + if (!wasEmpty && mListeners.isEmpty()) { + mHandler.sendEmptyMessage(MSG_STOP_LISTENING); + } } } - } - } - - 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 TwilightState getLastTwilightState() { + synchronized (mListeners) { + return mLastTwilightState; + } } - } + }); + } - @Override - public void registerListener(TwilightListener listener, Handler handler) { - synchronized (mLock) { - mListeners.add(new TwilightListenerRecord(listener, handler)); + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_BOOT_COMPLETED) { + final Context c = getContext(); + mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE); + mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE); - if (mListeners.size() == 1) { - mLocationHandler.enableLocationUpdates(); - } + mBootCompleted = true; + if (mHasListeners) { + startListening(); } } + } - @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); + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_LISTENING: + if (!mHasListeners) { + mHasListeners = true; + if (mBootCompleted) { + startListening(); } } - - if (mListeners.size() == 0) { - mLocationHandler.disableLocationUpdates(); + return true; + case MSG_STOP_LISTENING: + if (mHasListeners) { + mHasListeners = false; + if (mBootCompleted) { + stopListening(); + } } - } + return true; } - }; + return false; + } - // 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; + 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 (from == null) { - return true; - } + // 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(); + } + }; - // if new location is older than the current one, the device hasn't moved. - if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) { - return false; + final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED); + intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + getContext().registerReceiver(mTimeChangedReceiver, intentFilter); } - // 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; + // Force an update now that we have listeners registered. + updateTwilightState(); } - 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); - } + private void stopListening() { + if (DEBUG) Slog.d(TAG, "stopListening"); - public void enableLocationUpdates() { - sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); + if (mTimeChangedReceiver != null) { + getContext().unregisterReceiver(mTimeChangedReceiver); + mTimeChangedReceiver = null; } - public void disableLocationUpdates() { - sendEmptyMessage(MSG_DISABLE_LOCATION_UPDATES); + if (mLastTwilightState != null) { + mAlarmManager.cancel(this); } - public void requestLocationUpdate() { - sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); - } + mLocationManager.removeUpdates(this); + mLastLocation = null; + } - public void requestTwilightUpdate() { - sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE); + 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); } - @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; + // 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); } - 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(); - break; - } - } - - 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(); - } - }; - - // 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 onAlarm() { + if (DEBUG) Slog.d(TAG, "onAlarm"); + updateTwilightState(); + } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } + @Override + public void onLocationChanged(Location location) { + if (DEBUG) Slog.d(TAG, "onLocationChanged: " + location); + mLastLocation = location; + updateTwilightState(); + } - @Override - public void onProviderEnabled(String provider) { - } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } - @Override - public void onProviderDisabled(String provider) { - } - }; + @Override + public void onProviderEnabled(String provider) { + } - private final LocationListener mLocationListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - mLocationHandler.processNewLocation(location); - } + @Override + public void onProviderDisabled(String provider) { + } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { + /** + * 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 onProviderEnabled(String provider) { + 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 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 dec053b83948..a12965df11c0 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 boolean isNight() { - return mIsNight; + 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; } /** - * For twilight affects that change gradually over time, this is the amount they - * should currently be in effect. + * Returns the time (in UTC milliseconds from epoch) of the upcoming or previous sunset if + * it's day or night respectively. */ - public float getAmount() { - return mAmount; + 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() { + 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) + + " }"; } } |