/******************************************************************************* * Copyright 2013-2015 alladin-IT GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package at.alladin.rmbt.android.util; import android.content.Context; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; public abstract class GeoLocation { private static final String DEBUG_TAG = "Geolocation"; private final LocationManager locationManager; /** Allows to obtain the phone's location, to determine the country. */ /** The location Listener */ private LocListener coarseLocationListener; /** Allows to obtain the phone's location, to determine the country. */ /** The location Listener */ private LocListener fineLocationListener; private Location curLocation = null; private boolean started = false; private final boolean gpsEnabled; private final long minTime; // minimum time interval between location updates, in milliseconds private final float minDistance; // minimum distance between location updates, in meters public GeoLocation(final Context context, final boolean gpsEnabled) { this(context, gpsEnabled, 1000, 5); } public GeoLocation(final Context context, final boolean gpsEnabled, final long minTime, final float minDistance) { locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); this.gpsEnabled = gpsEnabled; this.minTime = minTime; this.minDistance = minDistance; } public static Location getLastKnownLocation(Context context) { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); final Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); criteria.setPowerRequirement(Criteria.POWER_HIGH); final String providerName = locationManager.getBestProvider(criteria, true); if (providerName == null) return null; return locationManager.getLastKnownLocation(providerName); } /** Load Listeners */ public void start() { if (!started) { started = true; final Criteria criteriaCoarse = new Criteria(); /* "Coarse" accuracy means "no need to use GPS". */ criteriaCoarse.setAccuracy(Criteria.ACCURACY_COARSE); criteriaCoarse.setPowerRequirement(Criteria.POWER_LOW); final String coarseProviderName = locationManager.getBestProvider(criteriaCoarse, true); if (coarseProviderName != null) { isBetterLocation(locationManager.getLastKnownLocation(coarseProviderName)); coarseLocationListener = new LocListener(); locationManager.requestLocationUpdates(coarseProviderName, minTime, minDistance, coarseLocationListener); } if (gpsEnabled) { final Criteria criteriaFine = new Criteria(); /* "Fine" accuracy means "use GPS". */ criteriaFine.setAccuracy(Criteria.ACCURACY_FINE); criteriaFine.setPowerRequirement(Criteria.POWER_HIGH); final String fineProviderName = locationManager.getBestProvider(criteriaFine, true); if (fineProviderName != null) { isBetterLocation(locationManager.getLastKnownLocation(fineProviderName)); fineLocationListener = new LocListener(); locationManager.requestLocationUpdates(fineProviderName, minTime, minDistance, fineLocationListener); } } } } /** Unload Listeners */ public void stop() { // reset all Managers & Listeners if (coarseLocationListener != null) { locationManager.removeUpdates(coarseLocationListener); coarseLocationListener = null; } if (fineLocationListener != null) { locationManager.removeUpdates(fineLocationListener); fineLocationListener = null; } started = false; } /** * Abstract Method. Called when location has changed * */ public abstract void onLocationChanged(Location curLocation); /** * A listener that logs callbacks. */ private class LocListener implements LocationListener { /* (non-Javadoc) * @see android.location.LocationListener#onLocationChanged(android.location.Location) */ @Override public void onLocationChanged(final Location location) { final String outString = "Location: " + String.valueOf(location.getLatitude()) + "/" + String.valueOf(location.getLongitude()) + " +/-" + String.valueOf(location.getAccuracy()) + "m provider: " + String.valueOf(location.getProvider()); Log.v(DEBUG_TAG, outString); isBetterLocation(location); } @Override public void onProviderDisabled(final String provider) { Log.d(DEBUG_TAG, "provider disabled: " + provider); } @Override public void onProviderEnabled(final String provider) { Log.d(DEBUG_TAG, "provider enabled: " + provider); } @Override public void onStatusChanged(final String provider, final int status, final Bundle extras) { Log.d(DEBUG_TAG, "status changed: " + provider + "=" + status); } } /** * Determines whether one Location reading is better than the current * Location fix * * @param newLocation * The new Location that you want to evaluate * @param curLocation * The current Location fix, to which you want to compare the new * one */ private void isBetterLocation(final Location newLocation) { if (newLocation == null) return; final long locTime = newLocation.getTime(); //milliseconds since January 1, 1970 // discard locations older than Config.GEO_ACCEPT_TIME milliseconds // System.nanoTime() and newLocation.getElapsedRealtimeNanos() would be // more accurate but would require API level 17 final long now = System.currentTimeMillis(); Log.d(DEBUG_TAG, "age: " + (now - locTime) + " ms"); if (now > locTime + Config.GEO_ACCEPT_TIME) return; if (curLocation == null || ! gpsEnabled) { // A new location is always better than no location curLocation = newLocation; onLocationChanged(curLocation); } else { // Check whether the new location fix is newer or older final long timeDelta = locTime - curLocation.getTime(); final boolean isSignificantlyNewer = timeDelta > Config.GEO_MIN_TIME; final boolean isSignificantlyOlder = timeDelta < -Config.GEO_MIN_TIME; final boolean isNewer = timeDelta > 0; // If it's been more than two minutes since the current location, // use the new location // because the user has likely moved if (isSignificantlyNewer) { curLocation = newLocation; onLocationChanged(curLocation); // If the new location is more than two minutes older, it must // be worse } else if (isSignificantlyOlder) { // keep old value } else { // Check whether the new location fix is more or less accurate final int accuracyDelta = (int) (newLocation.getAccuracy() - curLocation.getAccuracy()); final boolean isLessAccurate = accuracyDelta > 0; final boolean isMoreAccurate = accuracyDelta < 0; final boolean isSignificantlyLessAccurate = accuracyDelta > 200; // Check if the old and new location are from the same provider final boolean isFromSameProvider = isSameProvider(newLocation.getProvider(), curLocation.getProvider()); // Determine location quality using a combination of timeliness // and accuracy if (isMoreAccurate) { curLocation = newLocation; onLocationChanged(curLocation); } else if (isNewer && !isLessAccurate) { curLocation = newLocation; onLocationChanged(curLocation); } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { curLocation = newLocation; onLocationChanged(curLocation); } // keep old location otherwise } } } /** Checks whether two providers are the same */ private static boolean isSameProvider(final String provider1, final String provider2) { if (provider1 == null) return provider2 == null; return provider1.equals(provider2); } public Location getLastKnownLocation() { if (!started) throw new IllegalStateException(); return curLocation; } @Override protected void finalize() throws Throwable { // should be called manually, but just to be sure stop(); super.finalize(); } }