Add tests for attribution chain indicators

Test: atest CameraMicIndicatorsPermissionTest
Bug: 212434116
Change-Id: I03e820cfb7653ea7febdcd7bce2e2e05fcef3062
diff --git a/tests/tests/permission4/AndroidManifest.xml b/tests/tests/permission4/AndroidManifest.xml
index d4cc71a..4c5e49a 100644
--- a/tests/tests/permission4/AndroidManifest.xml
+++ b/tests/tests/permission4/AndroidManifest.xml
@@ -22,12 +22,10 @@
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
     <application>
-
         <uses-library android:name="android.test.runner" />
-
-        <activity android:name=".StartForFutureActivity" />
     </application>
 
     <instrumentation
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 06aef15..659f228 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
@@ -83,6 +83,7 @@
     }
 
     override fun finish() {
+        super.finish()
         cameraDevice?.close()
         cameraDevice = null
         recorder?.stop()
@@ -95,7 +96,6 @@
                     packageName)
         }
         appOpsManager = null
-        super.finish()
     }
 
     override fun onStop() {
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index 4128de9..de277d4 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -19,11 +19,13 @@
 import android.app.Instrumentation
 import android.app.UiAutomation
 import android.app.compat.CompatChanges
+import android.content.AttributionSource
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.hardware.camera2.CameraManager
 import android.os.Process
+import android.permission.PermissionManager
 import android.provider.DeviceConfig
 import android.provider.Settings
 import android.server.wm.WindowManagerStateHelper
@@ -37,6 +39,8 @@
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
@@ -47,11 +51,14 @@
 import org.junit.Test
 
 private const val APP_LABEL = "CtsCameraMicAccess"
+private const val APP_PKG = "android.permission4.cts.appthataccessescameraandmic"
+private const val SHELL_PKG = "com.android.shell"
 private const val USE_CAMERA = "use_camera"
 private const val USE_MICROPHONE = "use_microphone"
 private const val USE_HOTWORD = "use_hotword"
 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 PRIVACY_ITEM_ID = "com.android.systemui:id/privacy_item"
 private const val INDICATORS_FLAG = "camera_mic_icons_enabled"
 private const val PERMISSION_INDICATORS_NOT_PRESENT = 162547999L
 private const val IDLE_TIMEOUT_MILLIS: Long = 1000
@@ -65,6 +72,8 @@
     private val uiAutomation: UiAutomation = instrumentation.uiAutomation
     private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
     private val packageManager: PackageManager = context.packageManager
+    private val permissionManager: PermissionManager =
+        context.getSystemService(PermissionManager::class.java)!!
 
     private val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
     private var wasEnabled = false
@@ -160,26 +169,50 @@
         testCameraAndMicIndicator(useMic = false, useCamera = false, useHotword = true)
     }
 
+    @Test
+    fun testChainUsageWithOtherUsage() {
+        // TV has only the mic icon
+        assumeFalse(isTv)
+        testCameraAndMicIndicator(useMic = false, useCamera = true, chainUsage = true)
+    }
+
     private fun testCameraAndMicIndicator(
         useMic: Boolean,
         useCamera: Boolean,
-        useHotword: Boolean = false
+        useHotword: Boolean = false,
+        chainUsage: Boolean = false
     ) {
+        var chainAttribution: AttributionSource? = null
         openApp(useMic, useCamera, useHotword)
-        eventually {
-            val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
-            assertTrue("View with text $APP_LABEL not found", appView.exists())
-        }
+        try {
+            eventually {
+                val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+                assertTrue("View with text $APP_LABEL not found", appView.exists())
+            }
+            if (chainUsage) {
+                chainAttribution = createChainAttribution()
+                runWithShellPermissionIdentity {
+                    val ret = permissionManager.checkPermissionForStartDataDelivery(
+                        Manifest.permission.RECORD_AUDIO, chainAttribution!!, "")
+                    Assert.assertEquals(PermissionManager.PERMISSION_GRANTED, ret)
+                }
+            }
 
-        if (isTv) {
-            assertTvIndicatorsShown(useMic, useCamera, useHotword)
-        } else if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            assertCarIndicatorsShown(useMic, useCamera, useHotword)
-        } else {
-            // Hotword gets remapped to RECORD_AUDIO on handheld, so handheld should show a mic
-            // indicator
-            uiDevice.openQuickSettings()
-            assertPrivacyChipAndIndicatorsPresent(useMic || useHotword, useCamera)
+            if (isTv) {
+                assertTvIndicatorsShown(useMic, useCamera, useHotword)
+            } else if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+                assertCarIndicatorsShown(useMic, useCamera, useHotword, chainUsage)
+            } else {
+                // Hotword gets remapped to RECORD_AUDIO on handheld, so handheld should show a mic
+                // indicator
+                uiDevice.openQuickSettings()
+                assertPrivacyChipAndIndicatorsPresent(useMic || useHotword, useCamera, chainUsage)
+            }
+        } finally {
+            if (chainAttribution != null) {
+                permissionManager.finishDataDelivery(Manifest.permission.RECORD_AUDIO,
+                chainAttribution!!)
+            }
         }
     }
 
