diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java index c25b25a..d689c39 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java @@ -1,9 +1,9 @@ /* * DroidVNC-NG ListenIfAdapter. * - * Author: Christian Beier * - * Copyright (C) 2020 Kitchen Armor. + * Copyright (C) 2020 Christian Beier. * * You can redistribute and/or modify this program under the terms of the * GNU General Public License version 2 as published by the Free Software @@ -20,6 +20,10 @@ */ package net.christianbeier.droidvnc_ng; + +import java.net.NetworkInterface; +import java.util.ArrayList; + import android.content.res.Resources; import android.content.Context; import android.util.Log; @@ -30,118 +34,41 @@ import android.widget.Spinner; import android.view.LayoutInflater; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.net.NetworkInterface; -import java.util.ArrayList; - - -public class ListenIfAdapter extends ArrayAdapter { - // Class representing the data to show - static class NetworkInterfaceData { - // This represents the "Any" option - public static NetworkInterfaceData ANY; - - - private NetworkInterface nic; - private String techName; - private String friendlyName; - - - public NetworkInterfaceData(ListenIfAdapter lia) { - this(null, lia); - } - - - public NetworkInterfaceData(NetworkInterface nic, ListenIfAdapter lia) { - this.nic = nic; - - if (nic == null) { - this.techName = "0.0.0.0"; - this.friendlyName = lia.getStringForAny(); - - } else { - this.techName = this.nic.getName(); - String[] ifNameInfo = lia.extractIfInfo(this.techName); - if (ifNameInfo == null) { // No info found, consider "techName" as friendly - this.friendlyName = this.techName; - - } else { - this.friendlyName = lia.getFriendlyStringForInterface(ifNameInfo[0]); - if (ifNameInfo.length > 1) { - this.friendlyName += " " + ifNameInfo[1]; - } - } - } - } - - - public static NetworkInterfaceData getAnyOption(ListenIfAdapter lia) { - if (ANY == null) { - ANY = new NetworkInterfaceData(lia); - } - return ANY; - } - - - public String getName() { - return this.techName; - } - - - public String getFriendlyName() { - return this.friendlyName; - } - - - public String toString() { - return this.friendlyName + " (" + this.techName + ")"; - } - } - - - - +public class ListenIfAdapter extends ArrayAdapter implements NetworkInterfaceTester.OnNetworkStateChangedListener { // This adapter uses the ViewHolder pattern private static class ViewHolder { public TextView txtLabel; } // Data to be shown with the adapter - private ArrayList data; + private ArrayList data; private int dataSize; // Some context data for "easy retrieval" private Context mContext; - private Resources mResources; private LayoutInflater mInflater; - public ListenIfAdapter(ArrayList ifs, Context context) { + public ListenIfAdapter(NetworkInterfaceTester nit, Context context) { super(context, R.layout.spinner_row, R.id.spinner_text); this.mContext = context; - this.mResources = this.mContext.getResources(); this.mInflater = LayoutInflater.from(this.mContext); - this.data = new ArrayList<>(); - this.data.add(NetworkInterfaceData.getAnyOption(this)); - for (NetworkInterface nic : ifs) { - this.data.add(new NetworkInterfaceData(nic, this)); - } - this.dataSize = this.data.size(); + nit.addOnNetworkStateChangedListener(this); + this.onNetworkStateChanged(nit, null, false); } public int getItemPositionByIfName(String ifName) { int i = 0; - for (NetworkInterfaceData nid : this.data) { + for (NetworkInterfaceTester.NetIfData nid : this.data) { if (nid.getName().equals(ifName)) { return i; } @@ -156,7 +83,7 @@ public int getItemPositionByIfName(String ifName) { @Override public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { // Check if view must be recreated, using the famous ViewHolder pattern + if (convertView == null) { // Check if view must be recreated using the famous ViewHolder pattern convertView = this.mInflater.inflate(R.layout.spinner_row, parent, false); ViewHolder vh = new ViewHolder(); @@ -165,7 +92,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } ViewHolder vh = (ViewHolder)convertView.getTag(); - NetworkInterfaceData nid = this.getItem(position); + NetworkInterfaceTester.NetIfData nid = this.getItem(position); vh.txtLabel.setText(nid.toString()); return convertView; @@ -173,7 +100,7 @@ public View getView(int position, View convertView, ViewGroup parent) { @Override - public NetworkInterfaceData getItem(int position) { + public NetworkInterfaceTester.NetIfData getItem(int position) { if (0 <= position && position < this.getCount()) { return this.data.get(position); } @@ -181,7 +108,6 @@ public NetworkInterfaceData getItem(int position) { } - @Override public int getCount() { return this.dataSize; @@ -189,75 +115,8 @@ public int getCount() { - - private String getStringForAny() { - return this.mResources.getString(R.string.main_activity_settings_listenif_spin_any); - } - - - /** - * Since the majority of the interfaces have standard Linux names, this method tries to get a possible friendly name. - * Special thanks go to the pages: - * - https://stackoverflow.com/questions/33747787/android-networkinterface-what-are-the-meanings-of-names-of-networkinterface - * - https://stackoverflow.com/questions/47488435/meaning-of-network-interface-rmnet-ipa0 - * for some of the obscure interface names - * @param ifName name (technical) of the interface - * @return if known, a friendly name for the interface, otherwise, null - */ - private String getFriendlyStringForInterface(String ifName) { - String ifFriendlyName = null; // Null if no friendly name is known - - if (ifName.equals("eth")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_eth); - - } else if (ifName.equals("wlan")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_wifi); - - } else if (ifName.equals("lo")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_lpback); - - } else if (ifName.equals("tun")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_tun); - - } else if (ifName.equals("dummy")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_dum); - - } else if (ifName.equals("rmnet_data")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_rmndata); - - } else if (ifName.equals("rmnet_ipa")) { - ifFriendlyName = this.mResources.getString(R.string.main_activity_settings_listenif_spin_rmnipa); - - } - - - return ifFriendlyName; - } - - - - - /* - * This method is used to extract the name and the number of the particular interface - * from a full interface name (e.g. tun0 becomes "tun" and "0"). - * Null if no info was found (id est, interface is not known) - */ - private static final String IFS_REGEX = "(eth|wlan|tun|dummy|rmnet_data|rmnet_ipa)([0-9+])"; - - private String[] extractIfInfo(String ifName) { - Pattern pt = Pattern.compile(IFS_REGEX, Pattern.CASE_INSENSITIVE); - Matcher mc = pt.matcher(ifName); - - if (mc.matches()) { - return new String[] { mc.group(1), mc.group(2) }; - } - - // If not found, check for loopback - if (ifName.equalsIgnoreCase("lo")) { - return new String[] { "lo" }; - } - - // No useful info was found - return null; + public void onNetworkStateChanged(NetworkInterfaceTester nit, NetworkInterface iface, boolean enabled) { + this.data = nit.getAvailableInterfaces(); + this.dataSize = this.data.size(); } } \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java index fa5c172..10563c9 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java @@ -311,13 +311,14 @@ protected void onCreate(Bundle savedInstanceState) { }); - ListenIfAdapter lsif = new ListenIfAdapter(Utils.getAvailableNICs(), this); + NetworkInterfaceTester nit = new NetworkInterfaceTester(this); + ListenIfAdapter lsif = new ListenIfAdapter(nit, this); final Spinner listenInterfaceSpin = findViewById(R.id.settings_listening_interface); listenInterfaceSpin.setAdapter(lsif); listenInterfaceSpin.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int pos, long id) { - ListenIfAdapter.NetworkInterfaceData d = (ListenIfAdapter.NetworkInterfaceData)parent.getItemAtPosition(pos); + NetworkInterfaceTester.NetIfData d = (NetworkInterfaceTester.NetIfData)parent.getItemAtPosition(pos); if(!(prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, null) == null && d.getName().equals(mDefaults.getListenInterface()))) { SharedPreferences.Editor ed = prefs.edit(); ed.putString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, d.getName()); @@ -337,6 +338,7 @@ public void onNothingSelected(AdapterView parent) { + final EditText port = findViewById(R.id.settings_port); if(prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()) < 0) { port.setHint(R.string.main_activity_settings_port_not_listening); diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java b/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java index 844cc68..ad3cf01 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java @@ -766,6 +766,7 @@ static boolean isServerActive() { } + /** * This returns A SINGLE ipv4 address for the selected interface... giving the possibility * to the server to listen to that address only. @@ -816,7 +817,7 @@ static ArrayList getReachableIPv4s() { } } else { // Single interface: get all its IPv4 addresses - ArrayList ipv4s = Utils.getIPv4ForInterface(NetworkInterface.getByName(listenInterface)); + ArrayList ipv4s = Utils.getIPv4ForInterface(listenInterface); for (String ipv4 : ipv4s) { hosts.add(ipv4); } diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/NetworkInterfaceTester.java b/app/src/main/java/net/christianbeier/droidvnc_ng/NetworkInterfaceTester.java new file mode 100644 index 0000000..4fe2790 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/NetworkInterfaceTester.java @@ -0,0 +1,250 @@ +/* + * DroidVNC-NG broadcast receiver that listens for boot-completed events + * and starts MainService in turn. + * + * Author: elluisian + * + * Copyright (C) 2020, 2023 Christian Beier (info@christianbeier.net>). + * + * You can redistribute and/or modify this program under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. + */ +package net.christianbeier.droidvnc_ng; + +import android.net.LinkProperties; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkRequest; +import android.net.NetworkCapabilities; + +import android.content.Context; +import android.util.Log; + + +import java.net.SocketException; +import java.net.NetworkInterface; +import java.util.List; +import java.util.ArrayList; + + + +public class NetworkInterfaceTester extends ConnectivityManager.NetworkCallback { + public static final String TAG = "NetworkInterfaceTester"; + + public static interface OnNetworkStateChangedListener { + public void onNetworkStateChanged(NetworkInterfaceTester nit, NetworkInterface iface, boolean enabled); + } + + + public static class NetIfData { + private NetworkInterface nic; + private String name; + private String displayName; + private String friendlyName; + + private NetIfData(Context context) { + this(null, context); + } + + private NetIfData(NetworkInterface nic, Context context) { + this.nic = nic; + + if (nic == null) { + this.name = "0.0.0.0"; + this.displayName = context.getResources().getString(R.string.main_activity_settings_listenif_spin_any); + + } else { + this.name = this.nic.getName(); + this.displayName = this.nic.getDisplayName(); + + } + } + + public static NetIfData getAnyOption(Context context) { + if (NetworkInterfaceTester.IF_ANY == null) { + NetworkInterfaceTester.IF_ANY = new NetIfData(context); + } + return NetworkInterfaceTester.IF_ANY; + } + + public static NetIfData getOptionForNic(NetworkInterface nic, Context context) { + return new NetIfData(nic, context); + } + + + + public String getName() { + return this.name; + } + + public String getDisplayName() { + return this.displayName; + } + + public String toString() { + return this.getName(); + } + } + + + + private static NetIfData IF_ANY; + + + private ArrayList netIf; + private ArrayList networks; + private ArrayList netIfEnabled; + private int netIfSize; + + private Context context; + private ConnectivityManager manager; + + private List listeners; + + + + public NetworkInterfaceTester(Context context) { + this.context = context; + + this.netIf = Utils.getAvailableNICs(); + this.netIfSize = this.netIf.size(); + + this.netIfEnabled = new ArrayList<>(); + this.networks = new ArrayList<>(); + for (int i = 0; i < this.netIfSize; i++) { + try { + this.netIfEnabled.add(this.netIf.get(i).isUp()); + } catch (SocketException ex) { + // unused + } + this.networks.add(null); + } + + + this.listeners = new ArrayList<>(); + + this.manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + this.manager.registerNetworkCallback( + new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_VPN).build(), + this + ); + } + + + + + public ArrayList getAvailableInterfaces() { + ArrayList ls = new ArrayList<>(); + + ls.add(NetIfData.getAnyOption(this.context)); + for (int i = 0; i < this.netIfSize; i++) { + if (this.netIfEnabled.get(i)) { + NetIfData nid = NetIfData.getOptionForNic(this.netIf.get(i), this.context); + ls.add(nid); + } + } + + return ls; + } + + + + + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + int i = this.storeNetworkHashCode(network); + this.setEnabled(i, true); + this.updateListener(i, true); + } + + + @Override + public void onLost(Network network) { + super.onLost(network); + int i = this.getFromNetworkHashCode(network); + this.setEnabled(i, false); + this.updateListener(i, false); + } + + + + private int storeNetworkHashCode(Network network) { + LinkProperties prop = this.manager.getLinkProperties(network); + + NetworkInterface iface = null; + try { + iface = NetworkInterface.getByName(prop.getInterfaceName()); + } catch (SocketException ex) { + // unused + } + + int i = 0; + boolean found = false; + for (i = 0; !found && i < this.netIfSize; i++) { + if (iface.getName().equals(this.netIf.get(i).getName())) { + this.networks.set(i, network); + i--; + found = true; + } + } + + if (found) { + Log.d(TAG, "Added network " + iface.getName()); + } else { + Log.d(TAG, "No network found"); + } + + return found ? i : -1; + } + + + + private int getFromNetworkHashCode(Network network) { + int i = this.networks.indexOf(network); + + if (i != -1) { + Log.d(TAG, "Removed network " + this.netIf.get(i).getName()); + } else { + Log.d(TAG, "Network to remove not found"); + } + + return i; + } + + + + private void setEnabled(int i, boolean enabled) { + this.netIfEnabled.set(i, enabled); + } + + + + + public void addOnNetworkStateChangedListener(OnNetworkStateChangedListener onscl) { + if (this.listeners.indexOf(onscl) == -1) { + this.listeners.add(onscl); + } + } + + + private void updateListener(int i, boolean enabled) { + for (OnNetworkStateChangedListener onscl : this.listeners) { + onscl.onNetworkStateChanged(this, this.netIf.get(i), enabled); + } + } +} \ No newline at end of file