/**
* Funf: Open Sensing Framework
* Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland.
* Acknowledgments: Alan Gardner
* Contact: nadav@media.mit.edu
*
* This file is part of Funf.
*
* Funf is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Funf is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Funf. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.mit.media.funf.probe.builtin;
import java.lang.reflect.Field;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.util.Log;
import edu.mit.media.funf.Utils;
import edu.mit.media.funf.probe.Probe;
import edu.mit.media.funf.probe.builtin.ProbeKeys.LocationKeys;
public class LocationProbe extends Probe implements LocationKeys {
public static final long SIGNIFICANT_TIME_DIFFERENCE = 2*60*1000; // 2 minutes
// TODO: May turn MAX_DURATION into duration parameter
public static final long DEFAULT_DURATION = 2*60; // 2 minutes
public static final long DEFAULT_PERIOD = 60L * 30L; // 30 min;
// TODO: Turn GOOD_ENOUGH_ACCURACY into a parameter
public static final float GOOD_ENOUGH_ACCURACY = 80.0f;
private LocationManager mLocationManager;
private ProbeLocationListener listener;
private ProbeLocationListener passiveListener;
private Location latestLocation;
@Override
public Parameter[] getAvailableParameters() {
return new Parameter[] {
new Parameter(Parameter.Builtin.PERIOD, DEFAULT_PERIOD),
new Parameter(Parameter.Builtin.DURATION, DEFAULT_DURATION),
new Parameter(Parameter.Builtin.START, 0L),
new Parameter(Parameter.Builtin.END, 0L)
// TODO: come back to configuration parameters such as desiredAccuracy or duration
};
}
@Override
public String[] getRequiredPermissions() {
return new String[]{
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
};
}
@Override
public String[] getRequiredFeatures() {
return new String[]{};
}
private Location bestCachedLocation() {
Location lastKnownGpsLocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Location lastKnownNetLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Location bestCachedLocation = lastKnownGpsLocation;
if (bestCachedLocation == null ||
(lastKnownNetLocation != null && lastKnownNetLocation.getTime() > bestCachedLocation.getTime())) {
bestCachedLocation = lastKnownNetLocation;
}
return bestCachedLocation;
}
@Override
protected void onEnable() {
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
latestLocation = bestCachedLocation();
listener = new ProbeLocationListener();
passiveListener = new ProbeLocationListener();
String passiveProvider = getPassiveProvider();
if (passiveProvider != null) {
mLocationManager.requestLocationUpdates(getPassiveProvider(), 0, 0, passiveListener);
}
}
/**
* Supporting API level 7 which does not have PASSIVE provider
* @return
*/
private String getPassiveProvider() {
try {
Field passiveProviderField = LocationManager.class.getDeclaredField("PASSIVE_PROVIDER");
return (String)passiveProviderField.get(null);
} catch (SecurityException e) {
} catch (NoSuchFieldException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
return null;
}
@Override
protected void onDisable() {
mLocationManager.removeUpdates(passiveListener);
}
public void onRun(Bundle params) {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener);
}
@Override
public void onStop() {
mLocationManager.removeUpdates(listener);
sendProbeData();
}
@Override
public void sendProbeData() {
if (latestLocation != null) {
Bundle data = new Bundle();
data.putParcelable(LOCATION, latestLocation);
sendProbeData(Utils.millisToSeconds(latestLocation.getTime()), data);
}
}
private class ProbeLocationListener implements LocationListener{
public void onLocationChanged(Location newLocation) {
if (newLocation == null || (newLocation.getLatitude() == 0.0 && newLocation.getLongitude() == 0.0)){
// Hack to filter out 0.0,0.0 locations
return;
}
Log.i(TAG, "New location to be evaluated: " + newLocation.getAccuracy() + "m @ " + newLocation.getTime() );
if (isBetterThanCurrent(newLocation)) {
latestLocation = newLocation;
// If not running then start a timer to send out the best location get in the next default duration
Log.i(TAG, "Is Running: " + isRunning());
if (latestLocation.hasAccuracy() && latestLocation.getAccuracy() < GOOD_ENOUGH_ACCURACY) {
if (isRunning()) {
Log.i(TAG, "Good enough stop");
stop();
} else {
// TODO: set a timer for passive listened locations so they have a DURATION aspect like active scans
Log.i(TAG, "Passive location data send");
sendProbeData();
}
}
}
}
private boolean isBetterThanCurrent(Location newLocation) {
if (latestLocation == null) {
return true;
}
long timeDiff = newLocation.getTime() - latestLocation.getTime();
Log.i(TAG, "TIME DIFFERENCE: " + timeDiff);
Log.i(TAG, "Old accuracy: " + latestLocation + " New Accuracy: " + newLocation.getAccuracy());
return timeDiff > SIGNIFICANT_TIME_DIFFERENCE ||
(newLocation.getAccuracy() <= latestLocation.getAccuracy());
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras){
if (status == LocationProvider.OUT_OF_SERVICE) {
Log.i(TAG, "location provider out of service: "+provider);
}else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE){
Log.i(TAG, "location provider temp unavailable: "+provider);
}
}
}
}