Move UI tests used in snippet to part of integration tests.

We would like to utilize more single device tests to make sure
UI keeps working as expected before running complex end-to-end testing.

Ignore-AOSP-First: nearby_not_in_aosp_yet
Test: atest NearbyIntegrationUiTests
Test: atest CtsNearbyMultiDevicesTestSuite
Bug: 215862502
Change-Id: I28264b3a70c3ee985aae82ca8cf1fcfd955a8d3a
diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING
index dbaca33..d68bcc9 100644
--- a/nearby/TEST_MAPPING
+++ b/nearby/TEST_MAPPING
@@ -8,6 +8,9 @@
     },
     {
       "name": "NearbyIntegrationUntrustedTests"
+    },
+    {
+      "name": "NearbyIntegrationUiTests"
     }
   ],
   "postsubmit": [
diff --git a/nearby/tests/integration/ui/Android.bp b/nearby/tests/integration/ui/Android.bp
new file mode 100644
index 0000000..524c838
--- /dev/null
+++ b/nearby/tests/integration/ui/Android.bp
@@ -0,0 +1,40 @@
+// 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: "NearbyIntegrationUiTests",
+    defaults: ["mts-target-sdk-version-current"],
+    sdk_version: "test_current",
+    static_libs: ["NearbyIntegrationUiTestsLib"],
+    test_suites: ["device-tests"],
+}
+
+android_library {
+    name: "NearbyIntegrationUiTestsLib",
+    srcs: ["src/**/*.kt"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "junit",
+        "platform-test-rules",
+        "service-nearby-pre-jarjar",
+        "truth-prebuilt",
+    ],
+}
diff --git a/nearby/tests/integration/ui/AndroidManifest.xml b/nearby/tests/integration/ui/AndroidManifest.xml
new file mode 100644
index 0000000..9aea0c1
--- /dev/null
+++ b/nearby/tests/integration/ui/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.nearby.integration.ui">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.nearby.integration.ui"
+        android:label="Nearby Mainline Module Integration UI Tests" />
+
+</manifest>
diff --git a/nearby/tests/integration/ui/AndroidTest.xml b/nearby/tests/integration/ui/AndroidTest.xml
new file mode 100644
index 0000000..9dfcf7b
--- /dev/null
+++ b/nearby/tests/integration/ui/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="Runs Nearby Mainline Module Integration UI Tests">
+    <!-- Needed for pulling the screen record files. -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="NearbyIntegrationUiTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="NearbyIntegrationUiTests" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="com.google.android.tethering.next.apex" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.nearby.integration.ui" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+        <!-- test-timeout unit is ms, value = 5 min -->
+        <option name="test-timeout" value="300000" />
+    </test>
+
+    <!-- Only run NearbyIntegrationUiTests in MTS if the Nearby Mainline module is installed. -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+    </object>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/user/0/android.nearby.integration.ui/files" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
new file mode 100644
index 0000000..658775b
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.nearby.integration.ui
+
+import android.platform.test.rule.ArtifactSaver
+import android.platform.test.rule.ScreenRecordRule
+import android.platform.test.rule.TestWatcher
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.rules.Timeout
+import org.junit.runner.Description
+
+abstract class BaseUiTest {
+    @get:Rule
+    var mGlobalTimeout: Timeout = Timeout.seconds(100) // Test times out in 1.67 minutes
+
+    @get:Rule
+    val mTestWatcherRule: TestRule = object : TestWatcher() {
+        override fun failed(throwable: Throwable?, description: Description?) {
+            super.failed(throwable, description)
+            ArtifactSaver.onError(description, throwable)
+        }
+    }
+
+    @get:Rule
+    val mScreenRecordRule: TestRule = ScreenRecordRule()
+}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..5a3538e
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.nearby.integration.ui
+
+import android.content.Context
+import android.os.Bundle
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.nearby.common.eventloop.EventLoop
+import com.android.server.nearby.common.locator.Locator
+import com.android.server.nearby.common.locator.LocatorContextWrapper
+import com.android.server.nearby.fastpair.FastPairController
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import service.proto.Cache
+import service.proto.FastPairString.FastPairStrings
+import java.time.Clock
+
+/** An instrumented test to check Nearby half sheet UI showed correctly.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.integration.ui.CheckNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class CheckNearbyHalfSheetUiTest : BaseUiTest() {
+    private var waitHalfSheetPopupTimeoutMs: Long
+    private var halfSheetTitleText: String
+    private var halfSheetSubtitleText: String
+
+    init {
+        val arguments: Bundle = InstrumentationRegistry.getArguments()
+        waitHalfSheetPopupTimeoutMs = arguments.getLong(
+            WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
+            DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
+        )
+        halfSheetTitleText =
+            arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
+        halfSheetSubtitleText =
+            arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
+
+        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+    }
+
+    /** For multidevice test snippet only. Force overwrites the test arguments. */
+    fun updateTestArguments(
+        waitHalfSheetPopupTimeoutSeconds: Int,
+        halfSheetTitleText: String,
+        halfSheetSubtitleText: String
+    ) {
+        this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
+        this.halfSheetTitleText = halfSheetTitleText
+        this.halfSheetSubtitleText = halfSheetSubtitleText
+    }
+
+    @Before
+    fun setUp() {
+        val appContext = ApplicationProvider.getApplicationContext<Context>()
+        val locator = Locator(appContext).apply {
+            overrideBindingForTest(EventLoop::class.java, EventLoop.newInstance("test"))
+            overrideBindingForTest(
+                FastPairCacheManager::class.java,
+                FastPairCacheManager(appContext)
+            )
+            overrideBindingForTest(FootprintsDeviceManager::class.java, FootprintsDeviceManager())
+            overrideBindingForTest(Clock::class.java, Clock.systemDefaultZone())
+        }
+        val locatorContextWrapper = LocatorContextWrapper(appContext, locator)
+        locator.overrideBindingForTest(
+            FastPairController::class.java,
+            FastPairController(locatorContextWrapper)
+        )
+        val scanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
+            .setDeviceName(DEFAULT_HALF_SHEET_TITLE_TEXT)
+            .setFastPairStrings(
+                FastPairStrings.newBuilder()
+                    .setInitialPairingDescription(DEFAULT_HALF_SHEET_SUBTITLE_TEXT).build()
+            )
+            .build()
+        FastPairHalfSheetManager(locatorContextWrapper).showHalfSheet(scanFastPairStoreItem)
+    }
+
+    @Test
+    @ScreenRecord
+    fun checkNearbyHalfSheetUi() {
+        // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
+        val isConnectButtonShowed = device.wait(
+            Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
+            waitHalfSheetPopupTimeoutMs
+        )
+        assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
+            .that(isConnectButtonShowed).isTrue()
+
+        val halfSheetTitle =
+            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
+        assertThat(halfSheetTitle).isNotNull()
+        assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
+
+        val halfSheetSubtitle =
+            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
+        assertThat(halfSheetSubtitle).isNotNull()
+        assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
+
+        val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
+        assertThat(deviceImage).isNotNull()
+
+        val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
+        assertThat(infoButton).isNotNull()
+    }
+
+    companion object {
+        private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 30 * 1000L
+        private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
+        private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
+                "appear on devices linked with nearby-mainline-fpseeker@google.com"
+        private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
+        private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
+        private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
+        private lateinit var device: UiDevice
+
+        @AfterClass
+        @JvmStatic
+        fun teardownClass() {
+            // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
+            DismissNearbyHalfSheetUiTest().dismissHalfSheet()
+        }
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
similarity index 81%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
rename to nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
index 1d99d26..52d202a 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker.ui
+package android.nearby.integration.ui
 
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -27,14 +28,15 @@
  *
  * To run this test directly:
  * am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
+ * -e class android.nearby.integration.ui.DismissNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
  */
 @RunWith(AndroidJUnit4::class)
-class DismissNearbyHalfSheetUiTest {
+class DismissNearbyHalfSheetUiTest : BaseUiTest() {
     private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
 
     @Test
+    @ScreenRecord
     fun dismissHalfSheet() {
         device.pressHome()
         device.waitForIdle()
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
new file mode 100644
index 0000000..8b19d5c
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.nearby.integration.ui
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import com.android.server.nearby.fastpair.FastPairManager
+import com.android.server.nearby.util.Environment
+import com.google.common.truth.Truth.assertThat
+
+/** UiMap for Nearby Mainline Half Sheet. */
+object NearbyHalfSheetUiMap {
+    private val PACKAGE_NAME: String = getHalfSheetApkPkgName()
+    private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
+    private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
+    private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
+
+    object DevicePairingFragment {
+        val halfSheetTitle: BySelector =
+            By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
+        val halfSheetSubtitle: BySelector =
+            By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
+        val deviceImage: BySelector =
+            By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+        val connectButton: BySelector =
+            By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
+        val infoButton: BySelector =
+            By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+    }
+
+    // Vendors might override HalfSheetUX in their vendor partition, query the package name
+    // instead of hard coding. ex: Google overrides it in vendor/google/modules/TetheringGoogle.
+    fun getHalfSheetApkPkgName(): String {
+        val appContext = ApplicationProvider.getApplicationContext<Context>()
+        val resolveInfos: MutableList<ResolveInfo> =
+            appContext.packageManager.queryIntentActivities(
+                Intent(FastPairManager.ACTION_RESOURCES_APK),
+                ResolveInfoFlags.of(MATCH_SYSTEM_ONLY.toLong())
+            )
+
+        // remove apps that don't live in the nearby apex
+        resolveInfos.removeIf { !Environment.isAppInNearbyApex(it.activityInfo.applicationInfo) }
+
+        assertThat(resolveInfos).hasSize(1)
+
+        val halfSheetApkPkgName: String = resolveInfos[0].activityInfo.applicationInfo.packageName
+        Log.i("NearbyHalfSheetUiMap", "Found half-sheet APK at: $halfSheetApkPkgName")
+        return halfSheetApkPkgName
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..27264b51
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.nearby.integration.ui
+
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.integration.ui.PairByNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class PairByNearbyHalfSheetUiTest : BaseUiTest() {
+    init {
+        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+    }
+
+    @Before
+    fun setUp() {
+        CheckNearbyHalfSheetUiTest().apply {
+            setUp()
+            checkNearbyHalfSheetUi()
+        }
+    }
+
+    @Test
+    @ScreenRecord
+    fun clickConnectButton() {
+        val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
+        device.findObject(connectButton).click()
+        device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
+    }
+
+    companion object {
+        private const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
+        private lateinit var device: UiDevice
+
+        @AfterClass
+        @JvmStatic
+        fun teardownClass() {
+            // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
+            device.pressBack()
+            DismissNearbyHalfSheetUiTest().dismissHalfSheet()
+        }
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp
index 49bc2e9..b1bf9a7 100644
--- a/nearby/tests/multidevices/clients/Android.bp
+++ b/nearby/tests/multidevices/clients/Android.bp
@@ -24,9 +24,9 @@
         "MoblySnippetHelperLib",
         "NearbyFastPairProviderLib",
         "NearbyFastPairSeekerSharedLib",
+        "NearbyIntegrationUiTestsLib",
         "androidx.test.core",
         "androidx.test.ext.junit",
-        "androidx.test.uiautomator_uiautomator",
         "kotlin-stdlib",
         "mobly-snippet-lib",
         "truth-prebuilt",
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
index bfb7a50..a2c39f7 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
@@ -22,12 +22,12 @@
 import android.nearby.ScanCallback
 import android.nearby.ScanRequest
 import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
+import android.nearby.integration.ui.CheckNearbyHalfSheetUiTest
+import android.nearby.integration.ui.DismissNearbyHalfSheetUiTest
+import android.nearby.integration.ui.PairByNearbyHalfSheetUiTest
 import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
 import android.nearby.multidevices.fastpair.seeker.events.PairingCallbackEvents
 import android.nearby.multidevices.fastpair.seeker.events.ScanCallbackEvents
-import android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest
-import android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest
-import android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest
 import android.provider.Settings
 import androidx.test.core.app.ApplicationProvider
 import com.google.android.mobly.snippet.Snippet
@@ -86,14 +86,17 @@
         val deviceName = deviceMetadata.name!!
         val initialPairingDescriptionTemplateText = deviceMetadata.initialPairingDescription!!
 
-        CheckNearbyHalfSheetUiTest(
-            waitHalfSheetPopupTimeoutSeconds = timeout,
-            halfSheetTitleText = deviceName,
-            halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
-                deviceName,
-                FAKE_TEST_ACCOUNT_NAME
+        CheckNearbyHalfSheetUiTest().apply {
+            updateTestArguments(
+                waitHalfSheetPopupTimeoutSeconds = timeout,
+                halfSheetTitleText = deviceName,
+                halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
+                    deviceName,
+                    FAKE_TEST_ACCOUNT_NAME
+                )
             )
-        ).checkNearbyHalfSheetUi()
+            checkNearbyHalfSheetUi()
+        }
     }
 
     /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 84b5e89..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.nearby.multidevices.fastpair.seeker.ui
-
-import android.os.Bundle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to check Nearby half sheet UI showed correctly.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class CheckNearbyHalfSheetUiTest {
-    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-    private val waitHalfSheetPopupTimeoutMs: Long
-    private val halfSheetTitleText: String
-    private val halfSheetSubtitleText: String
-
-    constructor() {
-        val arguments: Bundle = InstrumentationRegistry.getArguments()
-        waitHalfSheetPopupTimeoutMs = arguments.getLong(
-            WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
-            DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
-        )
-        halfSheetTitleText =
-            arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
-        halfSheetSubtitleText =
-            arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
-    }
-
-    constructor(
-        waitHalfSheetPopupTimeoutSeconds: Int,
-        halfSheetTitleText: String,
-        halfSheetSubtitleText: String
-    ) {
-        this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
-        this.halfSheetTitleText = halfSheetTitleText
-        this.halfSheetSubtitleText = halfSheetSubtitleText
-    }
-
-    @Test
-    fun checkNearbyHalfSheetUi() {
-        // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
-        val isConnectButtonShowed = device.wait(
-            Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
-            waitHalfSheetPopupTimeoutMs
-        )
-        assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
-            .that(isConnectButtonShowed).isTrue()
-
-        val halfSheetTitle =
-            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
-        assertThat(halfSheetTitle).isNotNull()
-        assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
-
-        val halfSheetSubtitle =
-            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
-        assertThat(halfSheetSubtitle).isNotNull()
-        assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
-
-        val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
-        assertThat(deviceImage).isNotNull()
-
-        val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
-        assertThat(infoButton).isNotNull()
-    }
-
-    companion object {
-        private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 1000L
-        private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
-        private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
-                "appear on devices linked with nearby-mainline-fpseeker@google.com"
-        private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
-        private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
-        private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
deleted file mode 100644
index c94ff01..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.nearby.multidevices.fastpair.seeker.ui
-
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-
-/** UiMap for Nearby Mainline Half Sheet. */
-object NearbyHalfSheetUiMap {
-    private const val PACKAGE_NAME = "com.google.android.nearby.halfsheet"
-    private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
-    private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
-    private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
-
-    object DevicePairingFragment {
-        val halfSheetTitle: BySelector =
-            By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
-        val halfSheetSubtitle: BySelector =
-            By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
-        val deviceImage: BySelector =
-            By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
-        val connectButton: BySelector =
-            By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
-        val infoButton: BySelector =
-            By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 9028668..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.nearby.multidevices.fastpair.seeker.ui
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class PairByNearbyHalfSheetUiTest {
-    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-
-    @Test
-    fun clickConnectButton() {
-        val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
-        device.findObject(connectButton).click()
-        device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
-    }
-
-    companion object {
-        const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
-    }
-}
\ No newline at end of file