TwilightManager.java revision 15ad53853d367f5d593bb019d88fb613878fd8fc
115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes/* 215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Copyright (C) 2015 The Android Open Source Project 315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * you may not use this file except in compliance with the License. 615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * You may obtain a copy of the License at 715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * http://www.apache.org/licenses/LICENSE-2.0 915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 1015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Unless required by applicable law or agreed to in writing, software 1115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 1215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * See the License for the specific language governing permissions and 1415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * limitations under the License. 1515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 1615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 1715ad53853d367f5d593bb019d88fb613878fd8fcChris Banespackage android.support.v7.app; 1815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 1915ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.content.Context; 2015ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.Location; 2115ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.LocationManager; 2215ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.support.annotation.NonNull; 2315ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.text.format.DateUtils; 2415ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.util.Log; 2515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 2615ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport java.util.Calendar; 2715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 2815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes/** 2915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Class which managing whether we are in the night or not. 3015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 3115ad53853d367f5d593bb019d88fb613878fd8fcChris Banesclass TwilightManager { 3215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final String TAG = "TwilightManager"; 3415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNRISE = 6; // 6am 3615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNSET = 22; // 10pm 3715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final TwilightState sTwilightState = new TwilightState(); 3915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final Context mContext; 4115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final LocationManager mLocationManager; 4215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes TwilightManager(Context context) { 4415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes mContext = context; 4515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 4615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 4715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 4915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Returns true we are currently in the 'night'. 5015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 5115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * @return true if we are at night, false if the day. 5215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 5315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight() { 5415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightState state = sTwilightState; 5515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 5615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (isStateValid(state)) { 5715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If the current twilight state is still valid, use it 5815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 5915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 6015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 6115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, we will try and grab the last known location 6215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final Location location = getLastKnownLocation(); 6315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (location != null) { 6415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes updateState(location); 6515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 6615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 6715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 6815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Log.i(TAG, "Could not get last known location. This is probably because the app does not" 6915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " have any location permissions. Falling back to hardcoded" 7015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " sunrise/sunset values."); 7115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 7215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we don't have a location, we'll use our hardcoded sunrise/sunset values. 7315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // These aren't great, but it's better than nothing. 7415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Calendar calendar = Calendar.getInstance(); 7515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final int hour = calendar.get(Calendar.HOUR_OF_DAY); 7615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return hour < SUNRISE || hour >= SUNSET; 7715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 7815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 7915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocation() { 8015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Location coarseLocation = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER); 8115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Location fineLocation = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER); 8215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 8315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (coarseLocation != null && fineLocation != null) { 8415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we have both a fine and coarse location, use the latest 8515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (fineLocation.getTime() > coarseLocation.getTime()) { 8615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return fineLocation; 8715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 8815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return coarseLocation; 8915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 9015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 9115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, return the non-null one (if there is one) 9215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return fineLocation != null ? fineLocation : coarseLocation; 9315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 9415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 9515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 9615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocationForProvider(String provider) { 9715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (mLocationManager != null) { 9815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes try { 9915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (mLocationManager.isProviderEnabled(provider)) { 10015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return mLocationManager.getLastKnownLocation(provider); 10115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } catch (Exception e) { 10315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Log.d(TAG, "Failed to get last known location", e); 10415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return null; 10715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 10915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private boolean isStateValid(TwilightState state) { 11015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state != null && state.nextUpdate > System.currentTimeMillis(); 11115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 11215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 11315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private void updateState(@NonNull Location location) { 11415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightState state = sTwilightState; 11515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long now = System.currentTimeMillis(); 11615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightCalculator calculator = TwilightCalculator.getInstance(); 11715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 11815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate yesterday's twilight 11915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, 12015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 12115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long yesterdaySunset = calculator.sunset; 12215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 12315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate today's twilight 12415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude()); 12515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final boolean isNight = (calculator.state == TwilightCalculator.NIGHT); 12615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunrise = calculator.sunrise; 12715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunset = calculator.sunset; 12815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 12915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate tomorrow's twilight 13015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, 13115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 13215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long tomorrowSunrise = calculator.sunrise; 13315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 13415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Set next update 13515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate = 0; 13615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (todaySunrise == -1 || todaySunset == -1) { 13715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // In the case the day or night never ends the update is scheduled 12 hours later. 13815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; 13915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 14015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (now > todaySunset) { 14115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += tomorrowSunrise; 14215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else if (now > todaySunrise) { 14315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunset; 14415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 14515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunrise; 14615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 14715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // add some extra time to be on the safe side. 14815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += DateUtils.MINUTE_IN_MILLIS; 14915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 15015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 15115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Update the twilight state 15215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.isNight = isNight; 15315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.yesterdaySunset = yesterdaySunset; 15415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunrise = todaySunrise; 15515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunset = todaySunset; 15615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.tomorrowSunrise = tomorrowSunrise; 15715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.nextUpdate = nextUpdate; 15815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 15915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 16015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 16115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Describes whether it is day or night. 16215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 16315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static class TwilightState { 16415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight; 16515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long yesterdaySunset; 16615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunrise; 16715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunset; 16815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long tomorrowSunrise; 16915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate; 17015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 17115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes} 172