1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.Manifest; 20import android.content.Context; 21import android.location.Location; 22import android.location.LocationManager; 23import android.support.annotation.NonNull; 24import android.support.v4.content.PermissionChecker; 25import android.text.format.DateUtils; 26import android.util.Log; 27 28import java.util.Calendar; 29 30/** 31 * Class which managing whether we are in the night or not. 32 */ 33class TwilightManager { 34 35 private static final String TAG = "TwilightManager"; 36 37 private static final int SUNRISE = 6; // 6am 38 private static final int SUNSET = 22; // 10pm 39 40 private static final TwilightState sTwilightState = new TwilightState(); 41 42 private final Context mContext; 43 private final LocationManager mLocationManager; 44 45 TwilightManager(Context context) { 46 mContext = context; 47 mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 48 } 49 50 /** 51 * Returns true we are currently in the 'night'. 52 * 53 * @return true if we are at night, false if the day. 54 */ 55 boolean isNight() { 56 final TwilightState state = sTwilightState; 57 58 if (isStateValid(state)) { 59 // If the current twilight state is still valid, use it 60 return state.isNight; 61 } 62 63 // Else, we will try and grab the last known location 64 final Location location = getLastKnownLocation(); 65 if (location != null) { 66 updateState(location); 67 return state.isNight; 68 } 69 70 Log.i(TAG, "Could not get last known location. This is probably because the app does not" 71 + " have any location permissions. Falling back to hardcoded" 72 + " sunrise/sunset values."); 73 74 // If we don't have a location, we'll use our hardcoded sunrise/sunset values. 75 // These aren't great, but it's better than nothing. 76 Calendar calendar = Calendar.getInstance(); 77 final int hour = calendar.get(Calendar.HOUR_OF_DAY); 78 return hour < SUNRISE || hour >= SUNSET; 79 } 80 81 private Location getLastKnownLocation() { 82 Location coarseLoc = null; 83 Location fineLoc = null; 84 85 int permission = PermissionChecker.checkSelfPermission(mContext, 86 Manifest.permission.ACCESS_COARSE_LOCATION); 87 if (permission == PermissionChecker.PERMISSION_GRANTED) { 88 coarseLoc = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER); 89 } 90 91 permission = PermissionChecker.checkSelfPermission(mContext, 92 Manifest.permission.ACCESS_FINE_LOCATION); 93 if (permission == PermissionChecker.PERMISSION_GRANTED) { 94 fineLoc = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER); 95 } 96 97 if (fineLoc != null && coarseLoc != null) { 98 // If we have both a fine and coarse location, use the latest 99 return fineLoc.getTime() > coarseLoc.getTime() ? fineLoc : coarseLoc; 100 } else { 101 // Else, return the non-null one (if there is one) 102 return fineLoc != null ? fineLoc : coarseLoc; 103 } 104 } 105 106 private Location getLastKnownLocationForProvider(String provider) { 107 if (mLocationManager != null) { 108 try { 109 if (mLocationManager.isProviderEnabled(provider)) { 110 return mLocationManager.getLastKnownLocation(provider); 111 } 112 } catch (Exception e) { 113 Log.d(TAG, "Failed to get last known location", e); 114 } 115 } 116 return null; 117 } 118 119 private boolean isStateValid(TwilightState state) { 120 return state != null && state.nextUpdate > System.currentTimeMillis(); 121 } 122 123 private void updateState(@NonNull Location location) { 124 final TwilightState state = sTwilightState; 125 final long now = System.currentTimeMillis(); 126 final TwilightCalculator calculator = TwilightCalculator.getInstance(); 127 128 // calculate yesterday's twilight 129 calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, 130 location.getLatitude(), location.getLongitude()); 131 final long yesterdaySunset = calculator.sunset; 132 133 // calculate today's twilight 134 calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude()); 135 final boolean isNight = (calculator.state == TwilightCalculator.NIGHT); 136 final long todaySunrise = calculator.sunrise; 137 final long todaySunset = calculator.sunset; 138 139 // calculate tomorrow's twilight 140 calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, 141 location.getLatitude(), location.getLongitude()); 142 final long tomorrowSunrise = calculator.sunrise; 143 144 // Set next update 145 long nextUpdate = 0; 146 if (todaySunrise == -1 || todaySunset == -1) { 147 // In the case the day or night never ends the update is scheduled 12 hours later. 148 nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; 149 } else { 150 if (now > todaySunset) { 151 nextUpdate += tomorrowSunrise; 152 } else if (now > todaySunrise) { 153 nextUpdate += todaySunset; 154 } else { 155 nextUpdate += todaySunrise; 156 } 157 // add some extra time to be on the safe side. 158 nextUpdate += DateUtils.MINUTE_IN_MILLIS; 159 } 160 161 // Update the twilight state 162 state.isNight = isNight; 163 state.yesterdaySunset = yesterdaySunset; 164 state.todaySunrise = todaySunrise; 165 state.todaySunset = todaySunset; 166 state.tomorrowSunrise = tomorrowSunrise; 167 state.nextUpdate = nextUpdate; 168 } 169 170 /** 171 * Describes whether it is day or night. 172 */ 173 private static class TwilightState { 174 boolean isNight; 175 long yesterdaySunset; 176 long todaySunrise; 177 long todaySunset; 178 long tomorrowSunrise; 179 long nextUpdate; 180 } 181} 182