Import SettingsLibIpc-testutils

Bug: 365886533
Flag: EXEMPT Import library
Test: m SettingsLibIpc-testutils
Change-Id: I9d42c55911286e85a4dc154cd06d6f9edeb5b6d2
diff --git a/packages/SettingsLib/Ipc/Android.bp b/packages/SettingsLib/Ipc/Android.bp
index 61adb46..2c7209a 100644
--- a/packages/SettingsLib/Ipc/Android.bp
+++ b/packages/SettingsLib/Ipc/Android.bp
@@ -20,3 +20,15 @@
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+android_library {
+    name: "SettingsLibIpc-testutils",
+    srcs: ["testutils/**/*.kt"],
+    static_libs: [
+        "Robolectric_all-target_upstream",
+        "SettingsLibIpc",
+        "androidx.test.core",
+        "flag-junit",
+        "kotlinx-coroutines-android",
+    ],
+}
diff --git a/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt b/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt
new file mode 100644
index 0000000..8b2deaf
--- /dev/null
+++ b/packages/SettingsLib/Ipc/testutils/com/android/settingslib/ipc/MessengerServiceRule.kt
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.settingslib.ipc
+
+import android.app.Application
+import android.app.Service
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Build
+import android.os.Looper
+import androidx.test.core.app.ApplicationProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.robolectric.Robolectric
+import org.robolectric.Shadows
+import org.robolectric.android.controller.ServiceController
+
+/** Rule for messenger service testing. */
+open class MessengerServiceRule<C : MessengerServiceClient>(
+    private val serviceClass: Class<out MessengerService>,
+    val client: C,
+) : TestWatcher() {
+    val application: Application = ApplicationProvider.getApplicationContext()
+    val isRobolectric = Build.FINGERPRINT.contains("robolectric")
+
+    private var serviceController: ServiceController<out Service>? = null
+
+    override fun starting(description: Description) {
+        if (isRobolectric) {
+            runBlocking { setupRobolectricService() }
+        }
+    }
+
+    override fun finished(description: Description) {
+        client.close()
+        if (isRobolectric) {
+            runBlocking {
+                withContext(Dispatchers.Main) { serviceController?.run { unbind().destroy() } }
+            }
+        }
+    }
+
+    private suspend fun setupRobolectricService() {
+        if (Thread.currentThread() == Looper.getMainLooper().thread) {
+            throw IllegalStateException(
+                "To avoid deadlock, run test with @LooperMode(LooperMode.Mode.INSTRUMENTATION_TEST)"
+            )
+        }
+        withContext(Dispatchers.Main) {
+            serviceController = Robolectric.buildService(serviceClass)
+            val service = serviceController!!.create().get()
+            Shadows.shadowOf(application).apply {
+                setComponentNameAndServiceForBindService(
+                    ComponentName(application, serviceClass),
+                    service.onBind(Intent(application, serviceClass)),
+                )
+                setBindServiceCallsOnServiceConnectedDirectly(true)
+                setUnbindServiceCallsOnServiceDisconnected(false)
+            }
+        }
+    }
+}