From 14fc04a43ba1b02b0e26e75942b6e6b9c5f63cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Fri, 11 Sep 2026 17:11:43 +0200 Subject: [PATCH] Add an anonymization level picker to the debug bundle screen The troubleshoot screen exposed anonymization as a plain on/off switch that kept no state: it reset to off on every fragment recreation, so a bundle went up unanonymized unless the user toggled it again right before uploading. The strict level the Go binding already exports was unreachable, because EngineRunner passed AnonymizeLevelDefault unconditionally. Replace the switch with a None / Default / Strict picker that mirrors the desktop client. The selection persists in the app-wide SharedPreferences and defaults to Default. The row moves from the Logging section into the Debug bundle section, since it only affects the bundle. The picker is a bottom sheet built on the existing SplitTunnelModeSheet pattern, so each level carries a description explaining what it hides. The level-to-parameter mapping follows the desktop client: "none" turns the anonymize flag off, and the daemon only ever sees "default" or "strict". Level names and descriptions are taken from the already translated desktop strings for all ten locales. --- .../java/io/netbird/client/MainActivity.java | 4 +- .../io/netbird/client/ServiceAccessor.java | 2 +- .../ui/troubleshoot/AnonymizeLevelSheet.java | 78 +++++++++ .../ui/troubleshoot/TroubleshootFragment.java | 44 ++++- .../main/res/layout/fragment_troubleshoot.xml | 56 +++--- .../main/res/layout/sheet_anonymize_level.xml | 165 ++++++++++++++++++ app/src/main/res/values-de/strings.xml | 7 + app/src/main/res/values-es/strings.xml | 7 + app/src/main/res/values-fr/strings.xml | 7 + app/src/main/res/values-hu/strings.xml | 7 + app/src/main/res/values-it/strings.xml | 7 + app/src/main/res/values-ja/strings.xml | 7 + app/src/main/res/values-pt/strings.xml | 7 + app/src/main/res/values-ru/strings.xml | 7 + app/src/main/res/values-zh-rCN/strings.xml | 7 + app/src/main/res/values/strings.xml | 7 + .../io/netbird/client/tool/EngineRunner.java | 6 +- .../io/netbird/client/tool/Preferences.java | 16 ++ .../io/netbird/client/tool/VPNService.java | 4 +- 19 files changed, 408 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/io/netbird/client/ui/troubleshoot/AnonymizeLevelSheet.java create mode 100644 app/src/main/res/layout/sheet_anonymize_level.xml diff --git a/app/src/main/java/io/netbird/client/MainActivity.java b/app/src/main/java/io/netbird/client/MainActivity.java index 3256cba4..31e06e06 100644 --- a/app/src/main/java/io/netbird/client/MainActivity.java +++ b/app/src/main/java/io/netbird/client/MainActivity.java @@ -612,11 +612,11 @@ public void deselectRoute(String route) throws Exception { } @Override - public String debugBundle(boolean anonymize) throws Exception { + public String debugBundle(boolean anonymize, String anonymizeLevel) throws Exception { if (mBinder == null) { throw new Exception("VPN service not connected"); } - return mBinder.debugBundle(anonymize); + return mBinder.debugBundle(anonymize, anonymizeLevel); } @Override diff --git a/app/src/main/java/io/netbird/client/ServiceAccessor.java b/app/src/main/java/io/netbird/client/ServiceAccessor.java index bffc12fb..546055a0 100644 --- a/app/src/main/java/io/netbird/client/ServiceAccessor.java +++ b/app/src/main/java/io/netbird/client/ServiceAccessor.java @@ -31,7 +31,7 @@ public interface ServiceAccessor { void addRouteChangeListener(RouteChangeListener listener); void removeRouteChangeListener(RouteChangeListener listener); - String debugBundle(boolean anonymize) throws Exception; + String debugBundle(boolean anonymize, String anonymizeLevel) throws Exception; /** SSO session deadline as unix seconds; 0 when unknown or not bound. */ long sessionExpiresAt(); diff --git a/app/src/main/java/io/netbird/client/ui/troubleshoot/AnonymizeLevelSheet.java b/app/src/main/java/io/netbird/client/ui/troubleshoot/AnonymizeLevelSheet.java new file mode 100644 index 00000000..aa32b2bf --- /dev/null +++ b/app/src/main/java/io/netbird/client/ui/troubleshoot/AnonymizeLevelSheet.java @@ -0,0 +1,78 @@ +package io.netbird.client.ui.troubleshoot; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import io.netbird.client.databinding.SheetAnonymizeLevelBinding; +import io.netbird.client.tool.Preferences; + +public class AnonymizeLevelSheet extends BottomSheetDialogFragment { + + public interface OnLevelChangedListener { + void onAnonymizeLevelChanged(String level); + } + + private static final String ARG_LEVEL = "level"; + + private SheetAnonymizeLevelBinding binding; + + public static AnonymizeLevelSheet newInstance(String current) { + AnonymizeLevelSheet sheet = new AnonymizeLevelSheet(); + Bundle args = new Bundle(); + args.putString(ARG_LEVEL, current); + sheet.setArguments(args); + return sheet; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = SheetAnonymizeLevelBinding.inflate(inflater, container, false); + + binding.levelRowNone.setOnClickListener(v -> pick(Preferences.ANONYMIZE_LEVEL_NONE)); + binding.levelRowDefault.setOnClickListener(v -> pick(Preferences.ANONYMIZE_LEVEL_DEFAULT)); + binding.levelRowStrict.setOnClickListener(v -> pick(Preferences.ANONYMIZE_LEVEL_STRICT)); + + showCheckmarkFor(currentLevel()); + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + private String currentLevel() { + Bundle args = getArguments(); + if (args == null) { + return Preferences.ANONYMIZE_LEVEL_DEFAULT; + } + return args.getString(ARG_LEVEL, Preferences.ANONYMIZE_LEVEL_DEFAULT); + } + + private void showCheckmarkFor(String level) { + binding.levelCheckNone.setVisibility( + Preferences.ANONYMIZE_LEVEL_NONE.equals(level) ? View.VISIBLE : View.INVISIBLE); + binding.levelCheckStrict.setVisibility( + Preferences.ANONYMIZE_LEVEL_STRICT.equals(level) ? View.VISIBLE : View.INVISIBLE); + binding.levelCheckDefault.setVisibility( + Preferences.ANONYMIZE_LEVEL_NONE.equals(level) || Preferences.ANONYMIZE_LEVEL_STRICT.equals(level) + ? View.INVISIBLE : View.VISIBLE); + } + + private void pick(String level) { + if (getParentFragment() instanceof OnLevelChangedListener) { + ((OnLevelChangedListener) getParentFragment()).onAnonymizeLevelChanged(level); + } + dismiss(); + } +} diff --git a/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java b/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java index 2a6edff7..4a2220a5 100644 --- a/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java +++ b/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java @@ -20,16 +20,18 @@ import io.netbird.client.tool.Preferences; import io.netbird.client.tool.ProfileManagerWrapper; -public class TroubleshootFragment extends Fragment { +public class TroubleshootFragment extends Fragment implements AnonymizeLevelSheet.OnLevelChangedListener { private static final String LOGTAG = "TroubleshootFragment"; + private FragmentTroubleshootBinding binding; + private Preferences preferences; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentTroubleshootBinding.inflate(inflater, container, false); - Preferences preferences = new Preferences(inflater.getContext()); + preferences = new Preferences(inflater.getContext()); binding.switchTraceLog.setChecked(preferences.isTraceLogEnabled()); binding.switchTraceLog.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -44,9 +46,10 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, binding.switchTraceLog.toggle(); }); - binding.anonymizeLayout.setOnClickListener(v -> { - binding.switchAnonymize.toggle(); - }); + updateAnonymizeValue(); + binding.anonymizeLayout.setOnClickListener(v -> + AnonymizeLevelSheet.newInstance(preferences.getAnonymizeLevel()) + .show(getChildFragmentManager(), "anonymize_level")); initializeRemoteJobsSwitch(inflater.getContext()); @@ -63,6 +66,18 @@ public void onDestroyView() { binding = null; } + @Override + public void onAnonymizeLevelChanged(String level) { + preferences.setAnonymizeLevel(level); + if (binding != null) { + updateAnonymizeValue(); + } + } + + private void updateAnonymizeValue() { + binding.anonymizeValue.setText(anonymizeLevelLabel(preferences.getAnonymizeLevel())); + } + private void initializeRemoteJobsSwitch(Context context) { try { String configFilePath = new ProfileManagerWrapper(context).getActiveConfigPath(); @@ -88,11 +103,15 @@ private void generateDebugBundle() { return; } - boolean anonymize = binding.switchAnonymize.isChecked(); + String level = preferences.getAnonymizeLevel(); + boolean anonymize = !Preferences.ANONYMIZE_LEVEL_NONE.equals(level); + String anonymizeLevel = Preferences.ANONYMIZE_LEVEL_STRICT.equals(level) + ? Preferences.ANONYMIZE_LEVEL_STRICT + : Preferences.ANONYMIZE_LEVEL_DEFAULT; binding.buttonDebugBundle.setEnabled(false); new Thread(() -> { try { - String key = ((ServiceAccessor) activity).debugBundle(anonymize); + String key = ((ServiceAccessor) activity).debugBundle(anonymize, anonymizeLevel); activity.runOnUiThread(() -> { if (binding == null || !isAdded()) return; binding.buttonDebugBundle.setEnabled(true); @@ -111,4 +130,15 @@ private void generateDebugBundle() { } }).start(); } + + private static int anonymizeLevelLabel(String level) { + switch (level) { + case Preferences.ANONYMIZE_LEVEL_NONE: + return R.string.troubleshoot_anonymize_none; + case Preferences.ANONYMIZE_LEVEL_STRICT: + return R.string.troubleshoot_anonymize_strict; + default: + return R.string.troubleshoot_anonymize_default; + } + } } diff --git a/app/src/main/res/layout/fragment_troubleshoot.xml b/app/src/main/res/layout/fragment_troubleshoot.xml index 8c0f6ea3..5638f54a 100644 --- a/app/src/main/res/layout/fragment_troubleshoot.xml +++ b/app/src/main/res/layout/fragment_troubleshoot.xml @@ -54,7 +54,7 @@ + android:paddingEnd="16dp" + android:paddingTop="10dp" + android:paddingBottom="10dp"> - + android:orientation="vertical"> + + + + + - + + @@ -118,26 +137,21 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="2dp" - android:text="@string/troubleshoot_allow_remote_jobs_desc" + android:text="@string/troubleshoot_anonymize_desc" android:textColor="@color/nb_txt_light" android:textSize="12sp" /> - + android:textColor="@color/nb_txt_light" + android:textSize="14sp" + tools:text="@string/troubleshoot_anonymize_default" /> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index af607bc8..097a7e64 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -119,6 +119,13 @@ Trace-Logs aktivieren. Debug-Paket hochladen Sensible Daten anonymisieren + Verbirgt IP-Adressen, Domains und andere sensible Werte. + Keine + Alles bleibt lesbar. Nur innerhalb Ihrer Organisation weitergeben. + Standard + Verbirgt öffentliche IP-Adressen und Domains. Interne IPv4-Adressen und Peer-Namen bleiben für den Support lesbar. + Strikt + Verbirgt zusätzlich private, CGNAT- und Link-Local-Adressen, Peer-Namen und WireGuard-Schlüssel. Außerhalb Ihrer Organisation verwenden. Protokollierung Debug-Paket Rosenpass aktivieren diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2bfbc8bc..fbdadc3f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -119,6 +119,13 @@ Activar el nivel de registro de seguimiento. Subir paquete de diagnóstico Anonimizar datos sensibles + Oculta direcciones IP, dominios y otros valores sensibles. + Ninguno + Todo permanece legible. Compártelo solo dentro de tu organización. + Predeterminado + Oculta las direcciones IP públicas y los dominios. Las direcciones IPv4 internas y los nombres de los peers siguen legibles para el soporte. + Estricto + Oculta además las direcciones privadas, CGNAT y de enlace local, los nombres de los peers y las claves WireGuard. Úsalo fuera de tu organización. Registro Paquete de diagnóstico Habilitar Rosenpass diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a37df577..893690ee 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -119,6 +119,13 @@ Activer le niveau de journalisation trace. Envoyer le lot de diagnostic Anonymiser les données sensibles + Masque les adresses IP, les domaines et d\'autres valeurs sensibles. + Aucune + Tout reste lisible. À partager uniquement au sein de votre organisation. + Par défaut + Masque les adresses IP publiques et les domaines. Les adresses IPv4 internes et les noms des pairs restent lisibles pour le support. + Strict + Masque aussi les adresses privées, CGNAT et de lien local, les noms des pairs et les clés WireGuard. À utiliser hors de votre organisation. Journalisation Lot de diagnostic Activer Rosenpass diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e054a07c..9983d230 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -119,6 +119,13 @@ Trace naplózási szint engedélyezése. Hibakeresési csomag feltöltése Érzékeny adatok anonimizálása + Elrejti az IP-címeket, a tartományokat és más érzékeny értékeket. + Nincs + Minden olvasható marad. Csak a szervezeten belül ossza meg. + Alapértelmezett + Elrejti a nyilvános IP-címeket és tartományokat. A belső IPv4-címek és a peer-nevek olvashatók maradnak a támogatás számára. + Szigorú + A privát, CGNAT és link-local címeket, a peer-neveket és a WireGuard-kulcsokat is elrejti. A szervezeten kívül ezt használja. Naplózás Hibakeresési csomag Rosenpass engedélyezése diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a1d09564..04f09198 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -119,6 +119,13 @@ Abilita il livello di log di traccia. Carica pacchetto di debug Anonimizza dati sensibili + Nasconde indirizzi IP, domini e altri valori sensibili. + Nessuna + Tutto resta leggibile. Condividi solo all’interno della tua organizzazione. + Predefinito + Nasconde gli indirizzi IP pubblici e i domini. Gli indirizzi IPv4 interni e i nomi dei peer restano leggibili per il supporto. + Rigoroso + Nasconde anche gli indirizzi privati, CGNAT e link-local, i nomi dei peer e le chiavi WireGuard. Usalo fuori dalla tua organizzazione. Registrazione Pacchetto di debug Abilita Rosenpass diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index daff79b5..d7f87770 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -119,6 +119,13 @@ トレースログレベルを有効にします。 デバッグバンドルをアップロード 機密データを匿名化 + IP アドレス、ドメイン、その他の機密性の高い値を隠します。 + なし + すべてそのまま残ります。組織内でのみ共有してください。 + デフォルト + パブリック IP アドレスとドメインを隠します。内部 IPv4 アドレスとピア名はサポート向けに読める状態で残ります。 + 厳格 + プライベート、CGNAT、リンクローカルのアドレス、ピア名、WireGuard 鍵も隠します。組織外へ共有する場合に使用します。 ログ デバッグバンドル Rosenpass を有効にする diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c5c602b6..3955229e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -119,6 +119,13 @@ Habilitar o nível de log de trace. Enviar pacote de depuração Anonimizar dados sensíveis + Oculta endereços IP, domínios e outros valores sensíveis. + Nenhum + Tudo permanece legível. Compartilhe apenas dentro da sua organização. + Padrão + Oculta os endereços IP públicos e os domínios. Os endereços IPv4 internos e os nomes dos peers continuam legíveis para o suporte. + Estrito + Oculta também os endereços privados, CGNAT e link-local, os nomes dos peers e as chaves WireGuard. Use fora da sua organização. Registro Pacote de depuração Ativar Rosenpass diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8ed43a21..b7769ff6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -119,6 +119,13 @@ Включить уровень журналирования TRACE. Загрузить отладочный пакет Анонимизировать конфиденциальные данные + Скрывает IP-адреса, домены и другие конфиденциальные значения. + Нет + Всё остаётся читаемым. Передавайте только внутри вашей организации. + По умолчанию + Скрывает публичные IP-адреса и домены. Внутренние IPv4-адреса и имена пиров остаются читаемыми для поддержки. + Строгий + Скрывает также частные, CGNAT и link-local адреса, имена пиров и ключи WireGuard. Используйте за пределами вашей организации. Журналирование Отладочный пакет Включить Rosenpass diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 4b40dce6..d827e5df 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -119,6 +119,13 @@ 启用跟踪日志级别。 上传调试包 匿名化敏感数据 + 隐藏 IP 地址、域名和其他敏感值。 + 无 + 所有内容均保持可读。请仅在组织内部分享。 + 默认 + 隐藏公网 IP 地址和域名。内部 IPv4 地址和对等节点名称保持可读,便于支持人员查看。 + 严格 + 同时隐藏私有、CGNAT 和链路本地地址、对等节点名称以及 WireGuard 密钥。向组织外分享时请使用。 日志 调试包 启用 Rosenpass diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d9873929..1d626ca4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -188,6 +188,13 @@ Enable trace log level. Upload debug bundle Anonymize sensitive data + Hides IP addresses, domains, and other sensitive values. + None + Everything stays readable. Only share inside your organization. + Default + Hides public IP addresses and domains. Internal IPv4 addresses and peer names stay readable for support. + Strict + Also hides private, CGNAT and link-local addresses, peer names and WireGuard keys. Use it outside your organization. Logging Debug bundle Enable Rosenpass diff --git a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java index ec68f97e..85446af3 100644 --- a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java +++ b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java @@ -436,15 +436,13 @@ public void deselectRoute(String route) throws Exception { } } - public String debugBundle(boolean anonymize) throws Exception { + public String debugBundle(boolean anonymize, String anonymizeLevel) throws Exception { String configPath = profileManager.getActiveConfigPath(); String statePath = profileManager.getActiveStateFilePath(); String cacheDir = context.getCacheDir().getAbsolutePath(); var platformFiles = new AndroidPlatformFiles(configPath, statePath, cacheDir); try { - // The strict level stays unused until the troubleshoot screen - // grows an option for it. - return goClient.debugBundle(platformFiles, anonymize, Android.AnonymizeLevelDefault); + return goClient.debugBundle(platformFiles, anonymize, anonymizeLevel); } catch (Exception e) { Log.e(LOGTAG, "goClient error", e); throw e; diff --git a/tool/src/main/java/io/netbird/client/tool/Preferences.java b/tool/src/main/java/io/netbird/client/tool/Preferences.java index ddf57ca5..1ad4fd64 100644 --- a/tool/src/main/java/io/netbird/client/tool/Preferences.java +++ b/tool/src/main/java/io/netbird/client/tool/Preferences.java @@ -5,10 +5,18 @@ public class Preferences { + public static final String ANONYMIZE_LEVEL_NONE = "none"; + + public static final String ANONYMIZE_LEVEL_DEFAULT = "default"; + + public static final String ANONYMIZE_LEVEL_STRICT = "strict"; + private final String keyTraceLog = "tracelog"; private final String keyForceRelayConnection = "isConnectionForceRelayed"; + private final String keyAnonymizeLevel = "anonymizeLevel"; + private final SharedPreferences sharedPref; public Preferences(Context context) { @@ -26,6 +34,14 @@ public void disableTraceLog() { sharedPref.edit().putBoolean(keyTraceLog, false).apply(); } + public String getAnonymizeLevel() { + return sharedPref.getString(keyAnonymizeLevel, ANONYMIZE_LEVEL_DEFAULT); + } + + public void setAnonymizeLevel(String level) { + sharedPref.edit().putString(keyAnonymizeLevel, level).apply(); + } + public boolean isConnectionForceRelayed() { return sharedPref.getBoolean(keyForceRelayConnection, true); } diff --git a/tool/src/main/java/io/netbird/client/tool/VPNService.java b/tool/src/main/java/io/netbird/client/tool/VPNService.java index eed792d9..0f5e900a 100644 --- a/tool/src/main/java/io/netbird/client/tool/VPNService.java +++ b/tool/src/main/java/io/netbird/client/tool/VPNService.java @@ -330,8 +330,8 @@ public void removeRouteChangeListener(RouteChangeListener listener) { } } - public String debugBundle(boolean anonymize) throws Exception { - return engineRunner.debugBundle(anonymize); + public String debugBundle(boolean anonymize, String anonymizeLevel) throws Exception { + return engineRunner.debugBundle(anonymize, anonymizeLevel); } /**