diff --git a/app/src/main/java/com/wireguard/android/backend/Backend.java b/app/src/main/java/com/wireguard/android/backend/Backend.java index 64bb3d0..fe4b0bd 100644 --- a/app/src/main/java/com/wireguard/android/backend/Backend.java +++ b/app/src/main/java/com/wireguard/android/backend/Backend.java @@ -57,7 +57,7 @@ public interface Backend { * * @return Type name */ - String getTypeName(); + String getTypePrettyName(); /** * Determine version of underlying backend. diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java index 97cf0f8..e9ac079 100644 --- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -14,6 +14,7 @@ import android.support.v4.util.ArraySet; import android.util.Log; import com.wireguard.android.Application; +import com.wireguard.android.R; import com.wireguard.android.activity.MainActivity; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel.State; @@ -26,6 +27,7 @@ import com.wireguard.config.Peer; import java.net.InetAddress; import java.util.Collections; +import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -93,8 +95,8 @@ public final class GoBackend implements Backend { } @Override - public String getTypeName() { - return "Go userspace"; + public String getTypePrettyName() { + return context.getResources().getString(R.string.type_name_go_userspace); } @Override @@ -110,7 +112,7 @@ public final class GoBackend implements Backend { if (state == originalState) return originalState; if (state == State.UP && currentTunnel != null) - throw new IllegalStateException("Only one userspace tunnel can run at a time"); + throw new IllegalStateException(context.getResources().getString(R.string.multiple_tunnels_error)); Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state); setStateInternal(tunnel, tunnel.getConfig(), state); return getState(tunnel); @@ -122,10 +124,10 @@ public final class GoBackend implements Backend { if (state == State.UP) { Log.i(TAG, "Bringing tunnel up"); - Objects.requireNonNull(config, "Trying to bring up a tunnel with no config"); + Objects.requireNonNull(config, context.getResources().getString(R.string.no_config_error)); if (VpnService.prepare(context) != null) - throw new Exception("VPN service not authorized by user"); + throw new Exception(context.getResources().getString(R.string.vpn_not_authed_error)); final VpnService service; if (!vpnService.isDone()) @@ -134,7 +136,7 @@ public final class GoBackend implements Backend { try { service = vpnService.get(2, TimeUnit.SECONDS); } catch (final TimeoutException e) { - throw new Exception("Unable to start Android VPN service", e); + throw new Exception(context.getResources().getString(R.string.vpn_start_error), e); } if (currentTunnelHandle != -1) { @@ -172,12 +174,12 @@ public final class GoBackend implements Backend { builder.setBlocking(true); try (final ParcelFileDescriptor tun = builder.establish()) { if (tun == null) - throw new Exception("Unable to create tun device"); + throw new Exception(context.getResources().getString(R.string.tun_create_error)); Log.d(TAG, "Go backend v" + wgVersion()); currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig); } if (currentTunnelHandle < 0) - throw new Exception("Unable to turn tunnel on (wgTurnOn return " + currentTunnelHandle + ')'); + throw new Exception(String.format(Locale.getDefault(), context.getResources().getString(R.string.tunnel_on_error), currentTunnelHandle)); currentTunnel = tunnel; diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java index b69845b..7535b95 100644 --- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -10,6 +10,7 @@ import android.support.annotation.Nullable; import android.util.Log; import com.wireguard.android.Application; +import com.wireguard.android.R; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel.State; import com.wireguard.android.model.Tunnel.Statistics; @@ -36,9 +37,11 @@ public final class WgQuickBackend implements Backend { private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName(); private final File localTemporaryDir; + private final Context context; public WgQuickBackend(final Context context) { localTemporaryDir = new File(context.getCacheDir(), "tmp"); + this.context = context; } @Override @@ -84,8 +87,8 @@ public final class WgQuickBackend implements Backend { } @Override - public String getTypeName() { - return "Kernel module"; + public String getTypePrettyName() { + return context.getResources().getString(R.string.type_name_kernel_module); } @Override @@ -93,7 +96,7 @@ public final class WgQuickBackend implements Backend { final List output = new ArrayList<>(); if (Application.getRootShell() .run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty()) - throw new Exception("Unable to determine kernel module version"); + throw new Exception(context.getResources().getString(R.string.module_version_error)); return output.get(0); } @@ -125,6 +128,6 @@ public final class WgQuickBackend implements Backend { // noinspection ResultOfMethodCallIgnored tempFile.delete(); if (result != 0) - throw new Exception("Unable to configure tunnel (wg-quick returned " + result + ')'); + throw new Exception(context.getResources().getString(R.string.tunnel_config_error)); } } diff --git a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java index 654cb48..184dd6d 100644 --- a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java +++ b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java @@ -8,6 +8,7 @@ package com.wireguard.android.configStore; import android.content.Context; import android.util.Log; +import com.wireguard.android.R; import com.wireguard.config.Config; import com.wireguard.config.ParseException; @@ -17,6 +18,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.Set; import java9.util.stream.Collectors; @@ -40,7 +42,8 @@ public final class FileConfigStore implements ConfigStore { Log.d(TAG, "Creating configuration for tunnel " + name); final File file = fileFor(name); if (!file.createNewFile()) - throw new IOException("Configuration file " + file.getName() + " already exists"); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.config_file_exists_error), file.getName())); try (final FileOutputStream stream = new FileOutputStream(file, false)) { stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8)); } @@ -52,7 +55,8 @@ public final class FileConfigStore implements ConfigStore { Log.d(TAG, "Deleting configuration for tunnel " + name); final File file = fileFor(name); if (!file.delete()) - throw new IOException("Cannot delete configuration file " + file.getName()); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.config_delete_error), file.getName())); } @Override @@ -80,11 +84,13 @@ public final class FileConfigStore implements ConfigStore { final File file = fileFor(name); final File replacementFile = fileFor(replacement); if (!replacementFile.createNewFile()) - throw new IOException("Configuration for " + replacement + " already exists"); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.config_exists_error), replacement)); if (!file.renameTo(replacementFile)) { if (!replacementFile.delete()) Log.w(TAG, "Couldn't delete marker file for new name " + replacement); - throw new IOException("Cannot rename configuration file " + file.getName()); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.config_rename_error), file.getName())); } } @@ -93,7 +99,8 @@ public final class FileConfigStore implements ConfigStore { Log.d(TAG, "Saving configuration for tunnel " + name); final File file = fileFor(name); if (!file.isFile()) - throw new FileNotFoundException("Configuration file " + file.getName() + " not found"); + throw new FileNotFoundException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.config_not_found_error), file.getName())); try (final FileOutputStream stream = new FileOutputStream(file, false)) { stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8)); } diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java index 0931868..878f933 100644 --- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java @@ -24,6 +24,7 @@ import com.wireguard.config.ParseException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.Objects; public class ConfigNamingDialogFragment extends DialogFragment { @@ -68,7 +69,8 @@ public class ConfigNamingDialogFragment extends DialogFragment { try { config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8))); } catch (final IOException | ParseException exception) { - throw new RuntimeException("Invalid config passed to " + getClass().getSimpleName(), exception); + throw new RuntimeException(String.format(Locale.getDefault(), + getResources().getString(R.string.invalid_config_error), getClass().getSimpleName()), exception); } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java index 5140574..85d66ba 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -115,14 +115,15 @@ public class TunnelListFragment extends BaseFragment { int idx = name.lastIndexOf('/'); if (idx >= 0) { if (idx >= name.length() - 1) - throw new IllegalArgumentException("Illegal file name: " + name); + throw new IllegalArgumentException(String.format(Locale.getDefault(), + getResources().getString(R.string.illegal_filename_error), name)); name = name.substring(idx + 1); } boolean isZip = name.toLowerCase(Locale.ENGLISH).endsWith(".zip"); if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf")) name = name.substring(0, name.length() - ".conf".length()); else if (!isZip) - throw new IllegalArgumentException("File must be .conf or .zip"); + throw new IllegalArgumentException(getResources().getString(R.string.bad_extension_error)); if (isZip) { try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri)); @@ -161,7 +162,7 @@ public class TunnelListFragment extends BaseFragment { if (throwables.size() == 1) throw throwables.get(0); else if (throwables.isEmpty()) - throw new IllegalArgumentException("No configurations found"); + throw new IllegalArgumentException(getResources().getString(R.string.no_configs_error)); } return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()])); diff --git a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java index a7f4950..5babb9a 100644 --- a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java @@ -44,7 +44,8 @@ public class LogExporterPreference extends Preference { final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); final File file = new File(path, "wireguard-log.txt"); if (!path.isDirectory() && !path.mkdirs()) - throw new IOException("Cannot create output directory"); + throw new IOException( + getContext().getResources().getString(R.string.create_output_dir_error)); /* We would like to simply run `builder.redirectOutput(file);`, but this is API 26. * Instead we have to do this dance, since logcat appends. diff --git a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java index ec02eb0..2273d6d 100644 --- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java @@ -26,11 +26,11 @@ public class VersionPreference extends Preference { super(context, attrs); Application.getBackendAsync().thenAccept(backend -> { - versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypeName().toLowerCase(Locale.ENGLISH)); + versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { versionSummary = exception == null - ? getContext().getString(R.string.version_summary, backend.getTypeName(), version) - : getContext().getString(R.string.version_summary_unknown, backend.getTypeName().toLowerCase(Locale.ENGLISH)); + ? getContext().getString(R.string.version_summary, backend.getTypePrettyName(), version) + : getContext().getString(R.string.version_summary_unknown, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH)); notifyChanged(); }); }); diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java index 5e33ba1..9aa48d0 100644 --- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java @@ -55,7 +55,8 @@ public class ZipExporterPreference extends Preference { for (final Tunnel tunnel : tunnels) futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); if (futureConfigs.isEmpty()) { - exportZipComplete(null, new IllegalArgumentException("No tunnels exist")); + exportZipComplete(null, new IllegalArgumentException( + getContext().getResources().getString(R.string.no_tunnels_error))); return; } CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()])) @@ -65,7 +66,8 @@ public class ZipExporterPreference extends Preference { final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); final File file = new File(path, "wireguard-export.zip"); if (!path.isDirectory() && !path.mkdirs()) - throw new IOException("Cannot create output directory"); + throw new IOException( + getContext().getResources().getString(R.string.create_output_dir_error)); try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) { for (int i = 0; i < futureConfigs.size(); ++i) { zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf")); diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java index 00d54ab..0e04185 100644 --- a/app/src/main/java/com/wireguard/android/util/RootShell.java +++ b/app/src/main/java/com/wireguard/android/util/RootShell.java @@ -20,6 +20,7 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.Locale; import java.util.UUID; /** @@ -30,6 +31,7 @@ public class RootShell { private static final String SU = "su"; private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName(); + private final Context context; private final String deviceNotRootedMessage; private final File localBinaryDir; private final File localTemporaryDir; @@ -47,6 +49,7 @@ public class RootShell { localTemporaryDir = new File(cacheDir, "tmp"); preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n", BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir); + this.context = context; } private static boolean isExecutableInPath(final String name) { @@ -121,9 +124,10 @@ public class RootShell { } } if (markersSeen != 4) - throw new IOException("Expected 4 markers, received " + markersSeen); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.marker_count_error), markersSeen)); if (errnoStdout != errnoStderr) - throw new IOException("Unable to read exit status"); + throw new IOException(context.getResources().getString(R.string.exit_status_read_error)); Log.v(TAG, "exit: " + errnoStdout); return errnoStdout; } @@ -136,9 +140,9 @@ public class RootShell { if (isRunning()) return; if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs()) - throw new FileNotFoundException("Could not create local binary directory"); + throw new FileNotFoundException(context.getResources().getString(R.string.create_bin_dir_error)); if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs()) - throw new FileNotFoundException("Could not create local temporary directory"); + throw new FileNotFoundException(context.getResources().getString(R.string.create_temp_dir_error)); try { final ProcessBuilder builder = new ProcessBuilder().command(SU); builder.environment().put("LC_ALL", "C"); @@ -168,7 +172,8 @@ public class RootShell { if (line.contains("Permission denied")) throw new NoRootException(deviceNotRootedMessage); } - throw new IOException("Shell failed to start: " + process.exitValue()); + throw new IOException(String.format(Locale.getDefault(), + context.getResources().getString(R.string.shell_start_error), process.exitValue())); } } catch (final IOException | NoRootException e) { stop(); diff --git a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java index 143a872..ebdfd3d 100644 --- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java @@ -12,6 +12,7 @@ import android.util.Log; import com.wireguard.android.Application; import com.wireguard.android.BuildConfig; +import com.wireguard.android.R; import com.wireguard.android.util.RootShell.NoRootException; import java.io.File; @@ -41,6 +42,7 @@ public final class ToolsInstaller { @Nullable private static final File INSTALL_DIR = getInstallDir(); private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName(); + private final Context context; private final File localBinaryDir; private final Object lock = new Object(); private final File nativeLibraryDir; @@ -50,6 +52,7 @@ public final class ToolsInstaller { public ToolsInstaller(final Context context) { localBinaryDir = new File(context.getCacheDir(), "bin"); nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir); + this.context = context; } @Nullable @@ -102,7 +105,8 @@ public final class ToolsInstaller { } } if (!areToolsAvailable) - throw new FileNotFoundException("Required tools unavailable"); + throw new FileNotFoundException( + context.getResources().getString(R.string.tools_unavailable_error)); } } diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java index 7f5b67e..ee99c98 100644 --- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java +++ b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java @@ -218,7 +218,7 @@ public class FloatingActionsMenu extends ViewGroup { attr.recycle(); if (mLabelsStyle != 0 && expandsHorizontally()) { - throw new IllegalStateException("Action labels in horizontal expand orientation is not supported."); + throw new IllegalStateException(getResources().getString(R.string.horizontal_expand_error)); } createAddButton(context); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6202ee7..548f607 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,4 +114,34 @@ Unable to export tunnels: %s Saved to %s Zip file will be saved to downloads folder + + + VPN service not authorized by user + Unable to start Android VPN service + Trying to bring up a tunnel with no config + Unable to create tun device + Unable to turn tunnel on (wgTurnOn returned %i) + Unable to configure tunnel (wg-quick returned %i) + Unable to determine kernel module version + Configuration file %s already exists + Configuration for %s already exists + Cannot delete configuration file %s + Cannot rename configuration file %s + Configuration file %s not found + No tunnels exist + Cannot create output directory + Illegal file name %s + File must be .conf or .zip + No configurations found + Invalid config passed to %s + Only one userspace tunnel can run at a time + Action labels in horizontal expand orientation is not supported. + Expected 4 markers, received %i + Unable to read exit status + Could not create local binary directory + Could not create local temporary directory + Shell failed to start: %i + Required tools unavailable + Kernel module + Go userspace