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