feat: route infrared debug hardware modes

This commit is contained in:
2026-08-24 11:32:03 +08:00
parent 3952f9f324
commit 26ff049601
6 changed files with 191 additions and 1 deletions
+12
View File
@@ -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)
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application android:usesCleartextTraffic="true" />
</manifest>
@@ -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<Unit> = delegate().sendKey(profileId, deviceName, key)
override suspend fun learnKey(
profileId: ProviderProfileId,
deviceName: DeviceName,
key: String,
): DomainResult<Unit> = delegate().learnKey(profileId, deviceName, key)
override suspend fun downloadCodes(
profileId: ProviderProfileId,
codes: List<InfraredCode>,
): DomainResult<List<InfraredCodeDownloadResult>> = delegate().downloadCodes(profileId, codes)
}
@@ -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)
}
@@ -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<Unit> = disabledFailure()
override suspend fun learnKey(
profileId: ProviderProfileId,
deviceName: DeviceName,
key: String,
): DomainResult<Unit> = disabledFailure()
override suspend fun downloadCodes(
profileId: ProviderProfileId,
codes: List<InfraredCode>,
): DomainResult<List<InfraredCodeDownloadResult>> = disabledFailure()
private fun <T> disabledFailure(): DomainResult<T> = DomainResult.Failure(
DomainError.OperationFailed(
externalCode = "REAL_ABSDK_DISABLED",
detail = "Infrared hardware transport is disabled in release builds",
),
)
}
}
@@ -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<InfraredRuntimeMode> = modeFlow
override suspend fun currentMode(): InfraredRuntimeMode = modeFlow.value
override suspend fun setMode(mode: InfraredRuntimeMode): DomainResult<Unit> {
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)
}
}