From 26ff049601d3dbc5932af438837e8db52f3bec33 Mon Sep 17 00:00:00 2001
From: mxhsnd <2049295946@qq.com>
Date: Mon, 24 Aug 2026 11:32:03 +0800
Subject: [PATCH] feat: route infrared debug hardware modes
---
android/app/build.gradle.kts | 12 ++++
android/app/src/debug/AndroidManifest.xml | 4 ++
.../manager/di/DebugRoutingInfraredGateway.kt | 41 +++++++++++
.../abox/manager/di/FoundationModule.kt | 18 ++++-
.../abox/manager/di/InfraredReleaseModule.kt | 47 +++++++++++++
.../di/DebugRoutingInfraredGatewayTest.kt | 70 +++++++++++++++++++
6 files changed, 191 insertions(+), 1 deletion(-)
create mode 100644 android/app/src/debug/AndroidManifest.xml
create mode 100644 android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGateway.kt
create mode 100644 android/app/src/release/kotlin/com/flagship/abox/manager/di/InfraredReleaseModule.kt
create mode 100644 android/app/src/test/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGatewayTest.kt
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 9991fb1..3966529 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -45,6 +45,15 @@ android {
kotlinOptions {
jvmTarget = "17"
}
+
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ excludes += "/META-INF/DEPENDENCIES"
+ excludes += "/META-INF/LICENSE*"
+ excludes += "/META-INF/NOTICE*"
+ }
+ }
}
kapt {
@@ -54,7 +63,9 @@ kapt {
dependencies {
implementation(project(":domain"))
implementation(project(":android:core:designsystem"))
+ implementation(project(":android:feature:infrared"))
debugImplementation(project(":testing:fakes"))
+ debugImplementation(project(":absdk-bridge"))
implementation(platform(libs.compose.bom))
implementation(libs.androidx.activity.compose)
@@ -68,6 +79,7 @@ dependencies {
kapt(libs.hilt.compiler)
testImplementation(libs.junit)
+ testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.espresso.core)
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..c0638bc
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGateway.kt b/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGateway.kt
new file mode 100644
index 0000000..b3b6c01
--- /dev/null
+++ b/android/app/src/debug/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGateway.kt
@@ -0,0 +1,41 @@
+package com.flagship.abox.manager.di
+
+import com.flagship.abox.manager.domain.DeviceName
+import com.flagship.abox.manager.domain.DomainResult
+import com.flagship.abox.manager.domain.InfraredCode
+import com.flagship.abox.manager.domain.InfraredCodeDownloadResult
+import com.flagship.abox.manager.domain.InfraredGateway
+import com.flagship.abox.manager.domain.ProviderProfileId
+import com.flagship.abox.manager.infrared.runtime.InfraredRuntimeMode
+import com.flagship.abox.manager.infrared.runtime.InfraredRuntimeModeStore
+
+class DebugRoutingInfraredGateway(
+ private val modeStore: InfraredRuntimeModeStore,
+ private val fakeGateway: InfraredGateway,
+ private val realGateway: InfraredGateway,
+) : InfraredGateway {
+
+ private suspend fun delegate(): InfraredGateway {
+ return when (modeStore.currentMode()) {
+ InfraredRuntimeMode.REAL_ABSDK -> realGateway
+ InfraredRuntimeMode.FAKE -> fakeGateway
+ }
+ }
+
+ override suspend fun sendKey(
+ profileId: ProviderProfileId,
+ deviceName: DeviceName,
+ key: String,
+ ): DomainResult = delegate().sendKey(profileId, deviceName, key)
+
+ override suspend fun learnKey(
+ profileId: ProviderProfileId,
+ deviceName: DeviceName,
+ key: String,
+ ): DomainResult = delegate().learnKey(profileId, deviceName, key)
+
+ override suspend fun downloadCodes(
+ profileId: ProviderProfileId,
+ codes: List,
+ ): DomainResult> = delegate().downloadCodes(profileId, codes)
+}
diff --git a/android/app/src/debug/kotlin/com/flagship/abox/manager/di/FoundationModule.kt b/android/app/src/debug/kotlin/com/flagship/abox/manager/di/FoundationModule.kt
index 4c1e6c9..1680d07 100644
--- a/android/app/src/debug/kotlin/com/flagship/abox/manager/di/FoundationModule.kt
+++ b/android/app/src/debug/kotlin/com/flagship/abox/manager/di/FoundationModule.kt
@@ -1,10 +1,13 @@
package com.flagship.abox.manager.di
+import com.flagship.abox.manager.absdk.bridge.AbsdkBridge
+import com.flagship.abox.manager.absdk.bridge.CleartextAbsdkTransport
import com.flagship.abox.manager.domain.DeviceCatalog
import com.flagship.abox.manager.domain.InfraredGateway
import com.flagship.abox.manager.domain.SessionGateway
import com.flagship.abox.manager.domain.SocketGateway
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
+import com.flagship.abox.manager.infrared.runtime.InfraredRuntimeModeStore
import com.flagship.abox.manager.testing.fakes.FakeDeviceCatalog
import com.flagship.abox.manager.testing.fakes.FakeInfraredGateway
import com.flagship.abox.manager.testing.fakes.FakeSessionGateway
@@ -38,5 +41,18 @@ object FoundationModule {
@Provides
@Singleton
- fun provideInfraredGateway(): InfraredGateway = FakeInfraredGateway()
+ fun provideFakeInfraredGateway(): FakeInfraredGateway = FakeInfraredGateway()
+
+ @Provides
+ @Singleton
+ @OptIn(CleartextAbsdkTransport::class)
+ fun provideRealAbsdkBridge(): AbsdkBridge = AbsdkBridge.create()
+
+ @Provides
+ @Singleton
+ fun provideInfraredGateway(
+ modeStore: InfraredRuntimeModeStore,
+ fake: FakeInfraredGateway,
+ real: AbsdkBridge,
+ ): InfraredGateway = DebugRoutingInfraredGateway(modeStore, fake, real)
}
diff --git a/android/app/src/release/kotlin/com/flagship/abox/manager/di/InfraredReleaseModule.kt b/android/app/src/release/kotlin/com/flagship/abox/manager/di/InfraredReleaseModule.kt
new file mode 100644
index 0000000..705c2b0
--- /dev/null
+++ b/android/app/src/release/kotlin/com/flagship/abox/manager/di/InfraredReleaseModule.kt
@@ -0,0 +1,47 @@
+package com.flagship.abox.manager.di
+
+import com.flagship.abox.manager.domain.DeviceName
+import com.flagship.abox.manager.domain.DomainError
+import com.flagship.abox.manager.domain.DomainResult
+import com.flagship.abox.manager.domain.InfraredCode
+import com.flagship.abox.manager.domain.InfraredCodeDownloadResult
+import com.flagship.abox.manager.domain.InfraredGateway
+import com.flagship.abox.manager.domain.ProviderProfileId
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object InfraredReleaseModule {
+
+ @Provides
+ @Singleton
+ fun provideInfraredGateway(): InfraredGateway = object : InfraredGateway {
+ override suspend fun sendKey(
+ profileId: ProviderProfileId,
+ deviceName: DeviceName,
+ key: String,
+ ): DomainResult = disabledFailure()
+
+ override suspend fun learnKey(
+ profileId: ProviderProfileId,
+ deviceName: DeviceName,
+ key: String,
+ ): DomainResult = disabledFailure()
+
+ override suspend fun downloadCodes(
+ profileId: ProviderProfileId,
+ codes: List,
+ ): DomainResult> = disabledFailure()
+
+ private fun disabledFailure(): DomainResult = DomainResult.Failure(
+ DomainError.OperationFailed(
+ externalCode = "REAL_ABSDK_DISABLED",
+ detail = "Infrared hardware transport is disabled in release builds",
+ ),
+ )
+ }
+}
diff --git a/android/app/src/test/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGatewayTest.kt b/android/app/src/test/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGatewayTest.kt
new file mode 100644
index 0000000..339dc83
--- /dev/null
+++ b/android/app/src/test/kotlin/com/flagship/abox/manager/di/DebugRoutingInfraredGatewayTest.kt
@@ -0,0 +1,70 @@
+package com.flagship.abox.manager.di
+
+import com.flagship.abox.manager.domain.DeviceName
+import com.flagship.abox.manager.domain.DomainResult
+import com.flagship.abox.manager.domain.InfraredCode
+import com.flagship.abox.manager.domain.ProviderProfileId
+import com.flagship.abox.manager.infrared.runtime.InfraredRuntimeMode
+import com.flagship.abox.manager.infrared.runtime.InfraredRuntimeModeStore
+import com.flagship.abox.manager.testing.fakes.FakeInfraredGateway
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+private class TestModeStore : InfraredRuntimeModeStore {
+ private val modeFlow = MutableStateFlow(InfraredRuntimeMode.FAKE)
+ override val mode: Flow = modeFlow
+ override suspend fun currentMode(): InfraredRuntimeMode = modeFlow.value
+ override suspend fun setMode(mode: InfraredRuntimeMode): DomainResult {
+ modeFlow.value = mode
+ return DomainResult.Success(Unit)
+ }
+}
+
+class DebugRoutingInfraredGatewayTest {
+
+ private val modeStore = TestModeStore()
+ private val fake = FakeInfraredGateway()
+ private val real = FakeInfraredGateway()
+ private val router = DebugRoutingInfraredGateway(modeStore, fake, real)
+
+ private val profile = ProviderProfileId("profile1")
+ private val device = DeviceName("ir-1")
+
+ @Test
+ fun delegatesToFakeByDefault() = runTest {
+ val result = router.sendKey(profile, device, "key1")
+ assertTrue(result is DomainResult.Success)
+ assertEquals(1, fake.sentKeys.size)
+ assertTrue(real.sentKeys.isEmpty())
+
+ router.learnKey(profile, device, "key2")
+ assertEquals(1, fake.learnedKeys.size)
+ assertTrue(real.learnedKeys.isEmpty())
+
+ router.downloadCodes(profile, listOf(InfraredCode(device, "k", "code")))
+ assertEquals(1, fake.downloadCalls.size)
+ assertTrue(real.downloadCalls.isEmpty())
+ }
+
+ @Test
+ fun delegatesToRealWhenModeIsRealAbsdk() = runTest {
+ modeStore.setMode(InfraredRuntimeMode.REAL_ABSDK)
+
+ val result = router.sendKey(profile, device, "key1")
+ assertTrue(result is DomainResult.Success)
+ assertTrue(fake.sentKeys.isEmpty())
+ assertEquals(1, real.sentKeys.size)
+
+ router.learnKey(profile, device, "key2")
+ assertTrue(fake.learnedKeys.isEmpty())
+ assertEquals(1, real.learnedKeys.size)
+
+ router.downloadCodes(profile, listOf(InfraredCode(device, "k", "code")))
+ assertTrue(fake.downloadCalls.isEmpty())
+ assertEquals(1, real.downloadCalls.size)
+ }
+}