Loading tests/TrustTests/AndroidManifest.xml +11 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ <action android:name="android.service.trust.TrustAgentService" /> </intent-filter> </service> <service android:name=".IsActiveUnlockRunningTrustAgent" android:exported="true" Loading @@ -88,6 +89,16 @@ </intent-filter> </service> <service android:name=".UnlockAttemptTrustAgent" android:exported="true" android:label="Test Agent" android:permission="android.permission.BIND_TRUST_AGENT"> <intent-filter> <action android:name="android.service.trust.TrustAgentService" /> </intent-filter> </service> </application> <!-- self-instrumenting test package. --> Loading tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.trust.test import android.app.trust.TrustManager import android.content.Context import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule import android.trust.test.lib.ScreenLockRule import android.trust.test.lib.TestTrustListener import android.trust.test.lib.TrustAgentRule import android.util.Log import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith /** * Test for the impacts of reporting unlock attempts. * * atest TrustTests:UnlockAttemptTest */ @RunWith(AndroidJUnit4::class) class UnlockAttemptTest { private val context = getApplicationContext<Context>() private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager private val userId = context.userId private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) private val screenLockRule = ScreenLockRule(requireStrongAuth = true) private val lockStateTrackingRule = LockStateTrackingRule() private val trustAgentRule = TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false) private val trustListener = UnlockAttemptTrustListener() private val agent get() = trustAgentRule.agent @get:Rule val rule: RuleChain = RuleChain.outerRule(activityScenarioRule) .around(screenLockRule) .around(lockStateTrackingRule) .around(trustAgentRule) @Before fun setUp() { trustManager.registerTrustListener(trustListener) } @Test fun successfulUnlockAttempt_allowsTrustAgentToStart() = runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) { trustAgentRule.enableTrustAgent() triggerSuccessfulUnlock() trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START) } @Test fun successfulUnlockAttempt_notifiesTrustAgent() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldSuccessfulCount = agent.successfulUnlockCallCount val oldFailedCount = agent.failedUnlockCallCount triggerSuccessfulUnlock() assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1) assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount) } @Test fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0 triggerSuccessfulUnlock() assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo( oldTrustManagedChangedCount + 1 ) } @Test fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() = runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) { trustAgentRule.enableTrustAgent() triggerFailedUnlock() trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START) } @Test fun failedUnlockAttempt_notifiesTrustAgent() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldSuccessfulCount = agent.successfulUnlockCallCount val oldFailedCount = agent.failedUnlockCallCount triggerFailedUnlock() assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount) assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1) } @Test fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0 triggerFailedUnlock() assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo( oldTrustManagedChangedCount ) } private fun runUnlockAttemptTest( enableAndVerifyTrustAgent: Boolean, managingTrust: Boolean, testBlock: () -> Unit, ) { if (enableAndVerifyTrustAgent) { Log.i(TAG, "Triggering successful unlock") triggerSuccessfulUnlock() Log.i(TAG, "Enabling and waiting for trust agent") trustAgentRule.enableAndVerifyTrustAgentIsRunning( MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START ) Log.i(TAG, "Managing trust: $managingTrust") agent.setManagingTrust(managingTrust) await() } testBlock() } private fun triggerSuccessfulUnlock() { screenLockRule.successfulScreenLockAttempt() trustAgentRule.reportSuccessfulUnlock() await() } private fun triggerFailedUnlock() { screenLockRule.failedScreenLockAttempt() trustAgentRule.reportFailedUnlock() await() } companion object { private const val TAG = "UnlockAttemptTest" private fun await(millis: Long = 500) = Thread.sleep(millis) private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L } } class UnlockAttemptTrustAgent : BaseTrustAgentService() { var successfulUnlockCallCount: Long = 0 private set var failedUnlockCallCount: Long = 0 private set override fun onUnlockAttempt(successful: Boolean) { super.onUnlockAttempt(successful) if (successful) { successfulUnlockCallCount++ } else { failedUnlockCallCount++ } } } private class UnlockAttemptTrustListener : TestTrustListener() { var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>() var onTrustManagedChangedCount = mutableMapOf<Int, Int>() override fun onEnabledTrustAgentsChanged(userId: Int) { enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? -> if (curr == null) 0 else curr + 1 } } data class TrustChangedParams( val enabled: Boolean, val newlyUnlocked: Boolean, val userId: Int, val flags: Int, val trustGrantedMessages: MutableList<String>? ) val onTrustChangedCalls = mutableListOf<TrustChangedParams>() override fun onTrustChanged( enabled: Boolean, newlyUnlocked: Boolean, userId: Int, flags: Int, trustGrantedMessages: MutableList<String> ) { onTrustChangedCalls += TrustChangedParams( enabled, newlyUnlocked, userId, flags, trustGrantedMessages ) } override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? -> if (curr == null) 0 else curr + 1 } } } tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +43 −1 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.internal.widget.LockscreenCredential import com.google.common.truth.Truth.assertWithMessage import org.junit.rules.TestRule Loading @@ -32,13 +34,18 @@ import org.junit.runners.model.Statement /** * Sets a screen lock on the device for the duration of the test. * * @param requireStrongAuth Whether a strong auth is required at the beginning. * If true, trust agents will not be available until the user verifies their credentials. */ class ScreenLockRule : TestRule { class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule { private val context: Context = getApplicationContext() private val userId = context.userId private val uiDevice = UiDevice.getInstance(getInstrumentation()) private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService()) private val lockPatternUtils = LockPatternUtils(context) private var instantLockSavedValue = false private var strongAuthSavedValue: Int = 0 override fun apply(base: Statement, description: Description) = object : Statement() { override fun evaluate() { Loading @@ -46,10 +53,12 @@ class ScreenLockRule : TestRule { dismissKeyguard() setScreenLock() setLockOnPowerButton() configureStrongAuthState() try { base.evaluate() } finally { restoreStrongAuthState() removeScreenLock() revertLockOnPowerButton() dismissKeyguard() Loading @@ -57,6 +66,22 @@ class ScreenLockRule : TestRule { } } private fun configureStrongAuthState() { strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId) if (requireStrongAuth) { Log.d(TAG, "Triggering strong auth due to simulated lockdown") lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId) wait("strong auth required after lockdown") { lockPatternUtils.getStrongAuthForUser(userId) == STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN } } } private fun restoreStrongAuthState() { lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId) } private fun verifyNoScreenLockAlreadySet() { assertWithMessage("Screen Lock must not already be set on device") .that(lockPatternUtils.isSecure(context.userId)) Loading @@ -82,6 +107,22 @@ class ScreenLockRule : TestRule { } } fun successfulScreenLockAttempt() { lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0) lockPatternUtils.userPresent(context.userId) wait("strong auth not required") { lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED } } fun failedScreenLockAttempt() { lockPatternUtils.verifyCredential( LockscreenCredential.createPin(WRONG_PIN), context.userId, 0 ) } private fun setScreenLock() { lockPatternUtils.setLockCredential( LockscreenCredential.createPin(PIN), Loading Loading @@ -121,5 +162,6 @@ class ScreenLockRule : TestRule { companion object { private const val TAG = "ScreenLockRule" private const val PIN = "0000" private const val WRONG_PIN = "0001" } } tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt +49 −13 Original line number Diff line number Diff line Loading @@ -20,14 +20,15 @@ import android.app.trust.TrustManager import android.content.ComponentName import android.content.Context import android.trust.BaseTrustAgentService import android.trust.test.lib.TrustAgentRule.Companion.invoke import android.util.Log import androidx.test.core.app.ApplicationProvider.getApplicationContext import com.android.internal.widget.LockPatternUtils import com.google.common.truth.Truth.assertWithMessage import kotlin.reflect.KClass import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement import kotlin.reflect.KClass /** * Enables a trust agent and causes the system service to bind to it. Loading @@ -37,7 +38,9 @@ import kotlin.reflect.KClass * @constructor Creates the rule. Do not use; instead, use [invoke]. */ class TrustAgentRule<T : BaseTrustAgentService>( private val serviceClass: KClass<T> private val serviceClass: KClass<T>, private val startUnlocked: Boolean, private val startEnabled: Boolean, ) : TestRule { private val context: Context = getApplicationContext() private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager Loading @@ -48,11 +51,18 @@ class TrustAgentRule<T : BaseTrustAgentService>( override fun apply(base: Statement, description: Description) = object : Statement() { override fun evaluate() { verifyTrustServiceRunning() unlockDeviceWithCredential() enableTrustAgent() if (startUnlocked) { reportSuccessfulUnlock() } else { Log.i(TAG, "Trust manager not starting in unlocked state") } try { verifyAgentIsRunning() if (startEnabled) { enableAndVerifyTrustAgentIsRunning() } else { Log.i(TAG, "Trust agent ${serviceClass.simpleName} not enabled") } base.evaluate() } finally { disableTrustAgent() Loading @@ -64,12 +74,22 @@ class TrustAgentRule<T : BaseTrustAgentService>( assertWithMessage("Trust service is not running").that(trustManager).isNotNull() } private fun unlockDeviceWithCredential() { Log.d(TAG, "Unlocking device with credential") fun reportSuccessfulUnlock() { Log.i(TAG, "Reporting successful unlock") trustManager.reportUnlockAttempt(true, context.userId) } private fun enableTrustAgent() { fun reportFailedUnlock() { Log.i(TAG, "Reporting failed unlock") trustManager.reportUnlockAttempt(false, context.userId) } fun enableAndVerifyTrustAgentIsRunning(maxWait: Long = 30000L) { enableTrustAgent() verifyAgentIsRunning(maxWait) } fun enableTrustAgent() { val componentName = ComponentName(context, serviceClass.java) val userId = context.userId Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId") Loading @@ -79,12 +99,18 @@ class TrustAgentRule<T : BaseTrustAgentService>( lockPatternUtils.setEnabledTrustAgents(agents, userId) } private fun verifyAgentIsRunning() { wait("${serviceClass.simpleName} to be running") { fun verifyAgentIsRunning(maxWait: Long = 30000L) { wait("${serviceClass.simpleName} to be running", maxWait) { BaseTrustAgentService.instance(serviceClass) != null } } fun ensureAgentIsNotRunning(window: Long = 30000L) { ensure("${serviceClass.simpleName} is not running", window) { BaseTrustAgentService.instance(serviceClass) == null } } private fun disableTrustAgent() { val componentName = ComponentName(context, serviceClass.java) val userId = context.userId Loading @@ -97,13 +123,23 @@ class TrustAgentRule<T : BaseTrustAgentService>( companion object { /** * Creates a new rule for the specified agent class. Example usage: * Creates a new rule for the specified agent class. Starts with the device unlocked and * the trust agent enabled. Example usage: * ``` * @get:Rule val rule = TrustAgentRule<MyTestAgent>() * ``` * * Also supports setting different device lock and trust agent enablement states: * ``` * @get:Rule val rule = TrustAgentRule<MyTestAgent>(startUnlocked = false, startEnabled = false) * ``` */ inline operator fun <reified T : BaseTrustAgentService> invoke() = TrustAgentRule(T::class) inline operator fun <reified T : BaseTrustAgentService> invoke( startUnlocked: Boolean = true, startEnabled: Boolean = true, ) = TrustAgentRule(T::class, startUnlocked, startEnabled) private const val TAG = "TrustAgentRule" } Loading tests/TrustTests/src/android/trust/test/lib/utils.kt→tests/TrustTests/src/android/trust/test/lib/Utils.kt +32 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ internal fun wait( ) { var waited = 0L var count = 0 while (!conditionFunction.invoke(count)) { while (!conditionFunction(count)) { assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description") .that(waited <= maxWait) .isTrue() Loading @@ -49,3 +49,34 @@ internal fun wait( Thread.sleep(rate) } } /** * Ensures that [conditionFunction] is true with a failed assertion if it is not within [window] * ms. * * The condition function can perform additional logic (for example, logging or attempting to make * the condition become true). * * @param conditionFunction function which takes the attempt count & returns whether the condition * is met */ internal fun ensure( description: String? = null, window: Long = 30000L, rate: Long = 50L, conditionFunction: (count: Int) -> Boolean ) { var waited = 0L var count = 0 while (waited <= window) { assertWithMessage("Condition failed within $window ms: $description").that( conditionFunction( count ) ).isTrue() waited += rate count++ Log.i(TAG, "Ensuring $description ($waited/$window) #$count") Thread.sleep(rate) } } Loading
tests/TrustTests/AndroidManifest.xml +11 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ <action android:name="android.service.trust.TrustAgentService" /> </intent-filter> </service> <service android:name=".IsActiveUnlockRunningTrustAgent" android:exported="true" Loading @@ -88,6 +89,16 @@ </intent-filter> </service> <service android:name=".UnlockAttemptTrustAgent" android:exported="true" android:label="Test Agent" android:permission="android.permission.BIND_TRUST_AGENT"> <intent-filter> <action android:name="android.service.trust.TrustAgentService" /> </intent-filter> </service> </application> <!-- self-instrumenting test package. --> Loading
tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.trust.test import android.app.trust.TrustManager import android.content.Context import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule import android.trust.test.lib.ScreenLockRule import android.trust.test.lib.TestTrustListener import android.trust.test.lib.TrustAgentRule import android.util.Log import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith /** * Test for the impacts of reporting unlock attempts. * * atest TrustTests:UnlockAttemptTest */ @RunWith(AndroidJUnit4::class) class UnlockAttemptTest { private val context = getApplicationContext<Context>() private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager private val userId = context.userId private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) private val screenLockRule = ScreenLockRule(requireStrongAuth = true) private val lockStateTrackingRule = LockStateTrackingRule() private val trustAgentRule = TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false) private val trustListener = UnlockAttemptTrustListener() private val agent get() = trustAgentRule.agent @get:Rule val rule: RuleChain = RuleChain.outerRule(activityScenarioRule) .around(screenLockRule) .around(lockStateTrackingRule) .around(trustAgentRule) @Before fun setUp() { trustManager.registerTrustListener(trustListener) } @Test fun successfulUnlockAttempt_allowsTrustAgentToStart() = runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) { trustAgentRule.enableTrustAgent() triggerSuccessfulUnlock() trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START) } @Test fun successfulUnlockAttempt_notifiesTrustAgent() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldSuccessfulCount = agent.successfulUnlockCallCount val oldFailedCount = agent.failedUnlockCallCount triggerSuccessfulUnlock() assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1) assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount) } @Test fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0 triggerSuccessfulUnlock() assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo( oldTrustManagedChangedCount + 1 ) } @Test fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() = runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) { trustAgentRule.enableTrustAgent() triggerFailedUnlock() trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START) } @Test fun failedUnlockAttempt_notifiesTrustAgent() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldSuccessfulCount = agent.successfulUnlockCallCount val oldFailedCount = agent.failedUnlockCallCount triggerFailedUnlock() assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount) assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1) } @Test fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() = runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) { val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0 triggerFailedUnlock() assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo( oldTrustManagedChangedCount ) } private fun runUnlockAttemptTest( enableAndVerifyTrustAgent: Boolean, managingTrust: Boolean, testBlock: () -> Unit, ) { if (enableAndVerifyTrustAgent) { Log.i(TAG, "Triggering successful unlock") triggerSuccessfulUnlock() Log.i(TAG, "Enabling and waiting for trust agent") trustAgentRule.enableAndVerifyTrustAgentIsRunning( MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START ) Log.i(TAG, "Managing trust: $managingTrust") agent.setManagingTrust(managingTrust) await() } testBlock() } private fun triggerSuccessfulUnlock() { screenLockRule.successfulScreenLockAttempt() trustAgentRule.reportSuccessfulUnlock() await() } private fun triggerFailedUnlock() { screenLockRule.failedScreenLockAttempt() trustAgentRule.reportFailedUnlock() await() } companion object { private const val TAG = "UnlockAttemptTest" private fun await(millis: Long = 500) = Thread.sleep(millis) private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L } } class UnlockAttemptTrustAgent : BaseTrustAgentService() { var successfulUnlockCallCount: Long = 0 private set var failedUnlockCallCount: Long = 0 private set override fun onUnlockAttempt(successful: Boolean) { super.onUnlockAttempt(successful) if (successful) { successfulUnlockCallCount++ } else { failedUnlockCallCount++ } } } private class UnlockAttemptTrustListener : TestTrustListener() { var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>() var onTrustManagedChangedCount = mutableMapOf<Int, Int>() override fun onEnabledTrustAgentsChanged(userId: Int) { enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? -> if (curr == null) 0 else curr + 1 } } data class TrustChangedParams( val enabled: Boolean, val newlyUnlocked: Boolean, val userId: Int, val flags: Int, val trustGrantedMessages: MutableList<String>? ) val onTrustChangedCalls = mutableListOf<TrustChangedParams>() override fun onTrustChanged( enabled: Boolean, newlyUnlocked: Boolean, userId: Int, flags: Int, trustGrantedMessages: MutableList<String> ) { onTrustChangedCalls += TrustChangedParams( enabled, newlyUnlocked, userId, flags, trustGrantedMessages ) } override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? -> if (curr == null) 0 else curr + 1 } } }
tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +43 −1 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN import com.android.internal.widget.LockscreenCredential import com.google.common.truth.Truth.assertWithMessage import org.junit.rules.TestRule Loading @@ -32,13 +34,18 @@ import org.junit.runners.model.Statement /** * Sets a screen lock on the device for the duration of the test. * * @param requireStrongAuth Whether a strong auth is required at the beginning. * If true, trust agents will not be available until the user verifies their credentials. */ class ScreenLockRule : TestRule { class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule { private val context: Context = getApplicationContext() private val userId = context.userId private val uiDevice = UiDevice.getInstance(getInstrumentation()) private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService()) private val lockPatternUtils = LockPatternUtils(context) private var instantLockSavedValue = false private var strongAuthSavedValue: Int = 0 override fun apply(base: Statement, description: Description) = object : Statement() { override fun evaluate() { Loading @@ -46,10 +53,12 @@ class ScreenLockRule : TestRule { dismissKeyguard() setScreenLock() setLockOnPowerButton() configureStrongAuthState() try { base.evaluate() } finally { restoreStrongAuthState() removeScreenLock() revertLockOnPowerButton() dismissKeyguard() Loading @@ -57,6 +66,22 @@ class ScreenLockRule : TestRule { } } private fun configureStrongAuthState() { strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId) if (requireStrongAuth) { Log.d(TAG, "Triggering strong auth due to simulated lockdown") lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId) wait("strong auth required after lockdown") { lockPatternUtils.getStrongAuthForUser(userId) == STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN } } } private fun restoreStrongAuthState() { lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId) } private fun verifyNoScreenLockAlreadySet() { assertWithMessage("Screen Lock must not already be set on device") .that(lockPatternUtils.isSecure(context.userId)) Loading @@ -82,6 +107,22 @@ class ScreenLockRule : TestRule { } } fun successfulScreenLockAttempt() { lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0) lockPatternUtils.userPresent(context.userId) wait("strong auth not required") { lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED } } fun failedScreenLockAttempt() { lockPatternUtils.verifyCredential( LockscreenCredential.createPin(WRONG_PIN), context.userId, 0 ) } private fun setScreenLock() { lockPatternUtils.setLockCredential( LockscreenCredential.createPin(PIN), Loading Loading @@ -121,5 +162,6 @@ class ScreenLockRule : TestRule { companion object { private const val TAG = "ScreenLockRule" private const val PIN = "0000" private const val WRONG_PIN = "0001" } }
tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt +49 −13 Original line number Diff line number Diff line Loading @@ -20,14 +20,15 @@ import android.app.trust.TrustManager import android.content.ComponentName import android.content.Context import android.trust.BaseTrustAgentService import android.trust.test.lib.TrustAgentRule.Companion.invoke import android.util.Log import androidx.test.core.app.ApplicationProvider.getApplicationContext import com.android.internal.widget.LockPatternUtils import com.google.common.truth.Truth.assertWithMessage import kotlin.reflect.KClass import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement import kotlin.reflect.KClass /** * Enables a trust agent and causes the system service to bind to it. Loading @@ -37,7 +38,9 @@ import kotlin.reflect.KClass * @constructor Creates the rule. Do not use; instead, use [invoke]. */ class TrustAgentRule<T : BaseTrustAgentService>( private val serviceClass: KClass<T> private val serviceClass: KClass<T>, private val startUnlocked: Boolean, private val startEnabled: Boolean, ) : TestRule { private val context: Context = getApplicationContext() private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager Loading @@ -48,11 +51,18 @@ class TrustAgentRule<T : BaseTrustAgentService>( override fun apply(base: Statement, description: Description) = object : Statement() { override fun evaluate() { verifyTrustServiceRunning() unlockDeviceWithCredential() enableTrustAgent() if (startUnlocked) { reportSuccessfulUnlock() } else { Log.i(TAG, "Trust manager not starting in unlocked state") } try { verifyAgentIsRunning() if (startEnabled) { enableAndVerifyTrustAgentIsRunning() } else { Log.i(TAG, "Trust agent ${serviceClass.simpleName} not enabled") } base.evaluate() } finally { disableTrustAgent() Loading @@ -64,12 +74,22 @@ class TrustAgentRule<T : BaseTrustAgentService>( assertWithMessage("Trust service is not running").that(trustManager).isNotNull() } private fun unlockDeviceWithCredential() { Log.d(TAG, "Unlocking device with credential") fun reportSuccessfulUnlock() { Log.i(TAG, "Reporting successful unlock") trustManager.reportUnlockAttempt(true, context.userId) } private fun enableTrustAgent() { fun reportFailedUnlock() { Log.i(TAG, "Reporting failed unlock") trustManager.reportUnlockAttempt(false, context.userId) } fun enableAndVerifyTrustAgentIsRunning(maxWait: Long = 30000L) { enableTrustAgent() verifyAgentIsRunning(maxWait) } fun enableTrustAgent() { val componentName = ComponentName(context, serviceClass.java) val userId = context.userId Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId") Loading @@ -79,12 +99,18 @@ class TrustAgentRule<T : BaseTrustAgentService>( lockPatternUtils.setEnabledTrustAgents(agents, userId) } private fun verifyAgentIsRunning() { wait("${serviceClass.simpleName} to be running") { fun verifyAgentIsRunning(maxWait: Long = 30000L) { wait("${serviceClass.simpleName} to be running", maxWait) { BaseTrustAgentService.instance(serviceClass) != null } } fun ensureAgentIsNotRunning(window: Long = 30000L) { ensure("${serviceClass.simpleName} is not running", window) { BaseTrustAgentService.instance(serviceClass) == null } } private fun disableTrustAgent() { val componentName = ComponentName(context, serviceClass.java) val userId = context.userId Loading @@ -97,13 +123,23 @@ class TrustAgentRule<T : BaseTrustAgentService>( companion object { /** * Creates a new rule for the specified agent class. Example usage: * Creates a new rule for the specified agent class. Starts with the device unlocked and * the trust agent enabled. Example usage: * ``` * @get:Rule val rule = TrustAgentRule<MyTestAgent>() * ``` * * Also supports setting different device lock and trust agent enablement states: * ``` * @get:Rule val rule = TrustAgentRule<MyTestAgent>(startUnlocked = false, startEnabled = false) * ``` */ inline operator fun <reified T : BaseTrustAgentService> invoke() = TrustAgentRule(T::class) inline operator fun <reified T : BaseTrustAgentService> invoke( startUnlocked: Boolean = true, startEnabled: Boolean = true, ) = TrustAgentRule(T::class, startUnlocked, startEnabled) private const val TAG = "TrustAgentRule" } Loading
tests/TrustTests/src/android/trust/test/lib/utils.kt→tests/TrustTests/src/android/trust/test/lib/Utils.kt +32 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ internal fun wait( ) { var waited = 0L var count = 0 while (!conditionFunction.invoke(count)) { while (!conditionFunction(count)) { assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description") .that(waited <= maxWait) .isTrue() Loading @@ -49,3 +49,34 @@ internal fun wait( Thread.sleep(rate) } } /** * Ensures that [conditionFunction] is true with a failed assertion if it is not within [window] * ms. * * The condition function can perform additional logic (for example, logging or attempting to make * the condition become true). * * @param conditionFunction function which takes the attempt count & returns whether the condition * is met */ internal fun ensure( description: String? = null, window: Long = 30000L, rate: Long = 50L, conditionFunction: (count: Int) -> Boolean ) { var waited = 0L var count = 0 while (waited <= window) { assertWithMessage("Condition failed within $window ms: $description").that( conditionFunction( count ) ).isTrue() waited += rate count++ Log.i(TAG, "Ensuring $description ($waited/$window) #$count") Thread.sleep(rate) } }