diff --git a/AndroidManifest.xml b/AndroidManifest.xml --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -21,6 +21,7 @@ + diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -371,13 +371,17 @@ new Thread(new Runnable() { @Override public void run() { - + Log.d("KDE/LanLinkProvider", "Broadcasting udp package"); String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(CustomDevicesActivity.KEY_CUSTOM_DEVLIST_PREFERENCE, ""); - ArrayList iplist = new ArrayList<>(); + ArrayList ipList = new ArrayList<>(); if (!deviceListPrefs.isEmpty()) { - iplist = CustomDevicesActivity.deserializeIpList(deviceListPrefs); + ipList = CustomDevicesActivity.deserializeIpList(deviceListPrefs); + } + ipList.addAll(NetworkHelper.getNetworkBroadcastDestinations()); + + for (String ipstr : ipList) { + Log.d("KDE/LanLinkProvider", "|Destination ip: " + ipstr); } - iplist.add("255.255.255.255"); //Default: broadcast. NetworkPackage identity = NetworkPackage.createIdentityPackage(context); identity.set("tcpPort", MIN_PORT); @@ -395,7 +399,7 @@ if (bytes != null) { //Log.e("KDE/LanLinkProvider","Sending packet to "+iplist.size()+" ips"); - for (String ipstr : iplist) { + for (String ipstr : ipList) { try { InetAddress client = InetAddress.getByName(ipstr); socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT)); @@ -442,6 +446,7 @@ @Override public void onNetworkChange() { + Log.d("KDE/LanLinkProvider", "Network changed"); broadcastUdpPackage(); } diff --git a/src/org/kde/kdeconnect/Helpers/NetworkHelper.java b/src/org/kde/kdeconnect/Helpers/NetworkHelper.java --- a/src/org/kde/kdeconnect/Helpers/NetworkHelper.java +++ b/src/org/kde/kdeconnect/Helpers/NetworkHelper.java @@ -4,55 +4,227 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.text.TextUtils; import android.util.Log; +import java.io.BufferedReader; import java.io.FileReader; import java.io.LineNumberReader; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; public class NetworkHelper { + public static List getNetworkBroadcastDestinations() { + // TODO use a more specific broadcast ip + return Collections.singletonList("255.255.255.255"); + } + + @Deprecated public static boolean isOnMobileNetwork(Context context) { + // Stage 0 - check environment + Log.d("isOnMobileNetwork", "check begun", new Throwable("stack trace")); if (context == null) { + Log.d("isOnMobileNetwork", "false (no context)"); return false; } if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + Log.d("isOnMobileNetwork", "false (platform version too old)"); return false; //No good way to know it } + long startTime = System.nanoTime(); + // Stage 1 - check ConnectivityManager + //try { + boolean mobileFound = false; + boolean nonMobileFound = false; try { - boolean mobile = false; + Log.d("isOnMobileNetwork", "|checking ConnectivityManager networks"); final ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); Network[] networks = connMgr.getAllNetworks(); + Log.d("isOnMobileNetwork", "|found " + networks.length + " connection(s)"); for (Network network : networks) { + Log.d("isOnMobileNetwork", "||checking network: " + network); NetworkInfo info = connMgr.getNetworkInfo(network); if (info == null) { + Log.d("isOnMobileNetwork", "||couldn't get network info, skipping"); + continue; + } + Log.d("isOnMobileNetwork", "|||network info: " + info); + int type = info.getType(); + Log.d("isOnMobileNetwork", "|||network is of type: " + type); + if (info.getType() == ConnectivityManager.TYPE_MOBILE) { // it is a mobile data network + if (info.isConnected()) { + Log.d("isOnMobileNetwork", "|||found connected mobile data network"); + mobileFound = true; + } else { + Log.d("isOnMobileNetwork", "|||ignored mobile data network that isn't connected"); + } + mobileFound = mobileFound || info.isConnected(); + continue; + } // else it'sn't + if (info.isConnected()) { + Log.d("isOnMobileNetwork", "|||found connected non-mobile network"); + //return false; //We are connected to at least one non-mobile network + nonMobileFound = true; continue; + } else { + Log.d("isOnMobileNetwork", "|||ignored non-mobile network that isn't connected"); + } + } + Log.d("isOnMobileNetwork", "|ConnectivityManager check resulted in: mobile=" + mobileFound + ", nonmobile=" + nonMobileFound); + //Log.d("isOnMobileNetwork", "|checking ConnectivityManager interfaces"); + //String[] connInterfaces = (String[]) ConnectivityManager.class.getDeclaredMethod("getTetherableIfaces").invoke(connMgr); + //Log.d("isOnMobileNetwork", "|found " + connInterfaces.length + " interface(s)"); + //for (String iface : connInterfaces) { + // Log.d("isOnMobileNetwork", "||discovered interface: " + iface); + //} + } catch (Exception e) { + Log.e("isOnMobileNetwork", "Exception checking ConnectivityManager", e); + return false; + } + // Stage 2 - check WifiManager + boolean wifiTetherEnabled; + try { + Log.d("isOnMobileNetwork", "|checking WifiManager"); + final WifiManager wifiMgr = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + wifiTetherEnabled = (Boolean) WifiManager.class.getDeclaredMethod("isWifiApEnabled").invoke(wifiMgr); + Log.d("isOnMobileNetwork", "|WifiManager check resulted in: " + wifiTetherEnabled); + if (wifiTetherEnabled) { + nonMobileFound = true; + } + } catch (Exception e) { + Log.e("isOnMobileNetwork", "Exception reading WifiManager", e); + return false; + } + // Stage 3 - check NetworkInterface interfaces + try { + boolean usbTetherEnabled = false; + boolean btTetherEnabled = false; + boolean wifiEnabled = false; + Log.d("isOnMobileNetwork", "|checking NetworkInterface interfaces"); + List niInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + Log.d("isOnMobileNetwork", "|found " + niInterfaces.size() + " interface(s)"); + //TODO listen for changes + for (NetworkInterface iface : niInterfaces) { + String name = iface.getName(); + boolean up = iface.isUp(); + Log.d("isOnMobileNetwork", "||checking interface: up=" + up + ", name=" + name); + List inetaddresses = Collections.list(iface.getInetAddresses()); + Log.d("isOnMobileNetwork", "|||found " + inetaddresses.size() + " inetaddress(es)"); + for (InetAddress addr : inetaddresses) { + Log.d("isOnMobileNetwork", "||||inetaddress: " + addr); + } + List interfaceAddresses = iface.getInterfaceAddresses(); + Log.d("isOnMobileNetwork", "|||found " + inetaddresses.size() + " interfaceaddress(es)"); + for (InterfaceAddress addr : interfaceAddresses) { + Log.d("isOnMobileNetwork", "||||interfaceaddress: " + addr); + InetAddress broadcastAddr = addr.getBroadcast(); + Log.d("isOnMobileNetwork", "|||||interfaceaddress broadcast destination: " + broadcastAddr); } - if (info.getType() == ConnectivityManager.TYPE_MOBILE) { - mobile = info.isConnected(); + if (!up) { + Log.d("isOnMobileNetwork", "|||ignoring down interface"); continue; } - //Log.e(info.getTypeName(),""+info.isAvailable()); - if (info.isAvailable()) return false; //We are connected to at least one non-mobile network - } - if (mobile) { //We suspect we are on a mobile net - try { - //Check the number of network neighbours, on data it should be 0 - LineNumberReader is = new LineNumberReader(new FileReader("/proc/net/arp")); - is.skip(Long.MAX_VALUE); - //Log.e("NetworkHelper", "procnetarp has " + is.getLineNumber() + " lines"); - if (is.getLineNumber() > 1) { //The first line are the headers - return false; //I have neighbours, so this doesn't look like a mobile network + if (name.startsWith("rndis")) { + Log.d("isOnMobileNetwork", "|||found usb tethering interface. its broadcast ip might be usable!!!"); + usbTetherEnabled = true; + } else if (name.equals("bt-pan")) { + Log.d("isOnMobileNetwork", "|||found bluetooth tethering interface. its broadcast ip might be usable!!!"); + btTetherEnabled = true; + } else if (name.startsWith("wlan")) { + Log.d("isOnMobileNetwork", "|||found wifi interface"); + wifiEnabled = true; + if (wifiTetherEnabled) { + Log.d("isOnMobileNetwork", "||||seems like we are wifi tethering, its broadcast ip might be usable!!!"); + } else { + Log.d("isOnMobileNetwork", "||||seems like we are connected to an wifi ap, its broadcast ip might be usable!!!"); } - } catch (Exception e) { - Log.e("NetworkHelper", "Exception reading procnetarp"); - e.printStackTrace(); + } else { + Log.d("isOnMobileNetwork", "|||ignoring unknown interface"); } } - } catch(Exception e) { - e.printStackTrace(); - Log.d("isOnMobileNetwork", "Something went wrong, but this is non-critical."); + Log.d("isOnMobileNetwork", "|NetworkInterface check resulted in:" + + " usb=" + usbTetherEnabled + ", bluetooth=" + btTetherEnabled + + ", wifi=" + wifiEnabled); + if (usbTetherEnabled || btTetherEnabled || wifiEnabled) { + nonMobileFound = true; + } + } catch (Exception e) { + Log.e("isOnMobileNetwork", "Exception checking NetworkInterface interfaces", e); + return false; + } + // Stage 4 - check /proc/net/arp + boolean arpTetherFound = false; + //if (mobileFound && !nonMobileFound) { //We suspect we are on a mobile net + try { + Log.d("isOnMobileNetwork", "|checking arp"); + //Check the number of network neighbours, on data it should be 0 + BufferedReader in = new BufferedReader(new FileReader("/proc/net/arp")); + String line = in.readLine(); //The first line are the headers + if (TextUtils.isEmpty(line)) { + throw new AssertionError("Expected non-empty procnetarp headers"); + } + List arpLines = new LinkedList<>(); + while ((line = in.readLine()) != null) { + arpLines.add(line); + } + Log.d("isOnMobileNetwork", "||procnetarp has " + arpLines.size() + " address lines"); + if (arpLines.size() > 1) { + //return false; //I have neighbours, so this doesn't look like a mobile network + arpTetherFound = true; + } + List arpAddresses = new LinkedList<>(); + for (String arpLine : arpLines) { + String[] arpSections = arpLine.replaceAll("\\s+", "\t").split("\t"); + if (arpSections.length != 6) { + throw new AssertionError("Expected 6 procnetarp columns, got " + arpSections.length); + } + arpAddresses.add(arpSections[0]); + Log.d("isOnMobileNetwork", "|||arp row: " + arpLine); + } + Log.d("isOnMobileNetwork", "||procnetarp has " + arpAddresses.size() + " addresses"); + for (String address : arpAddresses) { + Log.d("isOnMobileNetwork", "|||arp address [usable!!!]: " + address); + } + Log.d("isOnMobileNetwork", "|arp check resulted in: " + arpTetherFound); + } catch (Exception e) { + Log.e("isOnMobileNetwork", "Exception reading procnetarp", e); + return false; + } + if (arpTetherFound) { + nonMobileFound = true; + } + // Finally, return results + Log.d("isOnMobileNetwork", (System.nanoTime() - startTime) + " nanos have elapsed"); + if (!mobileFound) { + if (nonMobileFound) { + Log.d("isOnMobileNetwork", "false (we are connected to only non-mobile network(s))"); + return false; + } else { + Log.d("isOnMobileNetwork", "false (we aren't connected to anything?)"); + return false; + } + } else { + if (nonMobileFound) { + Log.d("isOnMobileNetwork", "false (we are also connected to non-mobile network(s))"); + return false; + } else { + Log.d("isOnMobileNetwork", "true (we are connected to only mobile data network(s))"); + return true; + } } - return false; + //} + //} catch(Exception e) { + // e.printStackTrace(); + // Log.d("isOnMobileNetwork", "Something went wrong, but this is non-critical, falling back to false"); + // return false; + //} } }