Skip to content

Commit c202b13

Browse files
authored
Merge branch 'master' into RateLimiting
2 parents 9c8f26b + 16a748c commit c202b13

File tree

41 files changed

+1020
-255
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1020
-255
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- Display the hide amount button by default and remove its settings
1010
- Linux: add support for Wayland
1111
- Fix the copy buttons in the Pocket order confirmation page
12+
- Android: handle device disconnect while the app is in the background
13+
- Improve send result view show relevant infos and options to make a new transaction or go back
1214

1315
# 4.46.3
1416
- Fix camera access on linux

backend/backend.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ type Backend struct {
192192

193193
devices map[string]device.Interface
194194

195+
usbManager *usb.Manager
196+
195197
accountsAndKeystoreLock locker.Locker
196198
accounts AccountsList
197199
// keystore is nil if no keystore is connected.
@@ -611,13 +613,14 @@ func (backend *Backend) OnDeviceUninit(f func(string)) {
611613
// Start starts the background services. It returns a channel of events to handle by the library
612614
// client.
613615
func (backend *Backend) Start() <-chan interface{} {
614-
usb.NewManager(
616+
backend.usbManager = usb.NewManager(
615617
backend.arguments.MainDirectoryPath(),
616618
backend.arguments.BitBox02DirectoryPath(),
617619
backend.socksProxy,
618620
backend.environment.DeviceInfos,
619621
backend.Register,
620-
backend.Deregister).Start()
622+
backend.Deregister)
623+
backend.usbManager.Start()
621624

622625
httpClient, err := backend.socksProxy.GetHTTPClient()
623626
if err != nil {
@@ -637,6 +640,13 @@ func (backend *Backend) Start() <-chan interface{} {
637640
return backend.events
638641
}
639642

643+
// UsbUpdate triggers a scan of the USB devices to detect connects/disconnects.
644+
func (backend *Backend) UsbUpdate() {
645+
if backend.usbManager != nil {
646+
backend.usbManager.Update()
647+
}
648+
}
649+
640650
// DevicesRegistered returns a map of device IDs to device of registered devices.
641651
func (backend *Backend) DevicesRegistered() map[string]device.Interface {
642652
return backend.devices

backend/bridgecommon/bridgecommon.go

+10
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,13 @@ func Shutdown() {
359359
log.Info("Shutdown called, but backend not running")
360360
}
361361
}
362+
363+
// UsbUpdate wraps backend.UsbUpdate.
364+
func UsbUpdate() {
365+
mu.RLock()
366+
defer mu.RUnlock()
367+
if globalBackend == nil {
368+
return
369+
}
370+
globalBackend.UsbUpdate()
371+
}

backend/devices/usb/manager.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ type Manager struct {
8686
onRegister func(device.Interface) error
8787
onUnregister func(string)
8888

89+
updateCh chan struct{}
90+
8991
socksProxy socksproxy.SocksProxy
9092

9193
log *logrus.Entry
@@ -111,6 +113,7 @@ func NewManager(
111113
deviceInfos: deviceInfos,
112114
onRegister: onRegister,
113115
onUnregister: onUnregister,
116+
updateCh: make(chan struct{}),
114117
socksProxy: socksProxy,
115118

116119
log: logging.Get().WithGroup("manager"),
@@ -255,8 +258,19 @@ func (manager *Manager) checkIfRemoved(deviceID string) bool {
255258
return true
256259
}
257260

261+
// Update triggers a scan of the USB devices to detect connects/disconnects.
262+
func (manager *Manager) Update() {
263+
go func() {
264+
manager.updateCh <- struct{}{}
265+
}()
266+
}
267+
258268
func (manager *Manager) listen() {
259269
for {
270+
select {
271+
case <-manager.updateCh:
272+
case <-time.After(time.Second):
273+
}
260274
for deviceID, device := range manager.devices {
261275
// Check if device was removed.
262276
if manager.checkIfRemoved(deviceID) {
@@ -310,11 +324,11 @@ func (manager *Manager) listen() {
310324
manager.log.WithError(err).Error("Failed to execute on-register")
311325
}
312326
}
313-
time.Sleep(time.Second)
314327
}
315328
}
316329

317330
// Start listens for inserted/removed devices forever.
318331
func (manager *Manager) Start() {
319332
go manager.listen()
333+
manager.Update()
320334
}

backend/mobileserver/mobileserver.go

+5
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ func UsingMobileDataChanged() {
155155
bridgecommon.UsingMobileDataChanged()
156156
}
157157

158+
// UsbUpdate exposes `bridgecommon.UsbUpdate` to Java/Kotlin.
159+
func UsbUpdate() {
160+
bridgecommon.UsbUpdate()
161+
}
162+
158163
type goLogHook struct {
159164
}
160165

frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/GoService.java

+38
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
import android.app.NotificationManager;
66
import android.app.PendingIntent;
77
import android.app.Service;
8+
import android.content.BroadcastReceiver;
9+
import android.content.Context;
810
import android.content.Intent;
11+
import android.content.IntentFilter;
912
import android.os.Binder;
1013
import android.os.Build;
1114
import android.os.IBinder;
15+
import android.hardware.usb.UsbManager;
1216

1317
import androidx.core.app.NotificationCompat;
18+
import androidx.lifecycle.ViewModelProvider;
19+
import androidx.lifecycle.ViewModelStoreOwner;
1420

1521
import java.util.concurrent.locks.Lock;
1622
import java.util.concurrent.locks.ReentrantLock;
@@ -31,6 +37,20 @@ public class GoService extends Service {
3137

3238
private final int notificationId = 8;
3339

40+
private ViewModelStoreOwner viewModelStoreOwner;
41+
42+
private final BroadcastReceiver usbStateReceiver = new BroadcastReceiver() {
43+
@Override
44+
public void onReceive(Context context, Intent intent) {
45+
String action = intent.getAction();
46+
if (viewModelStoreOwner != null && UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
47+
GoViewModel viewModel = new ViewModelProvider(viewModelStoreOwner).get(GoViewModel.class);
48+
viewModel.setDevice(null);
49+
Mobileserver.usbUpdate();
50+
}
51+
}
52+
};
53+
3454
@Override
3555
public void onCreate() {
3656
Util.log("GoService onCreate()");
@@ -68,12 +88,26 @@ public void onCreate() {
6888
// focus. This is needed to avoid timeouts when the backend is polling the BitBox for e.g.
6989
// an address verification.
7090
startForeground(notificationId, notification);
91+
92+
// Register USB broadcast receiver to detect USB disconnects, even while the app is in the
93+
// background.
94+
IntentFilter filter = new IntentFilter();
95+
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
96+
97+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
98+
registerReceiver(usbStateReceiver, filter, Context.RECEIVER_EXPORTED);
99+
} else {
100+
registerReceiver(usbStateReceiver, filter);
101+
}
102+
71103
Util.log("GoService onCreate completed");
72104
}
73105

74106
@Override
75107
public void onDestroy() {
76108
Util.log("GoService onDestroy()");
109+
super.onDestroy();
110+
unregisterReceiver(usbStateReceiver);
77111
// It would be nice to call MobileServer.shutdown() here, but that function
78112
// is currently incomplete and can lead to unpredictable results.
79113
}
@@ -98,6 +132,10 @@ GoService getService() {
98132
}
99133
}
100134

135+
public void setViewModelStoreOwner(ViewModelStoreOwner owner) {
136+
this.viewModelStoreOwner = owner;
137+
}
138+
101139
@Override
102140
public IBinder onBind(Intent intent) {
103141
return binder;

frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public void onServiceConnected(ComponentName className,
8888
IBinder service) {
8989
GoService.GoServiceBinder binder = (GoService.GoServiceBinder) service;
9090
goService = binder.getService();
91+
goService.setViewModelStoreOwner(MainActivity.this);
9192
Util.log("Bind connection completed!");
9293
startServer();
9394
}

frontends/android/BitBoxApp/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildscript {
77

88
}
99
dependencies {
10-
classpath 'com.android.tools.build:gradle:8.3.0'
10+
classpath 'com.android.tools.build:gradle:8.8.0'
1111

1212
// NOTE: Do not place your application dependencies here; they belong
1313
// in the individual module build.gradle files
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

frontends/ios/BitBoxApp/BitBoxApp.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
D700B6032CA2FFAF000496D4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D765160F2B1F3B1500DC03A9 /* Preview Assets.xcassets */; };
1616
D700B6042CA2FFAF000496D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D765160C2B1F3B1500DC03A9 /* Assets.xcassets */; };
1717
D700B6062CA2FFAF000496D4 /* Mobileserver.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D76518BB2B1F8F7400DC03A9 /* Mobileserver.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18+
D721B20D2D369EC100767080 /* Bluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D721B20C2D369EC100767080 /* Bluetooth.swift */; };
19+
D721B20E2D36A00000767080 /* Bluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D721B20C2D369EC100767080 /* Bluetooth.swift */; };
1820
D76516092B1F3B1300DC03A9 /* BitBoxAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76516082B1F3B1300DC03A9 /* BitBoxAppApp.swift */; };
1921
D765160D2B1F3B1500DC03A9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D765160C2B1F3B1500DC03A9 /* Assets.xcassets */; };
2022
D76516102B1F3B1500DC03A9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D765160F2B1F3B1500DC03A9 /* Preview Assets.xcassets */; };
@@ -72,6 +74,7 @@
7274
D700B5F82C888CB9000496D4 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
7375
D700B5F92C986A3C000496D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
7476
D700B60A2CA2FFAF000496D4 /* BitBoxApp Testnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BitBoxApp Testnet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
77+
D721B20C2D369EC100767080 /* Bluetooth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bluetooth.swift; sourceTree = "<group>"; };
7578
D76516052B1F3B1300DC03A9 /* BitBoxApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitBoxApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
7679
D76516082B1F3B1300DC03A9 /* BitBoxAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitBoxAppApp.swift; sourceTree = "<group>"; };
7780
D765160C2B1F3B1500DC03A9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -154,6 +157,7 @@
154157
D76516322B1F3D1A00DC03A9 /* WebView.swift */,
155158
D765160C2B1F3B1500DC03A9 /* Assets.xcassets */,
156159
D765160E2B1F3B1500DC03A9 /* Preview Content */,
160+
D721B20C2D369EC100767080 /* Bluetooth.swift */,
157161
);
158162
path = BitBoxApp;
159163
sourceTree = "<group>";
@@ -396,6 +400,7 @@
396400
buildActionMask = 2147483647;
397401
files = (
398402
D700B5FD2CA2FFAF000496D4 /* WebView.swift in Sources */,
403+
D721B20E2D36A00000767080 /* Bluetooth.swift in Sources */,
399404
D700B5FE2CA2FFAF000496D4 /* BitBoxAppApp.swift in Sources */,
400405
);
401406
runOnlyForDeploymentPostprocessing = 0;
@@ -405,6 +410,7 @@
405410
buildActionMask = 2147483647;
406411
files = (
407412
D76516332B1F3D1B00DC03A9 /* WebView.swift in Sources */,
413+
D721B20D2D369EC100767080 /* Bluetooth.swift in Sources */,
408414
D76516092B1F3B1300DC03A9 /* BitBoxAppApp.swift in Sources */,
409415
);
410416
runOnlyForDeploymentPostprocessing = 0;

frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift

+16-7
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,35 @@ protocol SetMessageHandlersProtocol {
3939
}
4040

4141
class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol, UIDocumentInteractionControllerDelegate {
42+
private let bluetoothManager: BluetoothManager
43+
44+
init(bluetoothManager: BluetoothManager) {
45+
self.bluetoothManager = bluetoothManager
46+
}
47+
4248
func getSaveFilename(_ fileName: String?) -> String {
4349
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
4450
// fileName cannot be nil this is called by Go and Go strings cannot be nil/null.
4551
let fileURL = tempDirectory.appendingPathComponent(fileName!)
4652
return fileURL.path
4753
}
48-
54+
4955
func auth() {
5056
authenticateUser { success in
5157
// TODO: enabling auth but entering wrong passcode does not remove auth screen
5258
MobileserverAuthResult(success)
5359
}
5460
}
55-
61+
5662
func detectDarkTheme() -> Bool {
5763
return UIScreen.main.traitCollection.userInterfaceStyle == .dark
5864
}
59-
65+
6066
func deviceInfo() -> MobileserverGoDeviceInfoInterfaceProtocol? {
61-
// Return an instance conforming to GoserverGoDeviceInfoInterface
62-
// Replace 'GoDeviceInfo' with your implementation
63-
return nil
67+
if !bluetoothManager.isConnected() {
68+
return nil
69+
}
70+
return BluetoothDeviceInfo(bluetoothManager: bluetoothManager)
6471
}
6572

6673
func nativeLocale() -> String {
@@ -143,6 +150,8 @@ class GoAPI: NSObject, MobileserverGoAPIInterfaceProtocol, SetMessageHandlersPro
143150

144151
@main
145152
struct BitBoxAppApp: App {
153+
@StateObject private var bluetoothManager = BluetoothManager()
154+
146155
var body: some Scene {
147156
WindowGroup {
148157
GridLayout(alignment: .leading) {
@@ -167,7 +176,7 @@ struct BitBoxAppApp: App {
167176
} catch {
168177
print("Could not create Application Support directory: \(error)")
169178
}
170-
let goEnvironment = GoEnvironment()
179+
let goEnvironment = GoEnvironment(bluetoothManager: bluetoothManager)
171180
#if TARGET_TESTNET
172181
let testnet = true;
173182
#else

0 commit comments

Comments
 (0)