Skip to content

Commit 4fbf1a9

Browse files
Billy HuangGerrit Code Review
Billy Huang
authored and
Gerrit Code Review
committed
Merge "Add unit test coverage for unlock attempts in TrustTests" into main
2 parents ff58e5a + 79021d5 commit 4fbf1a9

File tree

5 files changed

+362
-15
lines changed

5 files changed

+362
-15
lines changed

tests/TrustTests/AndroidManifest.xml

+11
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
<action android:name="android.service.trust.TrustAgentService" />
7979
</intent-filter>
8080
</service>
81+
8182
<service
8283
android:name=".IsActiveUnlockRunningTrustAgent"
8384
android:exported="true"
@@ -88,6 +89,16 @@
8889
</intent-filter>
8990
</service>
9091

92+
<service
93+
android:name=".UnlockAttemptTrustAgent"
94+
android:exported="true"
95+
android:label="Test Agent"
96+
android:permission="android.permission.BIND_TRUST_AGENT">
97+
<intent-filter>
98+
<action android:name="android.service.trust.TrustAgentService" />
99+
</intent-filter>
100+
</service>
101+
91102
</application>
92103

93104
<!-- self-instrumenting test package. -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright (C) 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package android.trust.test
17+
18+
import android.app.trust.TrustManager
19+
import android.content.Context
20+
import android.trust.BaseTrustAgentService
21+
import android.trust.TrustTestActivity
22+
import android.trust.test.lib.LockStateTrackingRule
23+
import android.trust.test.lib.ScreenLockRule
24+
import android.trust.test.lib.TestTrustListener
25+
import android.trust.test.lib.TrustAgentRule
26+
import android.util.Log
27+
import androidx.test.core.app.ApplicationProvider.getApplicationContext
28+
import androidx.test.ext.junit.rules.ActivityScenarioRule
29+
import androidx.test.ext.junit.runners.AndroidJUnit4
30+
import com.google.common.truth.Truth.assertThat
31+
import org.junit.Before
32+
import org.junit.Rule
33+
import org.junit.Test
34+
import org.junit.rules.RuleChain
35+
import org.junit.runner.RunWith
36+
37+
/**
38+
* Test for the impacts of reporting unlock attempts.
39+
*
40+
* atest TrustTests:UnlockAttemptTest
41+
*/
42+
@RunWith(AndroidJUnit4::class)
43+
class UnlockAttemptTest {
44+
private val context = getApplicationContext<Context>()
45+
private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
46+
private val userId = context.userId
47+
private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
48+
private val screenLockRule = ScreenLockRule(requireStrongAuth = true)
49+
private val lockStateTrackingRule = LockStateTrackingRule()
50+
private val trustAgentRule =
51+
TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false)
52+
53+
private val trustListener = UnlockAttemptTrustListener()
54+
private val agent get() = trustAgentRule.agent
55+
56+
@get:Rule
57+
val rule: RuleChain =
58+
RuleChain.outerRule(activityScenarioRule)
59+
.around(screenLockRule)
60+
.around(lockStateTrackingRule)
61+
.around(trustAgentRule)
62+
63+
@Before
64+
fun setUp() {
65+
trustManager.registerTrustListener(trustListener)
66+
}
67+
68+
@Test
69+
fun successfulUnlockAttempt_allowsTrustAgentToStart() =
70+
runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
71+
trustAgentRule.enableTrustAgent()
72+
73+
triggerSuccessfulUnlock()
74+
75+
trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
76+
}
77+
78+
@Test
79+
fun successfulUnlockAttempt_notifiesTrustAgent() =
80+
runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
81+
val oldSuccessfulCount = agent.successfulUnlockCallCount
82+
val oldFailedCount = agent.failedUnlockCallCount
83+
84+
triggerSuccessfulUnlock()
85+
86+
assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1)
87+
assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount)
88+
}
89+
90+
@Test
91+
fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() =
92+
runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
93+
val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
94+
95+
triggerSuccessfulUnlock()
96+
97+
assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
98+
oldTrustManagedChangedCount + 1
99+
)
100+
}
101+
102+
@Test
103+
fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() =
104+
runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
105+
trustAgentRule.enableTrustAgent()
106+
107+
triggerFailedUnlock()
108+
109+
trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
110+
}
111+
112+
@Test
113+
fun failedUnlockAttempt_notifiesTrustAgent() =
114+
runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
115+
val oldSuccessfulCount = agent.successfulUnlockCallCount
116+
val oldFailedCount = agent.failedUnlockCallCount
117+
118+
triggerFailedUnlock()
119+
120+
assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount)
121+
assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1)
122+
}
123+
124+
@Test
125+
fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() =
126+
runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
127+
val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
128+
129+
triggerFailedUnlock()
130+
131+
assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
132+
oldTrustManagedChangedCount
133+
)
134+
}
135+
136+
private fun runUnlockAttemptTest(
137+
enableAndVerifyTrustAgent: Boolean,
138+
managingTrust: Boolean,
139+
testBlock: () -> Unit,
140+
) {
141+
if (enableAndVerifyTrustAgent) {
142+
Log.i(TAG, "Triggering successful unlock")
143+
triggerSuccessfulUnlock()
144+
Log.i(TAG, "Enabling and waiting for trust agent")
145+
trustAgentRule.enableAndVerifyTrustAgentIsRunning(
146+
MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START
147+
)
148+
Log.i(TAG, "Managing trust: $managingTrust")
149+
agent.setManagingTrust(managingTrust)
150+
await()
151+
}
152+
testBlock()
153+
}
154+
155+
private fun triggerSuccessfulUnlock() {
156+
screenLockRule.successfulScreenLockAttempt()
157+
trustAgentRule.reportSuccessfulUnlock()
158+
await()
159+
}
160+
161+
private fun triggerFailedUnlock() {
162+
screenLockRule.failedScreenLockAttempt()
163+
trustAgentRule.reportFailedUnlock()
164+
await()
165+
}
166+
167+
companion object {
168+
private const val TAG = "UnlockAttemptTest"
169+
private fun await(millis: Long = 500) = Thread.sleep(millis)
170+
private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L
171+
}
172+
}
173+
174+
class UnlockAttemptTrustAgent : BaseTrustAgentService() {
175+
var successfulUnlockCallCount: Long = 0
176+
private set
177+
var failedUnlockCallCount: Long = 0
178+
private set
179+
180+
override fun onUnlockAttempt(successful: Boolean) {
181+
super.onUnlockAttempt(successful)
182+
if (successful) {
183+
successfulUnlockCallCount++
184+
} else {
185+
failedUnlockCallCount++
186+
}
187+
}
188+
}
189+
190+
private class UnlockAttemptTrustListener : TestTrustListener() {
191+
var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>()
192+
var onTrustManagedChangedCount = mutableMapOf<Int, Int>()
193+
194+
override fun onEnabledTrustAgentsChanged(userId: Int) {
195+
enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? ->
196+
if (curr == null) 0 else curr + 1
197+
}
198+
}
199+
200+
data class TrustChangedParams(
201+
val enabled: Boolean,
202+
val newlyUnlocked: Boolean,
203+
val userId: Int,
204+
val flags: Int,
205+
val trustGrantedMessages: MutableList<String>?
206+
)
207+
208+
val onTrustChangedCalls = mutableListOf<TrustChangedParams>()
209+
210+
override fun onTrustChanged(
211+
enabled: Boolean,
212+
newlyUnlocked: Boolean,
213+
userId: Int,
214+
flags: Int,
215+
trustGrantedMessages: MutableList<String>
216+
) {
217+
onTrustChangedCalls += TrustChangedParams(
218+
enabled, newlyUnlocked, userId, flags, trustGrantedMessages
219+
)
220+
}
221+
222+
override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
223+
onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? ->
224+
if (curr == null) 0 else curr + 1
225+
}
226+
}
227+
}

tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt

+43-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext
2424
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
2525
import androidx.test.uiautomator.UiDevice
2626
import com.android.internal.widget.LockPatternUtils
27+
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
28+
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
2729
import com.android.internal.widget.LockscreenCredential
2830
import com.google.common.truth.Truth.assertWithMessage
2931
import org.junit.rules.TestRule
@@ -32,31 +34,54 @@ import org.junit.runners.model.Statement
3234

3335
/**
3436
* Sets a screen lock on the device for the duration of the test.
37+
*
38+
* @param requireStrongAuth Whether a strong auth is required at the beginning.
39+
* If true, trust agents will not be available until the user verifies their credentials.
3540
*/
36-
class ScreenLockRule : TestRule {
41+
class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule {
3742
private val context: Context = getApplicationContext()
43+
private val userId = context.userId
3844
private val uiDevice = UiDevice.getInstance(getInstrumentation())
3945
private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
4046
private val lockPatternUtils = LockPatternUtils(context)
4147
private var instantLockSavedValue = false
48+
private var strongAuthSavedValue: Int = 0
4249

4350
override fun apply(base: Statement, description: Description) = object : Statement() {
4451
override fun evaluate() {
4552
verifyNoScreenLockAlreadySet()
4653
dismissKeyguard()
4754
setScreenLock()
4855
setLockOnPowerButton()
56+
configureStrongAuthState()
4957

5058
try {
5159
base.evaluate()
5260
} finally {
61+
restoreStrongAuthState()
5362
removeScreenLock()
5463
revertLockOnPowerButton()
5564
dismissKeyguard()
5665
}
5766
}
5867
}
5968

69+
private fun configureStrongAuthState() {
70+
strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId)
71+
if (requireStrongAuth) {
72+
Log.d(TAG, "Triggering strong auth due to simulated lockdown")
73+
lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId)
74+
wait("strong auth required after lockdown") {
75+
lockPatternUtils.getStrongAuthForUser(userId) ==
76+
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
77+
}
78+
}
79+
}
80+
81+
private fun restoreStrongAuthState() {
82+
lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId)
83+
}
84+
6085
private fun verifyNoScreenLockAlreadySet() {
6186
assertWithMessage("Screen Lock must not already be set on device")
6287
.that(lockPatternUtils.isSecure(context.userId))
@@ -82,6 +107,22 @@ class ScreenLockRule : TestRule {
82107
}
83108
}
84109

110+
fun successfulScreenLockAttempt() {
111+
lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0)
112+
lockPatternUtils.userPresent(context.userId)
113+
wait("strong auth not required") {
114+
lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED
115+
}
116+
}
117+
118+
fun failedScreenLockAttempt() {
119+
lockPatternUtils.verifyCredential(
120+
LockscreenCredential.createPin(WRONG_PIN),
121+
context.userId,
122+
0
123+
)
124+
}
125+
85126
private fun setScreenLock() {
86127
lockPatternUtils.setLockCredential(
87128
LockscreenCredential.createPin(PIN),
@@ -121,5 +162,6 @@ class ScreenLockRule : TestRule {
121162
companion object {
122163
private const val TAG = "ScreenLockRule"
123164
private const val PIN = "0000"
165+
private const val WRONG_PIN = "0001"
124166
}
125167
}

0 commit comments

Comments
 (0)