@@ -201,7 +234,12 @@
         }
     }
 
-    private fun assertCarIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
+    private fun assertCarIndicatorsShown(
+        useMic: Boolean,
+        useCamera: Boolean,
+        useHotword: Boolean,
+        chainUsage: Boolean
+    ) {
         // Ensure the privacy chip is present (or not)
         var chipFound = false
         try {
@@ -222,6 +260,10 @@
         }
 
         eventually {
+            if (chainUsage) {
+                assertChainMicAndOtherCameraUsed()
+                return@eventually
+            }
             if (useCamera || useHotword) {
                 // There should be no microphone dialog when using hot word and/or camera
                 val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
@@ -239,7 +281,11 @@
         }
     }
 
-    private fun assertPrivacyChipAndIndicatorsPresent(useMic: Boolean, useCamera: Boolean) {
+    private fun assertPrivacyChipAndIndicatorsPresent(
+        useMic: Boolean,
+        useCamera: Boolean,
+        chainUsage: Boolean
+    ) {
         // Ensure the privacy chip is present
         eventually {
             val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
@@ -248,6 +294,10 @@
         }
 
         eventually {
+            if (chainUsage) {
+                assertChainMicAndOtherCameraUsed()
+                return@eventually
+            }
             if (useMic) {
                 val iconView = uiDevice.findObject(UiSelector().descriptionContains(micLabel))
                 assertTrue("View with description $micLabel not found", iconView.exists())
@@ -261,6 +311,38 @@
         }
     }
 
+    private fun createChainAttribution(): AttributionSource? {
+        var attrSource: AttributionSource? = null
+        runWithShellPermissionIdentity {
+            try {
+                val appUid = packageManager.getPackageUid(APP_PKG, 0)
+                val childAttribution = AttributionSource(appUid, APP_PKG, null)
+                val attribution = AttributionSource(Process.myUid(), context.packageName, null,
+                    null, permissionManager.registerAttributionSource(childAttribution))
+                attrSource = permissionManager.registerAttributionSource(attribution)
+            } catch (e: PackageManager.NameNotFoundException) {
+                Assert.fail("Expected to find a UID for $APP_LABEL")
+            }
+        }
+        return attrSource
+    }
+
+    private fun assertChainMicAndOtherCameraUsed() {
+        val shellLabel = try {
+            context.packageManager.getApplicationInfo(SHELL_PKG, 0)
+                .loadLabel(context.packageManager).toString()
+        } catch (e: PackageManager.NameNotFoundException) {
+            "Did not find shell package"
+        }
+
+        val usageViews = uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+        assertEquals("Expected two usage views", 2, usageViews.size)
+        val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
+        assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
+        val shellView = uiDevice.findObjects(By.textContains(shellLabel))
+        assertEquals("Expected only one shell view", 1, shellView.size)
+    }
+
     private fun pressBack() {
         uiDevice.pressBack()
         waitForIdle()