From 49471c7d3fb209c5badc0f13744d1710a3737d82 Mon Sep 17 00:00:00 2001 From: Dave McCloskey Date: Fri, 25 Feb 2022 01:34:18 +0000 Subject: [PATCH] Add an initial set of tests for Trust. This is based on the changes in http://ag/q/topic:au-cts1 which had to be rolled back. Bug: 221155933 Test: atest TrustTests Change-Id: I2e9b878256d0da7ed0017da1947dbd0e161f1aeb --- tests/TrustTests/Android.bp | 39 ++++++ tests/TrustTests/AndroidManifest.xml | 75 +++++++++++ tests/TrustTests/AndroidTest.xml | 28 +++++ tests/TrustTests/README.md | 40 ++++++ .../android/trust/BaseTrustAgentService.kt | 47 +++++++ .../src/android/trust/TrustTestActivity.kt | 30 +++++ .../trust/test/GrantAndRevokeTrustTest.kt | 95 ++++++++++++++ .../src/android/trust/test/LockUserTest.kt | 68 ++++++++++ .../trust/test/UserUnlockRequestTest.kt | 81 ++++++++++++ .../trust/test/lib/LockStateTrackingRule.kt | 83 +++++++++++++ .../android/trust/test/lib/ScreenLockRule.kt | 105 ++++++++++++++++ .../android/trust/test/lib/TrustAgentRule.kt | 117 ++++++++++++++++++ 12 files changed, 808 insertions(+) create mode 100644 tests/TrustTests/Android.bp create mode 100644 tests/TrustTests/AndroidManifest.xml create mode 100644 tests/TrustTests/AndroidTest.xml create mode 100644 tests/TrustTests/README.md create mode 100644 tests/TrustTests/src/android/trust/BaseTrustAgentService.kt create mode 100644 tests/TrustTests/src/android/trust/TrustTestActivity.kt create mode 100644 tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt create mode 100644 tests/TrustTests/src/android/trust/test/LockUserTest.kt create mode 100644 tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt create mode 100644 tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt create mode 100644 tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt create mode 100644 tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp new file mode 100644 index 0000000000000..c9c6c5cf193be --- /dev/null +++ b/tests/TrustTests/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "TrustTests", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "androidx.test.uiautomator", + "truth-prebuilt", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + test_suites: [ + "device-tests", + ], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml new file mode 100644 index 0000000000000..c94152da2bf61 --- /dev/null +++ b/tests/TrustTests/AndroidManifest.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/TrustTests/AndroidTest.xml b/tests/TrustTests/AndroidTest.xml new file mode 100644 index 0000000000000..61b711eb72736 --- /dev/null +++ b/tests/TrustTests/AndroidTest.xml @@ -0,0 +1,28 @@ + + + + diff --git a/tests/TrustTests/README.md b/tests/TrustTests/README.md new file mode 100644 index 0000000000000..3427e30536d96 --- /dev/null +++ b/tests/TrustTests/README.md @@ -0,0 +1,40 @@ +# TrustTests framework tests + +These tests test the "trust" part of the platform primarily implemented via TrustManagerService in +the system server and TrustAgentService in system apps. + +Tests are separated into separate files based on major groupings. When creating new tests, find a +_closely_ matching existing test file or create a new test file. Prefer many test files over large +test files. + +Each test file has its own trust agent. To create a new trust agent: + +1. Create a new class extending from `BaseTrustAgentService` class in your test file +2. Add a new `` stanza to `AndroidManifest.xml` in this directory for the new agent + following the pattern fo the existing agents. + +To run: + +```atest TrustTests``` + +## Testing approach: + +1. Test the agent service as a black box; avoid inspecting internal state of the service or + modifying the system code outside of this directory. +2. The primary interface to the system is through these three points: + 1. `TrustAgentService`, your agent created by the `TrustAgentRule` and accessible via + the `agent` property of the rule. + 1. Call command methods (e.g. `grantTrust`) directly on the agent + 2. Listen to events (e.g. `onUserRequestedUnlock`) by implementing the method in + your test's agent class and tracking invocations. See `UserUnlockRequestTest` for an + example. + 2. `TrustManager` which is the interface the rest of the system (e.g. SystemUI) has to the + service. + 1. Through this API, simulate system events that the service cares about + (e.g. `reportUnlockAttempt`). + 3. `TrustListener` which is the interface the rest of the system (e.g. SystemUI) uses to receive + events from the service. + 1. Through this, verify behavior that affects the rest of the system. For example, + see `LockStateTrackingRule`. +3. To re-use code between tests, prefer creating new rules alongside the existing rules or adding + functionality to a _closely_ matching existing rule. diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt new file mode 100644 index 0000000000000..493f3bd22d2b9 --- /dev/null +++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 + +import android.service.trust.TrustAgentService +import android.util.Log +import kotlin.reflect.KClass + +/** + * Base class for test trust agents. + */ +abstract class BaseTrustAgentService : TrustAgentService() { + + override fun onCreate() { + super.onCreate() + Log.d(TAG, "${this::class.simpleName} created") + instances[this::class] = this + } + + override fun onDestroy() { + super.onDestroy() + instances.remove(this::class) + } + + companion object { + private val instances = + mutableMapOf, BaseTrustAgentService>() + private const val TAG = "BaseTrustAgentService" + + fun instance(serviceClass: KClass): BaseTrustAgentService? { + return instances[serviceClass]!! + } + } +} diff --git a/tests/TrustTests/src/android/trust/TrustTestActivity.kt b/tests/TrustTests/src/android/trust/TrustTestActivity.kt new file mode 100644 index 0000000000000..6c56feace3d71 --- /dev/null +++ b/tests/TrustTests/src/android/trust/TrustTestActivity.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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 + +import android.app.Activity +import android.os.Bundle + +/** + * Activity for testing Trust. + */ +class TrustTestActivity : Activity() { + + public override fun onCreate(icicle: Bundle?) { + super.onCreate(icicle) + setTurnScreenOn(true) + } +} diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt new file mode 100644 index 0000000000000..790afd3891527 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 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.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing revokeTrust & grantTrust for non-renewable trust. + * + * atest TrustTests:GrantAndRevokeTrustTest + */ +@RunWith(AndroidJUnit4::class) +class GrantAndRevokeTrustTest { + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Before + fun manageTrust() { + trustAgentRule.agent.setManagingTrust(true) + } + + // This test serves a baseline for Grant tests, verifying that the default behavior of the + // device is to lock when put to sleep + @Test + fun sleepingDeviceWithoutGrantLocksDevice() { + uiDevice.sleep() + await() + + lockStateTrackingRule.assertLocked() + } + + @Test + fun grantKeepsDeviceUnlocked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) + uiDevice.sleep() + await() + + lockStateTrackingRule.assertUnlocked() + } + + @Test + fun grantKeepsDeviceUnlocked_untilRevoked() { + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) + await() + uiDevice.sleep() + trustAgentRule.agent.revokeTrust() + await() + + lockStateTrackingRule.assertLocked() + } + + companion object { + private const val TAG = "GrantAndRevokeTrustTest" + private const val GRANT_MESSAGE = "granted by test" + private fun await() = Thread.sleep(250) + } +} + +class GrantAndRevokeTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt new file mode 100644 index 0000000000000..83fc28fee8183 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TrustAgentRule +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing lockUser. + * + * atest TrustTests:LockUserTest + */ +@RunWith(AndroidJUnit4::class) +class LockUserTest { + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Ignore("Causes issues with subsequent tests") // TODO: Enable test + @Test + fun lockUser_locksTheDevice() { + Log.i(TAG, "Locking user") + trustAgentRule.agent.lockUser() + await() + + assertThat(lockStateTrackingRule.lockState.locked).isTrue() + } + + companion object { + private const val TAG = "LockUserTest" + private fun await() = Thread.sleep(250) + } +} + +class LockUserTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt new file mode 100644 index 0000000000000..f8783fbaf121d --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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.ScreenLockRule +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.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing the user unlock trigger. + * + * atest TrustTests:UserUnlockRequestTest + */ +@RunWith(AndroidJUnit4::class) +class UserUnlockRequestTest { + private val context: Context = getApplicationContext() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val userId = context.userId + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val trustAgentRule = TrustAgentRule() + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(trustAgentRule) + + @Test + fun reportUserRequestedUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount + trustManager.reportUserRequestedUnlock(userId) + await() + + assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount) + .isEqualTo(oldCount + 1) + } + + companion object { + private const val TAG = "UserUnlockRequestTest" + private fun await() = Thread.sleep(250) + } +} + +class UserUnlockRequestTrustAgent : BaseTrustAgentService() { + var onUserRequestedUnlockCallCount: Long = 0 + private set + + override fun onUserRequestedUnlock() { + Log.i(TAG, "onUserRequestedUnlock") + onUserRequestedUnlockCallCount++ + } + + companion object { + private const val TAG = "UserUnlockRequestTrustAgent" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt new file mode 100644 index 0000000000000..0023af8893e27 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 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.lib + +import android.app.trust.TrustManager +import android.app.trust.TrustManager.TrustListener +import android.content.Context +import android.util.Log +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.google.common.truth.Truth.assertThat +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Rule for tracking the lock state of the device based on events emitted to [TrustListener]. + */ +class LockStateTrackingRule : TestRule { + private val context: Context = getApplicationContext() + private val windowManager = WindowManagerGlobal.getWindowManagerService() + + @Volatile lateinit var lockState: LockState + private set + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + lockState = LockState(locked = windowManager.isKeyguardLocked) + val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + val listener = Listener() + + trustManager.registerTrustListener(listener) + try { + base.evaluate() + } finally { + trustManager.unregisterTrustListener(listener) + } + } + } + + fun assertLocked() = assertThat(lockState.locked).isTrue() + fun assertUnlocked() = assertThat(lockState.locked).isFalse() + + inner class Listener : TrustListener { + override fun onTrustChanged( + enabled: Boolean, + userId: Int, + flags: Int, + trustGrantedMessages: MutableList + ) { + Log.d(TAG, "Device became trusted=$enabled") + lockState = lockState.copy(locked = !enabled) + } + + override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { + } + + override fun onTrustError(message: CharSequence) { + } + } + + data class LockState( + val locked: Boolean? = null + ) + + companion object { + private const val TAG = "LockStateTrackingRule" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt new file mode 100644 index 0000000000000..c682a00eb8b98 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 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.lib + +import android.content.Context +import android.util.Log +import android.view.WindowManagerGlobal +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockscreenCredential +import com.google.common.truth.Truth.assertWithMessage +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Sets a screen lock on the device for the duration of the test. + */ +class ScreenLockRule : TestRule { + private val context: Context = getApplicationContext() + private val windowManager = WindowManagerGlobal.getWindowManagerService() + private val lockPatternUtils = LockPatternUtils(context) + private var instantLockSavedValue = false + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyNoScreenLockAlreadySet() + verifyKeyguardDismissed() + setScreenLock() + setLockOnPowerButton() + + try { + base.evaluate() + } finally { + removeScreenLock() + revertLockOnPowerButton() + } + } + } + + private fun verifyNoScreenLockAlreadySet() { + assertWithMessage("Screen Lock must not already be set on device") + .that(lockPatternUtils.isSecure(context.userId)) + .isFalse() + } + + private fun verifyKeyguardDismissed() { + windowManager.dismissKeyguard(null, null) + Thread.sleep(250) + assertWithMessage("Keyguard should be unlocked") + .that(windowManager.isKeyguardLocked) + .isFalse() + } + + private fun setScreenLock() { + lockPatternUtils.setLockCredential( + LockscreenCredential.createPin(PIN), + LockscreenCredential.createNone(), + context.userId + ) + assertWithMessage("Screen Lock should now be set") + .that(lockPatternUtils.isSecure(context.userId)) + .isTrue() + Log.i(TAG, "Device PIN set to $PIN") + } + + private fun setLockOnPowerButton() { + instantLockSavedValue = lockPatternUtils.getPowerButtonInstantlyLocks(context.userId) + lockPatternUtils.setPowerButtonInstantlyLocks(true, context.userId) + } + + private fun removeScreenLock() { + lockPatternUtils.setLockCredential( + LockscreenCredential.createNone(), + LockscreenCredential.createPin(PIN), + context.userId + ) + Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard") + Thread.sleep(50) + windowManager.dismissKeyguard(null, null) + } + + private fun revertLockOnPowerButton() { + lockPatternUtils.setPowerButtonInstantlyLocks(instantLockSavedValue, context.userId) + } + + companion object { + private const val TAG = "ScreenLockRule" + private const val PIN = "0000" + } +} diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt new file mode 100644 index 0000000000000..2a9e002764752 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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.lib + +import android.app.trust.TrustManager +import android.content.ComponentName +import android.content.Context +import android.trust.BaseTrustAgentService +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 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. + * + * The enabled agent can be accessed during the test via the [agent] property. + * + * @constructor Creates the rule. Do not use; instead, use [invoke]. + */ +class TrustAgentRule( + private val serviceClass: KClass +) : TestRule { + private val context: Context = getApplicationContext() + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val lockPatternUtils = LockPatternUtils(context) + + val agent get() = BaseTrustAgentService.instance(serviceClass) as T + + override fun apply(base: Statement, description: Description) = object : Statement() { + override fun evaluate() { + verifyTrustServiceRunning() + unlockDeviceWithCredential() + enableTrustAgent() + waitForEnablement() + + try { + verifyAgentIsRunning() + base.evaluate() + } finally { + disableTrustAgent() + } + } + } + + private fun verifyTrustServiceRunning() { + assertWithMessage("Trust service is not running").that(trustManager).isNotNull() + } + + private fun unlockDeviceWithCredential() { + Log.d(TAG, "Unlocking device with credential") + trustManager.reportUnlockAttempt(true, context.userId) + } + + private fun enableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = mutableListOf(componentName) + .plus(lockPatternUtils.getEnabledTrustAgents(userId)) + .distinct() + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + private fun waitForEnablement() { + Log.d(TAG, "Waiting for $WAIT_TIME ms") + Thread.sleep(WAIT_TIME) + Log.d(TAG, "Done waiting") + } + + private fun verifyAgentIsRunning() { + assertWithMessage("${serviceClass.simpleName} should be running") + .that(BaseTrustAgentService.instance(serviceClass)).isNotNull() + } + + private fun disableTrustAgent() { + val componentName = ComponentName(context, serviceClass.java) + val userId = context.userId + Log.i(TAG, "Disabling trust agent ${componentName.flattenToString()} for user $userId") + val agents = lockPatternUtils.getEnabledTrustAgents(userId).toMutableList() + .distinct() + .minus(componentName) + lockPatternUtils.setEnabledTrustAgents(agents, userId) + } + + companion object { + /** + * Creates a new rule for the specified agent class. Example usage: + * ``` + * @get:Rule val rule = TrustAgentRule() + * ``` + */ + inline operator fun invoke() = + TrustAgentRule(T::class) + + private const val TAG = "TrustAgentRule" + private val WAIT_TIME = 1000L + } +}