QNetworkInformation: add support for transportMedium on Android

It's part of the capabilities which we are already using. It also lets
us work around a pre-existing edge-case where, if you have a VPN enabled
and enable Airplane mode it will continue to tell you it is Online even
when it is not. This happens because VPN is reported as a transport and
when Airplane mode is enabled it may be left enabled as the _only_
transport.

At the same time clear the default filters (if any), and filter out
suspended connections. May not necessarily make any difference.

And add a comment for why we cannot use a technically more suitable
type of callback.

Task-number: QTBUG-91023
Change-Id: Ic26c4d4e8da139ec8606a0b1bf5fb7157bd0beaf
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Mårten Nordheim 2021-09-23 15:01:47 +02:00
parent 7fb855e175
commit 589389843c
4 changed files with 124 additions and 3 deletions

View File

@ -48,12 +48,14 @@ import android.net.ConnectivityManager.NetworkCallback;
import android.net.NetworkRequest;
import android.net.NetworkCapabilities;
import android.net.Network;
import android.os.Build;
public class QtAndroidNetworkInformation {
private static final String LOG_TAG = "QtAndroidNetworkInformation";
private static native void connectivityChanged();
private static native void behindCaptivePortalChanged(boolean state);
private static native void transportMediumChanged(Transport transportMedium);
private static QtNetworkInformationCallback m_callback = null;
private static final Object m_lock = new Object();
@ -62,8 +64,21 @@ public class QtAndroidNetworkInformation {
Connected, Unknown, Disconnected
}
// Keep synchronized with AndroidTransport in androidconnectivitymanager.h
enum Transport {
Unknown,
Bluetooth,
Cellular,
Ethernet,
LoWPAN,
Usb,
WiFi,
WiFiAware,
}
private static class QtNetworkInformationCallback extends NetworkCallback {
public AndroidConnectivity previousState = null;
public Transport previousTransport = null;
QtNetworkInformationCallback() {
}
@ -77,13 +92,42 @@ public class QtAndroidNetworkInformation {
s = AndroidConnectivity.Connected;
else
s = AndroidConnectivity.Unknown; // = we _may_ have Internet access
final Transport transport = getTransport(capabilities);
if (transport == Transport.Unknown) // If we don't have any transport media: override
s = AndroidConnectivity.Unknown;
setState(s);
setTransportMedium(transport);
final boolean captive
= capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
behindCaptivePortalChanged(captive);
}
private Transport getTransport(NetworkCapabilities capabilities)
{
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return Transport.WiFi;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return Transport.Cellular;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
return Transport.Bluetooth;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return Transport.Ethernet;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
// Build.VERSION_CODES.O
return Transport.WiFiAware;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN)) {
// Build.VERSION_CODES.O_MR1
return Transport.LoWPAN;
}/* else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB)) {
// Build.VERSION_CODES.S
return Transport.Usb;
}*/ // @todo: Uncomment once we can use SDK 31
return Transport.Unknown;
}
private void setState(AndroidConnectivity s) {
if (previousState != s) {
previousState = s;
@ -91,6 +135,13 @@ public class QtAndroidNetworkInformation {
}
}
private void setTransportMedium(Transport t) {
if (previousTransport != t) {
previousTransport = t;
transportMediumChanged(t);
}
}
@Override
public void onLost(Network network) {
setState(AndroidConnectivity.Disconnected);
@ -112,10 +163,17 @@ public class QtAndroidNetworkInformation {
ConnectivityManager manager = getConnectivityManager(context);
m_callback = new QtNetworkInformationCallback();
NetworkRequest.Builder builder = new NetworkRequest.Builder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
builder = builder.clearCapabilities();
builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
builder = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND);
}
NetworkRequest request = builder.build();
// Can't use registerDefaultNetworkCallback because it doesn't let us know when
// the network disconnects!
manager.registerNetworkCallback(request, m_callback);
}
}

View File

