Improve GPS test.

Add a 5 second interval - this one is important to test new
LocationManager filtering code that kicks in between 1s and 10s
for GPS.

Add a passive provider listener, to verify the provider impl
is not doing much more work than it should (and just filtering
results in LocationManager).

Change-Id: I777c687b6037cb5fbb1bf1011bad331fec752c45
diff --git a/apps/CtsVerifier/res/layout/pass_fail_text.xml b/apps/CtsVerifier/res/layout/pass_fail_text.xml
index 432f26d..e6b6a73 100644
--- a/apps/CtsVerifier/res/layout/pass_fail_text.xml
+++ b/apps/CtsVerifier/res/layout/pass_fail_text.xml
@@ -19,8 +19,9 @@
         android:layout_height="match_parent"
         >
 
-    <ScrollView android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+    <ScrollView android:id="@+id/scroll"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
             android:layout_weight="1"
             >
         <TextView android:id="@+id/text"
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 93cdfc5..31b5854 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/GpsTestActivity.java
@@ -24,8 +24,12 @@
 import android.graphics.Typeface;
 import android.location.LocationManager;
 import android.os.Bundle;
+import android.text.Layout;
 import android.text.Spannable;
 import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.View;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
 interface PassFailLog {
@@ -40,6 +44,7 @@
 public class GpsTestActivity extends PassFailButtons.Activity implements PassFailLog {
     private LocationManager mLocationManager;
     private TextView mTextView;
+    private ScrollView mScrollView;
 
     LocationVerifier mLocationVerifier;
     private int mTestNumber;
@@ -57,6 +62,7 @@
         mTextView = (TextView) findViewById(R.id.text);
         mTextView.setTypeface(Typeface.MONOSPACE);
         mTextView.setTextSize(15.0f);
+        mScrollView = (ScrollView) findViewById(R.id.scroll);
     }
 
     @Override
@@ -99,12 +105,18 @@
                 mLocationVerifier.start();
                 break;
             case 3:
+                // Test GPS with minTime = 5s
+                mLocationVerifier = new LocationVerifier(this, mLocationManager,
+                        LocationManager.GPS_PROVIDER, 5 * 1000, 4);
+                mLocationVerifier.start();
+                break;
+            case 4:
                 // Test GPS with minTime = 15s
                 mLocationVerifier = new LocationVerifier(this, mLocationManager,
                         LocationManager.GPS_PROVIDER, 15 * 1000, 4);
                 mLocationVerifier.start();
                 break;
-            case 4:
+            case 5:
                 log("All GPS tests complete", Color.GREEN);
                 getPassButton().setEnabled(true);
                 break;
@@ -115,6 +127,14 @@
     public void log(String s) {
         mTextView.append(s);
         mTextView.append("\n");
+
+        // Scroll to bottom
+        mScrollView.post(new Runnable() {
+            @Override
+            public void run() {
+                mScrollView.fullScroll(View.FOCUS_DOWN);
+            }
+        });
     }
 
     private void log(String s, int color) {
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 93c97eb..fd226a6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/LocationVerifier.java
@@ -22,9 +22,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.os.SystemClock;
 
-public class LocationVerifier implements LocationListener, Handler.Callback {
+public class LocationVerifier implements Handler.Callback {
     public static final String TAG = "CtsVerifierLocation";
 
     private static final int MSG_TIMEOUT = 1;
@@ -33,111 +32,158 @@
     private final PassFailLog mCb;
     private final String mProvider;
     private final long mInterval;
-    private final long mMinInterval;
-    private final long mMaxInterval;
+    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 long mLastTimestamp = -1;
-    private int mNumUpdates = 0;
+    private long mLastActiveTimestamp = -1;
+    private long mLastPassiveTimestamp = -1;
+    private int mNumActiveUpdates = 0;
+    private int mNumPassiveUpdates = 0;
     private boolean mRunning = false;
 
+    private class ActiveListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            if (!mRunning) return;
+
+            mNumActiveUpdates++;
+            scheduleTimeout();
+
+            long timestamp = location.getTime();
+            long delta = timestamp - mLastActiveTimestamp;
+            mLastActiveTimestamp = timestamp;
+
+            if (mNumActiveUpdates != 1 && delta < mMinActiveInterval) {
+                fail(mProvider + " location updated too fast: " + delta + "ms < " +
+                        mMinActiveInterval + "ms");
+                return;
+            }
+
+            mCb.log("active " + mProvider + " update (" + delta + "ms)");
+
+            if (!mProvider.equals(location.getProvider())) {
+                fail("wrong provider in callback, actual: " + location.getProvider() +
+                        " expected: " + mProvider);
+                return;
+            }
+
+            if (mNumActiveUpdates >= mRequestedUpdates) {
+                if (mNumPassiveUpdates < mRequestedUpdates - 1) {
+                    fail("passive location updates not working (expected: " + mRequestedUpdates +
+                            " received: " + mNumPassiveUpdates + ")");
+                }
+                pass();
+            }
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) { }
+        @Override
+        public void onProviderEnabled(String provider) { }
+        @Override
+        public void onProviderDisabled(String provider) { }
+    }
+
+    private class PassiveListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            if (!mRunning) return;
+            if (!location.getProvider().equals(mProvider)) return;
+
+            mNumPassiveUpdates++;
+            long timestamp = location.getTime();
+            long delta = timestamp - mLastPassiveTimestamp;
+            mLastPassiveTimestamp = timestamp;
+
+            if (mNumPassiveUpdates != 1 && delta < mMinPassiveInterval) {
+                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;
+            }
+
+            mCb.log("passive " + mProvider + " update (" + delta + "ms)");
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) { }
+        @Override
+        public void onProviderEnabled(String provider) { }
+        @Override
+        public void onProviderDisabled(String provider) { }
+    }
+
     public LocationVerifier(PassFailLog cb, LocationManager locationManager,
             String provider, long requestedInterval, int numUpdates) {
         mProvider = provider;
         mInterval = requestedInterval;
-        // Updates can be up to 100ms fast
-        mMinInterval = Math.max(0, requestedInterval - 100);
-        // timeout at 60 seconds
-        mMaxInterval = requestedInterval + 60 * 1000;
+        // Updates can be up to 100ms ahead of schedule
+        mMinActiveInterval = Math.max(0, requestedInterval - 100);
+        // 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;
         mLocationManager = locationManager;
         mCb = cb;
         mHandler = new Handler(this);
+        mActiveListener = new ActiveListener();
+        mPassiveListener = new PassiveListener();
     }
 
     public void start() {
-        mCb.log("enabling " + mProvider + " for " + mInterval + "ms updates");
         mRunning = true;
-        expectNextUpdate(mMaxInterval);
-        mLastTimestamp = SystemClock.elapsedRealtime();
+        scheduleTimeout();
+        mLastActiveTimestamp = System.currentTimeMillis();
+        mLastPassiveTimestamp = mLastActiveTimestamp;
+        mCb.log("enabling passive listener");
+        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0,
+                mPassiveListener);
+        mCb.log("enabling " + mProvider + " (minTime=" + mInterval + "ms)");
         mLocationManager.requestLocationUpdates(mProvider, mInterval, 0,
-                LocationVerifier.this);
+                mActiveListener);
     }
 
     public void stop() {
         mRunning = false;
-        mLocationManager.removeUpdates(LocationVerifier.this);
+        mCb.log("disabling " + mProvider);
+        mLocationManager.removeUpdates(mActiveListener);
+        mCb.log("disabling passive listener");
+        mLocationManager.removeUpdates(mPassiveListener);
         mHandler.removeMessages(MSG_TIMEOUT);
     }
 
     private void pass() {
         stop();
-        mCb.log("disabling " + mProvider);
         mCb.pass();
     }
 
     private void fail(String s) {
         stop();
-        mCb.log("disabling");
         mCb.fail(s);
     }
 
