From 10497d85e658e4c197f3b1f76130ceb6b9fff4c5 Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 21:16:02 +0100 Subject: [PATCH 1/7] res: Added needed text entries for interfaces - Added the entry "main_activity_settings_listening_interface", used as label for the spinner which will determine the used interface; - Added the entry "main_activity_settings_listenif_spin_any", used as label for the spinner entry that indicates the usage of "any interface"; --- app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values-es/strings.xml | 2 ++ app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values-it/strings.xml | 2 ++ app/src/main/res/values-ja/strings.xml | 2 ++ app/src/main/res/values-pt/strings.xml | 2 ++ app/src/main/res/values-zh-rCN/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 16 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ef080223..75ab4d8e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,6 +1,8 @@ droidVNC-NG Admin-Panel Einstellungen + Hörschnittstelle + Alle Port Eingehende Verbindungen deaktiviert Passwort diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0085fd8a..76f493a4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,6 +1,8 @@ Administración de droidVNC-NG Configuración + Interfaz en escucha + Todas Puerto Conexiones entrantes deshabilitadas Contraseña diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b1fef738..8016b6bc 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,6 +1,8 @@ Administration de droidVNC-NG Paramètres + Interface en écoute + Toutes Port Connexions d\'entrée désactivées Mot de passe diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c6d99fc5..b77a2b2d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2,6 +2,8 @@ Amministrazione di droidVNC-NG Impostazioni Porta + Interfaccia in ascolto + Tutte Connessioni in entrata disabilitate Password Chiave di accesso API Intent diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b3d3c10d..9c670b2d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,6 +1,8 @@ droidVNC-NG管理パネル 設定 + リスニングインターフェース + 全て ポート インバウンド接続は無効です パスワード diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6bdad9e0..6be815c5 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,6 +1,8 @@ Painel Administrativo do droidVNC-NG Configurações + Interface em escuta + Todas Porta Conexões de entrada desativadas Senha diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 51065a24..2056d0cf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2,6 +2,8 @@ droidVNC-NG管理面板 设置 端口 + 監聽介面 + 全部 禁用传入连接 密码 意图 API 访问密钥 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23faf81d..fa0f2f25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,6 +2,8 @@ droidVNC-NG droidVNC-NG Admin Panel Settings + Listening Interface + Any Port Inbound connections disabled Password From f2e2eca5b836da5a7dd91e005aded17ac33056fa Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 21:21:57 +0100 Subject: [PATCH 2/7] res: Added UI for interface choice - Added a section (label + spinner) needed to choose the interface to use ("any" included); --- app/src/main/res/layout/activity_main.xml | 28 +++++++++++++++++++++++ app/src/main/res/layout/spinner_row.xml | 11 +++++++++ 2 files changed, 39 insertions(+) create mode 100644 app/src/main/res/layout/spinner_row.xml diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 74b574e1..cf10974b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -34,6 +34,34 @@ android:layout_column="0" android:padding="10dp" android:hyphenationFrequency="full" + android:text="@string/main_activity_settings_listening_interface" /> + + + + + + + + + + + + + From 714739e8015e5af08460020bcbb25d65c60903d5 Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 21:25:20 +0100 Subject: [PATCH 3/7] Constants+Defaults: Added values for use with IFs - Added constant PREFS_KEY_SETTINGS_LISTEN_INTERFACE, to store the chosen interface to use; - Added listenInterface property with value "loopback" to Defaults; --- .../main/java/net/christianbeier/droidvnc_ng/Constants.java | 1 + app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java b/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java index 517cc2bd..30a7ec6a 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java @@ -25,6 +25,7 @@ public class Constants { /* user settings */ + public static final String PREFS_KEY_SETTINGS_LISTEN_INTERFACE = "settings_listen_interface"; public static final String PREFS_KEY_SETTINGS_PORT = "settings_port"; public static final String PREFS_KEY_SETTINGS_PASSWORD = "settings_password" ; public static final String PREFS_KEY_SETTINGS_START_ON_BOOT = "settings_start_on_boot" ; diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt b/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt index 1c77a21d..2f1315d3 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt @@ -41,6 +41,10 @@ class Defaults { private const val PREFS_KEY_DEFAULTS_ACCESS_KEY = "defaults_access_key" } + @EncodeDefault + var listenInterface = "loopback" + private set + @EncodeDefault var port = 5900 private set From 525e5154fbbb5fef6c1f5ba2faba9c91bbb8fc42 Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 23:05:05 +0100 Subject: [PATCH 4/7] java: Added ifaceutils package Added ifaceutils package which contains classes that will be helpful in detecting and managing NICs (Network Interface Card). - INetIfData, NetIfData and NetIfDataDecorator: These represent a single NIC, the last one can be used to customize the name of the interface; - IfCollector: As the name suggests, this class simply collects all the available interfaces at once; - NetworkInterfaceTester: As the name suggests, this class uses IfCollector and listens for network changes; --- .../droidvnc_ng/ifaceutils/INetIfData.java | 46 ++++ .../droidvnc_ng/ifaceutils/IfCollector.java | 150 ++++++++++++ .../droidvnc_ng/ifaceutils/NetIfData.java | 166 +++++++++++++ .../ifaceutils/NetIfDataDecorator.java | 81 +++++++ .../ifaceutils/NetworkInterfaceTester.java | 228 ++++++++++++++++++ 5 files changed, 671 insertions(+) create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/INetIfData.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/IfCollector.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfData.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfDataDecorator.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetworkInterfaceTester.java diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/INetIfData.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/INetIfData.java new file mode 100644 index 00000000..680cb91c --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/INetIfData.java @@ -0,0 +1,46 @@ +/* + * DroidVNC-NG INetIfData, interface defining what is needed to properly gather all the data related to a particular NIC (Network interface card). + * + * Author: elluisian + * + * Copyright (C) 2024 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.ifaceutils; + + +import android.net.Network; + + +import java.net.NetworkInterface; + + + +public interface INetIfData { + public static final String ANY_OPTION_ID = "any"; + public static final String ANY_OPTION_ID_ADDR = "0.0.0.0"; + public static final String LOOPBACK_OPTION_ID = "loopback"; + public static final String LOOPBACK_OPTION_ID_ADDR_1 = "localhost"; + public static final String LOOPBACK_OPTION_ID_ADDR_2 = "127.0.0.1"; + + + public String getName(); + public String getOptionId(); + public boolean isLoopback(); + public boolean isAny(); + + public NetworkInterface getNetworkInterface(); + public Network getNetwork(); +} \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/IfCollector.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/IfCollector.java new file mode 100644 index 00000000..b7126c56 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/IfCollector.java @@ -0,0 +1,150 @@ +/* + * DroidVNC-NG IfCollector is a collector of NetIfData instances, these represent available NICs (Network Interface Card). + * + * Author: elluisian + * + * Copyright (C) 2024 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.ifaceutils; + + +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; + + +import net.christianbeier.droidvnc_ng.Utils; + + + +public class IfCollector { + private static IfCollector COLL; + + private NetIfData any; + private NetIfData loopback; + private List nics; + private int nicsSize; + + + + public static synchronized IfCollector getInstance() { + if (IfCollector.COLL == null) { + IfCollector.COLL = new IfCollector(); + } + + return IfCollector.COLL; + } + + + private IfCollector() { + this.any = NetIfData.getAny(); + + List ifs = Utils.getAvailableNICs(); + this.nics = new ArrayList<>(); + + for (NetworkInterface inf : ifs) { + NetIfData nid = NetIfData.getNetIf(inf); + if (!nid.isLoopback()) { + this.nics.add(nid); + } else { + this.loopback = nid; + } + } + + this.nicsSize = this.nics.size(); + } + + + + + public int searchForNetIfByOptionId(String optionId) { + int i = 0; + boolean found = false; + for (i = 0; !found && i < this.nicsSize; i++) { + if (optionId.equals(this.nics.get(i).getOptionId())) { + i--; + found = true; + } + } + + return found ? i : -1; + } + + + public int searchForNetIfByNetwork(Network network) { + int i = 0; + boolean found = false; + for (i = 0; !found && i < this.nicsSize; i++) { + if (network.equals(this.nics.get(i).getNetwork())) { + i--; + found = true; + } + } + + return found ? i : -1; + } + + + + public void addNetIf(NetworkInterface iface, Network network) { + this.nics.add(NetIfData.getNetIf(iface, network)); + this.nicsSize++; + } + + + public List getEnabledNetIfs() { + List ls = new ArrayList<>(); + + for (int i = 0; i < this.nicsSize; i++) { + NetIfData nid = this.nics.get(i); + if (nid.isEnabled()) { + ls.add(nid); + } + } + + return ls; + } + + public NetIfData getAny() { + return this.any; + } + + public NetIfData getLoopback() { + return this.loopback; + } + + public NetIfData getNetIf(int i) { + if (0 <= i && i < this.nicsSize) { + return this.nics.get(i); + } + + return null; + } + + public int getSize() { + return this.nicsSize; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfData.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfData.java new file mode 100644 index 00000000..355d4706 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfData.java @@ -0,0 +1,166 @@ +/* + * DroidVNC-NG Concrete implementation of INetIfData. + * + * Author: elluisian + * + * Copyright (C) 2024 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 + * 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.ifaceutils; + + +import android.net.Network; + + +import java.net.SocketException; +import java.net.NetworkInterface; +import java.util.ArrayList; + + + +public class NetIfData implements INetIfData { + private static NetIfData ANY_IF; + + private NetworkInterface nic; + private Network net; + private String name; + + private boolean enabled; + private boolean anyFlag; + private boolean loopbackFlag; + + + + + private NetIfData() { + this(null); + } + + private NetIfData(NetworkInterface nic) { + this(nic, null); + } + + private NetIfData(NetworkInterface nic, Network net) { + this.name = null; + this.anyFlag = false; + this.loopbackFlag = false; + this.nic = nic; + this.net = net; + + try { + // Considering that when this.nic is null, it is highly probable that the referred interface is "any", consider it enabled + this.enabled = (nic == null) ? true : nic.isUp(); + + } catch(SocketException ex) { + this.enabled = false; + + } + + if (nic == null) { + this.name = NetIfData.ANY_OPTION_ID; + this.anyFlag = true; + + } else { + this.name = this.nic.getName(); + try { + this.loopbackFlag = this.nic.isLoopback(); + + } catch(SocketException ex) { + this.loopbackFlag = false; + + } + } + } + + + + + public String getName() { + return this.name; + } + + public String getOptionId() { + if (this.anyFlag) { + return NetIfData.ANY_OPTION_ID; + + } else if (this.loopbackFlag) { + return NetIfData.LOOPBACK_OPTION_ID; + + } + + return this.getName(); + } + + public boolean isAny() { + return this.anyFlag; + } + + public boolean isLoopback() { + return this.loopbackFlag; + } + + public NetworkInterface getNetworkInterface() { + return this.nic; + } + + + + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Network getNetwork() { + return this.net; + } + + public void setNetwork(Network net) { + this.net = net; + } + + + + + public static NetIfData getAny() { + if (NetIfData.ANY_IF == null) { + NetIfData.ANY_IF = new NetIfData(); + } + return NetIfData.ANY_IF; + } + + public static NetIfData getNetIf(NetworkInterface nic) { + return new NetIfData(nic); + } + + public static NetIfData getNetIf(NetworkInterface nic, Network net) { + return new NetIfData(nic, net); + } + + public static boolean isOptionIdAny(String optionId) { + return (optionId.equals(NetIfData.ANY_OPTION_ID) || + optionId.equals(NetIfData.ANY_OPTION_ID_ADDR)); + } + + public static boolean isOptionIdLoopback(String optionId) { + return (optionId.equals(NetIfData.LOOPBACK_OPTION_ID) || + optionId.equals(NetIfData.LOOPBACK_OPTION_ID_ADDR_1) || + optionId.equals(NetIfData.LOOPBACK_OPTION_ID_ADDR_2) + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfDataDecorator.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfDataDecorator.java new file mode 100644 index 00000000..55f69451 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetIfDataDecorator.java @@ -0,0 +1,81 @@ +/* + * DroidVNC-NG NetIfDataDecorator, generic decorator pattern for NetIfData instances. + * This can be used to customize the name of a NIC as a means to add friendly names, if needed. + * + * Author: elluisian + * + * Copyright (C) 2024 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.ifaceutils; + + +import android.content.Context; +import android.content.res.Resources; +import android.net.Network; + + +import java.net.NetworkInterface; + + + +public abstract class NetIfDataDecorator implements INetIfData { + protected NetIfData decorated; + protected Context mContext; + protected Resources mResources; + + + public NetIfDataDecorator(NetIfData decorated) { + this(decorated, null); + } + + + public NetIfDataDecorator(NetIfData decorated, Context context) { + this.decorated = decorated; + + if (context != null) { + this.mContext = context; + this.mResources = this.mContext.getResources(); + } + } + + + public abstract String getName(); + + + public final String getOptionId() { + return this.decorated.getOptionId(); + } + + public final boolean isAny() { + return this.decorated.isAny(); + } + + public final boolean isLoopback() { + return this.decorated.isLoopback(); + } + + public final NetworkInterface getNetworkInterface() { + return this.decorated.getNetworkInterface(); + } + + public final Network getNetwork() { + return this.decorated.getNetwork(); + } + + public final NetIfData getWrapped() { + return this.decorated; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetworkInterfaceTester.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetworkInterfaceTester.java new file mode 100644 index 00000000..745e2fa5 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ifaceutils/NetworkInterfaceTester.java @@ -0,0 +1,228 @@ +/* + * DroidVNC-NG NetworkInterfaceTester, this is used by MainService/MainActivity used to detect whether network connections are lost or added. + * + * Author: elluisian + * + * Copyright (C) 2024 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.ifaceutils; + + +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"; + private static NetworkInterfaceTester INSTANCE; + + + + public synchronized static NetworkInterfaceTester getInstance(Context context) { + if (NetworkInterfaceTester.INSTANCE == null) { + NetworkInterfaceTester.INSTANCE = new NetworkInterfaceTester(context); + } + return NetworkInterfaceTester.INSTANCE; + } + + + + public static interface OnNetworkStateChangedListener { + public void onNetworkStateChanged(NetworkInterfaceTester nit); + } + + + + private IfCollector ifCollector; + private Context context; + private ConnectivityManager manager; + private List listeners; + + + + private NetworkInterfaceTester(Context context) { + this.ifCollector = IfCollector.getInstance(); + + 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) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) // This is needed, otherwise, VPN start/stop is not detected + .build(), + this + ); + } + + + + public ArrayList getAvailableNetIfs() { + ArrayList ls = new ArrayList<>(); + + ls.add(this.ifCollector.getAny()); + ls.add(this.ifCollector.getLoopback()); + ls.addAll(this.ifCollector.getEnabledNetIfs()); + + return ls; + } + + + + + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + int i = this.storeNetwork(network); + + if (i != -1) { + NetIfData nid = this.ifCollector.getNetIf(i); + nid.setEnabled(true); + this.updateListener(); + } + } + + + @Override + public void onLost(Network network) { + super.onLost(network); + int i = this.getFromNetwork(network); + + if (i != -1) { + NetIfData nid = this.ifCollector.getNetIf(i); + nid.setEnabled(false); + this.updateListener(); + } + } + + + + + @Override + public void onLosing(Network network, int maxMsToLive) { + Log.d(TAG, "onLosing " + network); + } + + @Override + public void onBlockedStatusChanged(Network network, boolean blocked) { + Log.d(TAG, "onBlockedStatusChagned " + network); + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + Log.d(TAG, "onLinkPropertiesChanged " + network); + } + + + + + /* + * The idea is simple: during onLost, LinkProperties attributes are lost + * inside Network, so, memorize a reference to the Network instance, this way, + * we'll be able to understand what exact interface was lost. + */ + private int storeNetwork(Network network) { + LinkProperties prop = this.manager.getLinkProperties(network); + NetworkInterface iface = null; + try { + iface = NetworkInterface.getByName(prop.getInterfaceName()); + + } catch (SocketException ex) { + Log.d(TAG, "Socket exception has occurred, return -1"); + return -1; + } + + // No socket exception occurred, let's see if the interface is available + int i = this.ifCollector.searchForNetIfByOptionId(iface.getName()); + if (i == -1) { // if not found, it means that it has been created recently, add to list + this.ifCollector.addNetIf(iface, network); + + } else { + // Otherwise, the interface exists, replace network + NetIfData nid = this.ifCollector.getNetIf(i); + nid.setNetwork(network); + } + + Log.d(TAG, "Added network " + iface.getName()); + + return i; + } + + + private int getFromNetwork(Network network) { + int i = this.ifCollector.searchForNetIfByNetwork(network); + + if (i != -1) { + Log.d(TAG, "Removed network " + this.ifCollector.getNetIf(i).getOptionId()); + } else { + Log.d(TAG, "Network to remove not found"); + } + + return i; + } + + + + + public void addOnNetworkStateChangedListener(OnNetworkStateChangedListener onscl) { + if (this.listeners.indexOf(onscl) == -1) { + this.listeners.add(onscl); + } + } + + + private void updateListener() { + for (OnNetworkStateChangedListener onscl : this.listeners) { + onscl.onNetworkStateChanged(this); + } + } + + + public boolean isIfEnabled(String listenIf) { + if (NetIfData.isOptionIdLoopback(listenIf) || + NetIfData.isOptionIdAny(listenIf)) { + return true; + } + + NetIfData loopback = this.ifCollector.getLoopback(); + if (loopback.getName().equals(listenIf)) { + return true; + } + + + int i = this.ifCollector.searchForNetIfByOptionId(listenIf); + if (i != -1) { + NetIfData nid = this.ifCollector.getNetIf(i); + return nid.isEnabled(); + } + + return false; + } +} \ No newline at end of file From 2cda0f5119b0ae596d4937c850df702148ecaf02 Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 23:10:05 +0100 Subject: [PATCH 5/7] java: Added ListenIfAdapter and another decorator - ListenIfAdapter is an adapter class that will be used with the Spinner present in MainActivity in order to choose what interface to use; - NetIfDataDefaultNameDecorator simply gives friendly name to interfaces "loopback" and "any" (these are taken from strings.xml); --- .../droidvnc_ng/ListenIfAdapter.java | 159 ++++++++++++++++++ .../NetIfDataDefaultNameDecorator.java | 55 ++++++ 2 files changed, 214 insertions(+) create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java create mode 100644 app/src/main/java/net/christianbeier/droidvnc_ng/NetIfDataDefaultNameDecorator.java diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java b/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java new file mode 100644 index 00000000..ccb1e149 --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/ListenIfAdapter.java @@ -0,0 +1,159 @@ +/* + * DroidVNC-NG ListenIfAdapter, this is the adapter concrete implementation that is set to the Spinner in the main activity. + * + * Author: elluisian + * + * Copyright (C) 2024 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 + * 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.content.res.Resources; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.Spinner; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + + +import java.util.ArrayList; +import java.util.List; + + +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData; +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfDataDecorator; +import net.christianbeier.droidvnc_ng.ifaceutils.NetworkInterfaceTester; + + + +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 List data; + private int dataSize; + + + // Some context data for "easy retrieval" + private Context mContext; + private LayoutInflater mInflater; + + + // UI related + private Handler handler; + + + + public ListenIfAdapter(Context context) { + super(context, R.layout.spinner_row, R.id.spinner_text); + + this.mContext = context; + this.mInflater = LayoutInflater.from(this.mContext); + this.handler = new Handler(Looper.getMainLooper()); + + NetworkInterfaceTester nit = NetworkInterfaceTester.getInstance(context); + nit.addOnNetworkStateChangedListener(this); + this.onNetworkStateChanged(nit); + } + + + + public int getItemPositionByOptionId(String optionId) { + int i = 0; + for (NetIfDataDecorator nid : this.data) { + if (nid.getOptionId().equals(optionId)) { + return i; + } + i++; + } + + return 1; // return loopback position as default + } + + + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return this.handleViewRecreation(position, convertView, parent); + } + + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return this.handleViewRecreation(position, convertView, parent); + } + + + private View handleViewRecreation(int position, View convertView, ViewGroup parent) { + 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(); + vh.txtLabel = convertView.findViewById(R.id.spinner_text); + convertView.setTag(vh); + } + + ViewHolder vh = (ViewHolder)convertView.getTag(); + vh.txtLabel.setText(this.data.get(position).getName()); + + return convertView; + } + + + + @Override + public NetIfData getItem(int position) { + if (0 <= position && position < this.getCount()) { + return this.data.get(position).getWrapped(); + } + return null; + } + + + + @Override + public int getCount() { + return this.dataSize; + } + + + + public void onNetworkStateChanged(NetworkInterfaceTester nit) { + List avNetIfs = nit.getAvailableNetIfs(); + + this.data = new ArrayList<>(); + for (NetIfData nid : avNetIfs) { + this.data.add(new NetIfDataDefaultNameDecorator(nid, this.mContext)); + } + this.dataSize = this.data.size(); + + + // update Spinner + this.handler.post(new Runnable() { + public void run() { + ListenIfAdapter.this.notifyDataSetChanged(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/NetIfDataDefaultNameDecorator.java b/app/src/main/java/net/christianbeier/droidvnc_ng/NetIfDataDefaultNameDecorator.java new file mode 100644 index 00000000..db5c508b --- /dev/null +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/NetIfDataDefaultNameDecorator.java @@ -0,0 +1,55 @@ +/* + * DroidVNC-NG NetIfDataDefaultNameDecorator, a concrete decorator implementation for NetIfData. + * Given a NetIfData instance, this decorator shows the raw name of the referred NIC with a slight friendlier name for the "any" and "loopback" ones. + * + * Author: elluisian + * + * Copyright (C) 2024 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.content.Context; + + +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData; +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfDataDecorator; + + + +public class NetIfDataDefaultNameDecorator extends NetIfDataDecorator { + public NetIfDataDefaultNameDecorator(NetIfData decorated, Context context) { + super(decorated, context); + } + + + public String getName() { + String raw = this.decorated.getName(); + String display = null; + + String optionId = this.decorated.getOptionId(); + if (optionId.equals(NetIfData.ANY_OPTION_ID)) { + display = this.mContext.getResources().getString(R.string.main_activity_settings_listenif_spin_any); + raw = "0.0.0.0"; + + } else if (optionId.equals(NetIfData.LOOPBACK_OPTION_ID)) { + display = "Loopback"; + + } + + return display == null ? raw : String.format("%s (%s)", display, raw); + } +} \ No newline at end of file From e3466bfa966aa758ebc41a9b5302ba5cfd508b46 Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 23:13:21 +0100 Subject: [PATCH 6/7] Utils: Added methods to retrieve IPs and NICs - Added method getIPv4ForInterface(NetworkInterface) and getIPv4ForInterface(String) which returns a list of ip4 addresses given a NetworkInterface instance/String name of interface; - Added method getAvailableNICs(Boolean, Boolean) and getAvailableNICs() which return a list of NetworkInterface instances that represents available NICs on the system; --- .../net/christianbeier/droidvnc_ng/Utils.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/Utils.kt b/app/src/main/java/net/christianbeier/droidvnc_ng/Utils.kt index e5d7dad0..1be1d940 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/Utils.kt +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/Utils.kt @@ -17,6 +17,10 @@ import java.io.InputStreamReader import java.util.concurrent.locks.Lock import kotlin.concurrent.withLock +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; + object Utils { @JvmStatic @@ -102,6 +106,7 @@ object Utils { directory.deleteRecursively() } + /** * Rename a file. Does not overwrite! */ @@ -139,4 +144,65 @@ object Utils { } } + + @JvmStatic + fun getAvailableNICs(upIfOnly: Boolean, ipAvailOnly: Boolean): ArrayList { + val nics = ArrayList(); + + try { + // Thanks go to https://stackoverflow.com/a/20103869/361413 + val nis = NetworkInterface.getNetworkInterfaces(); + while (nis.hasMoreElements()) { + val ni = nis.nextElement(); + if (upIfOnly) { + if (ipAvailOnly) { + // Check if there are actual ipv4 addresses, if so, add the NetworkInterface + // Should we consider IPv6 also? Technically yes, but the program, at the moment, does not support them. + for (ia in ni.getInterfaceAddresses()) { + if (ia.getAddress().getAddress().count() == 4) { + nics.add(ni); + break; + } + } + } else { + nics.add(ni); + } + } else { + nics.add(ni); + } + } + } catch (e: SocketException) { + //unused + } + + return nics; + } + + + @JvmStatic + fun getAvailableNICs(): ArrayList { + return getAvailableNICs(false, true); + } + + + @JvmStatic + fun getIPv4ForInterface(ni: String): ArrayList{ + return getIPv4ForInterface(NetworkInterface.getByName(ni)); + } + + + @JvmStatic + fun getIPv4ForInterface(ni: NetworkInterface): ArrayList{ + val ipv4s = ArrayList(); + + for (ia in ni.getInterfaceAddresses()) { + //filter for ipv4/ipv6 + if (ia.getAddress().getAddress().count() == 4) { + //4 for ipv4, 16 for ipv6 + ipv4s.add(ia.getAddress().toString().replace("/", "")); + } + } + + return ipv4s; + } } \ No newline at end of file From f8ea6526a33d083114b63bee181e51e7c651831d Mon Sep 17 00:00:00 2001 From: elluisian Date: Sun, 16 Feb 2025 23:17:15 +0100 Subject: [PATCH 7/7] Main+Boot+droidvnc-nc: Added interface binding - droidvnc-ng.c: Method Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer modified to accept a futher "jstring listenIf" parameter. This way, it is possible to use rfbScreen->listenInterface property to perform interface binding; - MainActivity: Now it makes use of ListenIfAdapter, allowing to change the used interface (default is loopback); - MainService: Now it makes use of NetworkInterfaceTester and IfCollector to detect the state of the network and stop itself, if needed; - OnBootReceiver: Now it correctly detects whether an interface is disabled/non-existent and if so, it DOESN't start the service, moreover, it sends a notification about the problem. --- app/src/main/cpp/droidvnc-ng.c | 18 ++- .../droidvnc_ng/MainActivity.java | 40 ++++- .../droidvnc_ng/MainService.java | 139 +++++++++++++++--- .../droidvnc_ng/OnBootReceiver.java | 77 +++++++++- 4 files changed, 249 insertions(+), 25 deletions(-) diff --git a/app/src/main/cpp/droidvnc-ng.c b/app/src/main/cpp/droidvnc-ng.c index 3dbaffd9..d357b5b6 100644 --- a/app/src/main/cpp/droidvnc-ng.c +++ b/app/src/main/cpp/droidvnc-ng.c @@ -277,7 +277,7 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS } -JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jint port, jstring desktopname, jstring password, jstring httpRootDir) { +JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jstring listenIf, jint port, jstring desktopname, jstring password, jstring httpRootDir) { int argc = 0; @@ -305,6 +305,22 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS theScreen->setXCutTextUTF8 = onCutTextUTF8; theScreen->newClientHook = onClientConnected; + in_addr_t address = 0; // Default is 0.0.0.0 + if (listenIf != NULL) { + const char *cListenInterface = (*env)->GetStringUTFChars(env, listenIf, NULL); + int addrConvSucceded = rfbStringToAddr((char*)cListenInterface, &address); + (*env)->ReleaseStringUTFChars(env, listenIf, cListenInterface); + + if (!addrConvSucceded) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "vncStartServer: invalid listen address"); + Java_net_christianbeier_droidvnc_1ng_MainService_vncStopServer(env, thiz); + return JNI_FALSE; + } + } + + + // With the listenInterface property one can define where the server will be available + theScreen->listenInterface = address; theScreen->port = port; theScreen->ipv6port = port; 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 8fbabb88..d9384f81 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java @@ -61,6 +61,7 @@ import android.util.TypedValue; import android.view.View; import android.view.WindowManager; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; @@ -69,6 +70,9 @@ import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; +import android.widget.Spinner; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.AdapterView; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -83,6 +87,8 @@ import java.util.Objects; import java.util.UUID; +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData; + public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @@ -125,6 +131,7 @@ protected void onCreate(Bundle savedInstanceState) { mButtonToggle.setOnClickListener(view -> { Intent intent = new Intent(MainActivity.this, MainService.class); + intent.putExtra(MainService.EXTRA_LISTEN_INTERFACE, prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())); intent.putExtra(MainService.EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort())); intent.putExtra(MainService.EXTRA_PASSWORD, prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword())); intent.putExtra(MainService.EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer())); @@ -331,6 +338,34 @@ protected void onCreate(Bundle savedInstanceState) { }); + + ListenIfAdapter lsif = new ListenIfAdapter(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) { + NetIfData d = (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()); + ed.apply(); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + // Restore last selected interface + listenInterfaceSpin.setSelection( + lsif.getItemPositionByOptionId( + prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface()))); + + + + 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); @@ -875,7 +910,8 @@ private void updateAddressesDisplay() { if(MainService.getPort() >= 0) { HashMap> spans = new HashMap<>(); // uhh there must be a nice functional way for this - ArrayList hosts = MainService.getIPv4s(); + ArrayList hosts = MainService.getReachableIPv4s(); + StringBuilder sb = new StringBuilder(); sb.append(getString(R.string.main_activity_address)).append(" "); if(hosts.isEmpty()) { @@ -940,6 +976,7 @@ private void onServerStarted() { findViewById(R.id.outbound_buttons).setVisibility(View.VISIBLE); // indicate that changing these settings does not have an effect when the server is running + findViewById(R.id.settings_listening_interface).setEnabled(false); findViewById(R.id.settings_port).setEnabled(false); findViewById(R.id.settings_password).setEnabled(false); findViewById(R.id.settings_access_key).setEnabled(false); @@ -969,6 +1006,7 @@ private void onServerStopped() { findViewById(R.id.outbound_buttons).setVisibility(View.GONE); // indicate that changing these settings does have an effect when the server is stopped + findViewById(R.id.settings_listening_interface).setEnabled(true); findViewById(R.id.settings_port).setEnabled(true); findViewById(R.id.settings_password).setEnabled(true); findViewById(R.id.settings_access_key).setEnabled(true); 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 57e90a6c..31ece33f 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java @@ -72,7 +72,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; -public class MainService extends Service { +import net.christianbeier.droidvnc_ng.ifaceutils.IfCollector; +import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData; +import net.christianbeier.droidvnc_ng.ifaceutils.NetworkInterfaceTester; + +public class MainService extends Service implements NetworkInterfaceTester.OnNetworkStateChangedListener { private static final String TAG = "MainService"; static final int NOTIFICATION_ID = 11; @@ -82,6 +86,7 @@ public class MainService extends Service { public static final String ACTION_CONNECT_REPEATER = "net.christianbeier.droidvnc_ng.ACTION_CONNECT_REPEATER"; public static final String EXTRA_REQUEST_ID = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID"; public static final String EXTRA_REQUEST_SUCCESS = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_SUCCESS"; + public static final String EXTRA_LISTEN_INTERFACE = "net.christianbeier.droidvnc_ng.EXTRA_LISTEN_INTERFACE"; public static final String EXTRA_HOST = "net.christianbeier.droidvnc_ng.EXTRA_HOST"; public static final String EXTRA_PORT = "net.christianbeier.droidvnc_ng.EXTRA_PORT"; public static final String EXTRA_REPEATER_ID = "net.christianbeier.droidvnc_ng.EXTRA_REPEATER_ID"; @@ -119,9 +124,19 @@ public class MainService extends Service { final static String ACTION_HANDLE_NOTIFICATION_RESULT = "action_handle_notification_result"; + final static String ACTION_HANDLE_MEDIA_PROJECTION_RESULT = "action_handle_media_projection_result"; final static String EXTRA_MEDIA_PROJECTION_STATE = "state_media_projection"; + private static final String PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE = "server_last_listen_interface" ; + private static final String PREFS_KEY_SERVER_LAST_PORT = "server_last_port" ; + private static final String PREFS_KEY_SERVER_LAST_PASSWORD = "server_last_password" ; + private static final String PREFS_KEY_SERVER_LAST_FILE_TRANSFER = "server_last_file_transfer" ; + private static final String PREFS_KEY_SERVER_LAST_SHOW_POINTERS = "server_last_show_pointers" ; + private static final String PREFS_KEY_SERVER_LAST_FALLBACK_SCREEN_CAPTURE = "server_last_fallback_screen_capture" ; + private static final String PREFS_KEY_SERVER_LAST_START_REQUEST_ID = "server_last_start_request_id" ; + + private int mResultCode; private Intent mResultData; private PowerManager.WakeLock mWakeLock; @@ -209,7 +224,7 @@ public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) { } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private native boolean vncStartServer(int width, int height, int port, String desktopName, String password, String httpRootDir); + private native boolean vncStartServer(int width, int height, String listenIf, int port, String desktopName, String password, String httpRootDir); private native boolean vncStopServer(); private native boolean vncIsActive(); private native long vncConnectReverse(String host, int port); @@ -234,6 +249,7 @@ public void onCreate() { Log.d(TAG, "onCreate"); instance = this; + NetworkInterfaceTester.getInstance(this).addOnNetworkStateChangedListener(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { /* @@ -404,18 +420,24 @@ public int onStartCommand(Intent intent, int flags, int startId) startScreenCapture(); } else { DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY); + Intent startIntent = Objects.requireNonNull(MainServicePersistData.loadStartIntent(this)); + String listenIf = startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface()); + String listenAddress = MainService.getInterfaceListeningIPv4Address(listenIf); int port = startIntent.getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort())); String password = startIntent.getStringExtra(EXTRA_PASSWORD) != null ? startIntent.getStringExtra(EXTRA_PASSWORD) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword()); + // get device name String name = Utils.getDeviceName(this); boolean status = vncStartServer(displayMetrics.widthPixels, displayMetrics.heightPixels, + listenAddress, port, name, password, getFilesDir().getAbsolutePath() + File.separator + "novnc"); + Intent answer = new Intent(ACTION_START); answer.putExtra(EXTRA_REQUEST_ID, startIntent.getStringExtra(EXTRA_REQUEST_ID)); answer.putExtra(EXTRA_REQUEST_SUCCESS, status); @@ -454,11 +476,16 @@ public int onStartCommand(Intent intent, int flags, int startId) if (mResultCode != 0 && mResultData != null || (Build.VERSION.SDK_INT >= 30 && startIntent.getBooleanExtra(EXTRA_FALLBACK_SCREEN_CAPTURE, false))) { DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY); + + String listenIf = startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface()); + String listenAddress = MainService.getInterfaceListeningIPv4Address(listenIf); int port = startIntent.getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort())); String password = startIntent.getStringExtra(EXTRA_PASSWORD) != null ? startIntent.getStringExtra(EXTRA_PASSWORD) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword()); + String name = Utils.getDeviceName(this); boolean status = vncStartServer(displayMetrics.widthPixels, displayMetrics.heightPixels, + listenAddress, port, name, password, @@ -520,6 +547,13 @@ public int onStartCommand(Intent intent, int flags, int startId) MainServicePersistData.saveStartIntent(this, intent); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor ed = prefs.edit(); + + + ed.putString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, intent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? intent.getStringExtra(EXTRA_LISTEN_INTERFACE) : prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())); + ed.putInt(PREFS_KEY_SERVER_LAST_PORT, intent.getIntExtra(EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()))); + ed.putString(PREFS_KEY_SERVER_LAST_PASSWORD, intent.getStringExtra(EXTRA_PASSWORD) != null ? intent.getStringExtra(EXTRA_PASSWORD) : prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword())); + ed.putBoolean(PREFS_KEY_SERVER_LAST_FILE_TRANSFER, intent.getBooleanExtra(EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer()))); + ed.putBoolean(Constants.PREFS_KEY_INPUT_LAST_ENABLED, !intent.getBooleanExtra(EXTRA_VIEW_ONLY, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_VIEW_ONLY, mDefaults.getViewOnly()))); ed.putFloat(Constants.PREFS_KEY_SERVER_LAST_SCALING, intent.getFloatExtra(EXTRA_SCALING, prefs.getFloat(Constants.PREFS_KEY_SETTINGS_SCALING, mDefaults.getScaling()))); ed.apply(); @@ -874,6 +908,7 @@ private void sendBroadcastToOthersAndUs(Intent intent) { } } + static boolean isServerActive() { try { return instance.vncIsActive(); @@ -890,13 +925,41 @@ static int getClientCount() { } } + + /** - * Get non-loopback IPv4 addresses. - * @return A list of strings, each containing one IPv4 address. + * This returns A SINGLE ipv4 address for the selected option id (representing an interface)... giving the possibility + * to the server to listen to that address only. + * @param ifName name that defines the interface to be used (may be an actual interface name or an option id, like "any" and "loopback") + * @return null if the interface is 0.0.0.0 (any), otherwise, the needed ipv4 */ - static ArrayList getIPv4s() { + static String getInterfaceListeningIPv4Address(String ifName) { + if (!NetIfData.isOptionIdAny(ifName)) { + ArrayList ipv4s = null; + + if (NetIfData.isOptionIdLoopback(ifName)) { + ipv4s = Utils.getIPv4ForInterface(IfCollector.getInstance().getLoopback().getNetworkInterface()); + + } else { + ipv4s = Utils.getIPv4ForInterface(ifName); + + } + + if (ipv4s.size() > 0) { + return ipv4s.get(0); + } + } + + return null; + } + - Set hosts = new LinkedHashSet<>(); + /** + * Get all available IPv4 addresses for reaching the current running server. + * @return A list of strings, each containing one IPv4 address. + */ + static ArrayList getReachableIPv4s() { + ArrayList hosts = new ArrayList<>(); // if running on Chrome OS, this prop is set and contains the device's IPv4 address, // see https://chromeos.dev/en/games/optimizing-games-networking @@ -906,29 +969,48 @@ static ArrayList getIPv4s() { } // not running on Chrome OS - try { - // thanks go to https://stackoverflow.com/a/20103869/361413 - Enumeration nis = NetworkInterface.getNetworkInterfaces(); - NetworkInterface ni; - while (nis.hasMoreElements()) { - ni = nis.nextElement(); - if (!ni.isLoopback()/*not loopback*/ && ni.isUp()/*it works now*/) { - for (InterfaceAddress ia : ni.getInterfaceAddresses()) { - //filter for ipv4/ipv6 - if (ia.getAddress().getAddress().length == 4) { - //4 for ipv4, 16 for ipv6 - hosts.add(ia.getAddress().toString().replaceAll("/", "")); - } - } + String listenInterface = MainService.getListenInterface(); + IfCollector ifColl = IfCollector.getInstance(); + ArrayList ipv4s = null; + + if (NetIfData.isOptionIdAny(listenInterface)) { + // Any mode: get all the available NICs and add their IPv4 + for (int i = 0; i < ifColl.getSize(); i++) { + NetIfData nid = ifColl.getNetIf(i); + ipv4s = Utils.getIPv4ForInterface(nid.getNetworkInterface()); + for (String ipv4 : ipv4s) { + hosts.add(ipv4); } } - } catch (SocketException e) { - //unused + + } else { + // Single interface: get all its IPv4 addresses + if (NetIfData.isOptionIdLoopback(listenInterface)) { + ipv4s = Utils.getIPv4ForInterface(ifColl.getLoopback().getNetworkInterface()); + + } else { + ipv4s = Utils.getIPv4ForInterface(listenInterface); + } + + for (String ipv4 : ipv4s) { + hosts.add(ipv4); + } } return new ArrayList<>(hosts); } + + + static String getListenInterface() { + try { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(instance); + return prefs.getString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, new Defaults(instance).getListenInterface()); + } catch (Exception e) { + return null; + } + } + static int getPort() { try { return Objects.requireNonNull(MainServicePersistData.loadStartIntent(instance)).getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(instance).getInt(Constants.PREFS_KEY_SETTINGS_PORT, instance.mDefaults.getPort())); @@ -1044,6 +1126,7 @@ static Notification getCurrentNotification() { } } + /** * Helper that adds {@link #EXTRA_FALLBACK_SCREEN_CAPTURE} to the given intent if * PROJECT_MEDIA app op is not set. @@ -1070,4 +1153,16 @@ static void addFallbackScreenCaptureIfNotAppOp(Context context, Intent intent) { intent.putExtra(MainService.EXTRA_FALLBACK_SCREEN_CAPTURE, useFallback); } + + + + + public void onNetworkStateChanged(NetworkInterfaceTester nit) { + if (isServerActive()) { + if (!nit.isIfEnabled(getListenInterface())) { + // Stop if the server is active but the interface is not! + this.stopSelf(); + } + } + } } diff --git a/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java b/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java index 1c639002..29d9bb82 100644 --- a/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java +++ b/app/src/main/java/net/christianbeier/droidvnc_ng/OnBootReceiver.java @@ -24,6 +24,9 @@ import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -32,14 +35,35 @@ import android.os.SystemClock; import android.util.Log; + import androidx.core.content.ContextCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.preference.PreferenceManager; +import java.util.ArrayList; +import java.net.InetAddress; +import java.net.Socket; +import java.net.InetSocketAddress; +import java.io.IOException; + + +import net.christianbeier.droidvnc_ng.ifaceutils.NetworkInterfaceTester; -public class OnBootReceiver extends BroadcastReceiver { + +public class OnBootReceiver extends BroadcastReceiver { private static final String TAG = "OnBootReceiver"; + // Stuff for the notification, in case the listenIf is not available + private static NotificationManager notifManager; + private static String NTCHN_NAME = "NTCHN onBootReceiver"; + private static int NOTIFICATION_ID = 34; + private static NotificationChannel notifChannel; + + + + public void onReceive(Context context, Intent arg1) { if (Intent.ACTION_BOOT_COMPLETED.equals(arg1.getAction())) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -53,8 +77,18 @@ public void onReceive(Context context, Intent arg1) { return; } + // Check for availability of listenIf + String listenIf = prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, defaults.getListenInterface()); + NetworkInterfaceTester nit = NetworkInterfaceTester.getInstance(context); + if (!nit.isIfEnabled(listenIf)) { + Log.w(TAG, "onReceive: interface \"" + listenIf + "\" not available, sending a notification"); + this.sendNotification(context, "Failed to connect to interface \"%s\", is it down perhaps?", listenIf); + return; + } + Intent intent = new Intent(context.getApplicationContext(), MainService.class); intent.setAction(MainService.ACTION_START); + intent.putExtra(MainService.EXTRA_LISTEN_INTERFACE, listenIf); intent.putExtra(MainService.EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, defaults.getPort())); intent.putExtra(MainService.EXTRA_PASSWORD, prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, defaults.getPassword())); intent.putExtra(MainService.EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, defaults.getFileTransfer())); @@ -84,4 +118,45 @@ public void onReceive(Context context, Intent arg1) { } } + + + /* + * Stuff for notifications + */ + private void sendNotification(Context context, String message, String ...messageItems) { + this.setupNotificationChannel(context); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, context.getPackageName()) + .setSmallIcon(R.drawable.ic_notification_normal) + .setContentTitle(context.getResources().getString(R.string.app_name)) + .setContentText( + String.format(message, messageItems)) + .setSilent(true) + .setOngoing(false); + + if (Build.VERSION.SDK_INT >= 31) { + builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE); + } + + OnBootReceiver.notifManager.notify(NOTIFICATION_ID, builder.build()); + } + + + private void setupNotificationChannel(Context context) { + if (OnBootReceiver.notifManager == null) { + OnBootReceiver.notifManager = context.getSystemService(NotificationManager.class); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (OnBootReceiver.notifChannel == null) { + OnBootReceiver.notifChannel = new NotificationChannel( + context.getPackageName(), + NTCHN_NAME, + NotificationManager.IMPORTANCE_DEFAULT + ); + + OnBootReceiver.notifManager.createNotificationChannel(OnBootReceiver.notifChannel); + } + } + } }