From 7f2bfae1044b6e148dd860b63326c8a13e782ba0 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Sat, 19 Oct 2019 10:53:04 +0200 Subject: [PATCH] Fix broadcast discovery in Android On Android and potencially other mobile platforms, WiFi drivers are configured by default to discard any packet that is not directed to the unicast mac address of the interface, this way they could save some battery but breaks anything that is not unicast, such as broadcast discovery. To solve this problem On such platforms provide methods to handle low level multicast listening so we can receive advertisement from same broadcast domain nodes. --- .../src/retroshare/rsbroadcastdiscovery.h | 40 +++++++ .../src/services/broadcastdiscoveryservice.cc | 104 +++++++++++++++++- .../src/services/broadcastdiscoveryservice.h | 35 +++++- .../src/android/AndroidManifest.xml | 4 + 4 files changed, 180 insertions(+), 3 deletions(-) diff --git a/libretroshare/src/retroshare/rsbroadcastdiscovery.h b/libretroshare/src/retroshare/rsbroadcastdiscovery.h index a712c60af..1bb1e1cc6 100644 --- a/libretroshare/src/retroshare/rsbroadcastdiscovery.h +++ b/libretroshare/src/retroshare/rsbroadcastdiscovery.h @@ -107,5 +107,45 @@ public: */ virtual std::vector getDiscoveredPeers() = 0; + /** + * @brief Check if multicast listening is enabled + * @jsonapi{development} + * On some platforms such as Android multicast listening, which is needed + * for broadcast discovery, is not enabled by default at WiFi driver level + * @see enableMulticastListening, so this method check if it is enabled. + * On platforms that are not expected to have such a limitation this method + * always return true. + * @return true if enabled, false otherwise. + */ + virtual bool isMulticastListeningEnabled() = 0; + + /** + * @brief On platforms that need it enable low level multicast listening + * @jsonapi{development} + * On Android and potencially other mobile platforms, WiFi drivers are + * configured by default to discard any packet that is not directed to the + * unicast mac address of the interface, this way they could save some + * battery but breaks anything that is not unicast, such as broadcast + * discovery. On such platforms this method enable low level multicast + * listening so we can receive advertisement from same broadcast domain + * nodes. + * On platforms without such limitation does nothing and always return + * false. + * It is exposed as a public API so the UI can decide the right equilibrium + * between discoverability and battery saving. + * @return true if multicast listening has been enabled due to this call, + * false otherwise. + */ + virtual bool enableMulticastListening() = 0; + + /** + * @brief Disable multicast listening + * @jsonapi{development} + * The opposite of @see enableMulticastListening. + * @return true if multicast listening has been disabled due to this call, + * false otherwise. + */ + virtual bool disableMulticastListening() = 0; + virtual ~RsBroadcastDiscovery(); }; diff --git a/libretroshare/src/services/broadcastdiscoveryservice.cc b/libretroshare/src/services/broadcastdiscoveryservice.cc index 337b725f6..d49e04a17 100644 --- a/libretroshare/src/services/broadcastdiscoveryservice.cc +++ b/libretroshare/src/services/broadcastdiscoveryservice.cc @@ -25,6 +25,10 @@ #include #include +#ifdef __ANDROID__ +# include +#endif // def __ANDROID__ + #include "services/broadcastdiscoveryservice.h" #include "retroshare/rspeers.h" #include "serialiser/rsserializable.h" @@ -94,6 +98,12 @@ BroadcastDiscoveryService::BroadcastDiscoveryService( { if(mRsPeers.isHiddenNode(mRsPeers.getOwnId())) return; +#ifdef __ANDROID__ + createMulticastLock(); +#endif // def __ANDROID__ + + enableMulticastListening(); + mUdcParameters.set_can_discover(true); mUdcParameters.set_can_be_discovered(true); mUdcParameters.set_port(port); @@ -104,7 +114,10 @@ BroadcastDiscoveryService::BroadcastDiscoveryService( } BroadcastDiscoveryService::~BroadcastDiscoveryService() -{ mUdcPeer.Stop(true); } +{ + mUdcPeer.Stop(true); + disableMulticastListening(); +} std::vector BroadcastDiscoveryService::getDiscoveredPeers() @@ -203,6 +216,95 @@ RsBroadcastDiscoveryResult BroadcastDiscoveryService::createResult( return rbdr; } +bool BroadcastDiscoveryService::isMulticastListeningEnabled() +{ +#ifdef __ANDROID__ + return assertMulticastLockIsvalid() && + mWifiMulticastLock.callMethod("isHeld"); +#endif // def __ANDROID__ + + return true; +} + +bool BroadcastDiscoveryService::enableMulticastListening() +{ +#ifdef __ANDROID__ + if(assertMulticastLockIsvalid() && !isMulticastListeningEnabled()) + { + mWifiMulticastLock.callMethod("acquire"); + return true; + } +#endif // def __ANDROID__ + + return false; +} + +bool BroadcastDiscoveryService::disableMulticastListening() +{ +#ifdef __ANDROID__ + if(assertMulticastLockIsvalid() && isMulticastListeningEnabled()) + { + mWifiMulticastLock.callMethod("release"); + return true; + } +#endif // def __ANDROID__ + + return false; +} + +#ifdef __ANDROID__ +bool BroadcastDiscoveryService::createMulticastLock() +{ + Dbg2() << __PRETTY_FUNCTION__ << std::endl; + + constexpr auto fname = __PRETTY_FUNCTION__; + const auto failure = [&](const std::string& err) + { + RsErr() << fname << " " << err << std::endl; + return false; + }; + + if(mWifiMulticastLock.isValid()) + return failure("mWifiMulticastLock is already initialized"); + + QAndroidJniObject context = QtAndroid::androidContext(); + if(!context.isValid()) + return failure("Cannot retrieve Android context"); + + QAndroidJniObject WIFI_SERVICE = QAndroidJniObject::getStaticObjectField( + "android.content.Context", "WIFI_SERVICE", "Ljava/lang/String;"); + if(!WIFI_SERVICE.isValid()) + return failure("Cannot retrieve Context.WIFI_SERVICE value"); + + QAndroidJniObject wifiManager = context.callObjectMethod( + "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", + WIFI_SERVICE.object() ); + if(!wifiManager.isValid()) + return failure("Cannot retrieve Android Wifi Manager"); + + mWifiMulticastLock = wifiManager.callObjectMethod( + "createMulticastLock", + "(Ljava/lang/String;)Landroid/net/wifi/WifiManager$MulticastLock;", + QAndroidJniObject::fromString(fname).object() ); + if(!mWifiMulticastLock.isValid()) + return failure("Cannot create WifiManager.MulticastLock"); + + return true; +} + +bool BroadcastDiscoveryService::assertMulticastLockIsvalid() +{ + if(!mWifiMulticastLock.isValid()) + { + RsErr() << __PRETTY_FUNCTION__ << " mWifiMulticastLock is invalid!" + << std::endl; + print_stacktrace(); + return false; + } + return true; +} +#endif // def __ANDROID__ + RsBroadcastDiscovery::~RsBroadcastDiscovery() = default; RsBroadcastDiscoveryResult::~RsBroadcastDiscoveryResult() = default; RsBroadcastDiscoveryPeerFoundEvent::~RsBroadcastDiscoveryPeerFoundEvent() = default; diff --git a/libretroshare/src/services/broadcastdiscoveryservice.h b/libretroshare/src/services/broadcastdiscoveryservice.h index 17129c995..f7c52466a 100644 --- a/libretroshare/src/services/broadcastdiscoveryservice.h +++ b/libretroshare/src/services/broadcastdiscoveryservice.h @@ -27,8 +27,13 @@ #include +#ifdef __ANDROID__ +# include +#endif // def __ANDROID__ + #include "retroshare/rsbroadcastdiscovery.h" #include "util/rsthreads.h" +#include "util/rsdebug.h" namespace UDC = udpdiscovery; class RsPeers; @@ -37,13 +42,21 @@ class BroadcastDiscoveryService : public RsBroadcastDiscovery, public RsTickingThread { public: - // TODO: std::shared_ptr mRsPeers; BroadcastDiscoveryService(RsPeers& pRsPeers); - virtual ~BroadcastDiscoveryService() override; + ~BroadcastDiscoveryService() override; /// @see RsBroadcastDiscovery std::vector getDiscoveredPeers() override; + /// @see RsBroadcastDiscovery + bool isMulticastListeningEnabled() override; + + /// @see RsBroadcastDiscovery + bool enableMulticastListening() override; + + /// @see RsBroadcastDiscovery + bool disableMulticastListening() override; + /// @see RsTickingThread void data_tick() override; @@ -63,4 +76,22 @@ protected: RsBroadcastDiscoveryResult createResult( const UDC::IpPort& ipp, const std::string& uData ); + +#ifdef __ANDROID__ + /** Android WifiManager.MulticastLock */ + QAndroidJniObject mWifiMulticastLock; + + /** Initialize the wifi multicast lock without acquiring it + * Needed to enable multicast listening in Android, for RetroShare broadcast + * discovery inspired by: + * https://github.com/flutter/flutter/issues/16335#issuecomment-420547860 + */ + bool createMulticastLock(); + + /** Return false if mWifiMulticastLock is invalid and print error messages */ + bool assertMulticastLockIsvalid(); + +#endif // def __ANDROID__ + + RS_SET_CONTEXT_DEBUG_LEVEL(3) }; diff --git a/retroshare-service/src/android/AndroidManifest.xml b/retroshare-service/src/android/AndroidManifest.xml index cbf2676fa..d41a72995 100644 --- a/retroshare-service/src/android/AndroidManifest.xml +++ b/retroshare-service/src/android/AndroidManifest.xml @@ -85,4 +85,8 @@ + + +