Merge "CTS for ForegroundServiceAccessAppOpOccurred atom" into rvc-dev
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 9c55177..7458be5 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
@@ -22,7 +22,10 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
@@ -61,6 +64,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -71,6 +76,8 @@
 public class AtomTests {
     private static final String TAG = AtomTests.class.getSimpleName();
 
+    private static final String MY_PACKAGE_NAME = "com.android.server.cts.device.statsd";
+
     @Test
     public void testAudioState() {
         // TODO: This should surely be getTargetContext(), here and everywhere, but test first.
@@ -230,6 +237,58 @@
     }
 
     @Test
+    public void testForegroundServiceAccessAppOp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        Intent fgsIntent = new Intent(context, StatsdCtsForegroundService.class);
+        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
+        // No foreground service session
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION, true);
+
+        // Foreground service session 1
+        context.startService(fgsIntent);
+        while (!checkIfServiceRunning(context, StatsdCtsForegroundService.class.getName())) {
+            sleep(50);
+        }
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA, true);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION, true);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA, true);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO, false);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO, true);
+        context.stopService(fgsIntent);
+
+        // No foreground service session
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION, true);
+
+        // TODO(b/149098800): Start fgs a second time and log OPSTR_CAMERA again
+    }
+
+    /** @param doNote true if should use noteOp; false if should use startOp. */
+    private void noteAppOp(AppOpsManager appOpsManager, String opStr, boolean doNote) {
+        if (doNote) {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(appOpsManager,
+                    (aom) -> aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null,
+                            "statsdTest"));
+        } else {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(appOpsManager,
+                    (aom) -> aom.startOp(opStr, android.os.Process.myUid(),
+                            MY_PACKAGE_NAME, null, "statsdTest"));
+        }
+        sleep(500);
+    }
+
+    /** Check if service is running. */
+    public boolean checkIfServiceRunning(Context context, String serviceName) {
+        ActivityManager manager = context.getSystemService(ActivityManager.class);
+        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (serviceName.equals(service.service.getClassName()) && service.foreground) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Test
     public void testGpsScan() {
         Context context = InstrumentationRegistry.getContext();
         final LocationManager locManager = context.getSystemService(LocationManager.class);
@@ -396,7 +455,7 @@
 
     @Test
     public void testScheduledJob() throws Exception {
-        final ComponentName name = new ComponentName("com.android.server.cts.device.statsd",
+        final ComponentName name = new ComponentName(MY_PACKAGE_NAME,
                 StatsdJobService.class.getName());
 
         Context context = InstrumentationRegistry.getContext();
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 1d4b2d6..ac45c06 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -22,7 +22,6 @@
 import android.os.WakeLockLevelEnum;
 import android.server.ErrorSource;
 
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.ANROccurred;
@@ -40,12 +39,13 @@
 import com.android.os.AtomsProto.DangerousPermissionStateSampled;
 import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
 import com.android.os.AtomsProto.FlashlightStateChanged;
+import com.android.os.AtomsProto.ForegroundServiceAppOpSessionEnded;
 import com.android.os.AtomsProto.ForegroundServiceStateChanged;
 import com.android.os.AtomsProto.GpsScanStateChanged;
 import com.android.os.AtomsProto.HiddenApiUsed;
 import com.android.os.AtomsProto.IonHeapSize;
-import com.android.os.AtomsProto.LooperStats;
 import com.android.os.AtomsProto.LmkKillOccurred;
+import com.android.os.AtomsProto.LooperStats;
 import com.android.os.AtomsProto.MediaCodecStateChanged;
 import com.android.os.AtomsProto.OverlayStateChanged;
 import com.android.os.AtomsProto.PackageNotificationPreferences;
@@ -73,7 +73,6 @@
 import java.lang.ProcessBuilder;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -544,6 +543,51 @@
                 atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
     }
 
+
+    public void testForegroundServiceAccessAppOp() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final int atomTag = Atom.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED_FIELD_NUMBER;
+        final String name = "testForegroundServiceAccessAppOp";
+
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertWithMessage("Wrong atom size").that(data.size()).isEqualTo(3);
+        for (int i = 0; i < data.size(); i++) {
+            ForegroundServiceAppOpSessionEnded atom
+                    = data.get(i).getAtom().getForegroundServiceAppOpSessionEnded();
+            final int opName = atom.getAppOpName().getNumber();
+            final int acceptances = atom.getCountOpsAccepted();
+            final int rejections = atom.getCountOpsRejected();
+            final int count = acceptances + rejections;
+            int expectedCount = 0;
+            switch (opName) {
+                case ForegroundServiceAppOpSessionEnded.AppOpName.OP_CAMERA_VALUE:
+                    expectedCount = 2;
+                    break;
+                case ForegroundServiceAppOpSessionEnded.AppOpName.OP_FINE_LOCATION_VALUE:
+                    expectedCount = 1;
+                    break;
+                case ForegroundServiceAppOpSessionEnded.AppOpName.OP_RECORD_AUDIO_VALUE:
+                    expectedCount = 2;
+                    break;
+                case ForegroundServiceAppOpSessionEnded.AppOpName.OP_COARSE_LOCATION_VALUE:
+                    // fall-through
+                default:
+                    fail("Unexpected opName " + opName);
+            }
+            assertWithMessage("Wrong count for " + opName).that(count).isEqualTo(expectedCount);
+
+        }
+    }
+
     public void testGpsScan() throws Exception {
         if (statsdDisabled()) {
             return;