Skip to content

Commit 08e7f90

Browse files
authored
Merge pull request #792 from synonymdev/feat/trezor-hardware-support
feat: trezor hardware support
2 parents b176b92 + f010231 commit 08e7f90

35 files changed

Lines changed: 7052 additions & 2 deletions

app/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ val bcp47Locales = listOf(
4848
)
4949
val e2eBackendEnv = System.getenv("E2E_BACKEND") ?: "local"
5050
val e2eHomegateUrlEnv = System.getenv("E2E_HOMEGATE_URL") ?: "http://127.0.0.1:6288"
51+
val trezorBridgeEnv = System.getenv("TREZOR_BRIDGE")?.toBoolean()?.toString() ?: "false"
52+
val trezorBridgeUrlEnv = System.getenv("TREZOR_BRIDGE_URL") ?: "http://10.0.2.2:21325"
5153

5254
android {
5355
namespace = "to.bitkit"
@@ -65,6 +67,8 @@ android {
6567
buildConfigField("boolean", "E2E", System.getenv("E2E")?.toBoolean()?.toString() ?: "false")
6668
buildConfigField("String", "E2E_BACKEND", "\"$e2eBackendEnv\"")
6769
buildConfigField("String", "E2E_HOMEGATE_URL", "\"$e2eHomegateUrlEnv\"")
70+
buildConfigField("boolean", "TREZOR_BRIDGE", trezorBridgeEnv)
71+
buildConfigField("String", "TREZOR_BRIDGE_URL", "\"$trezorBridgeUrlEnv\"")
6872
buildConfigField("boolean", "GEO", System.getenv("GEO")?.toBoolean()?.toString() ?: "true")
6973
buildConfigField("String", "LOCALES", "\"${bcp47Locales.joinToString(",")}\"")
7074
}
@@ -217,7 +221,7 @@ composeCompiler {
217221
}
218222

219223
dependencies {
220-
implementation(fileTree("libs") { include("*.aar") })
224+
implementation(fileTree("libs") { include("*.aar", "*.jar") })
221225
implementation(libs.jna) { artifact { type = "aar" } }
222226
implementation(platform(libs.kotlin.bom))
223227
implementation(libs.core.ktx)

app/libs/btleplug.aar

24.6 KB
Binary file not shown.

app/libs/jni-utils.jar

12.8 KB
Binary file not shown.

app/proguard-rules.pro

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,11 @@
1818

1919
# If you keep the line number information, uncomment this to
2020
# hide the original source file name.
21-
#-renamesourcefileattribute SourceFile
21+
#-renamesourcefileattribute SourceFile
22+
23+
# btleplug (droidplug) Android Bluetooth support
24+
# These classes are loaded via JNI from Rust code
25+
-keep class com.nonpolynomial.btleplug.** { *; }
26+
27+
# jni-utils support library for btleplug
28+
-keep class io.github.gedgygedgy.rust.** { *; }

app/src/main/AndroidManifest.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@
2626
android:name="android.permission.FOREGROUND_SERVICE"
2727
tools:ignore="ForegroundServicePermission,ForegroundServicesPolicy" />
2828
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
29+
30+
<!-- USB Host support for Trezor hardware wallet -->
31+
<uses-feature
32+
android:name="android.hardware.usb.host"
33+
android:required="false" />
34+
35+
<!-- Bluetooth permissions for Trezor Safe 7 (Android 12+) -->
36+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
37+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
38+
android:usesPermissionFlags="neverForLocation" />
39+
40+
<!-- Bluetooth permissions (legacy, Android 11 and below) -->
41+
<uses-permission android:name="android.permission.BLUETOOTH"
42+
android:maxSdkVersion="30" />
43+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
44+
android:maxSdkVersion="30" />
45+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
46+
android:maxSdkVersion="30" />
47+
2948
<!-- Required for E2E tests connecting to local Electrum server (Android 16+) -->
3049
<uses-permission
3150
android:name="android.permission.NEARBY_WIFI_DEVICES"
@@ -132,6 +151,14 @@
132151
<data android:scheme="lnurlp" />
133152
</intent-filter>
134153

154+
<!-- USB device attached — auto-grants permission when Trezor is plugged in -->
155+
<intent-filter>
156+
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
157+
</intent-filter>
158+
<meta-data
159+
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
160+
android:resource="@xml/usb_device_filter" />
161+
135162
<meta-data
136163
android:name="android.app.shortcuts"
137164
android:resource="@xml/shortcuts" />

app/src/main/java/to/bitkit/App.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import coil3.ImageLoader
1111
import coil3.SingletonImageLoader
1212
import dagger.hilt.android.HiltAndroidApp
1313
import to.bitkit.env.Env
14+
import to.bitkit.services.BluetoothInit
1415
import javax.inject.Inject
1516

1617
@HiltAndroidApp
@@ -31,6 +32,8 @@ internal open class App : Application(), Configuration.Provider {
3132
SingletonImageLoader.setSafe { imageLoader }
3233
currentActivity = CurrentActivity().also { registerActivityLifecycleCallbacks(it) }
3334
Env.initAppStoragePath(filesDir.absolutePath)
35+
// Initialize btleplug for Bluetooth support (required before any BLE usage)
36+
BluetoothInit.ensureInitialized()
3437
}
3538

3639
companion object {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package to.bitkit.data
2+
3+
import android.content.Context
4+
import androidx.datastore.core.DataStore
5+
import androidx.datastore.dataStore
6+
import dagger.hilt.android.qualifiers.ApplicationContext
7+
import kotlinx.coroutines.flow.Flow
8+
import kotlinx.coroutines.flow.first
9+
import kotlinx.serialization.Serializable
10+
import to.bitkit.data.serializers.TrezorDataSerializer
11+
import to.bitkit.repositories.KnownDevice
12+
import javax.inject.Inject
13+
import javax.inject.Singleton
14+
15+
private val Context.trezorDataStore: DataStore<TrezorData> by dataStore(
16+
fileName = "trezor_device.json",
17+
serializer = TrezorDataSerializer
18+
)
19+
20+
@Singleton
21+
class TrezorStore @Inject constructor(
22+
@ApplicationContext private val context: Context,
23+
) {
24+
private val store = context.trezorDataStore
25+
26+
val data: Flow<TrezorData> = store.data
27+
28+
suspend fun loadKnownDevices(): List<KnownDevice> =
29+
store.data.first().knownDevices
30+
31+
suspend fun saveKnownDevices(devices: List<KnownDevice>) {
32+
store.updateData { it.copy(knownDevices = devices) }
33+
}
34+
35+
suspend fun reset() {
36+
store.updateData { TrezorData() }
37+
}
38+
}
39+
40+
@Serializable
41+
data class TrezorData(
42+
val knownDevices: List<KnownDevice> = emptyList(),
43+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package to.bitkit.data.serializers
2+
3+
import androidx.datastore.core.Serializer
4+
import kotlinx.serialization.SerializationException
5+
import to.bitkit.data.TrezorData
6+
import to.bitkit.di.json
7+
import to.bitkit.utils.Logger
8+
import java.io.InputStream
9+
import java.io.OutputStream
10+
11+
object TrezorDataSerializer : Serializer<TrezorData> {
12+
override val defaultValue: TrezorData = TrezorData()
13+
14+
override suspend fun readFrom(input: InputStream): TrezorData {
15+
return try {
16+
json.decodeFromString(input.readBytes().decodeToString())
17+
} catch (e: SerializationException) {
18+
Logger.error("Failed to deserialize: $e")
19+
defaultValue
20+
}
21+
}
22+
23+
override suspend fun writeTo(t: TrezorData, output: OutputStream) {
24+
output.write(json.encodeToString(t).encodeToByteArray())
25+
}
26+
}

app/src/main/java/to/bitkit/env/Env.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import to.bitkit.models.NodePeer
1313
import to.bitkit.utils.Logger
1414
import java.io.File
1515
import kotlin.io.path.Path
16+
import com.synonym.bitkitcore.Network as BitkitCoreNetwork
1617

1718
@Suppress("ConstPropertyName", "KotlinConstantConditions", "SimplifyBooleanWithConstants")
1819
internal object Env {
@@ -199,6 +200,17 @@ internal object Env {
199200
else -> "https://bitkit.stag0.blocktank.to/backups-ldk"
200201
}
201202

203+
fun electrumUrlForNetwork(network: BitkitCoreNetwork): String {
204+
val isE2eLocal = isE2eTest && e2eBackend == "local"
205+
return when (network) {
206+
BitkitCoreNetwork.BITCOIN -> ElectrumServers.MAINNET.ESPLORA
207+
BitkitCoreNetwork.TESTNET, BitkitCoreNetwork.TESTNET4, BitkitCoreNetwork.SIGNET ->
208+
ElectrumServers.TESTNET
209+
BitkitCoreNetwork.REGTEST ->
210+
if (isE2eLocal) ElectrumServers.REGTEST.LOCAL else ElectrumServers.REGTEST.STAG
211+
}
212+
}
213+
202214
// endregion
203215

204216
// region paths

app/src/main/java/to/bitkit/ext/Context.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package to.bitkit.ext
33
import android.app.Activity
44
import android.app.ActivityManager
55
import android.app.NotificationManager
6+
import android.bluetooth.BluetoothManager
67
import android.content.ClipData
78
import android.content.ClipboardManager
89
import android.content.Context
910
import android.content.Context.NOTIFICATION_SERVICE
1011
import android.content.ContextWrapper
1112
import android.content.Intent
1213
import android.content.pm.PackageManager.PERMISSION_GRANTED
14+
import android.hardware.usb.UsbManager
1315
import android.provider.Settings
1416
import androidx.core.app.NotificationManagerCompat
1517
import androidx.core.content.ContextCompat
@@ -31,6 +33,12 @@ val Context.clipboardManager: ClipboardManager
3133
val Context.activityManager: ActivityManager
3234
get() = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
3335

36+
val Context.usbManager: UsbManager
37+
get() = getSystemService(Context.USB_SERVICE) as UsbManager
38+
39+
val Context.bluetoothManager: BluetoothManager
40+
get() = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
41+
3442
// Permissions
3543

3644
fun Context.requiresPermission(permission: String): Boolean =

0 commit comments

Comments
 (0)