Merge "CTS Tests for GnssMetrics"
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index fc1b57e..9c55177 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -42,6 +42,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
+import android.location.GnssStatus;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
@@ -270,6 +271,81 @@
     }
 
     @Test
+    public void testGpsStatus() {
+        Context context = InstrumentationRegistry.getContext();
+        final LocationManager locManager = context.getSystemService(LocationManager.class);
+
+        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+            Log.e(TAG, "GPS provider is not enabled");
+            return;
+        }
+
+        // Time out set to 85 seconds (5 seconds for sleep and a possible 85 seconds if TTFF takes
+        // max time which would be around 90 seconds.
+        // This is based on similar location cts test timeout values.
+        final int TIMEOUT_IN_MSEC = 85_000;
+        final int SLEEP_TIME_IN_MSEC = 5_000;
+        // TTFF could take up to 90 seconds, thus we need to wait till TTFF does occur if it does
+        // not occur in the first SLEEP_TIME_IN_MSEC
+        final CountDownLatch mLatchTtff = new CountDownLatch(1);
+
+        GnssStatus.Callback gnssStatusCallback = new GnssStatus.Callback() {
+            @Override
+            public void onStarted() {
+                Log.v(TAG, "Gnss Status Listener Started");
+            }
+
+            @Override
+            public void onStopped() {
+                Log.v(TAG, "Gnss Status Listener Stopped");
+            }
+
+            @Override
+            public void onFirstFix(int ttffMillis) {
+                Log.v(TAG, "Gnss Status Listener Received TTFF");
+                mLatchTtff.countDown();
+            }
+
+            @Override
+            public void onSatelliteStatusChanged(GnssStatus status) {
+                Log.v(TAG, "Gnss Status Listener Received Status Update");
+            }
+        };
+
+        boolean gnssStatusCallbackAdded = locManager.registerGnssStatusCallback(
+                gnssStatusCallback, new Handler(Looper.getMainLooper()));
+        if (!gnssStatusCallbackAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.e(TAG, "Failed to start gnss status callback");
+        }
+
+        final LocationListener locListener = new LocationListener() {
+            public void onLocationChanged(Location location) {
+                Log.v(TAG, "onLocationChanged: location has been obtained");
+            }
+            public void onProviderDisabled(String provider) {
+                Log.v(TAG, "onProviderDisabled " + provider);
+            }
+            public void onProviderEnabled(String provider) {
+                Log.v(TAG, "onProviderEnabled " + provider);
+            }
+            public void onStatusChanged(String provider, int status, Bundle extras) {
+                Log.v(TAG, "onStatusChanged " + provider + " " + status);
+            }
+        };
+
+        locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                0,
+                0 /* minDistance */,
+                locListener,
+                Looper.getMainLooper());
+        sleep(SLEEP_TIME_IN_MSEC);
+        waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchTtff, null);
+        locManager.removeUpdates(locListener);
+        locManager.unregisterGnssStatusCallback(gnssStatusCallback);
+    }
+
+    @Test
     public void testScreenBrightness() {
         Context context = InstrumentationRegistry.getContext();
         PowerManager pm = context.getSystemService(PowerManager.class);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index d94316d..1d4b2d6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -583,6 +583,69 @@
         }
     }
 
+    public void testGnssStats() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        // Get GnssMetrics as a simple gauge metric.
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.GNSS_STATS_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+        // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
+        getDevice().executeShellCommand(String.format(
+                "settings put global location_background_throttle_package_whitelist %s",
+                DEVICE_SIDE_TEST_PACKAGE));
+
+        try {
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGpsStatus");
+
+            Thread.sleep(WAIT_TIME_LONG);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+
+            // Assert about GnssMetrics for the test app.
+            List<Atom> atoms = getGaugeMetricDataList();
+
+            boolean found = false;
+            for (Atom atom : atoms) {
+                AtomsProto.GnssStats state = atom.getGnssStats();
+                found = true;
+                assertThat(state.getLocationReports()).isGreaterThan((long) 0);
+                assertThat(state.getLocationFailureReports()).isAtLeast((long) 0);
+                assertThat(state.getTimeToFirstFixReports()).isGreaterThan((long) 0);
+                assertThat(state.getTimeToFirstFixMilliS()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyReports()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyMeters()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0Reports()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0DbMhz()).isGreaterThan((long) 0);
+                assertThat(state.getL5TopFourAverageCn0Reports()).isAtLeast((long) 0);
+                assertThat(state.getL5TopFourAverageCn0DbMhz()).isAtLeast((long) 0);
+                assertThat(state.getSvStatusReports()).isGreaterThan((long) 0);
+                assertThat(state.getSvStatusReportsUsedInFix()).isGreaterThan((long) 0);
+                assertThat(state.getL5SvStatusReports()).isAtLeast((long) 0);
+                assertThat(state.getL5SvStatusReportsUsedInFix()).isAtLeast((long) 0);
+            }
+            assertWithMessage(String.format("Did not find a matching atom"))
+                    .that(found).isTrue();
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+
     public void testMediaCodecActivity() throws Exception {
         if (statsdDisabled()) {
             return;