/**
*
* 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.math.BigDecimal;
import android.util.Log;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import edu.mit.media.funf.Schedule;
import edu.mit.media.funf.Schedule.DefaultSchedule;
import edu.mit.media.funf.config.Configurable;
import edu.mit.media.funf.json.IJsonObject;
import edu.mit.media.funf.probe.Probe.Base;
import edu.mit.media.funf.probe.Probe.PassiveProbe;
import edu.mit.media.funf.probe.Probe.RequiredFeatures;
import edu.mit.media.funf.probe.Probe.RequiredPermissions;
import edu.mit.media.funf.probe.Probe.RequiredProbes;
import edu.mit.media.funf.probe.builtin.ProbeKeys.LocationKeys;
import edu.mit.media.funf.time.TimeUtil;
import edu.mit.media.funf.util.LogUtil;
/**
* Filters the verbose location set for the most accurate location within a max wait time,
* ending early if it finds a location that has at most the goodEnoughAccuracy.
* Useful for sparse polling of location to limit battery usage.
* @author alangardner
*
*/
@RequiredPermissions({android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION})
@RequiredFeatures("android.hardware.location")
@Schedule.DefaultSchedule(interval=1800)
@RequiredProbes(LocationProbe.class)
public class SimpleLocationProbe extends Base implements PassiveProbe, LocationKeys {
@Configurable
private BigDecimal maxWaitTime = BigDecimal.valueOf(120);
@Configurable
private BigDecimal maxAge = BigDecimal.valueOf(120);
@Configurable
private BigDecimal goodEnoughAccuracy = BigDecimal.valueOf(80);
@Configurable
private boolean useGps = true;
@Configurable
private boolean useNetwork = true;
private LocationProbe locationProbe;
private BigDecimal startTime;
private IJsonObject bestLocation;
private Runnable sendLocationRunnable = new Runnable() {
@Override
public void run() {
sendCurrentBestLocation();
}
};
private DataListener listener = new DataListener() {
@Override
public void onDataReceived(IJsonObject completeProbeUri, IJsonObject data) {
Log.d(LogUtil.TAG, "SimpleLocationProbe received data: " + data.toString());
if (startTime == null) {
startTime = TimeUtil.getTimestamp();
getHandler().postDelayed(sendLocationRunnable, TimeUtil.secondsToMillis(maxWaitTime));
}
if (isBetterThanCurrent(data)) {
Log.d(LogUtil.TAG, "SimpleLocationProbe evaluated better location.");
bestLocation = data;
}
if (goodEnoughAccuracy != null && bestLocation.get(ACCURACY).getAsDouble() < goodEnoughAccuracy.doubleValue()) {
Log.d(LogUtil.TAG, "SimpleLocationProbe evaluated good enough location.");
if (getState() == State.RUNNING) { // Actively Running
stop();
} else if (getState() == State.ENABLED) { // Passive listening
// TODO: do we want to prematurely end this, or wait for the full duration
// Things to consider:
// - the device falling to sleep before we send
// - too much unrequested data if we send all values within accuracy limits
// (this will restart immediately if more passive data continues to come in)
getHandler().removeCallbacks(sendLocationRunnable);
sendCurrentBestLocation();
}
}
}
@Override
public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) {
}
};
private void sendCurrentBestLocation() {
Log.d(LogUtil.TAG, "SimpleLocationProbe sending current best location.");
if (bestLocation != null) {
JsonObject data = bestLocation.getAsJsonObject();
data.remove(PROBE); // Remove probe so that it fills with our probe name
sendData(data);
}
startTime = null;
bestLocation = null;
}
private boolean isBetterThanCurrent(IJsonObject newLocation) {
BigDecimal age = startTime.subtract(newLocation.get(TIMESTAMP).getAsBigDecimal());
return bestLocation == null ||
(age.doubleValue() < maxAge.doubleValue() &&
bestLocation.get(ACCURACY).getAsDouble() > newLocation.get(ACCURACY).getAsDouble());
}
@Override
protected void onEnable() {
super.onEnable();
JsonObject config = new JsonObject();
if (!useGps) {
config.addProperty("useGps", false);
}
if (!useNetwork) {
config.addProperty("useNetwork", false);
}
locationProbe = getGson().fromJson(config, LocationProbe.class);
locationProbe.registerPassiveListener(listener);
}
@Override
protected void onStart() {
super.onStart();
Log.d(LogUtil.TAG, "SimpleLocationProbe starting, registering listener");
startTime = TimeUtil.getTimestamp();
locationProbe.registerListener(listener);
getHandler().sendMessageDelayed(getHandler().obtainMessage(STOP_MESSAGE), TimeUtil.secondsToMillis(maxWaitTime));
}
@Override
protected void onStop() {
super.onStop();
Log.d(LogUtil.TAG, "SimpleLocationProbe stopping");
getHandler().removeMessages(STOP_MESSAGE);
locationProbe.unregisterListener(listener);
sendCurrentBestLocation();
}
@Override
protected void onDisable() {
super.onDisable();
locationProbe.unregisterPassiveListener(listener);
}
}