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 19f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banesimport android.Manifest; 2015ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.content.Context; 2115ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.Location; 2215ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.LocationManager; 2315ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.support.annotation.NonNull; 24f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banesimport android.support.v4.content.PermissionChecker; 2515ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.text.format.DateUtils; 2615ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.util.Log; 2715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 2815ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport java.util.Calendar; 2915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes/** 3115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Class which managing whether we are in the night or not. 3215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 3315ad53853d367f5d593bb019d88fb613878fd8fcChris Banesclass TwilightManager { 3415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final String TAG = "TwilightManager"; 3615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNRISE = 6; // 6am 3815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNSET = 22; // 10pm 3915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final TwilightState sTwilightState = new TwilightState(); 4115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final Context mContext; 4315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final LocationManager mLocationManager; 4415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes TwilightManager(Context context) { 4615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes mContext = context; 4715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 4815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 4915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 5015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 5115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Returns true we are currently in the 'night'. 5215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 5315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * @return true if we are at night, false if the day. 5415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 5515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight() { 5615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightState state = sTwilightState; 5715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 5815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (isStateValid(state)) { 5915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If the current twilight state is still valid, use it 6015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 6115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 6215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 6315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, we will try and grab the last known location 6415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final Location location = getLastKnownLocation(); 6515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (location != null) { 6615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes updateState(location); 6715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 6815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 6915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 7015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Log.i(TAG, "Could not get last known location. This is probably because the app does not" 7115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " have any location permissions. Falling back to hardcoded" 7215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " sunrise/sunset values."); 7315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 7415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we don't have a location, we'll use our hardcoded sunrise/sunset values. 7515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // These aren't great, but it's better than nothing. 7615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Calendar calendar = Calendar.getInstance(); 7715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final int hour = calendar.get(Calendar.HOUR_OF_DAY); 7815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return hour < SUNRISE || hour >= SUNSET; 7915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 8015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 8115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocation() { 820ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Location coarseLoc = null; 830ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Location fineLoc = null; 84f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes 85f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes int permission = PermissionChecker.checkSelfPermission(mContext, 860ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Manifest.permission.ACCESS_COARSE_LOCATION); 87f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes if (permission == PermissionChecker.PERMISSION_GRANTED) { 880ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes coarseLoc = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER); 89f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes } 90f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes 91f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes permission = PermissionChecker.checkSelfPermission(mContext, 920ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Manifest.permission.ACCESS_FINE_LOCATION); 93f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes if (permission == PermissionChecker.PERMISSION_GRANTED) { 940ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes fineLoc = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER); 95f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes } 9615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 970ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes if (fineLoc != null && coarseLoc != null) { 9815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we have both a fine and coarse location, use the latest 990ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes return fineLoc.getTime() > coarseLoc.getTime() ? fineLoc : coarseLoc; 10015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 10115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, return the non-null one (if there is one) 1020ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes return fineLoc != null ? fineLoc : coarseLoc; 10315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 10615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocationForProvider(String provider) { 10715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (mLocationManager != null) { 10815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes try { 10915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (mLocationManager.isProviderEnabled(provider)) { 11015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return mLocationManager.getLastKnownLocation(provider); 11115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 11215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } catch (Exception e) { 11315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Log.d(TAG, "Failed to get last known location", e); 11415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 11515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 11615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return null; 11715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 11815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 11915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private boolean isStateValid(TwilightState state) { 12015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state != null && state.nextUpdate > System.currentTimeMillis(); 12115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 12215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 12315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private void updateState(@NonNull Location location) { 12415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightState state = sTwilightState; 12515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long now = System.currentTimeMillis(); 12615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightCalculator calculator = TwilightCalculator.getInstance(); 12715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 12815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate yesterday's twilight 12915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, 13015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 13115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long yesterdaySunset = calculator.sunset; 13215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 13315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate today's twilight 13415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude()); 13515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final boolean isNight = (calculator.state == TwilightCalculator.NIGHT); 13615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunrise = calculator.sunrise; 13715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunset = calculator.sunset; 13815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 13915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate tomorrow's twilight 14015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, 14115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 14215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long tomorrowSunrise = calculator.sunrise; 14315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 14415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Set next update 14515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate = 0; 14615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (todaySunrise == -1 || todaySunset == -1) { 14715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // In the case the day or night never ends the update is scheduled 12 hours later. 14815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; 14915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 15015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (now > todaySunset) { 15115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += tomorrowSunrise; 15215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else if (now > todaySunrise) { 15315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunset; 15415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 15515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunrise; 15615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 15715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // add some extra time to be on the safe side. 15815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += DateUtils.MINUTE_IN_MILLIS; 15915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 16015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 16115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Update the twilight state 16215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.isNight = isNight; 16315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.yesterdaySunset = yesterdaySunset; 16415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunrise = todaySunrise; 16515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunset = todaySunset; 16615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.tomorrowSunrise = tomorrowSunrise; 16715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.nextUpdate = nextUpdate; 16815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 16915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 17015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 17115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Describes whether it is day or night. 17215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 17315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static class TwilightState { 17415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight; 17515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long yesterdaySunset; 17615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunrise; 17715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunset; 17815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long tomorrowSunrise; 17915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate; 18015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 18115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes} 182