Allow nested calls to callWithShellPermissionIdentity.
This is quite confusing that permissions are not merged when calling
callWithShellPermissionIdentity in a nested block.
Bug: 221401739
Test: atest CtsSafetyCenterTestCases
Change-Id: I8bb577e024ce49098fa3bc418ab52cb28037676f
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
index effe270..94abe35 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
@@ -35,6 +35,8 @@
import android.safetycenter.cts.testing.SafetySourceCtsData
import android.support.test.uiautomator.By
import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.StaleObjectException
+import android.support.test.uiautomator.UiDevice
import android.support.test.uiautomator.UiObject2
import android.util.Log
import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -81,11 +83,7 @@
return
}
safetyCenterCtsHelper.reset()
- }
-
- @After
- fun resetDeviceState() {
- resetDeviceRotation()
+ getUiDevice().resetRotation()
}
@Test
@@ -251,7 +249,7 @@
assertIssueDisplayed(safetySourceCtsData.informationIssue)
// Device rotation to trigger usage of savedinstancestate via config update
- rotateDevice()
+ uiDevice.rotate()
// Verify cards remain expanded
waitTextNotDisplayed("See all alerts")
@@ -310,7 +308,7 @@
null) {
return
}
- } catch (e: android.support.test.uiautomator.StaleObjectException) {
+ } catch (e: StaleObjectException) {
Log.d(
TAG,
"StaleObjectException while calling waitTextNotDisplayed, will retry " +
@@ -331,28 +329,27 @@
waitFindObject(By.text("See all alerts")).click()
}
- private fun rotateDevice() {
- val uiDevice = getUiDevice()
- if (uiDevice.isNaturalOrientation) {
- uiDevice.setOrientationLeft()
- } else {
- uiDevice.setOrientationNatural()
- }
- uiDevice.waitForIdle()
- }
-
- private fun resetDeviceRotation() {
- val uiDevice = getUiDevice()
- if (!uiDevice.isNaturalOrientation) {
- uiDevice.setOrientationNatural()
- uiDevice.waitForIdle()
- }
- }
-
companion object {
private val TAG = SafetyCenterActivityTest::class.java.simpleName
private val NOT_DISPLAYED_TIMEOUT = Duration.ofSeconds(20)
private val NOT_DISPLAYED_CHECK_INTERVAL = Duration.ofMillis(100)
private val FIND_TEXT_TIMEOUT = Duration.ofSeconds(25)
+
+ private fun UiDevice.rotate() {
+ if (isNaturalOrientation) {
+ setOrientationLeft()
+ } else {
+ setOrientationNatural()
+ }
+ waitForIdle()
+ }
+
+ private fun UiDevice.resetRotation() {
+ if (isNaturalOrientation) {
+ return
+ }
+ setOrientationNatural()
+ waitForIdle()
+ }
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
index 8981f71..cd67451 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -16,7 +16,6 @@
package android.safetycenter.cts
-import android.Manifest.permission.MANAGE_SAFETY_CENTER
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -116,7 +115,6 @@
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
-import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import kotlin.test.assertFailsWith
@@ -1369,29 +1367,22 @@
}
@Test
- // Permission is held for the entire test to avoid a racy scenario where the shell identity is
- // dropped while it's being acquired on another thread.
fun addOnSafetyCenterDataChangedListener_oneShot_doesntDeadlock() {
- callWithShellPermissionIdentity(
- {
- val listener = SafetyCenterCtsListener()
- val oneShotListener =
- object : OnSafetyCenterDataChangedListener {
- override fun onSafetyCenterDataChanged(safetyCenterData: SafetyCenterData) {
- safetyCenterManager.removeOnSafetyCenterDataChangedListener(this)
- listener.onSafetyCenterDataChanged(safetyCenterData)
- }
- }
- safetyCenterManager.addOnSafetyCenterDataChangedListener(
- directExecutor(), oneShotListener)
+ val listener = SafetyCenterCtsListener()
+ val oneShotListener =
+ object : OnSafetyCenterDataChangedListener {
+ override fun onSafetyCenterDataChanged(safetyCenterData: SafetyCenterData) {
+ safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(this)
+ listener.onSafetyCenterDataChanged(safetyCenterData)
+ }
+ }
+ safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
+ directExecutor(), oneShotListener)
- // Check that we don't deadlock when using a one-shot listener: this is because
- // adding the listener could call the listener while holding a lock on the binder
- // thread-pool; causing a deadlock when attempting to call the `SafetyCenterManager`
- // from that listener.
- listener.receiveSafetyCenterData()
- },
- MANAGE_SAFETY_CENTER)
+ // Check that we don't deadlock when using a one-shot listener. This is because adding the
+ // listener could call it while holding a lock; which would cause a deadlock if the listener
+ // wasn't oneway.
+ listener.receiveSafetyCenterData()
}
@Test
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
index fe10928..d72e5da 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
@@ -26,7 +26,7 @@
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceErrorDetails
import android.safetycenter.config.SafetyCenterConfig
-import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import android.safetycenter.cts.testing.ShellPermissions.callWithShellPermissionIdentity
import java.util.concurrent.Executor
/**
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
index da5a447..5172bfa 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
@@ -17,7 +17,6 @@
package android.safetycenter.cts.testing
import android.Manifest.permission.READ_SAFETY_CENTER_STATUS
-import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -25,7 +24,7 @@
import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED
import android.safetycenter.cts.testing.Coroutines.TIMEOUT_LONG
import android.safetycenter.cts.testing.Coroutines.runBlockingWithTimeout
-import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import android.safetycenter.cts.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
@@ -60,11 +59,10 @@
) =
callWithShellPermissionIdentity(
{
- SafetyCenterFlags.setSafetyCenterEnabledWithoutPermission(value)
+ SafetyCenterFlags.isEnabled = value
receiveSafetyCenterEnabledChanged(timeout)
},
- READ_SAFETY_CENTER_STATUS,
- WRITE_DEVICE_CONFIG)
+ READ_SAFETY_CENTER_STATUS)
fun reset() {
safetyCenterEnabledChangedChannel.cancel()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
index 6161e8b..84e06d0 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
@@ -23,7 +23,7 @@
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
import android.provider.DeviceConfig.Properties
-import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import android.safetycenter.cts.testing.ShellPermissions.callWithShellPermissionIdentity
/** A class that facilitates working with Safety Center flags. */
// TODO(b/219553295): Add timeout flags.
@@ -48,27 +48,19 @@
READ_DEVICE_CONFIG)
set(value) {
callWithShellPermissionIdentity(
- { setSafetyCenterEnabledWithoutPermission(value) }, WRITE_DEVICE_CONFIG)
+ {
+ val valueWasSet =
+ DeviceConfig.setProperty(
+ NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_CENTER_ENABLED,
+ /* value = */ value.toString(),
+ /* makeDefault = */ false)
+ require(valueWasSet) { "Could not set Safety Center flag value to: $value" }
+ },
+ WRITE_DEVICE_CONFIG)
}
/**
- * Sets the Safety Center device config flag to the given boolean [value], but without holding
- * the [WRITE_DEVICE_CONFIG] permission.
- *
- * [callWithShellPermissionIdentity] mutates a global state, so it is not possible to modify
- * [isEnabled] within another call to [callWithShellPermissionIdentity].
- */
- fun setSafetyCenterEnabledWithoutPermission(value: Boolean) {
- val valueWasSet =
- DeviceConfig.setProperty(
- NAMESPACE_PRIVACY,
- PROPERTY_SAFETY_CENTER_ENABLED,
- /* value = */ value.toString(),
- /* makeDefault = */ false)
- require(valueWasSet) { "Could not set Safety Center flag value to: $value" }
- }
-
- /**
* Returns a snapshot of all the Safety Center flags.
*
* This snapshot is only taken once and cached afterwards. This must be called at least once
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
index 8e30164..6e14e31 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
@@ -16,9 +16,7 @@
package android.safetycenter.cts.testing
-import android.Manifest.permission.MANAGE_SAFETY_CENTER
import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE
-import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -38,7 +36,9 @@
import android.safetycenter.SafetySourceErrorDetails
import android.safetycenter.cts.testing.Coroutines.TIMEOUT_LONG
import android.safetycenter.cts.testing.Coroutines.runBlockingWithTimeout
-import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.executeSafetyCenterIssueActionWithPermission
+import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission
+import android.safetycenter.cts.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
@@ -175,11 +175,10 @@
) =
callWithShellPermissionIdentity(
{
- refreshSafetySources(refreshReason)
+ refreshSafetySourcesWithPermission(refreshReason)
receiveRefreshSafetySources(timeout)
},
- SEND_SAFETY_CENTER_UPDATE,
- MANAGE_SAFETY_CENTER)
+ SEND_SAFETY_CENTER_UPDATE)
fun setSafetyCenterEnabledWithReceiverPermissionAndWait(
value: Boolean,
@@ -187,11 +186,10 @@
) =
callWithShellPermissionIdentity(
{
- SafetyCenterFlags.setSafetyCenterEnabledWithoutPermission(value)
+ SafetyCenterFlags.isEnabled = value
receiveSafetyCenterEnabledChanged(timeout)
},
- SEND_SAFETY_CENTER_UPDATE,
- WRITE_DEVICE_CONFIG)
+ SEND_SAFETY_CENTER_UPDATE)
fun SafetyCenterManager.executeSafetyCenterIssueActionWithReceiverPermissionAndWait(
issueId: String,
@@ -200,11 +198,10 @@
) =
callWithShellPermissionIdentity(
{
- executeSafetyCenterIssueAction(issueId, issueActionId)
+ executeSafetyCenterIssueActionWithPermission(issueId, issueActionId)
receiveInlineAction(timeout)
},
- SEND_SAFETY_CENTER_UPDATE,
- MANAGE_SAFETY_CENTER)
+ SEND_SAFETY_CENTER_UPDATE)
private fun createRefreshEvent(broadcastId: String) =
SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/ShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/ShellPermissions.kt
new file mode 100644
index 0000000..957e045
--- /dev/null
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/ShellPermissions.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts.testing
+
+import android.app.UiAutomation
+import android.app.UiAutomation.ALL_PERMISSIONS
+import android.safetycenter.cts.testing.ShellPermissions.adoptShellPermissionIdentityFor
+import androidx.annotation.GuardedBy
+import androidx.test.InstrumentationRegistry.getInstrumentation
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.collect.HashMultiset
+import com.google.common.collect.Multiset
+
+/** A class to facilitate working with System Shell permissions. */
+object ShellPermissions {
+
+ private val lock = Object()
+ @GuardedBy("lock") private val nestedPermissions: Multiset<String> = HashMultiset.create()
+
+ /**
+ * Behaves the same way as [SystemUtil.callWithShellPermissionIdentity], but allows nesting
+ * calls to it by merging the adopted [permissions] within each [block].
+ *
+ * Note that [SystemUtil.callWithShellPermissionIdentity] should NOT be used together with this
+ * method.
+ */
+ fun <T> callWithShellPermissionIdentity(block: () -> T, vararg permissions: String): T {
+ val uiAutomation = getInstrumentation().getUiAutomation()
+ val permissionsToAddForThisBlock =
+ if (permissions.isEmpty()) {
+ ALL_PERMISSIONS
+ } else {
+ permissions.toSet()
+ }
+ synchronized(lock) {
+ permissionsToAddForThisBlock.forEach { nestedPermissions.add(it) }
+ uiAutomation.adoptShellPermissionIdentityFor(nestedPermissions.elementSet())
+ }
+ try {
+ return block()
+ } finally {
+ synchronized(lock) {
+ permissionsToAddForThisBlock.forEach { nestedPermissions.remove(it) }
+ if (nestedPermissions.isEmpty()) {
+ uiAutomation.dropShellPermissionIdentity()
+ } else {
+ uiAutomation.adoptShellPermissionIdentityFor(nestedPermissions.elementSet())
+ }
+ }
+ }
+ }
+
+ private fun UiAutomation.adoptShellPermissionIdentityFor(permissionsToAdopt: Set<String>) {
+ if (permissionsToAdopt.containsAll(ALL_PERMISSIONS)) {
+ adoptShellPermissionIdentity()
+ } else {
+ adoptShellPermissionIdentity(*permissionsToAdopt.toTypedArray())
+ }
+ }
+}