/*
* Copyright 2011 Google Inc.
*
* 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 org.gdg.frisbee.android.utils;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import org.gdg.frisbee.android.utils.base.ILastLocationFinder;
import java.util.List;
import timber.log.Timber;
/**
* Optimized implementation of Last Location Finder for devices running Gingerbread
* and above.
*
* This class let's you find the "best" (most accurate and timely) previously
* detected location using whatever providers are available.
*
* Where a timely / accurate previous location is not detected it will
* return the newest location (where one exists) and setup a oneshot
* location update to find the current location.
*/
public class GingerbreadLastLocationFinder implements ILastLocationFinder {
private static final String SINGLE_LOCATION_UPDATE_ACTION = "org.gdg.frisbee.actions.SINGLE_LOCATION_UPDATE_ACTION";
private PendingIntent singleUpatePI;
private LocationListener locationListener;
private LocationManager locationManager;
private Context context;
private Criteria criteria;
/**
* Construct a new Gingerbread Last Location Finder.
* @param context Context
*/
public GingerbreadLastLocationFinder(Context context) {
this.context = context;
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
// Coarse accuracy is specified here to get the fastest possible result.
// The calling Activity will likely (or have already) request ongoing
// updates using the Fine location provider.
criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_LOW);
// Construct the Pending Intent that will be broadcast by the oneshot
// location update.
Intent updateIntent = new Intent(SINGLE_LOCATION_UPDATE_ACTION);
singleUpatePI = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Returns the most accurate and timely previously detected location.
* Where the last result is beyond the specified maximum distance or
* latency a one-off location update is returned via the {@link LocationListener}
* specified in {@link setChangedLocationListener}.
* @param minDistance Minimum distance before we require a location update.
* @param minTime Minimum time required between location updates.
* @return The most accurate and / or timely previously detected location.
*/
public Location getLastBestLocation(int minDistance, long minTime) {
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;
// Iterate through all the providers on the system, keeping
// note of the most accurate result within the acceptable time limit.
// If no result is found within maxTime, return the newest Location.
List<String> matchingProviders = locationManager.getAllProviders();
for (String provider : matchingProviders) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
float accuracy = location.getAccuracy();
long time = location.getTime();
if (time > minTime && accuracy < bestAccuracy) {
bestResult = location;
bestAccuracy = accuracy;
bestTime = time;
} else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime) {
bestResult = location;
bestTime = time;
}
}
}
// If the best result is beyond the allowed time limit, or the accuracy of the
// best result is wider than the acceptable maximum distance, request a single update.
// This check simply implements the same conditions we set when requesting regular
// location updates every [minTime] and [minDistance].
if (locationListener != null && (bestTime < minTime || bestAccuracy > minDistance)) {
List<String> providers = locationManager.getProviders(criteria, true);
try {
if (providers != null && providers.size() > 0) {
IntentFilter locIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION);
context.registerReceiver(singleUpdateReceiver, locIntentFilter);
locationManager.requestSingleUpdate(criteria, singleUpatePI);
}
} catch (SecurityException | IllegalArgumentException ex) {
Timber.e(ex, "fail to request location update, ignore");
}
}
return bestResult;
}
/**
* This {@link BroadcastReceiver} listens for a single location
* update before unregistering itself.
* The oneshot location update is returned via the {@link LocationListener}
* specified in {@link setChangedLocationListener}.
*/
private BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
context.unregisterReceiver(singleUpdateReceiver);
String key = LocationManager.KEY_LOCATION_CHANGED;
Location location = (Location) intent.getExtras().get(key);
if (locationListener != null && location != null) {
locationListener.onLocationChanged(location);
}
locationManager.removeUpdates(singleUpatePI);
}
};
/**
* {@inheritDoc}
*/
public void setChangedLocationListener(LocationListener l) {
locationListener = l;
}
/**
* {@inheritDoc}
*/
public void cancel() {
locationManager.removeUpdates(singleUpatePI);
}
}