diff --git a/android/app/src/main/java/org/openipc/camex/SettingsBottomSheet.java b/android/app/src/main/java/org/openipc/camex/SettingsBottomSheet.java
index 4bc1aea..ac568bf 100644
--- a/android/app/src/main/java/org/openipc/camex/SettingsBottomSheet.java
+++ b/android/app/src/main/java/org/openipc/camex/SettingsBottomSheet.java
@@ -16,6 +16,7 @@
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.button.MaterialButton;
+import com.google.android.material.button.MaterialButtonToggleGroup;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
@@ -29,10 +30,16 @@ public class SettingsBottomSheet extends BottomSheetDialogFragment {
private static final String PREFS = "camex";
private static final int DEFAULT_PORT = 5800;
private static final String DEFAULT_NAME = "android-client";
+ // camex defaults (see src/main.c)
+ private static final int DEFAULT_MTU = 1500;
+ private static final int DEFAULT_KEEPALIVE = 10;
+ private static final int DEFAULT_TIMEOUT = 20;
private TextInputEditText hostInput, portInput, nameInput, pskInput;
+ private TextInputEditText mtuInput, keepaliveInput, timeoutInput;
private TextInputLayout pskLayout;
private SwitchMaterial encryptSwitch;
+ private MaterialButtonToggleGroup transportGroup;
@Nullable
@Override
@@ -52,6 +59,10 @@ public void onViewCreated(@NonNull View v, @Nullable Bundle savedInstanceState)
pskInput = v.findViewById(R.id.input_psk);
pskLayout = v.findViewById(R.id.layout_psk);
encryptSwitch = v.findViewById(R.id.switch_encrypt);
+ transportGroup = v.findViewById(R.id.group_transport);
+ mtuInput = v.findViewById(R.id.input_mtu);
+ keepaliveInput = v.findViewById(R.id.input_keepalive);
+ timeoutInput = v.findViewById(R.id.input_server_timeout);
SharedPreferences prefs = requireContext().getSharedPreferences(PREFS, 0);
hostInput.setText(prefs.getString("server_host", ""));
@@ -64,6 +75,14 @@ public void onViewCreated(@NonNull View v, @Nullable Bundle savedInstanceState)
encryptSwitch.setOnCheckedChangeListener((b, checked) -> pskLayout.setEnabled(checked));
+ // Transport: default UDP unless "tcp" was saved.
+ boolean tcp = "tcp".equals(prefs.getString("transport", "udp"));
+ transportGroup.check(tcp ? R.id.btn_tcp : R.id.btn_udp);
+
+ mtuInput.setText(String.valueOf(prefs.getInt("mtu", DEFAULT_MTU)));
+ keepaliveInput.setText(String.valueOf(prefs.getInt("keepalive", DEFAULT_KEEPALIVE)));
+ timeoutInput.setText(String.valueOf(prefs.getInt("server_timeout", DEFAULT_TIMEOUT)));
+
MaterialButton save = v.findViewById(R.id.btn_save);
save.setOnClickListener(view -> save(prefs));
}
@@ -90,12 +109,37 @@ private void save(SharedPreferences prefs) {
String name = text(nameInput);
if (TextUtils.isEmpty(name)) name = DEFAULT_NAME;
+ int mtu = parseInt(mtuInput, DEFAULT_MTU);
+ if (mtu < 576 || mtu > 9000) {
+ mtuInput.setError(getString(R.string.err_mtu));
+ return;
+ }
+
+ int keepalive = parseInt(keepaliveInput, DEFAULT_KEEPALIVE);
+ if (keepalive < 0 || keepalive > 3600) {
+ keepaliveInput.setError(getString(R.string.err_keepalive));
+ return;
+ }
+
+ int timeout = parseInt(timeoutInput, DEFAULT_TIMEOUT);
+ if (timeout < 5 || timeout > 3600) {
+ timeoutInput.setError(getString(R.string.err_timeout));
+ return;
+ }
+
+ String transport =
+ transportGroup.getCheckedButtonId() == R.id.btn_tcp ? "tcp" : "udp";
+
prefs.edit()
.putString("server_host", host)
.putInt("server_port", port)
.putString("name", name)
+ .putString("transport", transport)
.putBoolean("encrypt", encryptSwitch.isChecked())
.putString("psk", text(pskInput))
+ .putInt("mtu", mtu)
+ .putInt("keepalive", keepalive)
+ .putInt("server_timeout", timeout)
.apply();
Toast.makeText(requireContext(), R.string.save, Toast.LENGTH_SHORT).show();
@@ -105,4 +149,14 @@ private void save(SharedPreferences prefs) {
private static String text(TextInputEditText input) {
return input.getText() == null ? "" : input.getText().toString().trim();
}
+
+ private static int parseInt(TextInputEditText input, int fallback) {
+ String s = text(input);
+ if (TextUtils.isEmpty(s)) return fallback;
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException ignored) {
+ return fallback;
+ }
+ }
}
diff --git a/android/app/src/main/java/org/openipc/camex/TunnelService.java b/android/app/src/main/java/org/openipc/camex/TunnelService.java
index 9fb9c34..6340c4a 100644
--- a/android/app/src/main/java/org/openipc/camex/TunnelService.java
+++ b/android/app/src/main/java/org/openipc/camex/TunnelService.java
@@ -72,6 +72,11 @@ public int onStartCommand(Intent intent, int flags, int startId) {
args.add("--name"); args.add(getName());
args.add("--server-host"); args.add(getServerHost());
args.add("--port"); args.add(String.valueOf(getServerPort()));
+ args.add("--transport"); args.add(getTransport());
+ args.add("--mtu"); args.add(String.valueOf(getInt("mtu", 1500)));
+ args.add("--keepalive"); args.add(String.valueOf(getInt("keepalive", 10)));
+ args.add("--server-timeout");
+ args.add(String.valueOf(getInt("server_timeout", 20)));
if (getEncrypt()) {
args.add("--encrypt");
String psk = getPsk();
@@ -112,6 +117,16 @@ private String getName() {
.getString("name", "android-client");
}
+ private String getTransport() {
+ // udp (default) or tcp
+ return getSharedPreferences("camex", MODE_PRIVATE)
+ .getString("transport", "udp");
+ }
+
+ private int getInt(String key, int def) {
+ return getSharedPreferences("camex", MODE_PRIVATE).getInt(key, def);
+ }
+
private boolean getEncrypt() {
return getSharedPreferences("camex", MODE_PRIVATE)
.getBoolean("encrypt", false);
diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml
index 0c85c93..54f23ae 100644
--- a/android/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -10,6 +10,6 @@
android:viewportWidth="108"
android:viewportHeight="108">
diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 9d1fba9..0000000
--- a/android/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
diff --git a/android/app/src/main/res/layout/bottom_sheet_settings.xml b/android/app/src/main/res/layout/bottom_sheet_settings.xml
index 453a954..310c4bd 100644
--- a/android/app/src/main/res/layout/bottom_sheet_settings.xml
+++ b/android/app/src/main/res/layout/bottom_sheet_settings.xml
@@ -64,11 +64,44 @@
android:maxLines="1" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b389137
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..0b27583
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..3194444
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2e4903d
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d2cbef2
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..73f9f12
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a7e81b9
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..65c0969
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ba73ba3
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..b56a604
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 5549ccb..e2eb7f7 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -17,7 +17,26 @@
Server host
Server port
Client name
- Encrypt traffic
+
+
+ Transport
+ UDP
+ TCP
+
+
+ Encrypt traffic (ChaCha20-Poly1305)
Pre-shared key
+
+
+ Advanced
+ MTU (576–9000)
+ Keepalive, sec (0 = off)
+ Server timeout, sec (5–3600)
+
Save
+
+
+ MTU must be 576–9000
+ Keepalive must be 0–3600
+ Timeout must be 5–3600