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 1950b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikasimport static android.Manifest.permission.ACCESS_COARSE_LOCATION; 2050b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikasimport static android.Manifest.permission.ACCESS_FINE_LOCATION; 2150b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas 22f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banesimport android.Manifest; 2350b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikasimport android.annotation.SuppressLint; 2415ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.content.Context; 2515ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.Location; 2615ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.location.LocationManager; 2715ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.support.annotation.NonNull; 2850b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikasimport android.support.annotation.RequiresPermission; 2912acd8f6d6926aaa5cc16543386e0aeee64716edChris Banesimport android.support.annotation.VisibleForTesting; 30f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banesimport android.support.v4.content.PermissionChecker; 3115ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.text.format.DateUtils; 3215ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport android.util.Log; 3315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3415ad53853d367f5d593bb019d88fb613878fd8fcChris Banesimport java.util.Calendar; 3515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 3615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes/** 3715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Class which managing whether we are in the night or not. 3815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 3915ad53853d367f5d593bb019d88fb613878fd8fcChris Banesclass TwilightManager { 4015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final String TAG = "TwilightManager"; 4215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNRISE = 6; // 6am 4415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static final int SUNSET = 22; // 10pm 4515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 4612acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes private static TwilightManager sInstance; 4712acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes 4812acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes static TwilightManager getInstance(@NonNull Context context) { 4912acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes if (sInstance == null) { 5012acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes context = context.getApplicationContext(); 5112acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes sInstance = new TwilightManager(context, 5212acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes (LocationManager) context.getSystemService(Context.LOCATION_SERVICE)); 5312acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes } 5412acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes return sInstance; 5512acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes } 5612acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes 5712acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes @VisibleForTesting 5812acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes static void setInstance(TwilightManager twilightManager) { 5912acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes sInstance = twilightManager; 6012acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes } 6115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 6215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final Context mContext; 6315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private final LocationManager mLocationManager; 6415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 6512acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes private final TwilightState mTwilightState = new TwilightState(); 6612acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes 6712acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes @VisibleForTesting 6812acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes TwilightManager(@NonNull Context context, @NonNull LocationManager locationManager) { 6915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes mContext = context; 7012acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes mLocationManager = locationManager; 7115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 7215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 7315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 7415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Returns true we are currently in the 'night'. 7515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * 7615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * @return true if we are at night, false if the day. 7715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 7815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight() { 7912acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes final TwilightState state = mTwilightState; 8015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 8112acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes if (isStateValid()) { 8215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If the current twilight state is still valid, use it 8315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 8415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 8515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 8615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, we will try and grab the last known location 8715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final Location location = getLastKnownLocation(); 8815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (location != null) { 8915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes updateState(location); 9015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return state.isNight; 9115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 9215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 9315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Log.i(TAG, "Could not get last known location. This is probably because the app does not" 9415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " have any location permissions. Falling back to hardcoded" 9515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes + " sunrise/sunset values."); 9615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 9715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we don't have a location, we'll use our hardcoded sunrise/sunset values. 9815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // These aren't great, but it's better than nothing. 9915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes Calendar calendar = Calendar.getInstance(); 10015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final int hour = calendar.get(Calendar.HOUR_OF_DAY); 10115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return hour < SUNRISE || hour >= SUNSET; 10215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 10315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 10450b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas @SuppressLint("MissingPermission") // permissions are checked for the needed call. 10515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocation() { 1060ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Location coarseLoc = null; 1070ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Location fineLoc = null; 108f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes 109f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes int permission = PermissionChecker.checkSelfPermission(mContext, 1100ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Manifest.permission.ACCESS_COARSE_LOCATION); 111f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes if (permission == PermissionChecker.PERMISSION_GRANTED) { 1120ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes coarseLoc = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER); 113f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes } 114f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes 115f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes permission = PermissionChecker.checkSelfPermission(mContext, 1160ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes Manifest.permission.ACCESS_FINE_LOCATION); 117f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes if (permission == PermissionChecker.PERMISSION_GRANTED) { 1180ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes fineLoc = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER); 119f0ec407dc5fdb420550ed29f9556909bd9f635a9Chris Banes } 12015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 1210ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes if (fineLoc != null && coarseLoc != null) { 12215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // If we have both a fine and coarse location, use the latest 1230ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes return fineLoc.getTime() > coarseLoc.getTime() ? fineLoc : coarseLoc; 12415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 12515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Else, return the non-null one (if there is one) 1260ceb9e98ba7ba67346e0dab85384560a23740fd0Chris Banes return fineLoc != null ? fineLoc : coarseLoc; 12715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 12815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 12915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 13050b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) 13115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private Location getLastKnownLocationForProvider(String provider) { 13250b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas try { 13350b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas if (mLocationManager.isProviderEnabled(provider)) { 13450b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas return mLocationManager.getLastKnownLocation(provider); 13515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 13650b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas } catch (Exception e) { 13750b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas Log.d(TAG, "Failed to get last known location", e); 13815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 13915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes return null; 14015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 14115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 14212acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes private boolean isStateValid() { 14350b45b25e10ddbfd726b91910e00bce6a1c63904Aurimas Liutikas return mTwilightState.nextUpdate > System.currentTimeMillis(); 14415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 14515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 14615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private void updateState(@NonNull Location location) { 14712acd8f6d6926aaa5cc16543386e0aeee64716edChris Banes final TwilightState state = mTwilightState; 14815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long now = System.currentTimeMillis(); 14915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final TwilightCalculator calculator = TwilightCalculator.getInstance(); 15015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 15115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate yesterday's twilight 15215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, 15315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 15415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long yesterdaySunset = calculator.sunset; 15515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 15615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate today's twilight 15715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude()); 15815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final boolean isNight = (calculator.state == TwilightCalculator.NIGHT); 15915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunrise = calculator.sunrise; 16015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long todaySunset = calculator.sunset; 16115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 16215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // calculate tomorrow's twilight 16315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, 16415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes location.getLatitude(), location.getLongitude()); 16515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes final long tomorrowSunrise = calculator.sunrise; 16615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 16715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Set next update 16815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate = 0; 16915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (todaySunrise == -1 || todaySunset == -1) { 17015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // In the case the day or night never ends the update is scheduled 12 hours later. 17115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; 17215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 17315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes if (now > todaySunset) { 17415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += tomorrowSunrise; 17515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else if (now > todaySunrise) { 17615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunset; 17715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } else { 17815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += todaySunrise; 17915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 18015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // add some extra time to be on the safe side. 18115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes nextUpdate += DateUtils.MINUTE_IN_MILLIS; 18215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 18315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 18415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes // Update the twilight state 18515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.isNight = isNight; 18615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.yesterdaySunset = yesterdaySunset; 18715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunrise = todaySunrise; 18815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.todaySunset = todaySunset; 18915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.tomorrowSunrise = tomorrowSunrise; 19015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes state.nextUpdate = nextUpdate; 19115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 19215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes 19315ad53853d367f5d593bb019d88fb613878fd8fcChris Banes /** 19415ad53853d367f5d593bb019d88fb613878fd8fcChris Banes * Describes whether it is day or night. 19515ad53853d367f5d593bb019d88fb613878fd8fcChris Banes */ 19615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes private static class TwilightState { 19715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes boolean isNight; 19815ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long yesterdaySunset; 19915ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunrise; 20015ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long todaySunset; 20115ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long tomorrowSunrise; 20215ad53853d367f5d593bb019d88fb613878fd8fcChris Banes long nextUpdate; 2032c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas 2042c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas TwilightState() { 2052c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas } 20615ad53853d367f5d593bb019d88fb613878fd8fcChris Banes } 20715ad53853d367f5d593bb019d88fb613878fd8fcChris Banes} 208