Skip to content

Commit dfb5813

Browse files
committed
frontend/android: refactor network related code
Our MainActivity code is quite complicated and mixes a lot of different things. This change moves the code related to network management (e.g. to display alerts when the network is not available) to a dedicated helper class and declutters the main activity a bit. It also fixes a bug that caused the app to misdetect mobile connection or missing connectivity when switching between WIFI and cellular connection.
1 parent a3c9914 commit dfb5813

File tree

4 files changed

+113
-94
lines changed

4 files changed

+113
-94
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
- Add feedback link to guide and about settings
5+
- Android: fix connectivity misdetection when switching between WIFI and cellular network.
56

67
## v4.49.0
78
- Bundle BitBox02 Nova firmware version v9.24.0

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import android.hardware.usb.UsbEndpoint;
99
import android.hardware.usb.UsbInterface;
1010
import android.hardware.usb.UsbManager;
11-
import android.net.ConnectivityManager;
12-
import android.net.NetworkCapabilities;
1311
import android.os.Handler;
1412

1513
import androidx.lifecycle.AndroidViewModel;
@@ -170,14 +168,7 @@ public void bluetoothConnect(String identifier) {
170168

171169
@Override
172170
public boolean usingMobileData() {
173-
// Adapted from https://stackoverflow.com/a/53243938
174-
ConnectivityManager cm = (ConnectivityManager) getApplication().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
175-
if (cm == null) {
176-
return false;
177-
}
178-
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
179-
return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
180-
171+
return networkHelper.usingMobileData();
181172
}
182173

183174
@Override
@@ -207,6 +198,15 @@ public boolean detectDarkTheme() {
207198
private final MutableLiveData<Boolean> authSetting = new MutableLiveData<>(false);
208199
private final GoEnvironment goEnvironment;
209200
private final GoAPI goAPI;
201+
private NetworkHelper networkHelper;
202+
203+
public NetworkHelper getNetworkHelper() {
204+
return networkHelper;
205+
}
206+
207+
public void setNetworkHelper(NetworkHelper networkHelper) {
208+
this.networkHelper = networkHelper;
209+
}
210210

211211
public GoViewModel(Application app) {
212212
super(app);

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

Lines changed: 7 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
import android.hardware.usb.UsbDevice;
1616
import android.hardware.usb.UsbManager;
1717
import android.net.ConnectivityManager;
18-
import android.net.Network;
19-
import android.net.NetworkCapabilities;
20-
import android.net.NetworkRequest;
2118
import android.net.Uri;
2219
import android.os.Bundle;
2320
import android.os.Handler;
@@ -62,47 +59,6 @@ public class MainActivity extends AppCompatActivity {
6259

6360
private BitBoxWebChromeClient webChrome;
6461

65-
private ConnectivityManager connectivityManager;
66-
private ConnectivityManager.NetworkCallback networkCallback;
67-
68-
private boolean hasInternetConnectivity(NetworkCapabilities capabilities) {
69-
// To avoid false positives, if we can't obtain connectivity info,
70-
// we return true.
71-
// Note: this should never happen per Android documentation, as:
72-
// - these can not be null it come from the onCapabilitiesChanged callback.
73-
// - when obtained with getNetworkCapabilities(network), they can only be null if the
74-
// network is null or unknown, but we guard against both in the caller.
75-
if (capabilities == null) {
76-
Util.log("Got null capabilities when we shouldn't have. Assuming we are online.");
77-
return true;
78-
}
79-
80-
81-
boolean hasInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
82-
83-
// We need to check for both internet and validated, since validated reports that the system
84-
// found connectivity the last time it checked. But if this callback triggers when going offline
85-
// (e.g. airplane mode), this bit would still be true when we execute this method.
86-
boolean isValidated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
87-
return hasInternet && isValidated;
88-
89-
// Fallback for older devices
90-
}
91-
92-
private void checkConnectivity() {
93-
Network activeNetwork = connectivityManager.getActiveNetwork();
94-
95-
// If there is no active network (e.g. airplane mode), there is no check to perform.
96-
if (activeNetwork == null) {
97-
Mobileserver.setOnline(false);
98-
return;
99-
}
100-
101-
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork);
102-
103-
Mobileserver.setOnline(hasInternetConnectivity(capabilities));
104-
}
105-
10662
// Connection to bind with GoService
10763
private final ServiceConnection connection = new ServiceConnection() {
10864

@@ -129,14 +85,6 @@ public void onReceive(Context context, Intent intent) {
12985
}
13086
};
13187

132-
private final BroadcastReceiver networkStateReceiver = new BroadcastReceiver() {
133-
@Override
134-
public void onReceive(Context context, Intent intent) {
135-
Mobileserver.usingMobileDataChanged();
136-
}
137-
};
138-
139-
14088
@Override
14189
public void onConfigurationChanged(Configuration newConfig) {
14290
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
@@ -248,20 +196,7 @@ protected void onCreate(Bundle savedInstanceState) {
248196
// In that case, handleIntent() is not called with ACTION_USB_DEVICE_ATTACHED.
249197
this.updateDevice();
250198

251-
connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
252-
networkCallback = new ConnectivityManager.NetworkCallback() {
253-
@Override
254-
public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull android.net.NetworkCapabilities capabilities) {
255-
super.onCapabilitiesChanged(network, capabilities);
256-
Mobileserver.setOnline(hasInternetConnectivity(capabilities));
257-
}
258-
// When we lose the network, onCapabilitiesChanged does not trigger, so we need to override onLost.
259-
@Override
260-
public void onLost(@NonNull Network network) {
261-
super.onLost(network);
262-
Mobileserver.setOnline(false);
263-
}
264-
};
199+
goViewModel.setNetworkHelper(new NetworkHelper((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE)));
265200

266201
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
267202
@Override
@@ -337,7 +272,7 @@ private void startServer() {
337272
goService.startServer(getApplicationContext().getFilesDir().getAbsolutePath(), gVM.getGoEnvironment(), gVM.getGoAPI());
338273

339274
// Trigger connectivity check (as the network may already be unavailable when the app starts).
340-
checkConnectivity();
275+
gVM.getNetworkHelper().checkConnectivity();
341276
}
342277

343278
@Override
@@ -355,15 +290,7 @@ protected void onStart() {
355290
Util.log("lifecycle: onStart");
356291
final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class);
357292
goViewModel.getIsDarkTheme().observe(this, this::setDarkTheme);
358-
359-
360-
NetworkRequest request = new NetworkRequest.Builder()
361-
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
362-
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
363-
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
364-
.build();
365-
// Register the network callback to listen for changes in network capabilities.
366-
connectivityManager.registerNetworkCallback(request, networkCallback);
293+
goViewModel.getNetworkHelper().registerNetworkCallback();
367294
}
368295

369296
@Override
@@ -389,11 +316,9 @@ protected void onResume() {
389316
ContextCompat.RECEIVER_NOT_EXPORTED
390317
);
391318

392-
// Listen on changes in the network connection. We are interested in if the user is connected to a mobile data connection.
393-
registerReceiver(this.networkStateReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
394-
395319
// Trigger connectivity check (as the network may already be unavailable when the app starts).
396-
checkConnectivity();
320+
final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class);
321+
goViewModel.getNetworkHelper().checkConnectivity();
397322

398323
Intent intent = getIntent();
399324
handleIntent(intent);
@@ -404,7 +329,6 @@ protected void onPause() {
404329
super.onPause();
405330
Util.log("lifecycle: onPause");
406331
unregisterReceiver(this.usbStateReceiver);
407-
unregisterReceiver(this.networkStateReceiver);
408332
}
409333

410334
private void handleIntent(Intent intent) {
@@ -472,9 +396,8 @@ private void updateDevice() {
472396
@Override
473397
protected void onStop() {
474398
super.onStop();
475-
if (connectivityManager != null && networkCallback != null) {
476-
connectivityManager.unregisterNetworkCallback(networkCallback);
477-
}
399+
final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class);
400+
goViewModel.getNetworkHelper().unregisterNetworkCallback();
478401
Util.log("lifecycle: onStop");
479402
}
480403

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package ch.shiftcrypto.bitboxapp;
2+
3+
import android.net.ConnectivityManager;
4+
import android.net.Network;
5+
import android.net.NetworkCapabilities;
6+
7+
import androidx.annotation.NonNull;
8+
9+
import mobileserver.Mobileserver;
10+
11+
public class NetworkHelper {
12+
private final ConnectivityManager connectivityManager;
13+
private final ConnectivityManager.NetworkCallback networkCallback;
14+
15+
public NetworkHelper(ConnectivityManager connectivityManager) {
16+
this.connectivityManager = connectivityManager;
17+
networkCallback = new ConnectivityManager.NetworkCallback() {
18+
@Override
19+
public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull android.net.NetworkCapabilities capabilities) {
20+
super.onCapabilitiesChanged(network, capabilities);
21+
checkConnectivity();
22+
Mobileserver.usingMobileDataChanged();
23+
}
24+
25+
@Override
26+
public void onLost(@NonNull Network network) {
27+
super.onLost(network);
28+
checkConnectivity();
29+
Mobileserver.usingMobileDataChanged();
30+
}
31+
32+
@Override
33+
public void onAvailable(@NonNull Network network) {
34+
super.onAvailable(network);
35+
checkConnectivity();
36+
Mobileserver.usingMobileDataChanged();
37+
}
38+
};
39+
}
40+
41+
public void registerNetworkCallback() {
42+
if (connectivityManager == null) {
43+
return;
44+
}
45+
46+
// Register the network callback to listen for changes in network capabilities.
47+
// It needs to be unregistered when the app is in background to avoid resources consumpion.
48+
// See https://developer.android.com/reference/android/net/ConnectivityManager#registerNetworkCallback(android.net.NetworkRequest,%20android.net.ConnectivityManager.NetworkCallback)
49+
connectivityManager.registerDefaultNetworkCallback(networkCallback);
50+
}
51+
52+
public void unregisterNetworkCallback() {
53+
if (connectivityManager != null && networkCallback != null) {
54+
connectivityManager.unregisterNetworkCallback(networkCallback);
55+
}
56+
}
57+
58+
// Fetches the active network and verifies if that provides internet access.
59+
public void checkConnectivity() {
60+
Network activeNetwork = connectivityManager.getActiveNetwork();
61+
// If there is no active network (e.g. airplane mode), there is no check to perform.
62+
if (activeNetwork == null) {
63+
Util.log("checkConnectivity: active network null");
64+
Mobileserver.setOnline(false);
65+
return;
66+
}
67+
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork);
68+
Mobileserver.setOnline(hasInternetConnectivity(capabilities));
69+
}
70+
71+
private boolean hasInternetConnectivity(NetworkCapabilities capabilities) {
72+
Util.log("hasInternetConnectivity");
73+
if (capabilities == null) {
74+
Util.log("hasInternetConnectivity: null capabilities");
75+
return false;
76+
}
77+
78+
boolean validated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
79+
Util.log("Has internet connectivity: " + validated);
80+
return validated;
81+
}
82+
83+
// if usingMobileData returns true, a banner will be desplayed in the app to warn about
84+
// possible network data consumption.
85+
public boolean usingMobileData() {
86+
if (connectivityManager == null) {
87+
return false;
88+
}
89+
90+
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
91+
boolean mobileData = capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
92+
Util.log("Using mobile data: " + mobileData);
93+
return mobileData;
94+
}
95+
}

0 commit comments

Comments
 (0)