Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
Expand Down
39 changes: 39 additions & 0 deletions src/android/src/ForegroundService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
Expand All @@ -16,13 +17,50 @@ public class ForegroundService extends Service {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
private WifiManager.MulticastLock multicastLock;

private void acquireMulticastLock() {
if (multicastLock != null && multicastLock.isHeld()) {
return;
}

try {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
if (wifiManager == null) {
QLog.d("ForegroundService", "WifiManager unavailable; cannot acquire multicast lock");
return;
}

multicastLock = wifiManager.createMulticastLock("QZ:ForegroundService");
multicastLock.setReferenceCounted(false);
multicastLock.acquire();
QLog.d("ForegroundService", "Acquired multicast lock");
} catch (Exception e) {
QLog.e("ForegroundService", "Failed to acquire multicast lock", e);
}
}

private void releaseMulticastLock() {
try {
if (multicastLock != null && multicastLock.isHeld()) {
multicastLock.release();
QLog.d("ForegroundService", "Released multicast lock");
}
} catch (Exception e) {
QLog.e("ForegroundService", "Failed to release multicast lock", e);
} finally {
multicastLock = null;
}
}

@Override
public void onCreate() {
super.onCreate();
acquireMulticastLock();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
acquireMulticastLock();
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent();
Expand Down Expand Up @@ -52,6 +90,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
}
@Override
public void onDestroy() {
releaseMulticastLock();
super.onDestroy();
}

Expand Down
136 changes: 132 additions & 4 deletions src/localipaddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,125 @@ char *getMacAddress(JNIEnv *env, jobject wifiInfoObj) {
int getIpAddress(JNIEnv *env, jobject wifiInfoObj) {
qDebug() << "getIpAddress.... ";
if (wifiInfoObj == NULL) {
return NULL;
return 0;
}
jclass jclz = env->GetObjectClass(wifiInfoObj);
jmethodID mid = env->GetMethodID(jclz, "getIpAddress", "()I");
return env->CallIntMethod(wifiInfoObj, mid);
}

/*
* Get the device's own IPv4 address via ConnectivityManager.getLinkProperties()
* on the active network. Unlike QNetworkInterface (which reads netlink route
* sockets and is blocked by SELinux for untrusted apps on modern Android) and
* the deprecated WifiManager.getConnectionInfo().getIpAddress() (which returns
* 0 on Android 10+ without extra permissions), this goes through the regular
* ConnectivityManager system service and is not subject to either restriction.
*/
QString getConnectivityManagerIp(JNIEnv *env, jobject jCtxObj) {
qDebug() << "getConnectivityManagerIp....";

// Reserve enough local reference slots for this whole call, regardless of
// the caller thread's default JNI local reference frame capacity.
if (env->PushLocalFrame(32) < 0) {
return QString();
}

jclass jCtxClz = env->FindClass("android/content/Context");
jfieldID fidConnService = env->GetStaticFieldID(jCtxClz, "CONNECTIVITY_SERVICE", "Ljava/lang/String;");
jstring jstrConnService = (jstring)env->GetStaticObjectField(jCtxClz, fidConnService);
jmethodID midGetSystemService =
env->GetMethodID(jCtxClz, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jobject connManager = env->CallObjectMethod(jCtxObj, midGetSystemService, jstrConnService);
if (connManager == NULL) {
env->PopLocalFrame(NULL);
return QString();
}

jclass connManagerClz = env->GetObjectClass(connManager);
// getActiveNetwork() is only available on API 23+; on older devices this
// GetMethodID throws NoSuchMethodError and returns NULL.
jmethodID midGetActiveNetwork = env->GetMethodID(connManagerClz, "getActiveNetwork", "()Landroid/net/Network;");
if (midGetActiveNetwork == NULL) {
env->ExceptionClear();
env->PopLocalFrame(NULL);
return QString();
}
jobject network = env->CallObjectMethod(connManager, midGetActiveNetwork);
if (network == NULL) {
env->PopLocalFrame(NULL);
return QString();
}

jmethodID midGetLinkProperties = env->GetMethodID(
connManagerClz, "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;");
jobject linkProperties = env->CallObjectMethod(connManager, midGetLinkProperties, network);
if (linkProperties == NULL) {
env->PopLocalFrame(NULL);
return QString();
}

jclass linkPropertiesClz = env->GetObjectClass(linkProperties);
jmethodID midGetLinkAddresses = env->GetMethodID(linkPropertiesClz, "getLinkAddresses", "()Ljava/util/List;");
jobject linkAddresses = env->CallObjectMethod(linkProperties, midGetLinkAddresses);
if (linkAddresses == NULL) {
env->PopLocalFrame(NULL);
return QString();
}

jclass listClz = env->GetObjectClass(linkAddresses);
jmethodID midSize = env->GetMethodID(listClz, "size", "()I");
jmethodID midGet = env->GetMethodID(listClz, "get", "(I)Ljava/lang/Object;");
const jint size = env->CallIntMethod(linkAddresses, midSize);

jclass linkAddressClz = env->FindClass("android/net/LinkAddress");
jmethodID midGetAddress = env->GetMethodID(linkAddressClz, "getAddress", "()Ljava/net/InetAddress;");
jclass inet4Clz = env->FindClass("java/net/Inet4Address");

QString result;
for (jint i = 0; i < size; ++i) {
jobject linkAddress = env->CallObjectMethod(linkAddresses, midGet, i);
if (linkAddress == NULL) {
continue;
}
jobject inetAddress = env->CallObjectMethod(linkAddress, midGetAddress);
env->DeleteLocalRef(linkAddress);
if (inetAddress == NULL) {
continue;
}
if (!env->IsInstanceOf(inetAddress, inet4Clz)) {
env->DeleteLocalRef(inetAddress);
continue;
}

jclass inetAddressClz = env->GetObjectClass(inetAddress);
jmethodID midIsLoopback = env->GetMethodID(inetAddressClz, "isLoopbackAddress", "()Z");
const bool isLoopback = env->CallBooleanMethod(inetAddress, midIsLoopback);
if (!isLoopback) {
jmethodID midGetHostAddress = env->GetMethodID(inetAddressClz, "getHostAddress", "()Ljava/lang/String;");
jstring jHostAddress = (jstring)env->CallObjectMethod(inetAddress, midGetHostAddress);
const char *hostAddressChars = env->GetStringUTFChars(jHostAddress, NULL);
result = QString::fromUtf8(hostAddressChars);
env->ReleaseStringUTFChars(jHostAddress, hostAddressChars);
env->DeleteLocalRef(jHostAddress);
}
env->DeleteLocalRef(inetAddressClz);
env->DeleteLocalRef(inetAddress);
if (!result.isEmpty()) {
break;
}
}

env->PopLocalFrame(NULL);
return result;
}
#endif

static bool isValidUsableIPv4(const QHostAddress &address) {
return address.protocol() == QAbstractSocket::IPv4Protocol && !address.isLoopback() &&
!address.isNull() && address != QHostAddress::AnyIPv4;
}

QHostAddress localipaddress::getIP(const QHostAddress &srcAddress) {
// Attempt to find the interface that corresponds with the provided
// address and determine this device's address from the interface
Expand All @@ -116,12 +227,29 @@ QHostAddress localipaddress::getIP(const QHostAddress &srcAddress) {
}
#ifdef Q_OS_ANDROID
QAndroidJniEnvironment env;

const QString connectivityIp = getConnectivityManagerIp(env, QtAndroid::androidContext().object());
if (env->ExceptionCheck()) {
// getActiveNetwork()/getLinkProperties() require API 23+; ignore on older devices.
env->ExceptionClear();
} else if (!connectivityIp.isEmpty()) {
QHostAddress connectivityAddress(connectivityIp);
if (isValidUsableIPv4(connectivityAddress)) {
qDebug() << "getIP from ConnectivityManager" << connectivityAddress;
return connectivityAddress;
}
}

jobject wifiManagerObj = getWifiManagerObj(env, QtAndroid::androidContext().object());
jobject wifiInfoObj = getWifiInfoObj(env, wifiManagerObj);
int ip = getIpAddress(env, wifiInfoObj);
QHostAddress qip = QHostAddress(qFromBigEndian<quint32>(ip));
qDebug() << "getIP from JNI" << qip;
return qip;
if (ip != 0) {
QHostAddress qip = QHostAddress(qFromBigEndian<quint32>(ip));
if (isValidUsableIPv4(qip)) {
qDebug() << "getIP from JNI" << qip;
return qip;
}
}
#endif
return QHostAddress();
}
Loading