Refuse to adopt shell permissions if they were already adopted.

This does not work because when the permissions are dropped, all
previously-adopted permissions will be dropped as well. Attempts
to do so will likely result in hard-to-understand test failures.

On S+, where we can detect this, fail with a clear error message.

Test: treehugger
Bug: 225092753
Change-Id: I1587ab77f885c595f0b9168c6739303c6227218e
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
index f557f18..a4dbd9a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -19,6 +19,7 @@
 package com.android.testutils
 
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
 import com.android.testutils.ExceptionUtils.ThrowingRunnable
 import com.android.testutils.ExceptionUtils.ThrowingSupplier
 
@@ -31,6 +32,23 @@
  */
 fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
     val autom = InstrumentationRegistry.getInstrumentation().uiAutomation
+
+    // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all
+    // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can
+    // detect this, refuse to do it.
+    //
+    // TODO: when R is deprecated, we could try to make this work instead.
+    // - Get the list of previously-adopted permissions.
+    // - Adopt the union of the previously-adopted and newly-requested permissions.
+    // - Run the task.
+    // - Adopt the previously-adopted permissions, dropping the ones just adopted.
+    //
+    // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted
+    // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions.
+    if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) {
+        throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested")
+    }
+
     autom.adoptShellPermissionIdentity(*permissions)
     try {
         return task()