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