diff options
Diffstat (limited to 'src/plugins/networkinformation/android')
8 files changed, 562 insertions, 0 deletions
diff --git a/src/plugins/networkinformation/android/CMakeLists.txt b/src/plugins/networkinformation/android/CMakeLists.txt new file mode 100644 index 0000000000..07d9201bbb --- /dev/null +++ b/src/plugins/networkinformation/android/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +set(java_sources + jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java +) + +qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend + INCLUDE_JARS ${QT_ANDROID_JAR} + SOURCES ${java_sources} + OUTPUT_DIR "${QT_BUILD_DIR}/jar" +) + +qt_path_join(destination ${INSTALL_DATADIR} "jar") + +install_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend + DESTINATION ${destination} + COMPONENT Devel +) + +qt_internal_add_plugin(QAndroidNetworkInformationPlugin + OUTPUT_NAME qandroidnetworkinformation + CLASS_NAME QAndroidNetworkInformationBackendFactory + PLUGIN_TYPE networkinformation + DEFAULT_IF ANDROID + SOURCES + qandroidnetworkinformationbackend.cpp + wrapper/androidconnectivitymanager.cpp wrapper/androidconnectivitymanager.h + LIBRARIES + Qt::NetworkPrivate +) + +set_property( + TARGET + QAndroidNetworkInformationPlugin + APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES + jar/Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend.jar +) + +set_property( + TARGET + QAndroidNetworkInformationPlugin + APPEND PROPERTY QT_ANDROID_PERMISSIONS + android.permission.ACCESS_NETWORK_STATE +) diff --git a/src/plugins/networkinformation/android/jar/.gitignore b/src/plugins/networkinformation/android/jar/.gitignore new file mode 100644 index 0000000000..364420a59a --- /dev/null +++ b/src/plugins/networkinformation/android/jar/.gitignore @@ -0,0 +1,6 @@ +.gradle/ +build/ +gradle/ +gradlew +gradlew.bat +local.properties diff --git a/src/plugins/networkinformation/android/jar/build.gradle b/src/plugins/networkinformation/android/jar/build.gradle new file mode 100644 index 0000000000..ea6d06c257 --- /dev/null +++ b/src/plugins/networkinformation/android/jar/build.gradle @@ -0,0 +1,51 @@ +// This is mainly used to allow Android Studio to easily read this folder as an android project. + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.4.0' + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) +} + +repositories { + google() + mavenCentral() +} + +android { + compileSdk 34 + + defaultConfig { + minSdkVersion 28 + } + + sourceSets { + main { + java.srcDir 'src/' + resources.srcDir 'libs/' + manifest.srcFile 'AndroidManifest.xml' + res.srcDirs = ['res/'] + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + android { + lintOptions { + abortOnError true + } + } +} diff --git a/src/plugins/networkinformation/android/jar/settings.gradle b/src/plugins/networkinformation/android/jar/settings.gradle new file mode 100644 index 0000000000..cbb1ff361b --- /dev/null +++ b/src/plugins/networkinformation/android/jar/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "QtAndroidNetworkInformationBackend" diff --git a/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java b/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java new file mode 100644 index 0000000000..6a56c506b0 --- /dev/null +++ b/src/plugins/networkinformation/android/jar/src/org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation.java @@ -0,0 +1,160 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +package org.qtproject.qt.android.networkinformation; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +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 networkConnectivityChanged(int connectivity); + private static native void genericInfoChanged(boolean captivePortal, boolean metered); + private static native void transportMediumChanged(int transportMedium); + + private static QtNetworkInformationCallback m_callback = null; + private static final Object m_lock = new Object(); + + // Keep synchronized with AndroidConnectivity in androidconnectivitymanager.h + enum AndroidConnectivity { + 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() { + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { + AndroidConnectivity s; + if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) + s = AndroidConnectivity.Disconnected; + else if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) + 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); + final boolean metered + = !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + genericInfoChanged(captive, metered); + } + + 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; + networkConnectivityChanged(s.ordinal()); + } + } + + private void setTransportMedium(Transport t) { + if (previousTransport != t) { + previousTransport = t; + transportMediumChanged(t.ordinal()); + } + } + + @Override + public void onLost(Network network) { + setState(AndroidConnectivity.Disconnected); + } + } + + private QtAndroidNetworkInformation() { + } + + public static AndroidConnectivity state() { + if (m_callback != null && m_callback.previousState != null) + return m_callback.previousState; + return AndroidConnectivity.Unknown; + } + + @SuppressLint("MissingPermission") + public static void registerReceiver(final Context context) { + synchronized (m_lock) { + if (m_callback == null) { + 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 (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); + } + } + } + + public static void unregisterReceiver(final Context context) { + synchronized (m_lock) { + if (m_callback != null) { + getConnectivityManager(context).unregisterNetworkCallback(m_callback); + m_callback = null; + } + } + } + + public static ConnectivityManager getConnectivityManager(final Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } +} diff --git a/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp b/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp new file mode 100644 index 0000000000..44e4d447d2 --- /dev/null +++ b/src/plugins/networkinformation/android/qandroidnetworkinformationbackend.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtNetwork/private/qnetworkinformation_p.h> + +#include "wrapper/androidconnectivitymanager.h" + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcNetInfoAndroid) +Q_LOGGING_CATEGORY(lcNetInfoAndroid, "qt.network.info.android"); + +static QString backendName() { + return QString::fromUtf16(QNetworkInformationBackend::PluginNames + [QNetworkInformationBackend::PluginNamesAndroidIndex]); +} + +class QAndroidNetworkInformationBackend : public QNetworkInformationBackend +{ + Q_OBJECT +public: + QAndroidNetworkInformationBackend(); + ~QAndroidNetworkInformationBackend() { m_valid = false; } + + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return featuresSupportedStatic(); + } + + static QNetworkInformation::Features featuresSupportedStatic() + { + using Feature = QNetworkInformation::Feature; + return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal + | Feature::TransportMedium); + } + + bool isValid() { return m_valid; } + +private: + Q_DISABLE_COPY_MOVE(QAndroidNetworkInformationBackend); + + void updateConnectivity(AndroidConnectivityManager::AndroidConnectivity connectivity); + void updateTransportMedium(AndroidConnectivityManager::AndroidTransport transport); + + bool m_valid = false; +}; + +class QAndroidNetworkInformationBackendFactory : public QNetworkInformationBackendFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QNetworkInformationBackendFactory_iid) + Q_INTERFACES(QNetworkInformationBackendFactory) +public: + QAndroidNetworkInformationBackendFactory() = default; + ~QAndroidNetworkInformationBackendFactory() = default; + QString name() const override { return backendName(); } + QNetworkInformation::Features featuresSupported() const override + { + return QAndroidNetworkInformationBackend::featuresSupportedStatic(); + } + + QNetworkInformationBackend * + create(QNetworkInformation::Features requiredFeatures) const override + { + if ((requiredFeatures & featuresSupported()) != requiredFeatures) + return nullptr; + auto backend = new QAndroidNetworkInformationBackend(); + if (!backend->isValid()) + delete std::exchange(backend, nullptr); + return backend; + } + +private: + Q_DISABLE_COPY_MOVE(QAndroidNetworkInformationBackendFactory); +}; + +QAndroidNetworkInformationBackend::QAndroidNetworkInformationBackend() +{ + auto conman = AndroidConnectivityManager::getInstance(); + if (!conman) + return; + m_valid = true; + setReachability(QNetworkInformation::Reachability::Unknown); + connect(conman, &AndroidConnectivityManager::connectivityChanged, this, + &QAndroidNetworkInformationBackend::updateConnectivity); + + connect(conman, &AndroidConnectivityManager::captivePortalChanged, this, + &QAndroidNetworkInformationBackend::setBehindCaptivePortal); + + connect(conman, &AndroidConnectivityManager::transportMediumChanged, this, + &QAndroidNetworkInformationBackend::updateTransportMedium); + + connect(conman, &AndroidConnectivityManager::meteredChanged, this, + &QAndroidNetworkInformationBackend::setMetered); +} + +void QAndroidNetworkInformationBackend::updateConnectivity( + AndroidConnectivityManager::AndroidConnectivity connectivity) +{ + using AndroidConnectivity = AndroidConnectivityManager::AndroidConnectivity; + static const auto mapState = [](AndroidConnectivity state) { + switch (state) { + case AndroidConnectivity::Connected: + return QNetworkInformation::Reachability::Online; + case AndroidConnectivity::Disconnected: + return QNetworkInformation::Reachability::Disconnected; + case AndroidConnectivity::Unknown: + default: + return QNetworkInformation::Reachability::Unknown; + } + }; + + setReachability(mapState(connectivity)); +} + +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 + +#include "qandroidnetworkinformationbackend.moc" diff --git a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp new file mode 100644 index 0000000000..3c9f952968 --- /dev/null +++ b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp @@ -0,0 +1,97 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "androidconnectivitymanager.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qjnienvironment.h> + +QT_BEGIN_NAMESPACE + +using namespace QNativeInterface; + +struct AndroidConnectivityManagerInstance +{ + AndroidConnectivityManagerInstance() : connManager(new AndroidConnectivityManager) { } + std::unique_ptr<AndroidConnectivityManager> connManager = nullptr; +}; +Q_GLOBAL_STATIC(AndroidConnectivityManagerInstance, androidConnManagerInstance) + +static const char networkInformationClass[] = + "org/qtproject/qt/android/networkinformation/QtAndroidNetworkInformation"; + +static void networkConnectivityChanged(JNIEnv *env, jobject obj, jint enumValue) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + const auto connectivity = + static_cast<AndroidConnectivityManager::AndroidConnectivity>(enumValue); + Q_EMIT androidConnManagerInstance->connManager->connectivityChanged(connectivity); +} +Q_DECLARE_JNI_NATIVE_METHOD(networkConnectivityChanged) + +static void genericInfoChanged(JNIEnv *env, jobject obj, jboolean captivePortal, jboolean metered) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + Q_EMIT androidConnManagerInstance->connManager->captivePortalChanged(captivePortal); + Q_EMIT androidConnManagerInstance->connManager->meteredChanged(metered); +} +Q_DECLARE_JNI_NATIVE_METHOD(genericInfoChanged) + +static void transportMediumChanged(JNIEnv *env, jobject obj, jint enumValue) +{ + Q_UNUSED(env); + Q_UNUSED(obj); + const auto transport = static_cast<AndroidConnectivityManager::AndroidTransport>(enumValue); + emit androidConnManagerInstance->connManager->transportMediumChanged(transport); +} +Q_DECLARE_JNI_NATIVE_METHOD(transportMediumChanged) + +Q_DECLARE_JNI_CLASS(ConnectivityManager, "android/net/ConnectivityManager") + +AndroidConnectivityManager::AndroidConnectivityManager() +{ + if (!registerNatives()) + return; + + QJniObject::callStaticMethod<void>(networkInformationClass, "registerReceiver", + QAndroidApplication::context()); +} + +AndroidConnectivityManager *AndroidConnectivityManager::getInstance() +{ + if (!androidConnManagerInstance()) + return nullptr; + return androidConnManagerInstance->connManager->isValid() + ? androidConnManagerInstance->connManager.get() + : nullptr; +} + +bool AndroidConnectivityManager::isValid() const +{ + return registerNatives(); +} + +AndroidConnectivityManager::~AndroidConnectivityManager() +{ + QJniObject::callStaticMethod<void>(networkInformationClass, "unregisterReceiver", + QAndroidApplication::context()); +} + +bool AndroidConnectivityManager::registerNatives() const +{ + static const bool registered = []() { + QJniEnvironment env; + return env.registerNativeMethods(networkInformationClass, { + Q_JNI_NATIVE_METHOD(networkConnectivityChanged), + Q_JNI_NATIVE_METHOD(genericInfoChanged), + Q_JNI_NATIVE_METHOD(transportMediumChanged), + }); + }(); + return registered; +} + +QT_END_NAMESPACE + +#include "moc_androidconnectivitymanager.cpp" diff --git a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h new file mode 100644 index 0000000000..d15faf0e8e --- /dev/null +++ b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.h @@ -0,0 +1,54 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef ANDROIDCONNECTIVITYMANAGER_H +#define ANDROIDCONNECTIVITYMANAGER_H + +#include <QObject> +#include <QtCore/qjniobject.h> + +QT_BEGIN_NAMESPACE + +class AndroidConnectivityManager : public QObject +{ + Q_OBJECT +public: + // Keep synchronized with AndroidConnectivity in QtAndroidNetworkInformation.java + 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(); + + inline bool isValid() const; + +Q_SIGNALS: + void connectivityChanged(AndroidConnectivity connectivity); + void captivePortalChanged(bool state); + void transportMediumChanged(AndroidTransport transport); + void meteredChanged(bool state); + +private: + friend struct AndroidConnectivityManagerInstance; + AndroidConnectivityManager(); + bool registerNatives() const; + + Q_DISABLE_COPY_MOVE(AndroidConnectivityManager); +}; + +QT_END_NAMESPACE + +#endif // ANDROIDCONNECTIVITYMANAGER_H |