diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java index 0468f83f..7cbcf559 100644 --- a/app/src/main/java/com/wireguard/android/QuickTileService.java +++ b/app/src/main/java/com/wireguard/android/QuickTileService.java @@ -16,7 +16,6 @@ import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel.State; import com.wireguard.android.model.TunnelManager; import com.wireguard.android.util.ExceptionLoggers; -import com.wireguard.android.util.RootShell; import java.util.Objects; @@ -63,16 +62,13 @@ public class QuickTileService extends TileService { tunnelManager.removeOnPropertyChangedCallback(onTunnelChangedCallback); } - @SuppressWarnings("unused") - private Void onToggleFinished(final State state, final Throwable throwable) { + private void onToggleFinished(final State state, final Throwable throwable) { if (throwable == null) - return null; - Log.e(TAG, "Cannot toggle tunnel", throwable); - final String message = throwable instanceof RootShell.NoRootException ? - getApplicationContext().getString(R.string.error_rootshell) : - getApplicationContext().getString(R.string.error_toggle) + ": " + throwable.getMessage(); - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); - return null; + return; + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + final String message = getString(R.string.toggle_error, error); + Log.e(TAG, message, throwable); + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } private void updateTile() { 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 5ac27af4..cb5eae5d 100644 --- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java @@ -55,7 +55,7 @@ public final class WgQuickBackend implements Backend { try { if (rootShell.run(output, "wg show interfaces") != 0 || output.isEmpty()) return Collections.emptySet(); - } catch (Exception e) { + } catch (final Exception ignored) { return Collections.emptySet(); } // wg puts all interface names on the same line. Split them into separate elements. diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java b/app/src/main/java/com/wireguard/android/fragment/TunnelController.java index 1a9fed6b..fd265d20 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelController.java @@ -19,7 +19,6 @@ import com.wireguard.android.databinding.TunnelListItemBinding; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel.State; import com.wireguard.android.util.ExceptionLoggers; -import com.wireguard.android.util.RootShell; /** * Helper method shared by TunnelListFragment and TunnelDetailFragment. @@ -48,7 +47,6 @@ public final class TunnelController { tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> { if (throwable == null) return; - Log.e(TAG, "Cannot set state of tunnel " + tunnel.getName(), throwable); final Context context = view.getContext(); if (throwable instanceof ErrnoException && ((ErrnoException) throwable).errno == OsConstants.ENODEV) { @@ -61,13 +59,13 @@ public final class TunnelController { // Make links work. ((TextView) dialog.findViewById(android.R.id.message)) .setMovementMethod(LinkMovementMethod.getInstance()); - } else if (throwable instanceof RootShell.NoRootException) { - Snackbar.make(view, R.string.error_rootshell, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, "WireGuard not supported"); } else { - final String message = - context.getString(checked ? R.string.error_up : R.string.error_down) + ": " - + ExceptionLoggers.unwrap(throwable).getMessage(); + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + final int messageResId = checked ? R.string.error_up : R.string.error_down; + final String message = context.getString(messageResId, error); Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); + Log.e(TAG, message, throwable); } }); } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java index f1cd7263..f72a6aea 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java @@ -60,17 +60,19 @@ public class TunnelEditorFragment extends BaseFragment { } private void onConfigSaved(final Config config, final Throwable throwable) { - if (throwable != null) { - Log.e(TAG, "Cannot save configuration", throwable); - final String message = "Cannot save configuration: " - + ExceptionLoggers.unwrap(throwable).getMessage(); + final String message; + if (throwable == null) { + message = getString(R.string.config_save_success, localTunnel.getName()); + Log.d(TAG, message); + onFinished(); + } else { + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getString(R.string.config_save_error, localTunnel.getName(), error); + Log.e(TAG, message, throwable); if (binding != null) { final CoordinatorLayout container = binding.mainContainer; Snackbar.make(container, message, Snackbar.LENGTH_LONG).show(); } - } else { - Log.d(TAG, "Successfully saved configuration for " + localTunnel.getName()); - onFinished(); } } @@ -191,38 +193,40 @@ public class TunnelEditorFragment extends BaseFragment { } private void onTunnelCreated(final Tunnel tunnel, final Throwable throwable) { - if (throwable != null) { - Log.e(TAG, "Cannot create tunnel", throwable); - final String message = "Cannot create tunnel: " - + ExceptionLoggers.unwrap(throwable).getMessage(); + final String message; + if (throwable == null) { + message = getString(R.string.tunnel_create_success, tunnel.getName()); + Log.d(TAG, message); + onFinished(); + } else { + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getString(R.string.tunnel_create_error, error); + Log.e(TAG, message, throwable); if (binding != null) { final CoordinatorLayout container = binding.mainContainer; Snackbar.make(container, message, Snackbar.LENGTH_LONG).show(); } - } else { - Log.d(TAG, "Successfully created tunnel " + tunnel.getName()); - localTunnel = tunnel; - onFinished(); } } - private void onTunnelRenamed(final Tunnel tunnel, final Throwable throwable) { - if (throwable != null) { - Log.e(TAG, "Cannot rename tunnel", throwable); - final String message = "Cannot rename tunnel: " - + ExceptionLoggers.unwrap(throwable).getMessage(); + final String message; + if (throwable == null) { + message = getString(R.string.tunnel_rename_success, localTunnel.getName(), + tunnel.getName()); + Log.d(TAG, message); + localTunnel = tunnel; + // Now save the rest of configuration changes. + Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel.getName()); + tunnel.setConfig(localConfig).whenComplete(this::onConfigSaved); + } else { + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getString(R.string.tunnel_rename_error, error); + Log.e(TAG, message, throwable); if (binding != null) { final CoordinatorLayout container = binding.mainContainer; Snackbar.make(container, message, Snackbar.LENGTH_LONG).show(); } - } else { - Log.d(TAG, "Successfully renamed tunnel to " + tunnel.getName()); - localTunnel = tunnel; - // Now save the rest of configuration changes. - Log.d(TAG, "Attempting to save config of " + tunnel.getName()); - tunnel.setConfig(localConfig) - .whenComplete(this::onConfigSaved); } } 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 4634889c..79bf5d02 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -148,13 +148,12 @@ public class TunnelListFragment extends BaseFragment { private void onTunnelDeletionFinished(final Integer count, final Throwable throwable) { final String message; - final String plural = count == 1 ? "" : "s"; if (throwable == null) { - message = "Successfully deleted " + count + " tunnel" + plural; + message = getResources().getQuantityString(R.plurals.delete_success, count, count); } else { - message = "Unable to delete tunnel" + plural + ": " - + ExceptionLoggers.unwrap(throwable).getMessage(); - Log.e(TAG, "Cannot delete tunnel" + plural, throwable); + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getResources().getQuantityString(R.plurals.delete_error, count, count, error); + Log.e(TAG, message, throwable); } if (binding != null) { final CoordinatorLayout container = binding.mainContainer; @@ -165,11 +164,11 @@ public class TunnelListFragment extends BaseFragment { private void onTunnelImportFinished(final Tunnel tunnel, final Throwable throwable) { final String message; if (throwable == null) { - message = "Successfully imported tunnel '" + tunnel.getName() + '\''; + message = getString(R.string.import_success, tunnel.getName()); } else { - message = "Cannot import tunnel: " - + ExceptionLoggers.unwrap(throwable).getMessage(); - Log.e(TAG, "Cannot import tunnel", throwable); + final String error = ExceptionLoggers.unwrap(throwable).getMessage(); + message = getString(R.string.import_error, error); + Log.e(TAG, message, throwable); } if (binding != null) { final CoordinatorLayout container = binding.mainContainer; @@ -245,7 +244,7 @@ public class TunnelListFragment extends BaseFragment { private void updateTitle(final ActionMode mode) { final int count = (int) getCheckedPositions().count(); - mode.setTitle(resources.getQuantityString(R.plurals.list_delete_title, count, count)); + mode.setTitle(resources.getQuantityString(R.plurals.delete_title, count, count)); } } 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 116fbd2c..2c498131 100644 --- a/app/src/main/java/com/wireguard/android/util/RootShell.java +++ b/app/src/main/java/com/wireguard/android/util/RootShell.java @@ -7,12 +7,14 @@ import android.util.Log; import com.wireguard.android.Application.ApplicationContext; import com.wireguard.android.Application.ApplicationScope; +import com.wireguard.android.R; import java.io.BufferedWriter; import java.io.BufferedReader; import java.io.OutputStreamWriter; import java.io.InputStreamReader; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.UUID; @@ -34,6 +36,7 @@ public class RootShell { {"libwg-quick.so", "wg-quick"} }; + private final String exceptionMessage; private final String preamble; private BufferedWriter stdin; @@ -59,6 +62,7 @@ public class RootShell { builder.append(String.format("export PATH=\"%s:$PATH\" TMPDIR=\"%s\";", binDir, tmpDir)); builder.append("id;\n"); + exceptionMessage = context.getString(R.string.error_root); preamble = builder.toString(); } @@ -72,7 +76,7 @@ public class RootShell { return false; } - private void ensureRoot() throws Exception { + private void ensureRoot() throws ErrnoException, IOException, NoRootException { try { if (process != null) { process.exitValue(); @@ -83,7 +87,7 @@ public class RootShell { } if (!isExecutable("su")) - throw new NoRootException(); + throw new NoRootException(exceptionMessage); try { final ProcessBuilder builder = new ProcessBuilder(); @@ -103,8 +107,8 @@ public class RootShell { int errno = process.exitValue(); String line; while ((line = stderr.readLine()) != null) { - if (line.contains("Permission denied")) - throw new NoRootException(); + if (line.contains("Permission denied")) + throw new NoRootException(exceptionMessage); } throw new ErrnoException("Unknown error when obtaining root access", errno); } catch (IllegalThreadStateException e) { @@ -112,7 +116,7 @@ public class RootShell { } if (id == null || !id.contains("uid=0")) - throw new NoRootException(); + throw new NoRootException(exceptionMessage); } catch (Exception e) { Log.w(TAG, "Session failed with exception", e); process.destroy(); @@ -121,7 +125,7 @@ public class RootShell { if (match.find()) { final int errno = Integer.valueOf(match.group(1)); if (errno == OsConstants.EACCES) - throw new NoRootException(); + throw new NoRootException(exceptionMessage); else throw new ErrnoException("Unknown error when obtaining root access", errno); } @@ -137,7 +141,8 @@ public class RootShell { * @param command Command to run as root. * @return The exit value of the last command run, or -1 if there was an internal error. */ - public int run(final List output, final String command) throws Exception { + public int run(final List output, final String command) + throws ErrnoException, IOException, NoRootException { ensureRoot(); StringBuilder builder = new StringBuilder(); @@ -205,6 +210,9 @@ public class RootShell { return errnoStdout; } - public class NoRootException extends Exception { + public static class NoRootException extends Exception { + public NoRootException(final String message) { + super(message); + } } } 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 aeffa364..4b844991 100644 --- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java @@ -1,12 +1,15 @@ package com.wireguard.android.util; import android.content.Context; +import android.system.ErrnoException; import android.system.OsConstants; import com.wireguard.android.Application.ApplicationContext; import com.wireguard.android.Application.ApplicationScope; +import com.wireguard.android.util.RootShell.NoRootException; import java.io.File; +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -67,7 +70,11 @@ public final class ToolsInstaller { } try { return rootShell.run(null, script.toString()); - } catch (Exception e) { + } catch (final ErrnoException e) { + return e.errno; + } catch (final IOException ignored) { + return OsConstants.EXIT_FAILURE; + } catch (final NoRootException ignored) { return OsConstants.EACCES; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d129454..7140bb7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,13 +1,23 @@ - - - %d configuration selected - %d configurations selected + + + Unable to delete %d tunnel: %s + Unable to delete %d tunnels: %s + + + Successfully deleted %d tunnel + Successfully deleted %d tunnels + + + %d tunnel selected + %d tunnels selected Add peer Addresses Allowed IPs WireGuard + Unable to configuration for “%s”: %s + Successfully saved configuration for “%s” Create WireGuard Tunnel Create from scratch Create from file @@ -15,20 +25,21 @@ DNS servers Edit Endpoint - Error bringing down WireGuard tunnel - Error bringing up WireGuard tunnel - Error toggling WireGuard tunnel - Please obtain root access and try again + Error bringing down tunnel: %s + Please obtain root access and try again + Error bringing up tunnel: %s Generate (auto) (generated) (optional) (random) + Unable to import tunnel: %s + Successfully imported “%s” Interface Listen port MTU Name - Your Android device does not currently have the WireGuard kernel module. Please talk to the manufacturer of your Android device or the author of your device’s ROM about including the WireGuard kernel module.

@@ -55,10 +66,15 @@ Restore on boot Save Settings + Error toggling WireGuard tunnel: %s wg and wg-quick are already installed - Command line tools could not be installed (no root?) + Unable to install command-line tools (no root?) Install optional tools for scripting into the system partition wg and wg-quick installed into the system partition Install command line tools Installing wg and wg-quick into the system partition + Unable to create tunnel: %s + Successfully created tunnel “%s” + Unable to rename tunnel: %s + Successfully renamed tunnel “%s” to “%s”