Add CTS test for StateTracker annotations

Test using a metric which slices by BleScanStateChanged atom and uses a
fake atom as the what for the metric.

Bug: 153880954
Test: atest
CtsStatsdHostTestCases: android.cts.statsd.metric.CountMetricsTests#testSlicedStateCountMetric
Change-Id: I27f561f83bbb638c065e059f677a76fc40f0e3a9
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 53682ad..b53d151 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
@@ -62,6 +62,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -85,6 +86,7 @@
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
 
 public class AtomTests {
     private static final String TAG = AtomTests.class.getSimpleName();
@@ -226,7 +228,68 @@
         performBleScan(scanSettings, Arrays.asList(scanFilter.build()), true);
     }
 
-    private static void performBleScan(ScanSettings scanSettings, List<ScanFilter> scanFilters, boolean waitForResult) {
+    @Test
+    public void testBleScanInterrupted() throws Exception {
+        performBleAction((bluetoothAdapter, bleScanner) -> {
+            ScanSettings scanSettings = new ScanSettings.Builder()
+                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+            ScanCallback scanCallback = new ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult result) {
+                    Log.v(TAG, "called onScanResult");
+                }
+                @Override
+                public void onScanFailed(int errorCode) {
+                    Log.v(TAG, "called onScanFailed");
+                }
+                @Override
+                public void onBatchScanResults(List<ScanResult> results) {
+                    Log.v(TAG, "called onBatchScanResults");
+                }
+            };
+
+            int uid = Process.myUid();
+            int whatAtomId = 9_999;
+
+            // Change state to State.ON.
+            bleScanner.startScan(null, scanSettings, scanCallback);
+            sleep(500);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            bluetoothAdapter.disable();
+            sleep(500);
+
+            // Trigger State.RESET so that new state is State.OFF.
+            if (!bluetoothAdapter.enable()) {
+                Log.e(TAG, "Could not enable bluetooth to trigger state reset");
+                return;
+            }
+            sleep(2_000); // Wait for Bluetooth to fully turn on.
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+        });
+    }
+
+    private static void writeSliceByBleScanStateChangedAtom(int atomId, int firstUid,
+                                                            boolean field2, boolean field3,
+                                                            boolean field4) {
+        final StatsEvent.Builder builder = StatsEvent.newBuilder()
+                .setAtomId(atomId)
+                .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
+                .writeBoolean(field2)
+                .writeBoolean(field3)
+                .writeBoolean(field4)
+                .usePooledBuffer();
+
+        StatsLog.write(builder.build());
+    }
+
+    /**
+     * Set up BluetoothLeScanner and perform the action in the callback.
+     * Restore Bluetooth to original state afterwards.
+     **/
+    private static void performBleAction(BiConsumer<BluetoothAdapter, BluetoothLeScanner> actions) {
         BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         if (bluetoothAdapter == null) {
             Log.e(TAG, "Device does not support Bluetooth");
@@ -238,7 +301,7 @@
                 Log.e(TAG, "Bluetooth is not enabled");
                 return;
             }
-            sleep(8_000);
+            sleep(2_000); // Wait for Bluetooth to fully turn on.
             bluetoothEnabledByTest = true;
         }
         BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
@@ -247,38 +310,45 @@
             return;
         }
 
-        CountDownLatch resultsLatch = new CountDownLatch(1);
-        ScanCallback scanCallback = new ScanCallback() {
-            @Override
-            public void onScanResult(int callbackType, ScanResult result) {
-                Log.v(TAG, "called onScanResult");
-                resultsLatch.countDown();
-            }
-            @Override
-            public void onScanFailed(int errorCode) {
-                Log.v(TAG, "called onScanFailed");
-            }
-            @Override
-            public void onBatchScanResults(List<ScanResult> results) {
-                Log.v(TAG, "called onBatchScanResults");
-                resultsLatch.countDown();
-            }
-        };
+        actions.accept(bluetoothAdapter, bleScanner);
 
-        bleScanner.startScan(scanFilters, scanSettings, scanCallback);
-        if (waitForResult) {
-            waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
-        } else {
-            sleep(2_000);
-        }
-        bleScanner.stopScan(scanCallback);
-
-        // Restore adapter state at end of test
+        // Restore adapter state
         if (bluetoothEnabledByTest) {
             bluetoothAdapter.disable();
         }
     }
 
