You've already forked android-client
mirror of
https://github.com/netbirdio/android-client.git
synced 2026-05-22 17:10:49 -07:00
Add force relay connection option (#77)
This adds a new switch control to the advanced settings so that users may opt to force usage of relay when connecting to peers. It also warns the user that this configuration change will only be applied the next time they connect to the VPN.
This commit is contained in:
@@ -9,12 +9,16 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import io.netbird.client.R;
|
||||
import io.netbird.client.databinding.ComponentSwitchBinding;
|
||||
import io.netbird.client.databinding.FragmentAdvancedBinding;
|
||||
import io.netbird.client.tool.Logcat;
|
||||
import io.netbird.client.tool.Preferences;
|
||||
@@ -28,6 +32,33 @@ public class AdvancedFragment extends Fragment {
|
||||
private FragmentAdvancedBinding binding;
|
||||
private io.netbird.gomobile.android.Preferences goPreferences;
|
||||
|
||||
private void showReconnectionNeededWarningDialog() {
|
||||
final View dialogView = getLayoutInflater().inflate(R.layout.dialog_simple_alert_message, null);
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme)
|
||||
.setView(dialogView)
|
||||
.create();
|
||||
|
||||
((TextView)dialogView.findViewById(R.id.txt_dialog)).setText(R.string.reconnectionNeededWarningMessage);
|
||||
dialogView.findViewById(R.id.btn_ok_dialog).setOnClickListener(v -> alertDialog.dismiss());
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private void configureForceRelayConnectionSwitch(@NonNull ComponentSwitchBinding binding, @NonNull Preferences preferences) {
|
||||
binding.switchTitle.setText(R.string.advanced_force_relay_conn);
|
||||
binding.switchDescription.setText(R.string.advanced_force_relay_conn_desc);
|
||||
|
||||
binding.switchControl.setChecked(preferences.isConnectionForceRelayed());
|
||||
binding.switchControl.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
preferences.enableForcedRelayConnection();
|
||||
} else {
|
||||
preferences.disableForcedRelayConnection();
|
||||
}
|
||||
|
||||
showReconnectionNeededWarningDialog();
|
||||
});
|
||||
}
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
@@ -122,6 +153,8 @@ public class AdvancedFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
configureForceRelayConnectionSwitch(binding.layoutForceRelayConnection, preferences);
|
||||
|
||||
// Initialize engine config switches (your settings)
|
||||
initializeEngineConfigSwitches();
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="28dp" />
|
||||
<solid android:color="@color/nb_bg" />
|
||||
</shape>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/switch_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
tools:text="Switch Title"
|
||||
android:textColor="@color/nb_txt_light" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switch_control"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/switch_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Switch Description"
|
||||
android:textColor="@color/nb_txt_light"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="48dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_rounded_nb_bg"
|
||||
android:maxWidth="560dp"
|
||||
android:minWidth="280dp"
|
||||
android:padding="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_dialog"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/exclamation_mark"
|
||||
android:src="@drawable/exclamation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/nb_orange" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/icon_dialog"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ultricies, lorem sit amet ultrices tincidunt, neque neque molestie lacus, dictum consequat sapien neque at quam. Proin vel justo nulla." />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_ok_dialog"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@android:string/ok"
|
||||
android:textColor="@color/nb_orange"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/txt_dialog"
|
||||
tools:text="@android:string/ok" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -5,6 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:id="@+id/scr_vw_advanced"
|
||||
tools:context=".ui.advanced.AdvancedFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
@@ -43,7 +44,7 @@
|
||||
android:textSize="14sp"
|
||||
android:inputType="textUri"
|
||||
android:background="@drawable/edit_text_white"
|
||||
android:padding="12dp"
|
||||
android:padding="16dp"
|
||||
android:textColor="@color/nb_txt"
|
||||
android:textColorHint="@color/nb_txt_light"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_server_label"
|
||||
@@ -438,13 +439,23 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_force_relay_connection"
|
||||
layout="@layout/component_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_disable_firewall" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_theme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_disable_firewall"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_force_relay_connection"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -105,4 +105,8 @@
|
||||
<string name="advanced_theme_dark">Dark</string>
|
||||
<string name="advanced_theme_light">Light</string>
|
||||
<string name="advanced_theme_desc">Choose the app appearance mode.</string>
|
||||
<string name="advanced_force_relay_conn">Force relay connection</string>
|
||||
<string name="advanced_force_relay_conn_desc">Forces usage of relay when connecting to peers</string>
|
||||
<string name="exclamation_mark">exclamation mark</string>
|
||||
<string name="reconnectionNeededWarningMessage">To apply the setting, you will need to reconnect.</string>
|
||||
</resources>
|
||||
|
||||
@@ -57,4 +57,8 @@
|
||||
<item name="trackTint">@color/switch_track_color</item>
|
||||
</style>
|
||||
<attr name="nbTabBackground" format="color|reference" />
|
||||
|
||||
<style name="AlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
+1
-1
Submodule netbird updated: f425870c8e...a53243eed0
@@ -0,0 +1,27 @@
|
||||
package io.netbird.client.tool;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import io.netbird.gomobile.android.Android;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EnvVarPackagerInstrumentedTest {
|
||||
@Test
|
||||
public void shouldReturnEnvironmentVariables() {
|
||||
var preferences = new Preferences(InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||
var environmentVariables = EnvVarPackager.getEnvironmentVariables(preferences);
|
||||
|
||||
Assert.assertNotNull(environmentVariables);
|
||||
var forceRelay = environmentVariables.get(Android.getEnvKeyNBForceRelay());
|
||||
var variableNotPresentInList = environmentVariables.get("UNKNOWN_VAR");
|
||||
var emptyString = "";
|
||||
|
||||
Assert.assertNotEquals(emptyString, forceRelay);
|
||||
Assert.assertEquals(emptyString, variableNotPresentInList);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("io.netbird.client.backend.test", appContext.getPackageName());
|
||||
assertEquals("io.netbird.client.tool.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.netbird.client.tool;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesInstrumentedTest {
|
||||
private static Preferences preferences;
|
||||
|
||||
private static Context getContext() {
|
||||
return InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
preferences = new Preferences(getContext());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
getContext().getSharedPreferences("netbird", Context.MODE_PRIVATE).edit().clear().apply();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreatePreferencesWithoutThrownException() {
|
||||
Preferences preferences = null;
|
||||
Exception thrown = null;
|
||||
|
||||
try {
|
||||
preferences = new Preferences(getContext());
|
||||
} catch (Exception e) {
|
||||
thrown = e;
|
||||
}
|
||||
|
||||
Assert.assertNull(thrown);
|
||||
Assert.assertNotNull(preferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFalseWhenConnectionForceRelayedIsNotSet() {
|
||||
Assert.assertFalse(preferences.isConnectionForceRelayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnTrueAfterEnablingForcedRelayConnection() {
|
||||
preferences.enableForcedRelayConnection();
|
||||
|
||||
Assert.assertTrue(preferences.isConnectionForceRelayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFalseAfterDisablingForcedRelayConnection() {
|
||||
preferences.enableForcedRelayConnection();
|
||||
preferences.disableForcedRelayConnection();
|
||||
|
||||
Assert.assertFalse(preferences.isConnectionForceRelayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFalseWhenTraceLogIsNotSet() {
|
||||
Assert.assertFalse(preferences.isTraceLogEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnTrueAfterEnablingTraceLog() {
|
||||
preferences.enableTraceLog();
|
||||
|
||||
Assert.assertTrue(preferences.isTraceLogEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFalseAfterDisablingTraceLog() {
|
||||
preferences.enableTraceLog();
|
||||
preferences.disableTraceLog();
|
||||
|
||||
Assert.assertFalse(preferences.isTraceLogEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnCorrectDefaultServer() {
|
||||
final var defaultServer = "https://api.netbird.io";
|
||||
|
||||
Assert.assertEquals(defaultServer, Preferences.defaultServer());
|
||||
}
|
||||
}
|
||||
@@ -60,12 +60,15 @@ class EngineRunner {
|
||||
engineIsRunning = true;
|
||||
Runnable r = () -> {
|
||||
DNSWatch dnsWatch = new DNSWatch(context);
|
||||
Preferences preferences = new Preferences(context);
|
||||
var envList = EnvVarPackager.getEnvironmentVariables(preferences);
|
||||
|
||||
try {
|
||||
notifyServiceStateListeners(true);
|
||||
if(urlOpener == null) {
|
||||
goClient.runWithoutLogin(dnsWatch.dnsServers(), () -> dnsWatch.setDNSChangeListener(this::changed));
|
||||
goClient.runWithoutLogin(dnsWatch.dnsServers(), () -> dnsWatch.setDNSChangeListener(this::changed), envList);
|
||||
} else {
|
||||
goClient.run(urlOpener, dnsWatch.dnsServers(), () -> dnsWatch.setDNSChangeListener(this::changed));
|
||||
goClient.run(urlOpener, dnsWatch.dnsServers(), () -> dnsWatch.setDNSChangeListener(this::changed), envList);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "goClient error", e);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.netbird.client.tool;
|
||||
|
||||
import io.netbird.gomobile.android.Android;
|
||||
import io.netbird.gomobile.android.EnvList;
|
||||
|
||||
public class EnvVarPackager {
|
||||
public static EnvList getEnvironmentVariables(Preferences preferences) {
|
||||
var envList = new EnvList();
|
||||
|
||||
envList.put(Android.getEnvKeyNBForceRelay(), String.valueOf(preferences.isConnectionForceRelayed()));
|
||||
|
||||
return envList;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import android.content.SharedPreferences;
|
||||
public class Preferences {
|
||||
|
||||
private final String keyTraceLog = "tracelog";
|
||||
|
||||
private final String keyForceRelayConnection = "isConnectionForceRelayed";
|
||||
|
||||
private final SharedPreferences sharedPref;
|
||||
|
||||
public static String configFile(Context context){
|
||||
@@ -27,6 +30,18 @@ public class Preferences {
|
||||
sharedPref.edit().putBoolean(keyTraceLog, false).apply();
|
||||
}
|
||||
|
||||
public boolean isConnectionForceRelayed() {
|
||||
return sharedPref.getBoolean(keyForceRelayConnection, false);
|
||||
}
|
||||
|
||||
public void enableForcedRelayConnection() {
|
||||
sharedPref.edit().putBoolean(keyForceRelayConnection, true).apply();
|
||||
}
|
||||
|
||||
public void disableForcedRelayConnection() {
|
||||
sharedPref.edit().putBoolean(keyForceRelayConnection, false).apply();
|
||||
}
|
||||
|
||||
public static String defaultServer() {
|
||||
return "https://api.netbird.io";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user