TwilightManager.java revision f0ec407dc5fdb420550ed29f9556909bd9f635a9
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 coarseLocation = null;
83        Location fineLocation = null;
84
85        int permission = PermissionChecker.checkSelfPermission(mContext,
86                Manifest.permission.ACCESS_FINE_LOCATION);
87        if (permission == PermissionChecker.PERMISSION_GRANTED) {
88            coarseLocation = getLastKnownLocationForProvider(LocationManager.NETWORK_PROVIDER);
89        }
90
91        permission = PermissionChecker.checkSelfPermission(mContext,
92                Manifest.permission.ACCESS_COARSE_LOCATION);
93        if (permission == PermissionChecker.PERMISSION_GRANTED) {
94            fineLocation = getLastKnownLocationForProvider(LocationManager.GPS_PROVIDER);
95        }
96
97        if (coarseLocation != null && fineLocation != null) {
98            // If we have both a fine and coarse location, use the latest
99            if (fineLocation.getTime() > coarseLocation.getTime()) {
100                return fineLocation;
101            } else {
102                return coarseLocation;
103            }
104        } else {
105            // Else, return the non-null one (if there is one)
106            return fineLocation != null ? fineLocation : coarseLocation;
107        }
108    }
109
110    private Location getLastKnownLocationForProvider(String provider) {
111        if (mLocationManager != null) {
112            try {
113                if (mLocationManager.isProviderEnabled(provider)) {
114                    return mLocationManager.getLastKnownLocation(provider);
115                }
116            } catch (Exception e) {
117                Log.d(TAG, "Failed to get last known location", e);
118            }
119        }
120        return null;
121    }
122
123    private boolean isStateValid(TwilightState state) {
124        return state != null && state.nextUpdate > System.currentTimeMillis();
125    }
126
127    private void updateState(@NonNull Location location) {
128        final TwilightState state = sTwilightState;
129        final long now = System.currentTimeMillis();
130        final TwilightCalculator calculator = TwilightCalculator.getInstance();
131
132        // calculate yesterday's twilight
133        calculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS,
134                location.getLatitude(), location.getLongitude());
135        final long yesterdaySunset = calculator.sunset;
136
137        // calculate today's twilight
138        calculator.calculateTwilight(now, location.getLatitude(), location.getLongitude());
139        final boolean isNight = (calculator.state == TwilightCalculator.NIGHT);
140        final long todaySunrise = calculator.sunrise;
141        final long todaySunset = calculator.sunset;
142
143        // calculate tomorrow's twilight
144        calculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS,
145                location.getLatitude(), location.getLongitude());
146        final long tomorrowSunrise = calculator.sunrise;
147
148        // Set next update
149        long nextUpdate = 0;
150        if (todaySunrise == -1 || todaySunset == -1) {
151            // In the case the day or night never ends the update is scheduled 12 hours later.
152            nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS;
153        } else {
154            if (now > todaySunset) {
155                nextUpdate += tomorrowSunrise;
156            } else if (now > todaySunrise) {
157                nextUpdate += todaySunset;
158            } else {
159                nextUpdate += todaySunrise;
160            }
161            // add some extra time to be on the safe side.
162            nextUpdate += DateUtils.MINUTE_IN_MILLIS;
163        }
164
165        // Update the twilight state
166        state.isNight = isNight;
167        state.yesterdaySunset = yesterdaySunset;
168        state.todaySunrise = todaySunrise;
169        state.todaySunset = todaySunset;
170        state.tomorrowSunrise = tomorrowSunrise;
171        state.nextUpdate = nextUpdate;
172    }
173
174    /**
175     * Describes whether it is day or night.
176     */
177    private static class TwilightState {
178        boolean isNight;
179        long yesterdaySunset;
180        long todaySunrise;
181        long todaySunset;
182        long tomorrowSunrise;
183        long nextUpdate;
184    }
185}
186