Merge "Add carrier privilege utils to CarrierConfigRule" into main

This commit is contained in:
Quang Luong 2025-03-17 23:35:22 -07:00 • committed by Gerrit Code Review
commit ab6342b70e
2 changed files with 195 additions and 134 deletions

View file

@ -18,20 +18,28 @@ package com.android.testutils.com.android.testutils
import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.READ_PHONE_STATE
import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.ConditionVariable
import android.os.ParcelFileDescriptor
import android.os.PersistableBundle
import android.os.Process
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel.isAtLeastU
import com.android.modules.utils.build.SdkLevel
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import java.security.MessageDigest
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.rules.TestRule
@ -46,12 +54,20 @@ private const val CARRIER_CONFIG_CHANGE_TIMEOUT_MS = 10_000L
* configuration automatically on teardown.
*/
class CarrierConfigRule : TestRule {
private val HEX_CHARS: CharArray = charArrayOf(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
)
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val uiAutomation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
private val ccm by lazy { context.getSystemService(CarrierConfigManager::class.java) }
// Map of (subId) -> (original values of overridden settings)
private val originalConfigs = mutableMapOf<Int, PersistableBundle>()
// Map of (subId) -> (original values of carrier service package)
private val originalCarrierServicePackages = mutableMapOf<Int, String?>()
override fun apply(base: Statement, description: Description): Statement {
return CarrierConfigStatement(base, description)
}
@ -118,6 +134,177 @@ class CarrierConfigRule : TestRule {
}
}
private fun runShellCommand(cmd: String) {
val fd: ParcelFileDescriptor = uiAutomation.executeShellCommand(cmd)
fd.close() // Don't care about the output.
}
/**
* Converts a byte array into a String of hexadecimal characters.
*
* @param bytes an array of bytes
* @return hex string representation of bytes array
*/
private fun bytesToHexString(bytes: ByteArray?): String? {
if (bytes == null) return null
val ret = StringBuilder(2 * bytes.size)
for (i in bytes.indices) {
var b: Int
b = 0x0f and (bytes[i].toInt() shr 4)
ret.append(HEX_CHARS[b])
b = 0x0f and bytes[i].toInt()
ret.append(HEX_CHARS[b])
}
return ret.toString()
}
private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
if (!SdkLevel.isAtLeastT()) {
throw UnsupportedOperationException(
"Acquiring carrier privilege requires at least T SDK"
)
}
fun getCertHash(): String {
val pkgInfo = context.packageManager.getPackageInfo(
context.opPackageName,
PackageManager.GET_SIGNATURES
)
val digest = MessageDigest.getInstance("SHA-256")
val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
return bytesToHexString(certHash)!!
}
val tm = context.getSystemService(TelephonyManager::class.java)!!
val cv = ConditionVariable()
val cpb = PrivilegeWaiterCallback(cv)
// The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
// T. This means the lambda will compile as a private method of this class taking a
// PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
// including private methods, this would fail with a link error when running on S-.
// To solve this, make the lambda serializable, which causes the compiler to emit a
// synthetic class instead of a synthetic method.
tryTest @JvmSerializableLambda {
val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
}
// Wait for the callback to be registered
assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
"Can't register CarrierPrivilegesCallback")
if (cpb.hasPrivilege == hold) {
if (hold) {
Log.w(TAG, "Package ${context.opPackageName} already is privileged")
} else {
Log.w(TAG, "Package ${context.opPackageName} already isn't privileged")
}
return@tryTest
}
if (hold) {
addConfigOverrides(subId, PersistableBundle().also {
it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
arrayOf(getCertHash()))
})
} else {
cleanUpNow()
}
} cleanup @JvmSerializableLambda {
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.unregisterCarrierPrivilegesCallback(cpb)
}
}
}
/**
* Acquires carrier privilege on the given subscription ID.
*/
fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
/**
* Drops carrier privilege from the given subscription ID.
*/
fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
/**
* Sets the carrier service package override for the given subscription ID. A null argument will
* clear any previously-set override.
*/
fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
if (!SdkLevel.isAtLeastU()) {
throw UnsupportedOperationException(
"Setting carrier service package override requires at least U SDK"
)
}
val tm = context.getSystemService(TelephonyManager::class.java)!!
val cv = ConditionVariable()
val cpb = CarrierServiceChangedWaiterCallback(cv)
// The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
// T. This means the lambda will compile as a private method of this class taking a
// PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
// including private methods, this would fail with a link error when running on S-.
// To solve this, make the lambda serializable, which causes the compiler to emit a
// synthetic class instead of a synthetic method.
tryTest @JvmSerializableLambda {
val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
}
// Wait for the callback to be registered
assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
"Can't register CarrierPrivilegesCallback")
if (cpb.pkgName == pkg) {
Log.w(TAG, "Carrier service package was already $pkg")
return@tryTest
}
if (!originalCarrierServicePackages.contains(subId)) {
originalCarrierServicePackages.put(subId, cpb.pkgName)
}
cv.close()
runAsShell(MODIFY_PHONE_STATE) {
if (null == pkg) {
// There is a bug in clear-carrier-service-package-override where not adding
// the -s argument will use the wrong slot index : b/299604822
runShellCommand("cmd phone clear-carrier-service-package-override" +
" -s $subId")
} else {
runShellCommand("cmd phone set-carrier-service-package-override $pkg" +
" -s $subId")
}
}
assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
"Can't modify carrier service package")
} cleanup @JvmSerializableLambda {
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.unregisterCarrierPrivilegesCallback(cpb)
}
}
}
private class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
CarrierPrivilegesCallback {
var hasPrivilege = false
override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
hasPrivilege = uids.contains(Process.myUid())
cv.open()
}
}
private class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
CarrierPrivilegesCallback {
var pkgName: String? = null
override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
this.pkgName = pkgName
cv.open()
}
}
/**
* Cleanup overrides that were added by the test case.
*
@ -138,6 +325,10 @@ class CarrierConfigRule : TestRule {
}
originalConfigs.clear()
}
originalCarrierServicePackages.forEach { (subId, pkg) ->
setCarrierServicePackageOverride(subId, pkg)
}
originalCarrierServicePackages.clear()
}
}
@ -145,7 +336,7 @@ private fun CarrierConfigManager.getConfigForSubIdCompat(
subId: Int,
keys: Set<String>
): PersistableBundle {
return if (isAtLeastU()) {
return if (SdkLevel.isAtLeastU()) {
// This method is U+
getConfigForSubId(subId, *keys.toTypedArray())
} else {

View file

@ -15,7 +15,6 @@
*/
package android.net.cts
import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.app.Instrumentation
@ -80,12 +79,10 @@ import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosS
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
import android.net.wifi.WifiInfo
import android.os.Build
import android.os.ConditionVariable
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import android.os.PersistableBundle
import android.os.Process
import android.os.SystemClock
import android.platform.test.annotations.AppModeFull
@ -94,19 +91,15 @@ import android.system.OsConstants.AF_INET6
import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.ArraySet
import android.util.DebugUtils.valueToString
import android.util.Log
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.ThrowingSupplier
import com.android.compatibility.common.util.UiccUtil
import com.android.modules.utils.build.SdkLevel
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.NetworkStackConstants.ETHER_MTU
@ -151,7 +144,6 @@ import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.time.Duration
import java.util.Arrays
import java.util.Random
@ -708,102 +700,6 @@ class NetworkAgentTest {
doTestAllowedUids(transports, uid, expectUidsPresent, specifier, transportInfo)
}
private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
fun getCertHash(): String {
val pkgInfo = realContext.packageManager.getPackageInfo(
realContext.opPackageName,
PackageManager.GET_SIGNATURES
)
val digest = MessageDigest.getInstance("SHA-256")
val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
return UiccUtil.bytesToHexString(certHash)!!
}
val tm = realContext.getSystemService(TelephonyManager::class.java)!!
val cv = ConditionVariable()
val cpb = PrivilegeWaiterCallback(cv)
// The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
// T. This means the lambda will compile as a private method of this class taking a
// PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
// including private methods, this would fail with a link error when running on S-.
// To solve this, make the lambda serializable, which causes the compiler to emit a
// synthetic class instead of a synthetic method.
tryTest @JvmSerializableLambda {
val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
}
// Wait for the callback to be registered
assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
if (cpb.hasPrivilege == hold) {
if (hold) {
Log.w(TAG, "Package ${realContext.opPackageName} already is privileged")
} else {
Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged")
}
return@tryTest
}
if (hold) {
carrierConfigRule.addConfigOverrides(subId, PersistableBundle().also {
it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
arrayOf(getCertHash()))
})
} else {
carrierConfigRule.cleanUpNow()
}
} cleanup @JvmSerializableLambda {
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.unregisterCarrierPrivilegesCallback(cpb)
}
}
}
private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
val tm = realContext.getSystemService(TelephonyManager::class.java)!!
val cv = ConditionVariable()
val cpb = CarrierServiceChangedWaiterCallback(cv)
// The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
// T. This means the lambda will compile as a private method of this class taking a
// PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
// including private methods, this would fail with a link error when running on S-.
// To solve this, make the lambda serializable, which causes the compiler to emit a
// synthetic class instead of a synthetic method.
tryTest @JvmSerializableLambda {
val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
}
// Wait for the callback to be registered
assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
if (cpb.pkgName == pkg) {
Log.w(TAG, "Carrier service package was already $pkg")
return@tryTest
}
cv.close()
runAsShell(MODIFY_PHONE_STATE) {
if (null == pkg) {
// There is a bug is clear-carrier-service-package-override where not adding
// the -s argument will use the wrong slot index : b/299604822
runShellCommand("cmd phone clear-carrier-service-package-override" +
" -s $subId")
} else {
// -s could set the subId, but this test works with the default subId.
runShellCommand("cmd phone set-carrier-service-package-override $pkg")
}
}
assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package")
} cleanup @JvmSerializableLambda {
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.unregisterCarrierPrivilegesCallback(cpb)
}
}
}
private fun String.execute() = runShellCommand(this).trim()
@Test
@ -856,8 +752,8 @@ class NetworkAgentTest {
if (!SdkLevel.isAtLeastU()) return@tryTest
// Acquiring carrier privilege is necessary to override the carrier service package.
val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId)
acquireCarrierPrivilege(defaultSubId)
setCarrierServicePackageOverride(defaultSubId, servicePackage)
carrierConfigRule.acquireCarrierPrivilege(defaultSubId)
carrierConfigRule.setCarrierServicePackageOverride(defaultSubId, servicePackage)
val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) {
tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex)
}
@ -896,10 +792,6 @@ class NetworkAgentTest {
expectUidsPresent = false)
doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
uid, expectUidsPresent = false)
} cleanupStep {
if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
} cleanup {
if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId)
}
}
@ -2001,25 +1893,3 @@ class NetworkAgentTest {
doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
}
}
// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as
// inner classes of the test class and will fail resolution on R as the test harness
// uses reflection to list all methods and classes
class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
CarrierPrivilegesCallback {
var hasPrivilege = false
override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
hasPrivilege = uids.contains(Process.myUid())
cv.open()
}
}
class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
CarrierPrivilegesCallback {
var pkgName: String? = null
override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
this.pkgName = pkgName
cv.open()
}
}