From a620310e9c1ce6787174c46b4afc3da0b56f7523 Mon Sep 17 00:00:00 2001 From: Quang Anh Luong Date: Sun, 16 Mar 2025 14:11:29 +0900 Subject: [PATCH] Add carrier privilege utils to CarrierConfigRule Move the existing helper methods to acquire carrier privileges in NetworkAgentTest.kt to CarrierConfigRule where it can be reused by other CTS tests. Also reset the carrier service package override in the rule cleanup. Bug: 216524590 Test: atest NetworkAgentTest Change-Id: Id907a2d641aa61838119e2be65654b9796464498 --- .../android/testutils/CarrierConfigRule.kt | 195 +++++++++++++++++- .../src/android/net/cts/NetworkAgentTest.kt | 134 +----------- 2 files changed, 195 insertions(+), 134 deletions(-) diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt index ae0de794b9..c9d2527e4a 100644 --- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt +++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt @@ -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() + // Map of (subId) -> (original values of carrier service package) + private val originalCarrierServicePackages = mutableMapOf() + 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, uids: MutableSet) { + hasPrivilege = uids.contains(Process.myUid()) + cv.open() + } + } + + private class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) : + CarrierPrivilegesCallback { + var pkgName: String? = null + override fun onCarrierPrivilegesChanged(p: MutableSet, u: MutableSet) {} + 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 ): PersistableBundle { - return if (isAtLeastU()) { + return if (SdkLevel.isAtLeastU()) { // This method is U+ getConfigForSubId(subId, *keys.toTypedArray()) } else { diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index 5e035a2723..6dc23ff527 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -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, uids: MutableSet) { - hasPrivilege = uids.contains(Process.myUid()) - cv.open() - } -} - -class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) : - CarrierPrivilegesCallback { - var pkgName: String? = null - override fun onCarrierPrivilegesChanged(p: MutableSet, u: MutableSet) {} - override fun onCarrierServiceChanged(pkgName: String?, uid: Int) { - this.pkgName = pkgName - cv.open() - } -}