From 3aa1cc4badfec6ffde5ec4d351bc23c2a783c56d Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 00:18:17 -0600 Subject: [PATCH 1/7] Enhance network interface checks for local connections --- .../java/be/ppareit/swiftp/FsService.java | 114 ++++++++++++++++-- 1 file changed, 106 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/be/ppareit/swiftp/FsService.java b/app/src/main/java/be/ppareit/swiftp/FsService.java index 3e19e5ae..e15473ee 100644 --- a/app/src/main/java/be/ppareit/swiftp/FsService.java +++ b/app/src/main/java/be/ppareit/swiftp/FsService.java @@ -448,17 +448,23 @@ public static InetAddress getLocalInetAddress() { ArrayList networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); for (NetworkInterface networkInterface : networkInterfaces) { // only check network interfaces that give local connection - if (!networkInterface.getName().matches("^(eth|wlan|tun).*")) + // Include ap0 for WiFi hotspot and rndis for USB tethering + if (!networkInterface.getName().matches("^(eth|wlan|ap|rndis|tun).*")) continue; for (InetAddress address : Collections.list(networkInterface.getInetAddresses())) { + // Accept site-local addresses (192.168.x.x, 10.x.x.x, 172.16-31.x.x) + // Also accept private addresses even if not classified as site-local if (!address.isLoopbackAddress() && !address.isLinkLocalAddress() - && address.isSiteLocalAddress() && address instanceof Inet4Address) { - if (returnAddress != null) { - Cat.w("Found more than one valid address local inet address, why???"); + String addressStr = address.getHostAddress(); + // Check if it's a private IP range (RFC 1918) + if (isPrivateIpAddress(addressStr) || address.isSiteLocalAddress()) { + if (returnAddress != null) { + Cat.w("Found more than one valid address local inet address, why???"); + } + returnAddress = address; } - returnAddress = address; } } } @@ -468,6 +474,34 @@ public static InetAddress getLocalInetAddress() { return returnAddress; } + /** + * Check if an IP address is in a private range (RFC 1918) + * @param ipAddress IP address as string + * @return true if address is in private range + */ + private static boolean isPrivateIpAddress(String ipAddress) { + try { + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) return false; + + int firstOctet = Integer.parseInt(parts[0]); + int secondOctet = Integer.parseInt(parts[1]); + + // 10.0.0.0 - 10.255.255.255 + if (firstOctet == 10) return true; + + // 172.16.0.0 - 172.31.255.255 + if (firstOctet == 172 && secondOctet >= 16 && secondOctet <= 31) return true; + + // 192.168.0.0 - 192.168.255.255 + if (firstOctet == 192 && secondOctet == 168) return true; + + return false; + } catch (Exception e) { + return false; + } + } + /** * Checks to see if we are connected to a local network, for instance wifi or ethernet * @@ -483,10 +517,35 @@ public static boolean isConnectedToLocalNetwork() { Log.d(TAG, "isConnectedToLocalNetwork: see if it is an WIFI AP"); WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); try { + // Try the reflection method first (works on older Android versions) Method method = wm.getClass().getDeclaredMethod("isWifiApEnabled"); connected = (Boolean) method.invoke(wm); } catch (Exception e) { - e.printStackTrace(); + Log.v(TAG, "isWifiApEnabled reflection failed, will check network interfaces"); + } + } + if (!connected) { + // Fallback: check for WiFi hotspot by looking for ap0 interface + Log.d(TAG, "isConnectedToLocalNetwork: checking for WiFi hotspot interface"); + try { + ArrayList networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface netInterface : networkInterfaces) { + String ifname = netInterface.getName(); + // ap0 is used for WiFi hotspot, check if it has valid IPv4 addresses + if (ifname.startsWith("ap")) { + ArrayList addresses = Collections.list(netInterface.getInetAddresses()); + for (InetAddress addr : addresses) { + if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) { + connected = true; + Log.d(TAG, "Found WiFi hotspot interface: " + ifname); + break; + } + } + if (connected) break; + } + } + } catch (SocketException e) { + Log.v(TAG, "Exception checking for ap0 interface: " + e); } } if (!connected) { @@ -494,14 +553,53 @@ public static boolean isConnectedToLocalNetwork() { try { ArrayList networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); for (NetworkInterface netInterface : networkInterfaces) { - if (netInterface.getDisplayName().startsWith("rndis")) { - connected = true; + String ifname = netInterface.getName(); + // rndis is used for USB tethering + if (ifname.startsWith("rndis")) { + ArrayList addresses = Collections.list(netInterface.getInetAddresses()); + for (InetAddress addr : addresses) { + if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) { + connected = true; + Log.d(TAG, "Found USB tethering interface: " + ifname); + break; + } + } + if (connected) break; } } } catch (SocketException e) { e.printStackTrace(); } } + if (!connected) { + // Final fallback: check if any non-loopback interfaces have valid local addresses + Log.d(TAG, "isConnectedToLocalNetwork: checking all network interfaces for local addresses"); + try { + ArrayList networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface netInterface : networkInterfaces) { + String ifname = netInterface.getName(); + // Skip loopback and certain system interfaces + if (ifname.startsWith("lo") || ifname.startsWith("dummy")) { + continue; + } + ArrayList addresses = Collections.list(netInterface.getInetAddresses()); + for (InetAddress addr : addresses) { + if (addr instanceof Inet4Address && !addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) { + String addressStr = addr.getHostAddress(); + // Check if it's a private IP + if (isPrivateIpAddress(addressStr)) { + connected = true; + Log.d(TAG, "Found private IP on interface " + ifname + ": " + addressStr); + break; + } + } + } + if (connected) break; + } + } catch (SocketException e) { + Log.v(TAG, "Exception during final network interface check: " + e); + } + } return connected; } From a4b68bd3b3997dd9e8849c3e2ca02ac1f234cde4 Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:30:42 +0000 Subject: [PATCH 2/7] Add android.yml for github actions --- .github/workflows/android.yml | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..42956082 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,36 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Build with Gradle + run: ./gradlew assembleFdroid_freeDebug \ No newline at end of file From 81c58fccd9e7a043a1c16807db4f2f491c6b72da Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:39:03 +0000 Subject: [PATCH 3/7] Use mavencentral instead of jcenter --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4e56255f..af7baa79 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() } } From 810a1aa136db7b74461e65b944a99ce6a3406c56 Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:46:41 +0000 Subject: [PATCH 4/7] Try a newer version of com.twofortyfouram:android-plugin-client-sdk-for-locale --- app/build.gradle | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 989306fc..46257cce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'com.google.android.material:material:1.12.0' - implementation 'com.twofortyfouram:android-plugin-client-sdk-for-locale:4.0.3' + implementation 'com.twofortyfouram:android-plugin-client-sdk-for-locale:5.0.0' implementation 'com.google.code.gson:gson:2.10.1' implementation 'org.jetbrains:annotations:23.0.0' implementation 'androidx.work:work-runtime:2.9.0' diff --git a/gradle.properties b/gradle.properties index 7ea7aefa..028cc3f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ android.enableJetifier=true org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" android.useAndroidX=true -org.gradle.unsafe.configuration-cache=true +org.gradle.unsafe.configuration-cache=false android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false From a24145370d4bce3fe3327de019fb86479f6a7ceb Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:49:09 +0000 Subject: [PATCH 5/7] Exclude problematic transitive dependencies and add the mmanually with compatible versions --- app/build.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 46257cce..fe17d928 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,12 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'com.google.android.material:material:1.12.0' - implementation 'com.twofortyfouram:android-plugin-client-sdk-for-locale:5.0.0' + implementation ('com.twofortyfouram:android-plugin-client-sdk-for-locale:4.0.3') { + exclude group: 'com.twofortyfouram', module: 'android-annotation' + exclude group: 'com.twofortyfouram', module: 'android-assertion' + } + implementation 'com.twofortyfouram:android-annotation:3.0.0' + implementation 'com.twofortyfouram:android-assertion:2.0.0' implementation 'com.google.code.gson:gson:2.10.1' implementation 'org.jetbrains:annotations:23.0.0' implementation 'androidx.work:work-runtime:2.9.0' From b00ab23f3722f82541d71a45868809e2370fa88d Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:52:12 +0000 Subject: [PATCH 6/7] Add missing logs_menu.xml --- app/src/main/res/menu/logs_menu.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/src/main/res/menu/logs_menu.xml diff --git a/app/src/main/res/menu/logs_menu.xml b/app/src/main/res/menu/logs_menu.xml new file mode 100644 index 00000000..cb28dfff --- /dev/null +++ b/app/src/main/res/menu/logs_menu.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file From c667f11a36c7f987b0d2144e1879e241f3b41f10 Mon Sep 17 00:00:00 2001 From: primesun Date: Tue, 31 Mar 2026 06:55:18 +0000 Subject: [PATCH 7/7] Upload apk as artifact --- .github/workflows/android.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 42956082..6b41e46b 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -33,4 +33,10 @@ jobs: ${{ runner.os }}-gradle- - name: Build with Gradle - run: ./gradlew assembleFdroid_freeDebug \ No newline at end of file + run: ./gradlew assembleFdroid_freeDebug + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: swiftp-debug-apk + path: app/build/outputs/apk/fdroid_free/debug/*.apk \ No newline at end of file