Assert mean and stdev of update deltas

Assert mean and standard deviation of location update deltas in GPS CTS
test, instead of individual values of deltas. This gives more
flexibility to implementations that cannot respect an exect schedule.
This still prevents implementations from computing too many fixes and
using too much battery.

Change-Id: Ic889c567bc7262073da87eba493ffee0f9e68b4d
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
index 31b5854..bb12b2b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
@@ -95,25 +95,25 @@
             case 1:
                 // Test GPS with minTime = 0
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 0, 10);
+                        LocationManager.GPS_PROVIDER, 0, 8);
                 mLocationVerifier.start();
                 break;
             case 2:
                 // Test GPS with minTime = 1s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 1000, 10);
+                        LocationManager.GPS_PROVIDER, 1 * 1000, 8);
                 mLocationVerifier.start();
                 break;
             case 3:
                 // Test GPS with minTime = 5s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 5 * 1000, 4);
+                        LocationManager.GPS_PROVIDER, 5 * 1000, 8);
                 mLocationVerifier.start();
                 break;
             case 4:
                 // Test GPS with minTime = 15s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
-                        LocationManager.GPS_PROVIDER, 15 * 1000, 4);
+                        LocationManager.GPS_PROVIDER, 15 * 1000, 8);
                 mLocationVerifier.start();
                 break;
             case 5:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
index 5bd5c49..b6d2a9e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
@@ -23,7 +23,11 @@
 import android.os.Handler;
 import android.os.Message;
 
