RESTRICT AUTOMERGE Add test for mic finishes

Ensure the indicator remains if an app tries to prematurely finish its
own mic usage

Bug: 258672042
Test: atest CtsPermission4TestCases
Change-Id: I5e3feaa80b1dd2c2a7293b33efbe270ee9472c22
diff --git a/tests/tests/permission4/Android.bp b/tests/tests/permission4/Android.bp
index 2ff3bee..72908e2 100644
--- a/tests/tests/permission4/Android.bp
+++ b/tests/tests/permission4/Android.bp
@@ -34,6 +34,7 @@
     ],
     test_suites: [
         "cts",
+        "sts",
         "vts10",
         "general-tests",
         "mts-permission",
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
index a7e0ab6..9dc1b45 100644
--- a/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
@@ -25,6 +25,7 @@
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
+        "sts",
         "vts10",
         "general-tests",
     ],
@@ -36,6 +37,6 @@
     ],
 
     srcs: [
-        "src/**/*.kt"
+        "src/**/*.kt",
     ],
 }
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt b/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
index 659f228..f7a5c31 100644
--- a/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
@@ -42,6 +42,7 @@
 private const val USE_CAMERA = "use_camera"
 private const val USE_MICROPHONE = "use_microphone"
 private const val USE_HOTWORD = "use_hotword"
+private const val FINISH_EARLY = "finish_early"
 private const val USE_DURATION_MS = 10000L
 private const val SAMPLE_RATE_HZ = 44100
 
@@ -62,12 +63,14 @@
     private var runMic = false
     private var hotwordFinished = false
     private var runHotword = false
+    private var finishEarly = false
 
     override fun onStart() {
         super.onStart()
         runCamera = intent.getBooleanExtra(USE_CAMERA, false)
         runMic = intent.getBooleanExtra(USE_MICROPHONE, false)
         runHotword = intent.getBooleanExtra(USE_HOTWORD, false)
+        finishEarly = intent.getBooleanExtra(FINISH_EARLY, false)
 
         if (runMic) {
             useMic()
@@ -193,6 +196,11 @@
             AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT)
         recorder = AudioRecord(MIC, SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, minSize)
         recorder?.startRecording()
+        if (finishEarly) {
+            appOpsManager = getSystemService(AppOpsManager::class.java)
+            appOpsManager?.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO, Process.myUid(), packageName)
+            return
+        }
         GlobalScope.launch {
             delay(USE_DURATION_MS)
             micFinished = true
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index fe9037a..ae666d9 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -27,6 +27,7 @@
 import android.os.Build
 import android.os.Process
 import android.permission.PermissionManager
+import android.platform.test.annotations.AsbSecurityTest
 import android.provider.DeviceConfig
 import android.provider.Settings
 import android.server.wm.WindowManagerStateHelper
@@ -60,6 +61,7 @@
 private const val USE_CAMERA = "use_camera"
 private const val USE_MICROPHONE = "use_microphone"
 private const val USE_HOTWORD = "use_hotword"
+private const val FINISH_EARLY = "finish_early"
 private const val INTENT_ACTION = "test.action.USE_CAMERA_OR_MIC"
 private const val PRIVACY_CHIP_ID = "com.android.systemui:id/privacy_chip"
 private const val CAR_MIC_PRIVACY_CHIP_ID = "com.android.systemui:id/mic_privacy_chip"
@@ -167,11 +169,17 @@
         Thread.sleep(DELAY_MILLIS)
     }
 
-    private fun openApp(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
+    private fun openApp(
+        useMic: Boolean,
+        useCamera: Boolean,
+        useHotword: Boolean,
+        finishEarly: Boolean = false
+    ) {
         context.startActivity(Intent(INTENT_ACTION).apply {
             putExtra(USE_CAMERA, useCamera)
             putExtra(USE_MICROPHONE, useMic)
             putExtra(USE_HOTWORD, useHotword)
+            putExtra(FINISH_EARLY, finishEarly)
             addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         })
     }
@@ -191,6 +199,13 @@
     }
 
     @Test
