summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt2
-rw-r--r--tests/FlickerTests/IME/Android.bp4
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt2
-rw-r--r--tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt7
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt2
-rw-r--r--tests/Input/Android.bp6
-rw-r--r--tests/Input/AndroidManifest.xml2
-rw-r--r--tests/Input/AndroidTest.xml6
-rw-r--r--tests/Input/assets/testPointerFillStyle.pngbin0 -> 569 bytes
-rw-r--r--tests/Input/src/com/android/test/input/AnrTest.kt39
-rw-r--r--tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt114
-rw-r--r--tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java5
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java5
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java5
-rw-r--r--tests/TrustTests/AndroidManifest.xml11
-rw-r--r--tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt227
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt44
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt62
-rw-r--r--tests/TrustTests/src/android/trust/test/lib/Utils.kt (renamed from tests/TrustTests/src/android/trust/test/lib/utils.kt)33
19 files changed, 551 insertions, 25 deletions
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
index 8a241de32a2b..209a14b3657d 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.service
import android.app.Instrumentation
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
import android.platform.test.rule.NavigationModeRule
import android.platform.test.rule.PressHomeRule
import android.platform.test.rule.UnlockScreenRule
@@ -48,6 +49,7 @@ object Utils {
clearCacheAfterParsing = false
)
)
+ .around(DisableNotificationCooldownSettingRule())
.around(PressHomeRule())
}
}
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index 3538949cbc8d..ccc3683f0b93 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -39,6 +39,10 @@ android_test {
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
+ test_suites: [
+ "device-tests",
+ "device-platinum-tests",
+ ],
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index dc5013519dbf..ed6e8df3e293 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -23,6 +23,7 @@ import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.component.ComponentNameMatcher
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import org.junit.FixMethodOrder
@@ -77,6 +78,7 @@ class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: LegacyFlickerTest) :
@Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
+ @FlakyTest(bugId = 330486656)
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index c29e71ce4c79..07fc2300286a 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.notification
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.FlickerTestData
@@ -37,6 +38,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
import org.junit.Assume
+import org.junit.ClassRule
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -208,5 +210,10 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+ /** Ensures that posted notifications will alert and HUN even just after boot. */
+ @ClassRule
+ @JvmField
+ val disablenotificationCooldown = DisableNotificationCooldownSettingRule()
}
}
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index c7da778b752b..c49b509a9db3 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -21,6 +21,7 @@ import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.flicker.subject.layers.LayersTraceSubject
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.surfaceflinger.Display
@@ -46,6 +47,7 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker
flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
ignoreLayers =
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
listOf(
ComponentNameMatcher.SPLASH_SCREEN,
ComponentNameMatcher.SNAPSHOT,
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index a85d809257cd..f367c38b06e9 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -18,25 +18,31 @@ android_test {
"src/**/*.java",
"src/**/*.kt",
],
+ asset_dirs: ["assets"],
kotlincflags: [
"-Werror",
],
platform_apis: true,
certificate: "platform",
static_libs: [
+ "android.view.flags-aconfig-java",
"androidx.test.core",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.uiautomator_uiautomator",
+ "collector-device-lib",
"compatibility-device-util-axt",
+ "cts-input-lib",
+ "cts-wm-util",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
"kotlin-test",
"mockito-target-minus-junit4",
"platform-test-annotations",
+ "platform-screenshot-diff-core",
"services.core.unboosted",
"servicestests-utils",
"testables",
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index 3b723ddf811f..a05d08ccceba 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.MONITOR_INPUT"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application android:label="InputTest" android:debuggable="true">
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
index f602c5124e77..8db37058af2b 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -28,4 +28,10 @@
<!-- Take screenshot upon test failure -->
<option name="screenshot-on-failure" value="true" />
</object>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="input_.*" />
+ <!-- Pull files created by tests, like the output of screenshot tests -->
+ <option name="directory-keys" value="/storage/emulated/0/InputTests" />
+ <option name="collect-on-run-ended-only" value="false" />
+ </metrics_collector>
</configuration>
diff --git a/tests/Input/assets/testPointerFillStyle.png b/tests/Input/assets/testPointerFillStyle.png
new file mode 100644
index 000000000000..b2354f8f4799
--- /dev/null
+++ b/tests/Input/assets/testPointerFillStyle.png
Binary files differ
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 4893d14ad79b..8d1fc508ffe7 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -21,26 +21,33 @@ import androidx.test.filters.MediumTest
import android.app.ActivityManager
import android.app.ApplicationExitInfo
+import android.content.Context
import android.graphics.Rect
+import android.hardware.display.DisplayManager
import android.os.Build
import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
import android.provider.Settings
import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
+import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry
import android.testing.PollingCheck
-import android.view.InputDevice
-import android.view.MotionEvent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.cts.input.DebugInputRule
+import com.android.cts.input.UinputTouchScreen
+
+import java.util.concurrent.TimeUnit
+
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +76,9 @@ class AnrTest {
private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
Build.HW_TIMEOUT_MULTIPLIER)
+ @get:Rule
+ val debugInputRule = DebugInputRule()
+
@Before
fun setUp() {
val contentResolver = instrumentation.targetContext.contentResolver
@@ -84,12 +94,14 @@ class AnrTest {
}
@Test
+ @DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Close() {
triggerAnr()
clickCloseAppOnAnrDialog()
}
@Test
+ @DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Wait() {
triggerAnr()
clickWaitOnAnrDialog()
@@ -105,7 +117,7 @@ class AnrTest {
val closeAppButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
- fail("Could not find anr dialog")
+ fail("Could not find anr dialog/close button")
return
}
closeAppButton.click()
@@ -150,6 +162,18 @@ class AnrTest {
assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
}
+ private fun clickOnObject(obj: UiObject2) {
+ val displayManager =
+ instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ val display = displayManager.getDisplay(obj.getDisplayId())
+ val touchScreen = UinputTouchScreen(instrumentation, display)
+
+ val rect: Rect = obj.visibleBounds
+ val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY())
+ pointer.lift()
+ touchScreen.close()
+ }
+
private fun triggerAnr() {
startUnresponsiveActivity()
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
@@ -160,13 +184,7 @@ class AnrTest {
return
}
- val rect: Rect = obj.visibleBounds
- val downTime = SystemClock.uptimeMillis()
- val downEvent = MotionEvent.obtain(downTime, downTime,
- MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
- downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-
- instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
+ clickOnObject(obj)
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
@@ -175,5 +193,6 @@ class AnrTest {
val flags = " -W -n "
val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
instrumentation.uiAutomation.executeShellCommand(startCmd)
+ waitForStableWindowGeometry(5L, TimeUnit.SECONDS)
}
}
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
new file mode 100644
index 000000000000..dac425329f48
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 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 com.android.test.input
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.os.Environment
+import android.view.ContextThemeWrapper
+import android.view.PointerIcon
+import android.view.flags.Flags.enableVectorCursorA11ySettings
+import android.view.flags.Flags.enableVectorCursors
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.assertAgainstGolden
+import platform.test.screenshot.matchers.BitmapMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/**
+ * Unit tests for PointerIcon.
+ *
+ * Run with:
+ * atest InputTests:com.android.test.input.PointerIconLoadingTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PointerIconLoadingTest {
+ private lateinit var context: Context
+ private lateinit var exactScreenshotMatcher: BitmapMatcher
+
+ @get:Rule
+ val testName = TestName()
+
+ @get:Rule
+ val screenshotRule = ScreenshotTestRule(GoldenPathManager(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ ASSETS_PATH,
+ TEST_OUTPUT_PATH,
+ PathConfig()
+ ), disableIconPool = false)
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ val config =
+ Configuration(context.resources.configuration).apply {
+ densityDpi = DENSITY_DPI
+ screenWidthDp = SCREEN_WIDTH_DP
+ screenHeightDp = SCREEN_HEIGHT_DP
+ smallestScreenWidthDp = SCREEN_WIDTH_DP
+ }
+ context = context.createConfigurationContext(config)
+
+ exactScreenshotMatcher = PixelPerfectMatcher()
+ }
+
+ @Test
+ fun testPointerFillStyle() {
+ assumeTrue(enableVectorCursors())
+ assumeTrue(enableVectorCursorA11ySettings())
+
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_GREEN),
+ /* force= */ true)
+
+ val pointerIcon =
+ PointerIcon.getLoadedSystemIcon(
+ ContextThemeWrapper(context, theme),
+ PointerIcon.TYPE_ARROW,
+ /* useLargeIcons= */ false)
+
+ pointerIcon.getBitmap().assertAgainstGolden(
+ screenshotRule,
+ testName.methodName,
+ exactScreenshotMatcher
+ )
+ }
+
+ companion object {
+ const val DENSITY_DPI = 160
+ const val SCREEN_WIDTH_DP = 480
+ const val SCREEN_HEIGHT_DP = 800
+ const val ASSETS_PATH = "tests/input/assets"
+ val TEST_OUTPUT_PATH = Environment.getExternalStorageDirectory().absolutePath +
+ "/InputTests/" +
+ PointerIconLoadingTest::class.java.simpleName
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5cdfb2858b33..5a27593c7a36 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -393,5 +393,10 @@ public class LegacyProtoLogImplTest {
this.mLogToLogcat = logToLogcat;
}
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
}
}
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index f6ac080ebf73..1d7b6b348e10 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -725,5 +725,10 @@ public class PerfettoProtoLogImplTest {
this.mLogToLogcat = logToLogcat;
}
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
}
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 4267c2c127ae..60456f9ea10f 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -174,5 +174,10 @@ public class ProtoLogImplTest {
this.mLogToLogcat = logToLogcat;
}
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
}
}
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
index 30cf345db34d..2f6c0dd14adc 100644
--- a/tests/TrustTests/AndroidManifest.xml
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -78,6 +78,7 @@
<action android:name="android.service.trust.TrustAgentService" />
</intent-filter>
</service>
+
<service
android:name=".IsActiveUnlockRunningTrustAgent"
android:exported="true"
@@ -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. -->
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
new file mode 100644
index 000000000000..2c9361df63fd
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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
+ }
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index f1edca3ff86e..1ccdcc623c5b 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -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
@@ -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() {
@@ -46,10 +53,12 @@ class ScreenLockRule : TestRule {
dismissKeyguard()
setScreenLock()
setLockOnPowerButton()
+ configureStrongAuthState()
try {
base.evaluate()
} finally {
+ restoreStrongAuthState()
removeScreenLock()
revertLockOnPowerButton()
dismissKeyguard()
@@ -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))
@@ -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),
@@ -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"
}
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
index 18bc029b6845..404c6d968b3a 100644
--- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -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.
@@ -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
@@ -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()
@@ -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")
@@ -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
@@ -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"
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
index e047202f6740..3b32b47a6160 100644
--- a/tests/TrustTests/src/android/trust/test/lib/utils.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
@@ -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()
@@ -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)
+ }
+}