Adds call to android.os.SecurityStateManager.getGlobalSecurityState() for API >= 35
Test: Updated device tests.
Relnote: `getGlobalSecurityState()` now returns the global security state from the system service for SDK 35+.
Bug: 369392426
Change-Id: I7b9dac324d0b24748e586c26600914037c2960c5
diff --git a/security/security-state/build.gradle b/security/security-state/build.gradle
index 48b0c87..92c605c 100644
--- a/security/security-state/build.gradle
+++ b/security/security-state/build.gradle
@@ -54,6 +54,7 @@
}
android {
+ compileSdk 35
namespace "androidx.security.state"
}
diff --git a/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt b/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
index d9ec8e5c..d0852df 100644
--- a/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
+++ b/security/security-state/src/androidTest/java/androidx/security/state/SecurityStateManagerTest.kt
@@ -18,10 +18,12 @@
import android.content.Context
import android.os.Build
+import android.os.Bundle
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -29,7 +31,6 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
class SecurityStateManagerTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -40,37 +41,90 @@
securityStateManager = SecurityStateManager(context)
}
- @Test
- fun testGetGlobalSecurityState() {
- val bundle = securityStateManager.getGlobalSecurityState()
-
- // Check if dates are in the format YYYY-MM-DD
+ /** Returns `true` if [date] is in the format "YYYY-MM-DD". */
+ private fun matchesDateFormat(date: String): Boolean {
val dateRegex = "^\\d{4}-\\d{2}-\\d{2}$"
- assertTrue(bundle.getString("system_spl")!!.matches(dateRegex.toRegex()))
- assertTrue(bundle.getString("vendor_spl")!!.matches(dateRegex.toRegex()))
+ return date.matches(dateRegex.toRegex())
+ }
- // Check if kernel version is in the format X.X.XX
+ /** Returns `true` if [kernel] is in the format "X.X.XX". */
+ private fun matchesKernelFormat(kernel: String): Boolean {
val versionRegex = "^\\d+\\.\\d+\\.\\d+$"
- assertTrue(bundle.getString("kernel_version")!!.matches(versionRegex.toRegex()))
+ return kernel.matches(versionRegex.toRegex())
+ }
- // Webview keys are expected to have specific naming and version formats
+ /** Returns `true` if a key for a WebView package exists in [bundle]. */
+ private fun containsWebViewPackage(bundle: Bundle): Boolean {
var foundWebView = false
- val nameRegex = "^com\\.[a-zA-Z0-9_.]+\\.webview$"
+ // https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/webview-providers.md#package-name
+ val nameRegex =
+ "^(?:com\\.google\\.android\\.apps\\.chrome|com\\.google\\.android\\.webview|com\\.android\\.(?:chrome|webview)|com\\.chrome)(?:\\.(?:beta|dev|canary|debug))?\$"
val versionRegexWebView = "^\\d+\\.\\d+\\.\\d+\\.\\d+$"
for (key in bundle.keySet()) {
- if (key.contains("webview")) {
+ if (key.matches(nameRegex.toRegex())) {
foundWebView = true
- val nameMatch = key.matches(nameRegex.toRegex())
- val versionMatch = bundle.getString(key)!!.matches(versionRegexWebView.toRegex())
-
- assertTrue("Webview name format incorrect: $key", nameMatch)
- assertTrue(
- "Webview version format incorrect for $key: ${bundle.getString(key)}",
- versionMatch
- )
- break
+ val value = bundle.getString(key)
+ if (value!!.isNotEmpty()) {
+ assertTrue(
+ "WebView version format incorrect for $key: $value",
+ value.matches(versionRegexWebView.toRegex())
+ )
+ break
+ }
}
}
- assertTrue("No webview key found in bundle", foundWebView)
+ return foundWebView
+ }
+
+ /** Returns `true` if a key for the module metadata package name exists in [bundle]. */
+ private fun containsModuleMetadataPackage(bundle: Bundle): Boolean {
+ return bundle.keySet().any { it.contains("modulemetadata") }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+ @Test
+ fun testGetGlobalSecurityState_sdkAbove29() {
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
+ assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
+ assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
+ assertTrue(containsModuleMetadataPackage(bundle))
+ assertTrue(containsWebViewPackage(bundle))
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O, maxSdkVersion = Build.VERSION_CODES.P)
+ @Test
+ fun testGetGlobalSecurityState_sdkAbove25Below29_doesNotContainModuleMetadata() {
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
+ assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
+ assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
+ assertTrue(containsWebViewPackage(bundle))
+ assertFalse(containsModuleMetadataPackage(bundle))
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M, maxSdkVersion = Build.VERSION_CODES.N_MR1)
+ @Test
+ fun testGetGlobalSecurityState_sdkAbove22Below26_doesNotContainModuleMetadataOrWebView() {
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertTrue(matchesDateFormat(bundle.getString("system_spl")!!))
+ assertTrue(matchesDateFormat(bundle.getString("vendor_spl")!!))
+ assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
+ assertFalse(containsModuleMetadataPackage(bundle))
+ assertFalse(containsWebViewPackage(bundle))
+ }
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.LOLLIPOP,
+ maxSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1
+ )
+ @Test
+ fun testGetGlobalSecurityState_sdkBelow23_containsOnlyKernel() {
+ val bundle = securityStateManager.getGlobalSecurityState()
+ assertTrue(matchesKernelFormat(bundle.getString("kernel_version")!!))
+ assertFalse(bundle.containsKey("system_spl"))
+ assertFalse(bundle.containsKey("vendor_spl"))
+ assertFalse(containsModuleMetadataPackage(bundle))
+ assertFalse(containsWebViewPackage(bundle))
}
}
diff --git a/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt b/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
index 011d418..9329c9b 100644
--- a/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
+++ b/security/security-state/src/main/java/androidx/security/state/SecurityStateManager.kt
@@ -27,7 +27,7 @@
import java.util.regex.Pattern
/**
- * This class is a wrapper around AOSP {@link android.os.SecurityStateManager} service API added in
+ * This class is a wrapper around AOSP [android.os.SecurityStateManager] service API added in
* SDK 35. Support for features on older SDKs is provided on a best effort basis.
*
* Manages the retrieval and storage of security patch levels and module information for an Android
@@ -81,8 +81,10 @@
*/
@SuppressLint("NewApi") // Lint does not detect version check below.
public open fun getGlobalSecurityState(moduleMetadataProvider: String? = null): Bundle {
+ if (getAndroidSdkInt() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ return getGlobalSecurityStateFromService()
+ }
return Bundle().apply {
- // TODO(musashi): add call to SecurityStateManager API when it becomes available
if (getAndroidSdkInt() >= Build.VERSION_CODES.M) {
putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH)
@@ -114,6 +116,30 @@
}
/**
+ * Returns the current global security state from the system service on SDK 35+.
+ *
+ * @return A [Bundle] that contains the global security state information as string-to-string
+ * key-value pairs.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @SuppressLint("WrongConstant")
+ private fun getGlobalSecurityStateFromService(): Bundle {
+ val securityStateManagerService =
+ context.getSystemService(Context.SECURITY_STATE_SERVICE)
+ as android.os.SecurityStateManager
+ val globalSecurityState = securityStateManagerService.globalSecurityState
+ val vendorSpl = globalSecurityState.getString(KEY_VENDOR_SPL, "")
+ if (vendorSpl.isEmpty()) {
+ // Assume vendor SPL == system SPL
+ globalSecurityState.putString(
+ KEY_VENDOR_SPL,
+ globalSecurityState.getString(KEY_SYSTEM_SPL)
+ )
+ }
+ return globalSecurityState
+ }
+
+ /**
* Fetches the security patch level (SPL) for a specific package by its package name. This is
* typically used to get version information for modules that may have their own update cycles
* independent of the system OS.