+import java.util.List;
+import java.util.ArrayList;
+
 public class LocationVerifier implements Handler.Callback {
+
     public static final String TAG = "CtsVerifierLocation";
 
     private static final int MSG_TIMEOUT = 1;
@@ -31,18 +35,29 @@
     /** Timing failures on first NUM_IGNORED_UPDATES updates are ignored. */
     private static final int NUM_IGNORED_UPDATES = 2;
 
+    /* The mean computed for the deltas should not be smaller
+     * than mInterval * MIN_MEAN_RATIO */
+    private static final double MIN_MEAN_RATIO = 0.75;
+
+    /**
+     * The standard deviation computed for the deltas should not be bigger
+     * than mInterval * ALLOWED_STDEV_ERROR_RATIO
+     * or MIN_STDEV_MS, whichever is higher.
+     */
+    private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
+    private static final long MIN_STDEV_MS = 1000;
+
     private final LocationManager mLocationManager;
     private final PassFailLog mCb;
     private final String mProvider;
     private final long mInterval;
-    private final long mMinActiveInterval;
-    private final long mMinPassiveInterval;
     private final long mTimeout;
     private final Handler mHandler;
     private final int mRequestedUpdates;
     private final ActiveListener mActiveListener;
     private final PassiveListener mPassiveListener;
 
+    private boolean isTestOutcomeSet = false;
     private long mLastActiveTimestamp = -1;
     private long mLastPassiveTimestamp = -1;
     private int mNumActiveUpdates = 0;
@@ -50,6 +65,9 @@
     private boolean mRunning = false;
     private boolean mActiveLocationArrive = false;
 
+    private List<Long> mActiveDeltas = new ArrayList();
+    private List<Long> mPassiveDeltas = new ArrayList();
+
     private class ActiveListener implements LocationListener {
         @Override
         public void onLocationChanged(Location location) {
@@ -63,30 +81,17 @@
             long delta = timestamp - mLastActiveTimestamp;
             mLastActiveTimestamp = timestamp;
 
-            if (delta < mMinActiveInterval) {
-                if (mNumActiveUpdates > NUM_IGNORED_UPDATES ) {
-                    fail(mProvider + " location updated too fast: " + delta + "ms < " +
-                         mMinActiveInterval + "ms");
-                    return;
-                } else {
-                    mCb.log("WARNING: active " + mProvider + " location updated too fast: " +
-                         delta + "ms < " + mMinActiveInterval + "ms");
-                }
-            }
-
-            mCb.log("active " + mProvider + " update (" + delta + "ms)");
-
-            if (!mProvider.equals(location.getProvider())) {
-                fail("wrong provider in callback, actual: " + location.getProvider() +
-                        " expected: " + mProvider);
+            if (mNumActiveUpdates <= NUM_IGNORED_UPDATES ) {
+                mCb.log("(ignored) active " + mProvider + " update (" + delta + "ms)");
                 return;
             }
 
+            mActiveDeltas.add(delta);
+            mCb.log("active " + mProvider + " update (" + delta + "ms)");
+
             if (mNumActiveUpdates >= mRequestedUpdates) {
-                if (mNumPassiveUpdates < mRequestedUpdates - 1) {
-                    fail("passive location updates not working (expected: " + mRequestedUpdates +
-                            " received: " + mNumPassiveUpdates + ")");
-                }
+                assertMeanAndStdev(mProvider, mActiveDeltas);
+                assertMeanAndStdev(LocationManager.PASSIVE_PROVIDER, mPassiveDeltas);
                 pass();
             }
         }
@@ -99,6 +104,45 @@
         public void onProviderDisabled(String provider) { }
     }
 
+    private void assertMeanAndStdev(String provider, List<Long> deltas) {
+        double mean = computeMean(deltas);
+        double stdev = computeStdev(mean, deltas);
+
+        double minMean = mInterval * MIN_MEAN_RATIO;
+        if (mean < minMean) {
+            fail(provider + " provider mean too small: " + mean
+                 + " (min: " + minMean + ")");
+            return;
+        }
+
+        double maxStdev = Math.max(MIN_STDEV_MS, mInterval * ALLOWED_STDEV_ERROR_RATIO);
+        if (stdev > maxStdev) {
+            fail (provider + " provider stdev too big: "
+                  + stdev + " (max: " + maxStdev + ")");
+            return;
+        }
+
+        mCb.log(provider + " provider mean: " + mean);
+        mCb.log(provider + " provider stdev: " + stdev);
+    }
+
+    private double computeMean(List<Long> deltas) {
+        long accumulator = 0;
+        for (long d : deltas) {
+            accumulator += d;
+        }
+        return accumulator / deltas.size();
+    }
+
+    private double computeStdev(double mean, List<Long> deltas) {
+        double accumulator = 0;
+        for (long d : deltas) {
+            double diff = d - mean;
+            accumulator += diff * diff;
+        }
+        return Math.sqrt(accumulator / (deltas.size() - 1));
+    }
+
     private class PassiveListener implements LocationListener {
         @Override
         public void onLocationChanged(Location location) {
@@ -119,22 +163,12 @@
             long delta = timestamp - mLastPassiveTimestamp;
             mLastPassiveTimestamp = timestamp;
 
-            if (delta < mMinPassiveInterval) {
-                if (mNumPassiveUpdates > NUM_IGNORED_UPDATES) {
-                    fail("passive " + mProvider + " location updated too fast: " + delta + "ms < " +
-                         mMinPassiveInterval + "ms");
-                    mCb.log("when passive updates are much much faster than active updates it " +
-                            "suggests the location provider implementation is not power efficient");
-                    if (LocationManager.GPS_PROVIDER.equals(mProvider)) {
-                        mCb.log("check GPS_CAPABILITY_SCHEDULING in GPS driver");
-                    }
-                    return;
-                } else {
-                    mCb.log("WARNING: passive " + mProvider + " location updated too fast: " +
-                            delta + "ms < " + mMinPassiveInterval + "ms");
-                }
+            if (mNumPassiveUpdates <= NUM_IGNORED_UPDATES) {
+                mCb.log("(ignored) passive " + mProvider + " update (" + delta + "ms)");
+                return;
             }
 
+            mPassiveDeltas.add(delta);
             mCb.log("passive " + mProvider + " update (" + delta + "ms)");
         }
 
@@ -150,12 +184,6 @@
             String provider, long requestedInterval, int numUpdates) {
         mProvider = provider;
         mInterval = requestedInterval;
-        // Updates can be up to 15% of the request interval ahead of schedule
-        mMinActiveInterval = Math.max(0, (long) (requestedInterval * 0.85));
-        // Allow passive updates to be up to 10x faster than active updates,
-        // beyond that it is very likely the implementation is not taking
-        // advantage of the interval to be power efficient
-        mMinPassiveInterval = mMinActiveInterval / 10;
         // timeout at 60 seconds after interval time
         mTimeout = requestedInterval + 60 * 1000;
         mRequestedUpdates = numUpdates + NUM_IGNORED_UPDATES;
@@ -189,13 +217,19 @@
     }
 
     private void pass() {
-        stop();
-        mCb.pass();
+        if (!isTestOutcomeSet) {
+            stop();
+            mCb.pass();
+            isTestOutcomeSet = true;
+        }
     }
 
     private void fail(String s) {
-        stop();
-        mCb.fail(s);
+        if (!isTestOutcomeSet) {
+            stop();
+            mCb.fail(s);
+            isTestOutcomeSet = true;
+        }
     }
 
     private void scheduleTimeout() {