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
diff --git a/api/current.txt b/api/current.txt
index 8ae12a2..093d4c358 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10504,6 +10504,7 @@
     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 @@
     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 @@
 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 @@
 
     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 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 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 void reset() {
         mProvider = null;
         mTime = 0;
+        mElapsedRealtimeNano = 0;
         mLatitude = 0;
         mLongitude = 0;
         mHasAltitude = false;
@@ -467,23 +472,62 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
-     * 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 @@
     }
 
     /**
+     * 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 @@
     @Override public String toString() {
         return "Location[mProvider=" + mProvider +
             ",mTime=" + mTime +
+            ",mElapsedRealtimeNano=" + mElapsedRealtimeNano +
             ",mLatitude=" + mLatitude +
             ",mLongitude=" + mLongitude +
             ",mHasAltitude=" + mHasAltitude +
@@ -700,6 +789,7 @@
             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 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 @@
 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 @@
     }
 
     /**
-     * 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 @@
      * 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 @@
             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 @@
 
         // 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 @@
             /* 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 @@
                         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 @@
                 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 @@
                 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..38871d78 100755
--- a/services/java/com/android/server/location/LocationBasedCountryDetector.java
+++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -114,7 +114,9 @@
         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;
                 }
             }