summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Pelly <npelly@google.com>2012-07-18 13:13:37 -0700
committerNick Pelly <npelly@google.com>2012-07-20 10:07:07 -0700
commit2eeeec248a38ff33999c83f4b8d5bab7d50e79d2 (patch)
tree2886aaefdd09bbafa7853dbbbb29aa866846045f
parentb8acd060d409f0e81ab3510b429cb86d3f34adb8 (diff)
downloadframeworks_base-2eeeec248a38ff33999c83f4b8d5bab7d50e79d2.zip
frameworks_base-2eeeec248a38ff33999c83f4b8d5bab7d50e79d2.tar.gz
frameworks_base-2eeeec248a38ff33999c83f4b8d5bab7d50e79d2.tar.bz2
Improve Location object.
Add getElapsedRealtimeNano(): Currently Location just has getTime() and setTime() based on UTC time. This is entirely unreliable since it is not guaranteed monotonic. There is a lot of code that compares fix age based on deltas - and it is all broken in the case of a system clock change. System clock can change when switching cellular networks (and in some cases when switching towers). Document the meaning of getAccuracy(): It is the horizontal, 95% confidence radius. Make some fields mandatory if they are reported by a LocationProvider: All Locations returned by a LocationProvider must include at the minimum a lat, long, timestamps, and accuracy. This is necessary to perform fused location. There are no public API's for applications to feed locations into a location provider so this should not cause any breakage. If a LocationProvider does not fill in enough fields on a Location object then it is dropped, and logged. Bug: 4305998 Change-Id: I7df77125d8a64e174d7bc8c2708661b4f33461ea
-rw-r--r--api/current.txt2
-rw-r--r--location/java/android/location/Location.java107
-rw-r--r--location/java/android/location/LocationManager.java21
-rw-r--r--services/java/com/android/server/LocationManagerService.java8
-rw-r--r--services/java/com/android/server/UiModeManagerService.java6
-rwxr-xr-xservices/java/com/android/server/location/GpsLocationProvider.java3
-rwxr-xr-xservices/java/com/android/server/location/LocationBasedCountryDetector.java4
7 files changed, 137 insertions, 14 deletions
diff --git a/api/current.txt b/api/current.txt
index 8ae12a2..093d4c3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10504,6 +10504,7 @@ package android.location {
method public float getAccuracy();
method public double getAltitude();
method public float getBearing();
+ method public long getElapsedRealtimeNano();
method public android.os.Bundle getExtras();
method public double getLatitude();
method public double getLongitude();
@@ -10523,6 +10524,7 @@ package android.location {
method public void setAccuracy(float);
method public void setAltitude(double);
method public void setBearing(float);
+ method public void setElapsedRealtimeNano(long);
method public void setExtras(android.os.Bundle);
method public void setLatitude(double);
method public void setLongitude(double);
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index aacf857..5ad60ab 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -19,6 +19,7 @@ package android.location;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.Printer;
import java.text.DecimalFormat;
@@ -59,6 +60,7 @@ public class Location implements Parcelable {
private String mProvider;
private long mTime = 0;
+ private long mElapsedRealtimeNano = 0;
private double mLatitude = 0.0;
private double mLongitude = 0.0;
private boolean mHasAltitude = false;
@@ -84,6 +86,7 @@ public class Location implements Parcelable {
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mProvider=" + mProvider + " mTime=" + mTime);
+ pw.println(prefix + "mElapsedRealtimeNano=" + mElapsedRealtimeNano);
pw.println(prefix + "mLatitude=" + mLatitude + " mLongitude=" + mLongitude);
pw.println(prefix + "mHasAltitude=" + mHasAltitude + " mAltitude=" + mAltitude);
pw.println(prefix + "mHasSpeed=" + mHasSpeed + " mSpeed=" + mSpeed);
@@ -118,6 +121,7 @@ public class Location implements Parcelable {
public void set(Location l) {
mProvider = l.mProvider;
mTime = l.mTime;
+ mElapsedRealtimeNano = l.mElapsedRealtimeNano;
mLatitude = l.mLatitude;
mLongitude = l.mLongitude;
mHasAltitude = l.mHasAltitude;
@@ -137,6 +141,7 @@ public class Location implements Parcelable {
public void reset() {
mProvider = null;
mTime = 0;
+ mElapsedRealtimeNano = 0;
mLatitude = 0;
mLongitude = 0;
mHasAltitude = false;
@@ -467,23 +472,62 @@ public class Location implements Parcelable {
}
/**
- * Returns the UTC time of this fix, in milliseconds since January 1,
+ * Return the UTC time of this fix, in milliseconds since January 1,
* 1970.
+ * <p>Note that the UTC time on a device is not monotonic: it
+ * can jump forwards or backwards unpredictably. So always use
+ * {@link #getElapsedRealtimeNano()} when calculating time deltas.
+ * <p>On the other hand, {@link #getTime()} is useful for presenting
+ * a human readable time to the user, or for carefully comparing
+ * location fixes across reboot or across devices.
+ * <p>This method will always return a valid timestamp on
+ * Locations generated by a {@link LocationProvider}.
+ *
+ * @return time of fix, in milliseconds since January 1, 1970.
*/
public long getTime() {
return mTime;
}
/**
- * Sets the UTC time of this fix, in milliseconds since January 1,
+ * Set the UTC time of this fix, in milliseconds since January 1,
* 1970.
+ *
+ * @param time UTC time of this fix, in milliseconds since January 1, 1970
*/
public void setTime(long time) {
mTime = time;
}
/**
- * Returns the latitude of this fix.
+ * Return the time of this fix, in elapsed real-time since system boot.
+ * <p>This value can be reliably compared to
+ * {@link android.os.SystemClock#elapsedRealtimeNano()},
+ * to calculate the age of a fix, and to compare Location fixes, since
+ * elapsed real-time is guaranteed monotonic for each system boot, and
+ * continues to increment even when the system is in deep sleep.
+ * <p>This method will always return a valid timestamp on
+ * Locations generated by a {@link LocationProvider}.
+ *
+ * @return elapsed real-time of fix, in nanoseconds since system boot.
+ */
+ public long getElapsedRealtimeNano() {
+ return mElapsedRealtimeNano;
+ }
+
+ /**
+ * Set the time of this fix, in elapsed real-time since system boot.
+ *
+ * @param time elapsed real-time of fix, in nanoseconds since system boot.
+ */
+ public void setElapsedRealtimeNano(long time) {
+ mElapsedRealtimeNano = time;
+ }
+
+ /**
+ * Return the latitude of this fix.
+ * <p>This method will always return a valid latitude on
+ * Locations generated by a {@link LocationProvider}.
*/
public double getLatitude() {
return mLatitude;
@@ -497,7 +541,9 @@ public class Location implements Parcelable {
}
/**
- * Returns the longitude of this fix.
+ * Return the longitude of this fix.
+ * <p>This method will always return a valid longitude on
+ * Locations generated by a {@link LocationProvider}.
*/
public double getLongitude() {
return mLongitude;
@@ -619,16 +665,27 @@ public class Location implements Parcelable {
}
/**
- * Returns true if the provider is able to report accuracy information,
- * false otherwise. The default implementation returns false.
+ * Return true if this Location has an associated accuracy.
+ * <p>All Location objects generated by a {@link LocationProvider}
+ * will have an accuracy.
*/
public boolean hasAccuracy() {
return mHasAccuracy;
}
/**
- * Returns the accuracy of the fix in meters. If hasAccuracy() is false,
- * 0.0 is returned.
+ * Return the accuracy of this Location fix.
+ * <p>Accuracy is measured in meters, and indicates the
+ * radius of 95% confidence.
+ * In other words, there is a 95% probability that the
+ * true location is within a circle centered at the reported
+ * location, with radius of the reported accuracy.
+ * <p>This is only a measure of horizontal accuracy, and does
+ * not indicate the accuracy of bearing, velocity or altitude
+ * if those are included in this Location.
+ * <p>If {@link #hasAccuracy} is false, 0.0 is returned.
+ * <p>All Location object generated by a {@link LocationProvider}
+ * will have a valid accuracy.
*/
public float getAccuracy() {
return mAccuracy;
@@ -653,6 +710,37 @@ public class Location implements Parcelable {
}
/**
+ * Return true if this Location object has enough data set to
+ * be considered a valid fix from a {@link LocationProvider}.
+ * @see #makeComplete
+ * @hide
+ */
+ public boolean isComplete() {
+ if (mProvider == null) return false;
+ if (!mHasAccuracy) return false;
+ if (mTime == 0) return false;
+ if (mElapsedRealtimeNano == 0) return false;
+ return true;
+ }
+
+ /**
+ * Helper to fill in incomplete fields.
+ * Only use this to assist in backwards compatibility
+ * with Location objects received from applications.
+ * @see #isComplete
+ * @hide
+ */
+ public void makeComplete() {
+ if (mProvider == null) mProvider = "?";
+ if (!mHasAccuracy) {
+ mHasAccuracy = true;
+ mAccuracy = 100.0f;
+ }
+ if (mTime == 0) mTime = System.currentTimeMillis();
+ if (mElapsedRealtimeNano == 0) mElapsedRealtimeNano = SystemClock.elapsedRealtimeNano();
+ }
+
+ /**
* Returns additional provider-specific information about the
* location fix as a Bundle. The keys and values are determined
* by the provider. If no additional information is available,
@@ -681,6 +769,7 @@ public class Location implements Parcelable {
@Override public String toString() {
return "Location[mProvider=" + mProvider +
",mTime=" + mTime +
+ ",mElapsedRealtimeNano=" + mElapsedRealtimeNano +
",mLatitude=" + mLatitude +
",mLongitude=" + mLongitude +
",mHasAltitude=" + mHasAltitude +
@@ -700,6 +789,7 @@ public class Location implements Parcelable {
String provider = in.readString();
Location l = new Location(provider);
l.mTime = in.readLong();
+ l.mElapsedRealtimeNano = in.readLong();
l.mLatitude = in.readDouble();
l.mLongitude = in.readDouble();
l.mHasAltitude = in.readInt() != 0;
@@ -726,6 +816,7 @@ public class Location implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mProvider);
parcel.writeLong(mTime);
+ parcel.writeLong(mElapsedRealtimeNano);
parcel.writeDouble(mLatitude);
parcel.writeDouble(mLongitude);
parcel.writeInt(mHasAltitude ? 1 : 0);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index ff74f41..15a2928 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -19,11 +19,13 @@ package android.location;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Message;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.location.DummyLocationProvider;
@@ -1220,8 +1222,11 @@ public class LocationManager {
}
/**
- * Sets a mock location for the given provider. This location will be used in place
- * of any actual location from the provider.
+ * Sets a mock location for the given provider.
+ * <p>This location will be used in place of any actual location from the provider.
+ * The location object must have a minimum number of fields set to be
+ * considered a valid LocationProvider Location, as per documentation
+ * on {@link Location} class.
*
* @param provider the provider name
* @param loc the mock location
@@ -1230,8 +1235,20 @@ public class LocationManager {
* or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
* Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
* @throws IllegalArgumentException if no provider with the given name exists
+ * @throws IllegalArgumentException if the location is incomplete
*/
public void setTestProviderLocation(String provider, Location loc) {
+ if (!loc.isComplete()) {
+ if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
+ // for backwards compatibility, allow mock locations that are incomplete
+ Log.w(TAG, "Incomplete Location object", new Throwable());
+ loc.makeComplete();
+ } else {
+ throw new IllegalArgumentException(
+ "Location object not complete. Missing timestamps or accuracy?");
+ }
+ }
+
try {
mService.setTestProviderLocation(provider, loc);
} catch (RemoteException ex) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 06b056d..1498a11 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1532,6 +1532,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission");
}
+ if (!location.isComplete()) {
+ Log.w(TAG, "Dropping incomplete location: " + location);
+ return;
+ }
+
mLocationHandler.removeMessages(MESSAGE_LOCATION_CHANGED, location);
Message m = Message.obtain(mLocationHandler, MESSAGE_LOCATION_CHANGED, location);
m.arg1 = (passive ? 1 : 0);
@@ -1588,7 +1593,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// Check whether sufficient time has passed
long minTime = record.mMinTime;
- if (loc.getTime() - lastLoc.getTime() < minTime - MAX_PROVIDER_SCHEDULING_JITTER) {
+ long delta = (loc.getElapsedRealtimeNano() - lastLoc.getElapsedRealtimeNano()) / 1000000L;
+ if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER) {
return false;
}
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index d1f92a7..5299b71 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -308,7 +308,7 @@ class UiModeManagerService extends IUiModeManager.Stub {
/* if new location is older than the current one, the devices hasn't
* moved.
*/
- if (location.getTime() < mLocation.getTime()) {
+ if (location.getElapsedRealtimeNano() < mLocation.getElapsedRealtimeNano()) {
return false;
}
@@ -764,7 +764,8 @@ class UiModeManagerService extends IUiModeManager.Stub {
mLocationManager.getLastKnownLocation(providers.next());
// pick the most recent location
if (location == null || (lastKnownLocation != null &&
- location.getTime() < lastKnownLocation.getTime())) {
+ location.getElapsedRealtimeNano() <
+ lastKnownLocation.getElapsedRealtimeNano())) {
location = lastKnownLocation;
}
}
@@ -781,6 +782,7 @@ class UiModeManagerService extends IUiModeManager.Stub {
location.setLatitude(0);
location.setAccuracy(417000.0f);
location.setTime(System.currentTimeMillis());
+ location.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano());
}
synchronized (mLock) {
mLocation = location;
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 4ad6140..8e75d94 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -1078,6 +1078,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
mLocation.setLatitude(latitude);
mLocation.setLongitude(longitude);
mLocation.setTime(timestamp);
+ // It would be nice to push the elapsed real-time timestamp
+ // further down the stack, but this is still useful
+ mLocation.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano());
}
if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
mLocation.setAltitude(altitude);
diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java
index d4fb8ee..38871d7 100755
--- a/services/java/com/android/server/location/LocationBasedCountryDetector.java
+++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -114,7 +114,9 @@ public class LocationBasedCountryDetector extends CountryDetectorBase {
for (String provider : providers) {
Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
if (lastKnownLocation != null) {
- if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) {
+ if (bestLocation == null ||
+ bestLocation.getElapsedRealtimeNano() <
+ lastKnownLocation.getElapsedRealtimeNano()) {
bestLocation = lastKnownLocation;
}
}