diff options
author | Nick Pelly <npelly@google.com> | 2012-07-18 13:13:37 -0700 |
---|---|---|
committer | Nick Pelly <npelly@google.com> | 2012-07-20 10:07:07 -0700 |
commit | 2eeeec248a38ff33999c83f4b8d5bab7d50e79d2 (patch) | |
tree | 2886aaefdd09bbafa7853dbbbb29aa866846045f | |
parent | b8acd060d409f0e81ab3510b429cb86d3f34adb8 (diff) | |
download | frameworks_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
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; } } |