@ -65,13 +65,17 @@ public:
static QNetworkInformation::Features featuresSupportedStatic()
{
using Feature = QNetworkInformation::Feature;
return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal);
return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal
| Feature::TransportMedium);
}
bool isValid() { return m_valid; }
private:
Q_DISABLE_COPY_MOVE(QAndroidNetworkInformationBackend);
void updateTransportMedium(AndroidConnectivityManager::AndroidTransport transport);
bool m_valid = false;
};
@ -129,6 +133,36 @@ QAndroidNetworkInformationBackend::QAndroidNetworkInformationBackend()
connect(conman, &AndroidConnectivityManager::captivePortalChanged, this,
&QAndroidNetworkInformationBackend::setBehindCaptivePortal);
connect(conman, &AndroidConnectivityManager::transportMediumChanged, this,
&QAndroidNetworkInformationBackend::updateTransportMedium);
}
void QAndroidNetworkInformationBackend::updateTransportMedium(
AndroidConnectivityManager::AndroidTransport transport)
{
using AndroidTransport = AndroidConnectivityManager::AndroidTransport;
using TransportMedium = QNetworkInformation::TransportMedium;
static const auto mapTransport = [](AndroidTransport state) -> TransportMedium {
switch (state) {
case AndroidTransport::Cellular:
return TransportMedium::Cellular;
case AndroidTransport::WiFi:
return TransportMedium::WiFi;
case AndroidTransport::Bluetooth:
return TransportMedium::Bluetooth;
case AndroidTransport::Ethernet:
return TransportMedium::Ethernet;
// These are not covered yet (but may be in the future)
case AndroidTransport::Usb:
case AndroidTransport::LoWPAN:
case AndroidTransport::WiFiAware:
case AndroidTransport::Unknown:
return TransportMedium::Unknown;
}
};
setTransportMedium(mapTransport(transport));
}
QT_END_NAMESPACE

View File

@ -70,6 +70,15 @@ static void behindCaptivePortalChanged(JNIEnv *env, jobject obj, jboolean state)
Q_EMIT androidConnManagerInstance->connManager->captivePortalChanged(state);
}
static void transportMediumChangedCallback(JNIEnv *env, jobject obj, jobject enumValue)
{
Q_UNUSED(env);
Q_UNUSED(obj);
const jint value = QJniObject(enumValue).callMethod<jint>("ordinal");
const auto transport = static_cast<AndroidConnectivityManager::AndroidTransport>(value);
emit androidConnManagerInstance->connManager->transportMediumChanged(transport);
}
AndroidConnectivityManager::AndroidConnectivityManager()
{
if (!registerNatives())
@ -132,11 +141,16 @@ bool AndroidConnectivityManager::registerNatives()
if (!networkReceiver.isValid())
return false;
const QByteArray transportEnumSig =
QByteArray("(L") + networkInformationClass + "$Transport;)V";
jclass clazz = env->GetObjectClass(networkReceiver.object());
static JNINativeMethod methods[] = {
{ "connectivityChanged", "()V", reinterpret_cast<void *>(networkConnectivityChanged) },
{ "behindCaptivePortalChanged", "(Z)V",
reinterpret_cast<void *>(behindCaptivePortalChanged) }
reinterpret_cast<void *>(behindCaptivePortalChanged) },
{ "transportMediumChanged", transportEnumSig.data(),
reinterpret_cast<void *>(transportMediumChangedCallback) },
};
const bool ret = (env->RegisterNatives(clazz, methods, std::size(methods)) == JNI_OK);
env->DeleteLocalRef(clazz);

View File

@ -51,6 +51,20 @@ class AndroidConnectivityManager : public QObject
public:
enum class AndroidConnectivity { Connected, Unknown, Disconnected };
Q_ENUM(AndroidConnectivity);
// Keep synchronized with Transport in QtAndroidNetworkInformation.java
enum class AndroidTransport {
Unknown,
Bluetooth,
Cellular,
Ethernet,
LoWPAN,
Usb,
WiFi,
WiFiAware,
};
Q_ENUM(AndroidTransport);
static AndroidConnectivityManager *getInstance();
~AndroidConnectivityManager();
@ -60,6 +74,7 @@ public:
Q_SIGNALS:
void connectivityChanged();
void captivePortalChanged(bool state);
void transportMediumChanged(AndroidTransport transport);
private:
friend struct AndroidConnectivityManagerInstance;