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.annotation.VisibleForTesting; 25import android.support.v4.content.PermissionChecker; 26import android.text.format.DateUtils; 27import android.util.Log; 28 29import java.util.Calendar; 30 31/** 32 * Class which managing whether we are in the night or not. 33 */ 34class TwilightManager { 35 36 private static final String TAG = "TwilightManager"; 37 38 private static final int SUNRISE = 6; // 6am 39 private static final int SUNSET = 22; // 10pm 40 41 private static TwilightManager sInstance; 42 43 static TwilightManager getInstance(@NonNull Context context) { 44 if (sInstance == null) { 45 context = context.getApplicationContext(); 46 sInstance = new TwilightManager(context, 47 (LocationManager) context.getSystemService(Context.LOCATION_SERVICE)); 48 } 49 return sInstance; 50 } 51 52 @VisibleForTesting 53 static void setInstance(TwilightManager twilightManager) { 54 sInstance = twilightManager; 55 } 56 57 private final Context mContext; 58 private final LocationManager mLocationManager; 59 60 private final TwilightState mTwilightState = new TwilightState(); 61 62 @VisibleForTesting 63 TwilightManager(@NonNull Context context, @NonNull LocationManager locationManager) { 64 mContext = context; 65 mLocationManager = locationManager; 66 } 67 68 /** 69 * Returns true we are currently in the 'night'. 70 * 71 * @return true if we are at night, false if the day. 72 */ 73 boolean isNight() { 74 final TwilightState state = mTwilightState; 75 76 if (isStateValid()) { 77 // If the current twilight state is still valid, use it 78 return state.isNight; 79 } 80 81 // Else, we will try and grab the last known location 82 final Location location = getLastKnownLocation(); 83 if (location != null) { 84 updateState(location); 85 return state.isNight; 86 } 87 88 Log.i(TAG, "Could not get last known location. This is probably because the app does not" 89 + " have any location permissions. Falling back to hardcoded" 90 + " sunrise/sunset values."); 91 92 // If we don't have a location, we'll use our hardcoded sunrise/sunset values. 93 // These aren't great, but it's better than nothing. 94 Calendar calendar = Calendar.getInstance(); 95 final int hour = calendar.get(Calendar.HOUR_OF_DAY); 96 return hour < SUNRISE || hour >= SUNSET; 97 } 98 99 private Location getLastKnownLocation() { 100 Location coarseLoc = null; 101 Location fineLoc = null; 102 103 int permission = PermissionChecker.checkSelfPermission(mContext, 104 Manifest.permission.ACCESS_COARSE_LOCATION); 105 if (permission == PermissionChecker.PERMISSION_GRANTED) { 106 coarseLoc = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER); 107 } 108 109 permission = PermissionChecker.checkSelfPermission(mContext, 110 Manifest.permission.ACCESS_FINE_LOCATION); 111 if (permission == PermissionChecker.PERMISSION_GRANTED) { 112 fineLoc = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER); 113 } 114 115 if (fineLoc != null && coarseLoc != null) { 116 // If we have both a fine and coarse location, use the latest 117 return fineLoc.getTime() > coarseLoc.getTime() ? fineLoc : coarseLoc; 118 } else { 119 // Else, return the non-null one (if there is one) 120 return fineLoc != null ? fineLoc : coarseLoc; 121 } 122 } 123 124 private Location getLastKnownLocationForProvider(String provider) { 125 if (mLocationManager != null) { 126 try { 127 if (mLocationManager.isProviderEnabled(provider)) { 128 return mLocationManager.getLastKnownLocation(provider); 129 } 130 } catch (Exception e) { 131 Log.d(TAG, "Failed to get last known location", e); 132 } 133 } 134 return null; 135 } 136 137 private boolean isStateValid() { 138 return mTwilightState != null && mTwilightState.nextUpdate > System.currentTimeMillis(); 139 } 140 141 private void updateState(@NonNull Location location) { 142 final TwilightState state = mTwilightState; 143 final long now = System.currentTimeMillis(); 144 final TwilightCalculator calculator = TwilightCalculator.getInstance(); 145 146 // calculate yesterday's twilight 147 calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, 148 location.getLatitude(), location.getLongitude()); 149 final long yesterdaySunset = calculator.sunset; 150 151 // calculate today's twilight 152 calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude()); 153 final boolean isNight = (calculator.state == TwilightCalculator.NIGHT); 154 final long todaySunrise = calculator.sunrise; 155 final long todaySunset = calculator.sunset; 156 157 // calculate tomorrow's twilight 158 calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, 159 location.getLatitude(), location.getLongitude()); 160 final long tomorrowSunrise = calculator.sunrise; 161 162 // Set next update 163 long nextUpdate = 0; 164 if (todaySunrise == -1 || todaySunset == -1) { 165 // In the case the day or night never ends the update is scheduled 12 hours later. 166 nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; 167 } else { 168 if (now > todaySunset) { 169 nextUpdate += tomorrowSunrise; 170 } else if (now > todaySunrise) { 171 nextUpdate += todaySunset; 172 } else { 173 nextUpdate += todaySunrise; 174 } 175 // add some extra time to be on the safe side. 176 nextUpdate += DateUtils.MINUTE_IN_MILLIS; 177 } 178 179 // Update the twilight state 180 state.isNight = isNight; 181 state.yesterdaySunset = yesterdaySunset; 182 state.todaySunrise = todaySunrise; 183 state.todaySunset = todaySunset; 184 state.tomorrowSunrise = tomorrowSunrise; 185 state.nextUpdate = nextUpdate; 186 } 187 188 /** 189 * Describes whether it is day or night. 190 */ 191 private static class TwilightState { 192 boolean isNight; 193 long yesterdaySunset; 194 long todaySunrise; 195 long todaySunset; 196 long tomorrowSunrise; 197 long nextUpdate; 198 199 TwilightState() { 200 } 201 } 202} 203