-
Notifications
You must be signed in to change notification settings - Fork 111
frontend/android: refactor network related code #3673
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,10 +14,6 @@ | |
| import android.content.res.Configuration; | ||
| import android.hardware.usb.UsbDevice; | ||
| import android.hardware.usb.UsbManager; | ||
| import android.net.ConnectivityManager; | ||
| import android.net.Network; | ||
| import android.net.NetworkCapabilities; | ||
| import android.net.NetworkRequest; | ||
| import android.net.Uri; | ||
| import android.os.Bundle; | ||
| import android.os.Handler; | ||
|
|
@@ -62,47 +58,6 @@ public class MainActivity extends AppCompatActivity { | |
|
|
||
| private BitBoxWebChromeClient webChrome; | ||
|
|
||
| private ConnectivityManager connectivityManager; | ||
| private ConnectivityManager.NetworkCallback networkCallback; | ||
|
|
||
| private boolean hasInternetConnectivity(NetworkCapabilities capabilities) { | ||
| // To avoid false positives, if we can't obtain connectivity info, | ||
| // we return true. | ||
| // Note: this should never happen per Android documentation, as: | ||
| // - these can not be null it come from the onCapabilitiesChanged callback. | ||
| // - when obtained with getNetworkCapabilities(network), they can only be null if the | ||
| // network is null or unknown, but we guard against both in the caller. | ||
|
Comment on lines
-69
to
-74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment and the comment below are very useful, any reason you did not copy them to the new file like the test of this function?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this applies anymore in the proposed implementation: I tested this implementation on various Android versions and I never experienced false positives (or negatives) with this approach.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the comment below, about the VALIDATED capability: I don't think that the comment matches closely the API docs. I wanted to replace it but eventually I forgot about it, I'll make a new one trying to clarify 🙏 |
||
| if (capabilities == null) { | ||
| Util.log("Got null capabilities when we shouldn't have. Assuming we are online."); | ||
| return true; | ||
| } | ||
|
|
||
|
|
||
| boolean hasInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); | ||
|
|
||
| // We need to check for both internet and validated, since validated reports that the system | ||
| // found connectivity the last time it checked. But if this callback triggers when going offline | ||
| // (e.g. airplane mode), this bit would still be true when we execute this method. | ||
| boolean isValidated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); | ||
| return hasInternet && isValidated; | ||
|
|
||
| // Fallback for older devices | ||
| } | ||
|
|
||
| private void checkConnectivity() { | ||
| Network activeNetwork = connectivityManager.getActiveNetwork(); | ||
|
|
||
| // If there is no active network (e.g. airplane mode), there is no check to perform. | ||
| if (activeNetwork == null) { | ||
| Mobileserver.setOnline(false); | ||
| return; | ||
| } | ||
|
|
||
| NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(activeNetwork); | ||
|
|
||
| Mobileserver.setOnline(hasInternetConnectivity(capabilities)); | ||
| } | ||
|
|
||
| // Connection to bind with GoService | ||
| private final ServiceConnection connection = new ServiceConnection() { | ||
|
|
||
|
|
@@ -129,14 +84,6 @@ public void onReceive(Context context, Intent intent) { | |
| } | ||
| }; | ||
|
|
||
| private final BroadcastReceiver networkStateReceiver = new BroadcastReceiver() { | ||
| @Override | ||
| public void onReceive(Context context, Intent intent) { | ||
| Mobileserver.usingMobileDataChanged(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is deleted with no replacement, so it seems the mobile data warning banner will never be shown?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in my tests it was displayed anyway. But in the meantime I have heavily refactored the code anyway. |
||
| } | ||
| }; | ||
|
|
||
|
|
||
| @Override | ||
| public void onConfigurationChanged(Configuration newConfig) { | ||
| int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; | ||
|
|
@@ -248,21 +195,6 @@ protected void onCreate(Bundle savedInstanceState) { | |
| // In that case, handleIntent() is not called with ACTION_USB_DEVICE_ATTACHED. | ||
| this.updateDevice(); | ||
|
|
||
| connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); | ||
| networkCallback = new ConnectivityManager.NetworkCallback() { | ||
| @Override | ||
| public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull android.net.NetworkCapabilities capabilities) { | ||
| super.onCapabilitiesChanged(network, capabilities); | ||
| Mobileserver.setOnline(hasInternetConnectivity(capabilities)); | ||
| } | ||
| // When we lose the network, onCapabilitiesChanged does not trigger, so we need to override onLost. | ||
| @Override | ||
| public void onLost(@NonNull Network network) { | ||
| super.onLost(network); | ||
| Mobileserver.setOnline(false); | ||
| } | ||
| }; | ||
|
|
||
| getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { | ||
| @Override | ||
| public void handleOnBackPressed() { | ||
|
|
@@ -336,8 +268,8 @@ private void startServer() { | |
| final GoViewModel gVM = ViewModelProviders.of(this).get(GoViewModel.class); | ||
| goService.startServer(getApplicationContext().getFilesDir().getAbsolutePath(), gVM.getGoEnvironment(), gVM.getGoAPI()); | ||
|
|
||
| // Trigger connectivity check (as the network may already be unavailable when the app starts). | ||
| checkConnectivity(); | ||
| // Trigger connectivity and mobile connection check (as the network may already be unavailable when the app starts). | ||
| gVM.getNetworkHelper().checkConnectivity(); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -355,15 +287,7 @@ protected void onStart() { | |
| Util.log("lifecycle: onStart"); | ||
| final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class); | ||
| goViewModel.getIsDarkTheme().observe(this, this::setDarkTheme); | ||
|
|
||
|
|
||
| NetworkRequest request = new NetworkRequest.Builder() | ||
| .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) | ||
| .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) | ||
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) | ||
| .build(); | ||
| // Register the network callback to listen for changes in network capabilities. | ||
| connectivityManager.registerNetworkCallback(request, networkCallback); | ||
| goViewModel.getNetworkHelper().registerNetworkCallback(); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -389,11 +313,9 @@ protected void onResume() { | |
| ContextCompat.RECEIVER_NOT_EXPORTED | ||
| ); | ||
|
|
||
| // Listen on changes in the network connection. We are interested in if the user is connected to a mobile data connection. | ||
| registerReceiver(this.networkStateReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); | ||
|
|
||
| // Trigger connectivity check (as the network may already be unavailable when the app starts). | ||
| checkConnectivity(); | ||
| final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class); | ||
| goViewModel.getNetworkHelper().checkConnectivity(); | ||
|
|
||
| Intent intent = getIntent(); | ||
| handleIntent(intent); | ||
|
|
@@ -404,7 +326,6 @@ protected void onPause() { | |
| super.onPause(); | ||
| Util.log("lifecycle: onPause"); | ||
| unregisterReceiver(this.usbStateReceiver); | ||
| unregisterReceiver(this.networkStateReceiver); | ||
| } | ||
|
|
||
| private void handleIntent(Intent intent) { | ||
|
|
@@ -472,9 +393,8 @@ private void updateDevice() { | |
| @Override | ||
| protected void onStop() { | ||
| super.onStop(); | ||
| if (connectivityManager != null && networkCallback != null) { | ||
| connectivityManager.unregisterNetworkCallback(networkCallback); | ||
| } | ||
| final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class); | ||
| goViewModel.getNetworkHelper().unregisterNetworkCallback(); | ||
| Util.log("lifecycle: onStop"); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package ch.shiftcrypto.bitboxapp; | ||
|
|
||
| import android.net.ConnectivityManager; | ||
| import android.net.Network; | ||
| import android.net.NetworkCapabilities; | ||
| import android.os.Handler; | ||
| import android.os.Looper; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
|
|
||
| import mobileserver.Mobileserver; | ||
|
|
||
| public class NetworkHelper { | ||
| private final ConnectivityManager connectivityManager; | ||
| private final ConnectivityManager.NetworkCallback networkCallback; | ||
|
|
||
| public NetworkHelper(ConnectivityManager connectivityManager) { | ||
| this.connectivityManager = connectivityManager; | ||
| networkCallback = new ConnectivityManager.NetworkCallback() { | ||
| @Override | ||
| public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull android.net.NetworkCapabilities capabilities) { | ||
| Util.log("onCapabilitiesChanged"); | ||
| super.onCapabilitiesChanged(network, capabilities); | ||
| checkNetworkConnectivity(network); | ||
| } | ||
|
|
||
| @Override | ||
| public void onLost(@NonNull Network network) { | ||
| Util.log("onLost"); | ||
| super.onLost(network); | ||
| // Workaround: onLost could trigger while the network is still winding down. | ||
| // Checking immediately for connection and mobile usage could lead to wrong results. | ||
| // see https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network) | ||
| // Since onCapabilitiesChanged doesn't seem to be enough, this is the only solution I | ||
| // found to make this reliable. It's not beautiful, but seems to fix the issue. | ||
| new Handler(Looper.getMainLooper()).postDelayed(() -> { | ||
| checkConnectivity(); | ||
| Mobileserver.usingMobileDataChanged(); | ||
| }, 250); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public void registerNetworkCallback() { | ||
| if (connectivityManager == null) { | ||
| return; | ||
| } | ||
|
|
||
| // Register the network callback to listen for changes in network capabilities. | ||
| // It needs to be unregistered when the app is in background to avoid resource consumption. | ||
| // See https://developer.android.com/reference/android/net/ConnectivityManager#registerNetworkCallback(android.net.NetworkRequest,%20android.net.ConnectivityManager.NetworkCallback) | ||
| connectivityManager.registerDefaultNetworkCallback(networkCallback); | ||
| } | ||
|
|
||
| public void unregisterNetworkCallback() { | ||
| if (connectivityManager != null && networkCallback != null) { | ||
| connectivityManager.unregisterNetworkCallback(networkCallback); | ||
| } | ||
| } | ||
|
|
||
| // Fetches the active network and verifies if that provides internet access. | ||
| public void checkConnectivity() { | ||
| if (connectivityManager == null) { | ||
| Mobileserver.setOnline(true); | ||
| return; | ||
| } | ||
| Network activeNetwork = connectivityManager.getActiveNetwork(); | ||
| checkNetworkConnectivity(activeNetwork); | ||
| } | ||
|
|
||
| private void checkNetworkConnectivity(Network network) { | ||
| // We force the server to fetch the mobile data status and possibly update the | ||
| // related banner. | ||
| Mobileserver.usingMobileDataChanged(); | ||
|
|
||
| if (network == null) { | ||
| Util.log("checkConnectivity: network null"); | ||
| Mobileserver.setOnline(false); | ||
| return; | ||
| } | ||
|
|
||
| NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); | ||
| Mobileserver.setOnline(hasInternetConnectivity(capabilities)); | ||
| } | ||
|
|
||
| private boolean hasInternetConnectivity(NetworkCapabilities capabilities) { | ||
| Util.log("hasInternetConnectivity"); | ||
| if (capabilities == null) { | ||
| Util.log("hasInternetConnectivity: null capabilities"); | ||
| return false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the original PR where @bznein introduced the connectivity check, we carefully returned true in all the failure cases, so that the user does not get false banners about being offline:
It seems you have changed back all these defaults. Please change it back. See also
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered in my other comment about this 😇 |
||
| } | ||
|
|
||
| // NET_CAPABILITY_INTERNET means that the network should be able to provide internet access, | ||
| // NET_CAPABILITY_VALIDATED means that the network connectivity was successfully detected. | ||
| // see https://developer.android.com/reference/android/net/NetworkCapabilities#NET_CAPABILITY_VALIDATED | ||
| // Checking only VALIDATED would be probably enough, but checking for both | ||
| // is probably better for reliability. | ||
| boolean hasInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); | ||
| boolean isValidated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); | ||
| Util.log("Has internet connectivity: " + (hasInternet && isValidated)); | ||
| return hasInternet && isValidated; | ||
| } | ||
|
|
||
| // if usingMobileData returns true, a banner will be displayed in the app to warn about | ||
| // possible network data consumption. | ||
| public boolean usingMobileData() { | ||
| if (connectivityManager == null) { | ||
| return false; | ||
| } | ||
|
|
||
| NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork()); | ||
| boolean mobileData = capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); | ||
| Util.log("Using mobile data: " + mobileData); | ||
| return mobileData; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention dropped support for Android <6 too?