+
+    private static void performBleScan(ScanSettings scanSettings, List<ScanFilter> scanFilters, boolean waitForResult) {
+        performBleAction((bluetoothAdapter, bleScanner) -> {
+            CountDownLatch resultsLatch = new CountDownLatch(1);
+            ScanCallback scanCallback = new ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult result) {
+                    Log.v(TAG, "called onScanResult");
+                    resultsLatch.countDown();
+                }
+                @Override
+                public void onScanFailed(int errorCode) {
+                    Log.v(TAG, "called onScanFailed");
+                }
+                @Override
+                public void onBatchScanResults(List<ScanResult> results) {
+                    Log.v(TAG, "called onBatchScanResults");
+                    resultsLatch.countDown();
+                }
+            };
+
+            bleScanner.startScan(scanFilters, scanSettings, scanCallback);
+            if (waitForResult) {
+                waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
+            } else {
+                sleep(2_000);
+            }
+            bleScanner.stopScan(scanCallback);
+        });
+    }
+
     @Test
     public void testCameraState() throws Exception {
         Context context = InstrumentationRegistry.getContext();
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
index 4eeb74c..aaf94af 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -21,8 +21,12 @@
 import android.cts.statsd.atom.DeviceAtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.AttributionNode;
+import com.android.os.AtomsProto.BleScanStateChanged;
 import com.android.os.StatsLog;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
@@ -296,7 +300,7 @@
         assertThat(bucketInfo.getCount()).isEqualTo(1);
         assertWithMessage("First report's bucket should be less than 1 day")
                 .that(bucketInfo.getEndBucketElapsedNanos())
-                .isLessThan(bucketInfo.getStartBucketElapsedNanos() + 
+                .isLessThan(bucketInfo.getStartBucketElapsedNanos() +
                         1_000_000_000L * 60L * 60L * 24L);
 
         //Second report should have a count of 2.
@@ -307,4 +311,118 @@
         }
         assertThat(totalCount).isEqualTo(2);
     }
+
+    public void testSlicedStateCountMetric() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+        int whatMatcherId = 3;
+        int stateId = 4;
+
+        // Atom 9999 {
+        //     repeated AttributionNode attribution_node = 1;
+        //     optional bool is_filtered = 2;
+        //     optional bool is_first_match = 3;
+        //     optional bool is_opportunistic = 4;
+        // }
+        int whatAtomId = 9_999;
+
+        StatsdConfigProto.AtomMatcher whatMatcher =
+                MetricsUtils.getAtomMatcher(whatAtomId)
+                        .setId(whatMatcherId)
+                        .build();
+
+        StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder()
+            .setId(stateId)
+            .setAtomId(Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER)
+            .build();
+
+        StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder()
+            .setStateAtomId(Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER)
+            .setFieldsInWhat(FieldMatcher.newBuilder()
+                    .setField(whatAtomId)
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(1)
+                            .setPosition(Position.FIRST)
+                            .addChild(FieldMatcher.newBuilder()
+                                    .setField(AttributionNode.UID_FIELD_NUMBER)
+                            )
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(2)
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(3)
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(4)
+                    )
+            )
+            .setFieldsInState(FieldMatcher.newBuilder()
+                    .setField(Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER)
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(BleScanStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
+                            .setPosition(Position.FIRST)
+                            .addChild(FieldMatcher.newBuilder()
+                                    .setField(AttributionNode.UID_FIELD_NUMBER)
+                            )
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(BleScanStateChanged.IS_FILTERED_FIELD_NUMBER)
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(BleScanStateChanged.IS_FIRST_MATCH_FIELD_NUMBER)
+                    )
+                    .addChild(FieldMatcher.newBuilder()
+                            .setField(BleScanStateChanged.IS_OPPORTUNISTIC_FIELD_NUMBER)
+                    )
+            )
+            .build();
+
+        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                    .setId(MetricsUtils.COUNT_METRIC_ID)
+                    .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                    .setWhat(whatMatcherId)
+                    .addSliceByState(stateId)
+                    .addStateLink(stateLink)
+                )
+                .addAtomMatcher(whatMatcher)
+                .addState(state);
+        uploadConfig(builder);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBleScanInterrupted");
+
+        StatsLogReport metricReport = getStatsLogReport();
+        LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
+        assertThat(metricReport.hasCountMetrics()).isTrue();
+
+        StatsLogReport.CountMetricDataWrapper dataWrapper = metricReport.getCountMetrics();
+        assertThat(dataWrapper.getDataCount()).isEqualTo(2);
+
+        CountMetricData data = dataWrapper.getData(0);
+        assertThat(data.getSliceByStateCount()).isEqualTo(1);
+        assertThat(data.getSliceByState(0).getAtomId())
+                .isEqualTo(Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER);
+        assertThat(data.getSliceByState(0).getValue())
+                .isEqualTo(BleScanStateChanged.State.OFF.ordinal());
+        long totalCount = data.getBucketInfoList().stream()
+                .mapToLong(CountBucketInfo::getCount)
+                .sum();
+        assertThat(totalCount).isEqualTo(3);
+
+        data = dataWrapper.getData(1);
+        assertThat(data.getSliceByStateCount()).isEqualTo(1);
+        assertThat(data.getSliceByState(0).getAtomId())
+                .isEqualTo(Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER);
+        assertThat(data.getSliceByState(0).getValue())
+                .isEqualTo(BleScanStateChanged.State.ON.ordinal());
+        totalCount = data.getBucketInfoList().stream()
+                .mapToLong(CountBucketInfo::getCount)
+                .sum();
+        assertThat(totalCount).isEqualTo(2);
+    }
 }