-    private void expectNextUpdate(long timeout) {
+    private void scheduleTimeout() {
         mHandler.removeMessages(MSG_TIMEOUT);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), timeout);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), mTimeout);
     }
 
     @Override
     public boolean handleMessage(Message msg) {
         if (!mRunning) return true;
-        fail("timeout (" + mMaxInterval + "ms) waiting for " +
+        fail("timeout (" + mTimeout + "ms) waiting for " +
                 mProvider + " location change");
         return true;
     }
-
-    @Override
-    public void onLocationChanged(Location location) {
-        if (!mRunning) return;
-
-        mNumUpdates++;
-        expectNextUpdate(mMaxInterval);
-
-        long timestamp = SystemClock.elapsedRealtime();
-        long delta = timestamp - mLastTimestamp;
-        mLastTimestamp = timestamp;
-
-        if (delta > mMaxInterval) {
-            fail(mProvider + " location changed too slow: " + delta + "ms > " +
-                    mMaxInterval + "ms");
-            return;
-        } else if (mNumUpdates == 1) {
-            mCb.log("received " + mProvider + " location (1st update, " + delta + "ms)");
-        } else if (delta < mMinInterval) {
-            fail(mProvider + " location updated too fast: " + delta + "ms < " +
-                    mMinInterval + "ms");
-            return;
-        } else {
-            mCb.log("received " + mProvider + " location (" + delta + "ms)");
-        }
-
-        if (!mProvider.equals(location.getProvider())) {
-            fail("wrong provider in callback, actual: " + location.getProvider() +
-                    " expected: " + mProvider);
-            return;
-        }
-
-        if (mNumUpdates >= mRequestedUpdates) {
-            pass();
-        }
-    }
-
-    @Override
-    public void onStatusChanged(String provider, int status, Bundle extras) {  }
-
-    @Override
-    public void onProviderEnabled(String provider) {  }
-
-    @Override
-    public void onProviderDisabled(String provider) {   }
 }
\ No newline at end of file