Files
frameworks_base/location/java/com/android/internal/location/TrackProvider.java
Mike Lockwood 4e50b78bda AI 144452: More Location Manager cleanup:
Remove 1 Hz "heartbeat" polling of location providers from LocationManagerService.
  Now location providers report their location to LocationManagerService via
  LocationManager.setLocation() rather than waiting to be polled.
  This reduces GPS fix latency by up to one second.
  Remove LocationProvderImpl.getLocation().
  Since we are no longer polling, this method is no longer necessary.
  BUG=1729031

Automated import of CL 144452
2009-04-03 08:24:43 -07:00

733 lines
23 KiB
Java

// Copyright 2007 The Android Open Source Project
package com.android.internal.location;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import android.location.Criteria;
import android.location.ILocationManager;
import android.location.Location;
import android.location.LocationProviderImpl;
import android.os.Bundle;
import android.util.Config;
import android.util.Log;
/**
* A dummy provider that returns positions interpolated from a sequence
* of caller-supplied waypoints. The waypoints are supplied as a
* String containing one or more numeric quadruples of the form:
* <br>
* <code>
* <time in millis> <latitude> <longitude> <altitude>
* </code>
*
* <p> The waypoints must be supplied in increasing timestamp order.
*
* <p> The time at which the provider is constructed is considered to
* be time 0, and further requests for positions will return a
* position that is linearly interpolated between the waypoints whose
* timestamps are closest to the amount of wall clock time that has
* elapsed since time 0.
*
* <p> Following the time of the last waypoint, the position of that
* waypoint will continue to be returned indefinitely.
*
* {@hide}
*/
public class TrackProvider extends LocationProviderImpl {
static final String LOG_TAG = "TrackProvider";
private static final long INTERVAL = 1000L;
private boolean mEnabled = true;
private TrackProviderThread mThread;
private double mLatitude;
private double mLongitude;
private boolean mHasAltitude;
private boolean mHasBearing;
private boolean mHasSpeed;
private double mAltitude;
private float mBearing;
private float mSpeed;
private Bundle mExtras;
private long mBaseTime;
private long mLastTime = -1L;
private long mTime;
private long mMinTime;
private long mMaxTime;
private List<Waypoint> mWaypoints = new ArrayList<Waypoint>();
private int mWaypointIndex = 0;
private boolean mRequiresNetwork = false;
private boolean mRequiresSatellite = false;
private boolean mRequiresCell = false;
private boolean mHasMonetaryCost = false;
private boolean mSupportsAltitude = true;
private boolean mSupportsSpeed = true;
private boolean mSupportsBearing = true;
private boolean mRepeat = false;
private int mPowerRequirement = Criteria.POWER_LOW;
private int mAccuracy = Criteria.ACCURACY_COARSE;
private float mTrackSpeed = 100.0f; // km/hr - default for kml tracks
private Location mInitialLocation;
private class TrackProviderThread extends Thread {
private boolean mDone = false;
public TrackProviderThread() {
super("TrackProviderThread");
}
public void run() {
// thread exits after disable() is called
synchronized (this) {
while (!mDone) {
try {
wait(INTERVAL);
} catch (InterruptedException e) {
}
if (!mDone) {
TrackProvider.this.update();
}
}
}
}
synchronized void setDone() {
mDone = true;
notify();
}
}
private void close(Reader rdr) {
try {
if (rdr != null) {
rdr.close();
}
} catch (IOException e) {
Log.w(LOG_TAG, "Exception closing reader", e);
}
}
public void readTrack(File trackFile) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(trackFile), 8192);
String s;
long lastTime = -Long.MAX_VALUE;
while ((s = br.readLine()) != null) {
String[] tokens = s.split("\\s+");
if (tokens.length != 4 && tokens.length != 6) {
Log.e(LOG_TAG, "Got track \"" + s +
"\", wanted <time> <long> <lat> <alt> [<bearing> <speed>]");
continue;
}
long time;
double longitude, latitude, altitude;
try {
time = Long.parseLong(tokens[0]);
longitude = Double.parseDouble(tokens[1]);
latitude = Double.parseDouble(tokens[2]);
altitude = Double.parseDouble(tokens[3]);
} catch (NumberFormatException e) {
Log.e(LOG_TAG, "Got track \"" + s +
"\", wanted <time> <long> <lat> <alt> " +
"[<bearing> <speed>]", e);
continue;
}
Waypoint w = new Waypoint(getName(), time, latitude, longitude, altitude);
if (tokens.length >= 6) {
float bearing, speed;
try {
bearing = Float.parseFloat(tokens[4]);
speed = Float.parseFloat(tokens[5]);
w.setBearing(bearing);
w.setSpeed(speed);
} catch (NumberFormatException e) {
Log.e(LOG_TAG, "Ignoring bearing and speed \"" +
tokens[4] + "\", \"" + tokens[5] + "\"", e);
}
}
if (mInitialLocation == null) {
mInitialLocation = w.getLocation();
}
// Ignore waypoints whose time is less than or equal to 0 or
// the time of the previous waypoint
if (time < 0) {
Log.e(LOG_TAG, "Ignoring waypoint at negative time=" + time);
continue;
}
if (time <= lastTime) {
Log.e(LOG_TAG, "Ignoring waypoint at time=" + time +
" (< " + lastTime + ")");
continue;
}
mWaypoints.add(w);
lastTime = time;
}
setTimes();
return;
} catch (IOException e) {
Log.e(LOG_TAG, "Exception reading track file", e);
mWaypoints.clear();
} finally {
close(br);
}
}
public void readKml(File kmlFile) {
FileReader kmlReader = null;
try {
kmlReader = new FileReader(kmlFile);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(kmlReader);
// Concatenate the text of each <coordinates> tag
boolean inCoordinates = false;
StringBuilder sb = new StringBuilder();
int eventType = xpp.getEventType();
do {
if (eventType == XmlPullParser.START_DOCUMENT) {
// do nothing
} else if (eventType == XmlPullParser.END_DOCUMENT) {
// do nothing
} else if (eventType == XmlPullParser.START_TAG) {
String startTagName = xpp.getName();
if (startTagName.equals("coordinates")) {
inCoordinates = true;
}
} else if (eventType == XmlPullParser.END_TAG) {
String endTagName = xpp.getName();
if (endTagName.equals("coordinates")) {
inCoordinates = false;
}
} else if (eventType == XmlPullParser.TEXT) {
if (inCoordinates) {
sb.append(xpp.getText());
sb.append(' ');
}
}
eventType = xpp.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
String coordinates = sb.toString();
// Parse the "lon,lat,alt" triples and supply times
// for each waypoint based on a constant speed
Location loc = null;
double KM_PER_HOUR = mTrackSpeed;
double KM_PER_METER = 1.0 / 1000.0;
double MILLIS_PER_HOUR = 60.0 * 60.0 * 1000.0;
double MILLIS_PER_METER =
(1.0 / KM_PER_HOUR) * (KM_PER_METER) * (MILLIS_PER_HOUR);
long time = 0L;
StringTokenizer st = new StringTokenizer(coordinates, ", ");
while (st.hasMoreTokens()) {
try {
String lon = st.nextToken();
String lat = st.nextToken();
String alt = st.nextToken();
if (Config.LOGD) {
Log.d(LOG_TAG,
"lon=" + lon + ", lat=" + lat + ", alt=" + alt);
}
double nLongitude = Double.parseDouble(lon);
double nLatitude = Double.parseDouble(lat);
double nAltitude = Double.parseDouble(alt);
Location nLoc = new Location(getName());
nLoc.setLatitude(nLatitude);
nLoc.setLongitude(nLongitude);
if (loc != null) {
double distance = loc.distanceTo(nLoc);
if (Config.LOGD) {
Log.d(LOG_TAG, "distance = " + distance);
}
time += (long) (distance * MILLIS_PER_METER);
}
Waypoint w = new Waypoint(getName(), time,
nLatitude, nLongitude, nAltitude);
if (supportsSpeed()) {
w.setSpeed(mTrackSpeed);
}
if (supportsBearing()) {
w.setBearing(0.0f);
}
mWaypoints.add(w);
if (mInitialLocation == null) {
mInitialLocation = w.getLocation();
}
loc = nLoc;
} catch (NumberFormatException nfe) {
Log.e(LOG_TAG, "Got NumberFormatException reading KML data: " +
nfe, nfe);
}
}
setTimes();
return;
} catch (IOException ioe) {
mWaypoints.clear();
Log.e(LOG_TAG, "Exception reading KML data: " + ioe, ioe);
// fall through
} catch (XmlPullParserException xppe) {
mWaypoints.clear();
Log.e(LOG_TAG, "Exception reading KML data: " + xppe, xppe);
// fall through
} finally {
close(kmlReader);
}
}
public void readNmea(String name, File file) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file), 8192);
String s;
String provider = getName();
NmeaParser parser = new NmeaParser(name);
while ((s = br.readLine()) != null) {
boolean newWaypoint = parser.parseSentence(s);
if (newWaypoint) {
Location loc = parser.getLocation();
Waypoint w = new Waypoint(loc);
mWaypoints.add(w);
// Log.i(TAG, "Got waypoint " + w);
}
}
setTimes();
return;
} catch (IOException ioe) {
Log.e(LOG_TAG, "Exception reading NMEA data: " + ioe);
mWaypoints.clear();
} finally {
close(br);
}
}
private static boolean booleanVal(String tf) {
return (tf == null) || (tf.equalsIgnoreCase("true"));
}
private static int intVal(String val) {
try {
return (val == null) ? 0 : Integer.parseInt(val);
} catch (NumberFormatException nfe) {
return 0;
}
}
private static float floatVal(String val) {
try {
return (val == null) ? 0 : Float.parseFloat(val);
} catch (NumberFormatException nfe) {
return 0.0f;
}
}
public void readProperties(File propertiesFile) {
BufferedReader br = null;
if (!propertiesFile.exists()) {
return;
}
try {
if (Config.LOGD) {
Log.d(LOG_TAG, "Loading properties file " +
propertiesFile.getPath());
}
br = new BufferedReader(new FileReader(propertiesFile), 8192);
String s;
while ((s = br.readLine()) != null) {
StringTokenizer st = new StringTokenizer(s);
String command = null;
String value = null;
if (!st.hasMoreTokens()) {
continue;
}
command = st.nextToken();
if (st.hasMoreTokens()) {
value = st.nextToken();
}
if (command.equalsIgnoreCase("requiresNetwork")) {
setRequiresNetwork(booleanVal(value));
} else if (command.equalsIgnoreCase("requiresSatellite")) {
setRequiresSatellite(booleanVal(value));
} else if (command.equalsIgnoreCase("requiresCell")) {
setRequiresCell(booleanVal(value));
} else if (command.equalsIgnoreCase("hasMonetaryCost")) {
setHasMonetaryCost(booleanVal(value));
} else if (command.equalsIgnoreCase("supportsAltitude")) {
setSupportsAltitude(booleanVal(value));
} else if (command.equalsIgnoreCase("supportsBearing")) {
setSupportsBearing(booleanVal(value));
} else if (command.equalsIgnoreCase("repeat")) {
setRepeat(booleanVal(value));
} else if (command.equalsIgnoreCase("supportsSpeed")) {
setSupportsSpeed(booleanVal(value));
} else if (command.equalsIgnoreCase("powerRequirement")) {
setPowerRequirement(intVal(value));
} else if (command.equalsIgnoreCase("accuracy")) {
setAccuracy(intVal(value));
} else if (command.equalsIgnoreCase("trackspeed")) {
setTrackSpeed(floatVal(value));
} else {
Log.e(LOG_TAG, "Unknown command \"" + command + "\"");
}
}
} catch (IOException ioe) {
Log.e(LOG_TAG, "IOException reading properties file " +
propertiesFile.getPath(), ioe);
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
Log.w(LOG_TAG, "IOException closing properties file " +
propertiesFile.getPath(), e);
}
}
}
public TrackProvider(String name, ILocationManager locationManager) {
super(name, locationManager);
setTimes();
}
public TrackProvider(String name, ILocationManager locationManager, File file) {
this(name, locationManager);
String filename = file.getName();
if (filename.endsWith("kml")) {
readKml(file);
} else if (filename.endsWith("nmea")) {
readNmea(getName(), file);
} else if (filename.endsWith("track")) {
readTrack(file);
} else {
Log.e(LOG_TAG, "Can't initialize TrackProvider from file " +
filename + " (not *kml, *nmea, or *track)");
}
setTimes();
}
private void setTimes() {
mBaseTime = System.currentTimeMillis();
if (mWaypoints.size() >= 2) {
mMinTime = mWaypoints.get(0).getTime();
mMaxTime = mWaypoints.get(mWaypoints.size() - 1).getTime();
} else {
mMinTime = mMaxTime = 0;
}
}
private double interp(double d0, double d1, float frac) {
return d0 + frac * (d1 - d0);
}
private void update() {
long time = System.currentTimeMillis() - mBaseTime;
List<Waypoint> waypoints = mWaypoints;
if (waypoints == null) {
return;
}
int size = waypoints.size();
if (size < 2) {
return;
}
long t = time;
if (t < mMinTime) {
t = mMinTime;
}
if (mRepeat) {
t -= mMinTime;
long deltaT = mMaxTime - mMinTime;
t %= 2 * deltaT;
if (t > deltaT) {
t = 2 * deltaT - t;
}
t += mMinTime;
} else if (t > mMaxTime) {
t = mMaxTime;
}
// Locate the time interval for the current time
// We slide the window since we don't expect to move
// much between calls
Waypoint w0 = waypoints.get(mWaypointIndex);
Waypoint w1 = waypoints.get(mWaypointIndex + 1);
// If the right end of the current interval is too early,
// move forward to the next waypoint
while (t > w1.getTime()) {
w0 = w1;
w1 = waypoints.get(++mWaypointIndex + 1);
}
// If the left end of the current interval is too late,
// move back to the previous waypoint
while (t < w0.getTime()) {
w1 = w0;
w0 = waypoints.get(--mWaypointIndex);
}
// Now we know that w0.mTime <= t <= w1.mTime
long w0Time = w0.getTime();
long w1Time = w1.getTime();
long dt = w1Time - w0Time;
float frac = (dt == 0) ? 0 : ((float) (t - w0Time) / dt);
mLatitude = interp(w0.getLatitude(), w1.getLatitude(), frac);
mLongitude = interp(w0.getLongitude(), w1.getLongitude(), frac);
mHasAltitude = w0.hasAltitude() && w1.hasAltitude();
if (mSupportsAltitude && mHasAltitude) {
mAltitude = interp(w0.getAltitude(), w1.getAltitude(), frac);
}
if (mSupportsBearing) {
mHasBearing = frac <= 0.5f ? w0.hasBearing() : w1.hasBearing();
if (mHasBearing) {
mBearing = frac <= 0.5f ? w0.getBearing() : w1.getBearing();
}
}
if (mSupportsSpeed) {
mHasSpeed = frac <= 0.5f ? w0.hasSpeed() : w1.hasSpeed();
if (mHasSpeed) {
mSpeed = frac <= 0.5f ? w0.getSpeed() : w1.getSpeed();
}
}
mLastTime = time;
mTime = time;
}
public void setRequiresNetwork(boolean requiresNetwork) {
mRequiresNetwork = requiresNetwork;
}
@Override public boolean requiresNetwork() {
return mRequiresNetwork;
}
public void setRequiresSatellite(boolean requiresSatellite) {
mRequiresSatellite = requiresSatellite;
}
@Override public boolean requiresSatellite() {
return mRequiresSatellite;
}
public void setRequiresCell(boolean requiresCell) {
mRequiresCell = requiresCell;
}
@Override public boolean requiresCell() {
return mRequiresCell;
}
public void setHasMonetaryCost(boolean hasMonetaryCost) {
mHasMonetaryCost = hasMonetaryCost;
}
@Override public boolean hasMonetaryCost() {
return mHasMonetaryCost;
}
public void setSupportsAltitude(boolean supportsAltitude) {
mSupportsAltitude = supportsAltitude;
}
@Override public boolean supportsAltitude() {
return mSupportsAltitude;
}
public void setSupportsSpeed(boolean supportsSpeed) {
mSupportsSpeed = supportsSpeed;
}
@Override public boolean supportsSpeed() {
return mSupportsSpeed;
}
public void setSupportsBearing(boolean supportsBearing) {
mSupportsBearing = supportsBearing;
}
@Override public boolean supportsBearing() {
return mSupportsBearing;
}
public void setRepeat(boolean repeat) {
mRepeat = repeat;
}
public void setPowerRequirement(int powerRequirement) {
if (powerRequirement < Criteria.POWER_LOW ||
powerRequirement > Criteria.POWER_HIGH) {
throw new IllegalArgumentException("powerRequirement = " +
powerRequirement);
}
mPowerRequirement = powerRequirement;
}
@Override public int getPowerRequirement() {
return mPowerRequirement;
}
public void setAccuracy(int accuracy) {
mAccuracy = accuracy;
}
@Override public int getAccuracy() {
return mAccuracy;
}
public void setTrackSpeed(float trackSpeed) {
mTrackSpeed = trackSpeed;
}
@Override public synchronized void enable() {
mEnabled = true;
mThread = new TrackProviderThread();
mThread.start();
}
@Override public synchronized void disable() {
mEnabled = false;
if (mThread != null) {
mThread.setDone();
try {
mThread.join();
} catch (InterruptedException e) {
}
mThread = null;
}
}
@Override public boolean isEnabled() {
return mEnabled;
}
@Override public int getStatus(Bundle extras) {
return AVAILABLE;
}
public Location getInitialLocation() {
return mInitialLocation;
}
}
/**
* A simple tuple of (time stamp, latitude, longitude, altitude), plus optional
* extras.
*
* {@hide}
*/
class Waypoint {
public Location mLocation;
public Waypoint(Location location) {
mLocation = location;
}
public Waypoint(String providerName, long time, double latitude, double longitude,
double altitude) {
mLocation = new Location(providerName);
mLocation.setTime(time);
mLocation.setLatitude(latitude);
mLocation.setLongitude(longitude);
mLocation.setAltitude(altitude);
}
public long getTime() {
return mLocation.getTime();
}
public double getLatitude() {
return mLocation.getLatitude();
}
public double getLongitude() {
return mLocation.getLongitude();
}
public boolean hasAltitude() {
return mLocation.hasAltitude();
}
public double getAltitude() {
return mLocation.getAltitude();
}
public boolean hasBearing() {
return mLocation.hasBearing();
}
public void setBearing(float bearing) {
mLocation.setBearing(bearing);
}
public float getBearing() {
return mLocation.getBearing();
}
public boolean hasSpeed() {
return mLocation.hasSpeed();
}
public void setSpeed(float speed) {
mLocation.setSpeed(speed);
}
public float getSpeed() {
return mLocation.getSpeed();
}
public Bundle getExtras() {
return mLocation.getExtras();
}
public Location getLocation() {
return new Location(mLocation);
}
@Override public String toString() {
return "Waypoint[mLocation=" + mLocation + "]";
}
}