package com.openxc;
import java.lang.reflect.Method;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.util.Log;
import com.google.common.base.MoreObjects;
import com.openxc.measurements.Latitude;
import com.openxc.measurements.Longitude;
import com.openxc.measurements.Measurement;
import com.openxc.measurements.UnrecognizedMeasurementTypeException;
import com.openxc.measurements.VehicleSpeed;
/**
* Propagate GPS and vehicle speed updates from OpenXC to the normal Android
* location system.
*
* If we have at least latitude, longitude and vehicle speed from the vehicle,
* we send out a new location for the LocationManager.GPS_PROVIDER and
* VEHICLE_LOCATION_PROVIDER providers in the Android location service.
*
* Developers can either use the standard Android location framework
* with vehicle locations enabled in OpenXC, or the specific OpenXC
* Latitude/Longitude measurements directly.
*/
public class VehicleLocationProvider implements Measurement.Listener {
public final static String TAG = "VehicleLocationProvider";
public final static String VEHICLE_LOCATION_PROVIDER = "vehicle";
private VehicleManager mVehicleManager;
private LocationManager mLocationManager;
private boolean mOverwriteNativeStatus;
private boolean mNativeGpsOverridden;
public VehicleLocationProvider(Context context, VehicleManager vehicleManager) {
mVehicleManager = vehicleManager;
mLocationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
if(mLocationManager == null) {
Log.w(TAG, "Cannot load location service from Android, won't be able to overwrite GPS");
} else {
setupMockLocations();
}
}
public void stop() {
if(mVehicleManager != null) {
mVehicleManager.removeListener(Latitude.class, this);
mVehicleManager.removeListener(Longitude.class, this);
mVehicleManager.removeListener(VehicleSpeed.class, this);
}
if(mLocationManager != null && mLocationManager.getProvider(
VEHICLE_LOCATION_PROVIDER) != null) {
mLocationManager.removeTestProvider(VEHICLE_LOCATION_PROVIDER);
}
}
@Override
public void receive(Measurement measurement) {
if(measurement instanceof Latitude || measurement instanceof Longitude
|| measurement instanceof VehicleSpeed) {
// To keep down the number of times we touch the Android location
// system to update the location, only update if we have a change in
// the measurements we use.
updateLocation();
}
}
/**
* Enable or disable overwriting Android's native GPS values with those from
* the vehicle.
*
* If enabled, the GPS_PROVIDER from Android will respond with data taken
* from the vehicle interface. The native GPS values will be used until GPS
* data is actually received from the vehicle, so if the specific car you're
* plugged into doesn't have GPS then the values will not be overwritten,
* regardless of this setting.
*/
public void setOverwritingStatus(boolean enabled) {
mOverwriteNativeStatus = enabled;
mNativeGpsOverridden = false;
if(mLocationManager != null && !enabled) {
try {
mLocationManager.removeTestProvider(
LocationManager.GPS_PROVIDER);
Log.d(TAG, "Disabled overwriting native GPS with OpenXC GPS");
} catch(IllegalArgumentException e) {
Log.d(TAG, "Unable to remove GPS test provider - " +
"probably wasn't added yet");
}
mVehicleManager.removeListener(Latitude.class, this);
mVehicleManager.removeListener(Longitude.class, this);
mVehicleManager.removeListener(VehicleSpeed.class, this);
} else {
Log.d(TAG, "Enabled overwriting native GPS with OpenXC GPS");
mVehicleManager.addListener(Latitude.class, this);
mVehicleManager.addListener(Longitude.class, this);
mVehicleManager.addListener(VehicleSpeed.class, this);
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("enabled", mOverwriteNativeStatus)
.toString();
}
private void makeLocationComplete(Location location) {
if(android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.JELLY_BEAN) {
// TODO When android 4.2 hits the maven repository we can simplify
// this and call the makeComplete method directly, until then we use
// reflection to load it without getting a compilation error.
Method makeCompleteMethod;
try {
makeCompleteMethod = Location.class.getMethod("makeComplete");
makeCompleteMethod.invoke(location);
} catch(NoSuchMethodException e) {
} catch(Exception e) {
}
} else {
location.setTime(System.currentTimeMillis());
}
}
private void updateLocation() {
if(mLocationManager == null || !mOverwriteNativeStatus) {
return;
}
try {
Latitude latitude = (Latitude) mVehicleManager.get(Latitude.class);
Longitude longitude = (Longitude) mVehicleManager.get(
Longitude.class);
VehicleSpeed speed = (VehicleSpeed) mVehicleManager.get(
VehicleSpeed.class);
// Only enable overwriting the built-in Android GPS provider if we
// actually receive a GPS update from the vehicle. This is to avoid
// killing GPS just by having the OpenXC app installed (because it's
// always running the service in the background).
overwriteNativeProvider();
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(latitude.getValue().doubleValue());
location.setLongitude(longitude.getValue().doubleValue());
location.setSpeed((float) speed.getValue().doubleValue());
makeLocationComplete(location);
try {
mLocationManager.setTestProviderLocation(
LocationManager.GPS_PROVIDER, location);
location.setProvider(VEHICLE_LOCATION_PROVIDER);
mLocationManager.setTestProviderLocation(
VEHICLE_LOCATION_PROVIDER, location);
} catch(SecurityException e) {
Log.w(TAG, "Unable to use mocked locations, " +
"insufficient privileges - make sure mock locations " +
"are allowed in device settings", e);
} catch(IllegalArgumentException e) {
Log.w(TAG, "Unable to set test provider location", e);
}
} catch(NoValueException e) {
Log.w(TAG, "Can't update location, complete measurements not available yet");
} catch(UnrecognizedMeasurementTypeException e) {
// This is dumb that we know these measurements are good, but
// we still could get an exception. One of the annoying things about
// allowing such a flexible API for Measurements. If they all had
// the same parent class instead of the same Interface, we could
// have a static method that returned the ID and you'd be able to
// get rid of this exception type.
}
}
private void overwriteNativeProvider() {
if(mOverwriteNativeStatus && !mNativeGpsOverridden && mLocationManager != null) {
try {
mLocationManager.addTestProvider(LocationManager.GPS_PROVIDER,
false, false, false, false, false, true, false, 0, 5);
mLocationManager.setTestProviderEnabled(
LocationManager.GPS_PROVIDER, true);
} catch(SecurityException e) {
Log.w(TAG, "Unable to use mocked locations, " +
"insufficient privileges - make sure mock locations " +
"are allowed in device settings", e);
}
}
}
private void setupMockLocations() {
try {
if(mLocationManager.getProvider(
VEHICLE_LOCATION_PROVIDER) == null) {
mLocationManager.addTestProvider(VEHICLE_LOCATION_PROVIDER,
false, false, false, false, false, true, false, 0, 5);
}
mLocationManager.setTestProviderEnabled(
VEHICLE_LOCATION_PROVIDER, true);
} catch(SecurityException e) {
Log.w(TAG, "Unable to use mocked locations, " +
"insufficient privileges - make sure mock locations " +
"are allowed in device settings", e);
mLocationManager = null;
}
}
}