+    @AsbSecurityTest(cveBugId = [242537498])
+    fun testMicIndicatorWithManualFinishOpStillShows() {
+        changeSafetyCenterFlag(false.toString())
+        testCameraAndMicIndicator(useMic = true, useCamera = false, finishEarly = true)
+    }
+
+    @Test
     fun testHotwordIndicatorBehavior() {
         changeSafetyCenterFlag(false.toString())
         testCameraAndMicIndicator(useMic = false, useCamera = false, useHotword = true)
@@ -268,10 +283,11 @@
         useCamera: Boolean,
         useHotword: Boolean = false,
         chainUsage: Boolean = false,
-        safetyCenterEnabled: Boolean = false
+        safetyCenterEnabled: Boolean = false,
+        finishEarly: Boolean = false
     ) {
         var chainAttribution: AttributionSource? = null
-        openApp(useMic, useCamera, useHotword)
+        openApp(useMic, useCamera, useHotword, finishEarly)
         try {
             eventually {
                 val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
@@ -286,20 +302,23 @@
                 }
             }
 
-            if (isTv) {
-                assertTvIndicatorsShown(useMic, useCamera, useHotword)
-            } else if (isCar) {
-                assertCarIndicatorsShown(useMic, useCamera, useHotword, chainUsage)
-            } else {
-                // Hotword gets remapped to RECORD_AUDIO on handheld, so handheld should show a mic
-                // indicator
+            if (!isTv && !isCar) {
                 uiDevice.openQuickSettings()
-                assertPrivacyChipAndIndicatorsPresent(
-                    useMic,
-                    useCamera,
-                    chainUsage,
-                    safetyCenterEnabled
-                )
+            }
+            assertIndicatorsShown(useMic, useCamera, useHotword, chainUsage,
+                safetyCenterEnabled)
+
+            if (finishEarly) {
+                // Assert that the indicator doesn't go away
+                val indicatorGoneException: Exception? = try {
+                    eventually {
+                        assertIndicatorsShown(false, false, false)
+                    }
+                    null
+                } catch (e: Exception) {
+                    e
+                }
+                assertNotNull("Expected the indicator to be present", indicatorGoneException)
             }
         } finally {
             if (chainAttribution != null) {
@@ -309,8 +328,25 @@
         }
     }
 
+    private fun assertIndicatorsShown(
+        useMic: Boolean,
+        useCamera: Boolean,
+        useHotword: Boolean = false,
+        chainUsage: Boolean = false,
+        safetyCenterEnabled: Boolean = false,
+        ) {
+        if (isTv) {
+            assertTvIndicatorsShown(useMic, useCamera, useHotword)
+        } else if (isCar) {
+            assertCarIndicatorsShown(useMic, useCamera, useHotword, chainUsage)
+        } else {
+            assertPrivacyChipAndIndicatorsPresent(useMic, useCamera, chainUsage,
+                safetyCenterEnabled)
+        }
+    }
+
     private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
-        if (useMic || useHotword) {
+        if (useMic || useHotword || (!useMic && !useCamera && !useHotword)) {
             val found = WindowManagerStateHelper()
                 .waitFor("Waiting for the mic indicator window to come up") {
                     it.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE) &&
@@ -345,7 +381,7 @@
                 assertNotNull("Did not find camera chip", cameraPrivacyChip)
                 // Click to chip to show the panel.
                 cameraPrivacyChip.click()
-            } else if (useHotword) {
+            } else {
                 assertNull("Found mic chip, but did not expect to", micPrivacyChip)
                 assertNull("Found camera chip, but did not expect to", cameraPrivacyChip)
             }
@@ -357,18 +393,7 @@
                 assertChainMicAndOtherCameraUsed(false)
                 return@eventually
             }
-            if (useHotword) {
-                // There should be no privacy panel when using hot word
-                val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
-                assertFalse("View with text $micLabel found, but did not expect to",
-                    micLabelView.exists())
-                val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
-                assertFalse("View with text $cameraLabel found, but did not expect to",
-                    cameraLabelView.exists())
-                val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
-                assertFalse("View with text $APP_LABEL found, but did not expect to",
-                    appView.exists())
-            } else if (useMic) {
+            if (useMic) {
                 // There should be a mic privacy panel after mic privacy chip is clicked
                 val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
                 assertTrue("View with text $micLabel not found", micLabelView.exists())
@@ -380,6 +405,17 @@
                 assertTrue("View with text $cameraLabel not found", cameraLabelView.exists())
                 val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
                 assertTrue("View with text $APP_LABEL not found", appView.exists())
+            } else {
+                // There should be no privacy panel when using hot word
+                val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
+                assertFalse("View with text $micLabel found, but did not expect to",
+                    micLabelView.exists())
+                val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
+                assertFalse("View with text $cameraLabel found, but did not expect to",
+                    cameraLabelView.exists())
+                val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+                assertFalse("View with text $APP_LABEL found, but did not expect to",
+                    appView.exists())
             }
         }
     }
@@ -419,6 +455,7 @@
                     uiDevice.findObjects(By.res(SAFETY_CENTER_ITEM_ID)).size > 0)
             }
         }
+        uiDevice.pressBack()
     }
 
     private fun createChainAttribution(): AttributionSource? {