diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java index 2463896e..198684c1 100644 --- a/app/src/main/java/com/wireguard/android/QuickTileService.java +++ b/app/src/main/java/com/wireguard/android/QuickTileService.java @@ -23,7 +23,7 @@ import android.widget.Toast; import com.wireguard.android.activity.MainActivity; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel.State; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.widget.SlashDrawable; import java.util.Objects; @@ -114,7 +114,7 @@ public class QuickTileService extends TileService { @Nullable final Throwable throwable) { if (throwable == null) return; - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); final String message = getString(R.string.toggle_error, error); Log.e(TAG, message, throwable); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 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 b9cb573d..301bbd76 100644 --- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java +++ b/app/src/main/java/com/wireguard/android/backend/GoBackend.java @@ -126,7 +126,7 @@ public final class GoBackend implements Backend { Objects.requireNonNull(config, context.getString(R.string.no_config_error)); if (VpnService.prepare(context) != null) - throw new Exception(context.getString(R.string.vpn_not_authed_error)); + throw new Exception(context.getString(R.string.vpn_not_authorized_error)); final VpnService service; if (!vpnService.isDone()) diff --git a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java index 20633c3e..49cb51ee 100644 --- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java @@ -22,7 +22,7 @@ import com.wireguard.android.Application; import com.wireguard.android.R; import com.wireguard.android.databinding.AppListDialogFragmentBinding; import com.wireguard.android.model.ApplicationData; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.util.ObservableKeyedArrayList; import com.wireguard.android.util.ObservableKeyedList; @@ -73,7 +73,7 @@ public class AppListDialogFragment extends DialogFragment { appData.clear(); appData.addAll(data); } else { - final String error = throwable != null ? ExceptionLoggers.unwrapMessage(throwable) : "Unknown"; + final String error = ErrorMessages.get(throwable); final String message = activity.getString(R.string.error_fetching_apps, error); Toast.makeText(activity, message, Toast.LENGTH_LONG).show(); dismissAllowingStateLoss(); diff --git a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java index e3003f2e..3b2199aa 100644 --- a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java @@ -25,7 +25,7 @@ import com.wireguard.android.databinding.TunnelDetailFragmentBinding; 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.ErrorMessages; /** * Base class for fragments that need to know the currently-selected tunnel. Only does anything when @@ -111,7 +111,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> { if (throwable == null) return; - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); final int messageResId = checked ? R.string.error_up : R.string.error_down; final String message = getContext().getString(messageResId, error); final View view = getView(); 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 2cdf79ae..e56b4286 100644 --- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java @@ -27,8 +27,8 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; public class ConfigNamingDialogFragment extends DialogFragment { - private static final String KEY_CONFIG_TEXT = "config_text"; + @Nullable private ConfigNamingDialogFragmentBinding binding; @Nullable private Config config; @Nullable private InputMethodManager imm; @@ -65,23 +65,26 @@ public class ConfigNamingDialogFragment extends DialogFragment { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Bundle arguments = getArguments(); + final String configText = arguments.getString(KEY_CONFIG_TEXT); + final byte[] configBytes = configText.getBytes(StandardCharsets.UTF_8); try { - config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8))); + config = Config.parse(new ByteArrayInputStream(configBytes)); } catch (final BadConfigException | IOException e) { - throw new RuntimeException(getResources().getString(R.string.invalid_config_error, getClass().getSimpleName()), e); + throw new IllegalArgumentException("Invalid config passed to " + getClass().getSimpleName(), e); } } @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final Activity activity = getActivity(); + final Activity activity = Objects.requireNonNull(getActivity()); - imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); - alertDialogBuilder.setTitle(R.string.import_from_qrcode); + alertDialogBuilder.setTitle(R.string.import_from_qr_code); - binding = ConfigNamingDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false); + binding = ConfigNamingDialogFragmentBinding.inflate(activity.getLayoutInflater(), null, false); binding.executePendingBindings(); alertDialogBuilder.setView(binding.getRoot()); 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 f1250e64..7c4a162a 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java @@ -28,7 +28,7 @@ import com.wireguard.android.databinding.TunnelEditorFragmentBinding; import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.TunnelManager; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.viewmodel.ConfigProxy; import com.wireguard.config.Config; @@ -63,7 +63,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); onFinished(); } else { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); message = getString(R.string.config_save_error, savedTunnel.getName(), error); Log.e(TAG, message, throwable); if (binding != null) { @@ -140,7 +140,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi try { newConfig = binding.getConfig().resolve(); } catch (final Exception e) { - final String error = ExceptionLoggers.unwrapMessage(e); + final String error = ErrorMessages.get(e); final String tunnelName = tunnel == null ? binding.getName() : tunnel.getName(); final String message = getString(R.string.config_save_error, tunnelName, error); Log.e(TAG, message, e); @@ -208,7 +208,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); onFinished(); } else { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); message = getString(R.string.tunnel_create_error, error); Log.e(TAG, message, throwable); if (binding != null) { @@ -227,7 +227,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel.getName()); renamedTunnel.setConfig(newConfig).whenComplete((a, b) -> onConfigSaved(renamedTunnel, b)); } else { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); message = getString(R.string.tunnel_rename_error, error); Log.e(TAG, message, throwable); if (binding != null) { 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 4a6103ba..5871f262 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -37,7 +37,7 @@ import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter; import com.wireguard.android.databinding.TunnelListFragmentBinding; import com.wireguard.android.databinding.TunnelListItemBinding; import com.wireguard.android.model.Tunnel; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.widget.MultiselectableRelativeLayout; import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; import com.wireguard.config.BadConfigException; @@ -270,7 +270,7 @@ public class TunnelListFragment extends BaseFragment { final IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(this); intentIntegrator.setOrientationLocked(false); intentIntegrator.setBeepEnabled(false); - intentIntegrator.setPrompt(getString(R.string.qrcode_hint)); + intentIntegrator.setPrompt(getString(R.string.qr_code_hint)); intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE)); if (binding != null) @@ -301,7 +301,7 @@ public class TunnelListFragment extends BaseFragment { if (throwable == null) { message = getResources().getQuantityString(R.plurals.delete_success, count, count); } else { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); message = getResources().getQuantityString(R.plurals.delete_error, count, count, error); Log.e(TAG, message, throwable); } @@ -314,7 +314,7 @@ public class TunnelListFragment extends BaseFragment { String message = null; for (final Throwable throwable : throwables) { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); message = getString(R.string.import_error, error); Log.e(TAG, message, throwable); } 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 3836f9ac..3f59c7f8 100644 --- a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java @@ -17,7 +17,7 @@ import android.util.Log; import com.wireguard.android.Application; import com.wireguard.android.R; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.util.FragmentUtils; import java.io.BufferedReader; @@ -76,7 +76,7 @@ public class LogExporterPreference extends Preference { private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) { if (throwable != null) { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); final String message = getContext().getString(R.string.log_export_error, error); Log.e(TAG, message, throwable); Snackbar.make( @@ -98,7 +98,7 @@ public class LogExporterPreference extends Preference { @Override public CharSequence getTitle() { - return getContext().getString(R.string.log_exporter_title); + return getContext().getString(R.string.log_export_title); } @Override 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 f5385a2b..a2646053 100644 --- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java @@ -18,7 +18,7 @@ import android.util.Log; import com.wireguard.android.Application; import com.wireguard.android.R; import com.wireguard.android.model.Tunnel; -import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.util.ErrorMessages; import com.wireguard.android.util.FragmentUtils; import com.wireguard.config.Config; @@ -86,7 +86,7 @@ public class ZipExporterPreference extends Preference { private void exportZipComplete(@Nullable final String filePath, @Nullable final Throwable throwable) { if (throwable != null) { - final String error = ExceptionLoggers.unwrapMessage(throwable); + final String error = ErrorMessages.get(throwable); final String message = getContext().getString(R.string.zip_export_error, error); Log.e(TAG, message, throwable); Snackbar.make( @@ -108,7 +108,7 @@ public class ZipExporterPreference extends Preference { @Override public CharSequence getTitle() { - return getContext().getString(R.string.zip_exporter_title); + return getContext().getString(R.string.zip_export_title); } @Override diff --git a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java new file mode 100644 index 00000000..0f49448c --- /dev/null +++ b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java @@ -0,0 +1,130 @@ +/* + * Copyright © 2018 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.util; + +import android.content.res.Resources; +import android.support.annotation.Nullable; + +import com.wireguard.android.Application; +import com.wireguard.android.R; +import com.wireguard.config.BadConfigException; +import com.wireguard.config.BadConfigException.Location; +import com.wireguard.config.BadConfigException.Reason; +import com.wireguard.config.InetEndpoint; +import com.wireguard.config.InetNetwork; +import com.wireguard.config.ParseException; +import com.wireguard.crypto.Key.Format; +import com.wireguard.crypto.KeyFormatException; +import com.wireguard.crypto.KeyFormatException.Type; + +import java.net.InetAddress; +import java.util.EnumMap; +import java.util.Map; + +import java9.util.Maps; + +public final class ErrorMessages { + private static final Map BCE_REASON_MAP = new EnumMap<>(Maps.of( + Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key, + Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number, + Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value, + Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute, + Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section, + Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value, + Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error, + Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute, + Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section + )); + private static final Map KFE_FORMAT_MAP = new EnumMap<>(Maps.of( + Format.BASE64, R.string.key_length_explanation_base64, + Format.BINARY, R.string.key_length_explanation_binary, + Format.HEX, R.string.key_length_explanation_hex + )); + private static final Map KFE_TYPE_MAP = new EnumMap<>(Maps.of( + Type.CONTENTS, R.string.key_contents_error, + Type.LENGTH, R.string.key_length_error + )); + private static final Map PE_CLASS_MAP = Maps.of( + InetAddress.class, R.string.parse_error_inet_address, + InetEndpoint.class, R.string.parse_error_inet_endpoint, + InetNetwork.class, R.string.parse_error_inet_network, + Integer.class, R.string.parse_error_integer + ); + + private ErrorMessages() { + // Prevent instantiation + } + + public static String get(@Nullable final Throwable throwable) { + final Resources resources = Application.get().getResources(); + if (throwable == null) + return resources.getString(R.string.unknown_error); + final Throwable rootCause = rootCause(throwable); + final String message; + if (rootCause instanceof BadConfigException) { + final BadConfigException bce = (BadConfigException) rootCause; + final String reason = getBadConfigExceptionReason(resources, bce); + final String context = bce.getLocation() == Location.TOP_LEVEL ? + resources.getString(R.string.bad_config_context_top_level, + bce.getSection().getName()) : + resources.getString(R.string.bad_config_context, + bce.getSection().getName(), + bce.getLocation().getName()); + final String explanation = getBadConfigExceptionExplanation(resources, bce); + message = resources.getString(R.string.bad_config_error, reason, context) + explanation; + } else if (rootCause.getMessage() != null) { + message = rootCause.getMessage(); + } else { + final String errorType = rootCause.getClass().getSimpleName(); + message = resources.getString(R.string.generic_error, errorType); + } + return message; + } + + private static String getBadConfigExceptionExplanation(final Resources resources, + final BadConfigException bce) { + if (bce.getCause() instanceof KeyFormatException) { + final KeyFormatException kfe = (KeyFormatException) bce.getCause(); + if (kfe.getType() == Type.LENGTH) + return resources.getString(KFE_FORMAT_MAP.get(kfe.getFormat())); + } else if (bce.getCause() instanceof ParseException) { + final ParseException pe = (ParseException) bce.getCause(); + if (pe.getMessage() != null) + return ": " + pe.getMessage(); + } else if (bce.getLocation() == Location.LISTEN_PORT) { + return resources.getString(R.string.bad_config_explanation_udp_port); + } else if (bce.getLocation() == Location.MTU) { + return resources.getString(R.string.bad_config_explanation_positive_number); + } else if (bce.getLocation() == Location.PERSISTENT_KEEPALIVE) { + return resources.getString(R.string.bad_config_explanation_pka); + } + return ""; + } + + private static String getBadConfigExceptionReason(final Resources resources, + final BadConfigException bce) { + if (bce.getCause() instanceof KeyFormatException) { + final KeyFormatException kfe = (KeyFormatException) bce.getCause(); + return resources.getString(KFE_TYPE_MAP.get(kfe.getType())); + } else if (bce.getCause() instanceof ParseException) { + final ParseException pe = (ParseException) bce.getCause(); + final String type = resources.getString(PE_CLASS_MAP.containsKey(pe.getParsingClass()) ? + PE_CLASS_MAP.get(pe.getParsingClass()) : R.string.parse_error_generic); + return resources.getString(R.string.parse_error_reason, type, pe.getText()); + } + return resources.getString(BCE_REASON_MAP.get(bce.getReason()), bce.getText()); + } + + private static Throwable rootCause(final Throwable throwable) { + Throwable cause = throwable; + while (cause.getCause() != null) { + if (cause instanceof BadConfigException) + break; + cause = cause.getCause(); + } + return cause; + } +} diff --git a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java index 3cb5ca9f..5188112c 100644 --- a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java +++ b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java @@ -5,17 +5,9 @@ package com.wireguard.android.util; -import android.content.res.Resources; import android.support.annotation.Nullable; import android.util.Log; -import com.wireguard.android.Application; -import com.wireguard.android.R; -import com.wireguard.config.BadConfigException; -import com.wireguard.config.ParseException; -import com.wireguard.crypto.KeyFormatException; - -import java9.util.concurrent.CompletionException; import java9.util.function.BiConsumer; /** @@ -28,53 +20,12 @@ public enum ExceptionLoggers implements BiConsumer { E(Log.ERROR); private static final String TAG = "WireGuard/" + ExceptionLoggers.class.getSimpleName(); - private final int priority; ExceptionLoggers(final int priority) { this.priority = priority; } - public static Throwable unwrap(final Throwable throwable) { - if (throwable instanceof CompletionException && throwable.getCause() != null) - return throwable.getCause(); - if (throwable instanceof ParseException && throwable.getCause() != null) - return throwable.getCause(); - return throwable; - } - - public static String unwrapMessage(final Throwable throwable) { - final Throwable innerThrowable = unwrap(throwable); - final Resources resources = Application.get().getResources(); - String message; - if (innerThrowable instanceof BadConfigException) { - final BadConfigException configException = (BadConfigException) innerThrowable; - message = resources.getString(R.string.parse_error, configException.getText(), configException.getLocation()); - final Throwable cause = unwrap(configException); - if (cause.getMessage() != null) - message += ": " + cause.getMessage(); - } else if (innerThrowable instanceof KeyFormatException) { - final KeyFormatException keyFormatException = (KeyFormatException) innerThrowable; - switch (keyFormatException.getFormat()) { - case BASE64: - message = resources.getString(R.string.key_length_base64_exception_message); - break; - case BINARY: - message = resources.getString(R.string.key_length_exception_message); - break; - case HEX: - message = resources.getString(R.string.key_length_hex_exception_message); - break; - default: - // Will never happen, as getFormat is not nullable. - message = null; - } - } else { - message = innerThrowable.getMessage(); - } - return message != null ? message : innerThrowable.getClass().getSimpleName(); - } - @Override public void accept(final Object result, @Nullable final Throwable throwable) { if (throwable != null) 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 e48e3564..2dc02b56 100644 --- a/app/src/main/java/com/wireguard/android/util/RootShell.java +++ b/app/src/main/java/com/wireguard/android/util/RootShell.java @@ -123,9 +123,9 @@ public class RootShell { } } if (markersSeen != 4) - throw new IOException(context.getString(R.string.marker_count_error, markersSeen)); + throw new IOException(context.getString(R.string.shell_marker_count_error, markersSeen)); if (errnoStdout != errnoStderr) - throw new IOException(context.getString(R.string.exit_status_read_error)); + throw new IOException(context.getString(R.string.shell_exit_status_read_error)); Log.v(TAG, "exit: " + errnoStdout); return errnoStdout; } 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 ee99c980..8b69ce35 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(getResources().getString(R.string.horizontal_expand_error)); + throw new IllegalStateException("Action labels in horizontal expand orientation are not supported"); } createAddButton(context); diff --git a/app/src/main/res/layout/tunnel_list_fragment.xml b/app/src/main/res/layout/tunnel_list_fragment.xml index d255005a..38d0dcbd 100644 --- a/app/src/main/res/layout/tunnel_list_fragment.xml +++ b/app/src/main/res/layout/tunnel_list_fragment.xml @@ -89,7 +89,7 @@ android:layout_height="wrap_content" android:onClick="@{fragment::onRequestScanQRCode}" app:fabSize="mini" - app:fab_title="@string/create_from_qrcode" + app:fab_title="@string/create_from_qr_code" app:srcCompat="@drawable/ic_action_scan_qr_code_white" /> Addresses Allowed IPs WireGuard + %s\'s %s + %s + %s in %s + : Must be positive and no more than 65535 + : Must be positive + : Must be a valid UDP port number + Invalid key + Invalid number + Invalid value + Missing attribute + Missing section + Missing value + Syntax error + Unknown attribute + Unknown section + Value out of range File must be .conf or .zip Cancel Cannot delete configuration file %s - Configuration for %s already exists - Configuration file %s already exists - Configuration file %s not found - Cannot rename configuration file %s - Unable to save configuration for “%1$s”: %2$s + Configuration for “%s” already exists + Configuration file “%s” already exists + Configuration file “%s” not found + Cannot rename configuration file “%s” + Cannot save configuration for “%1$s”: %2$s Successfully saved configuration for “%s” Create WireGuard Tunnel - Could not create local binary directory + Cannot create local binary directory Create from scratch Create from file or archive - Create from QR code + Create from QR code Cannot create output directory - Could not create local temporary directory + Cannot create local temporary directory Create Tunnel - Currently using light day theme - Currently using dark night theme + Currently using light (day) theme + Currently using dark (night) theme Use dark theme Delete Deselect All @@ -59,28 +75,25 @@ Error bringing up tunnel: %s Exclude private IPs Excluded Applications - Unable to read exit status Generate + Unknown “%s” error (auto) (generated) (optional) (random) - Action labels in horizontal expand orientation is not supported. - Illegal file name %s + Illegal file name “%s” Unable to import tunnel: %s - Import Tunnel from QR Code + Import Tunnel from QR Code Imported “%s” Interface - Invalid config passed to %s - WireGuard base64 keys must be 44 characters encoding 32 bytes - WireGuard keys must be 32 bytes - WireGuard hex keys must be 64 characters encoding 32 bytes + : WireGuard base64 keys must be 44 characters (32 bytes) + : WireGuard keys must be 32 bytes + : WireGuard hex keys must be 64 characters (32 bytes) Listen port Unable to export log: %s - Saved to %s + Saved to “%s” Log file will be saved to downloads folder - Export log file - Expected 4 markers, received %i + Export log file Unable to determine kernel module version MTU Only one userspace tunnel can run at a time @@ -88,7 +101,12 @@ Trying to bring up a tunnel with no config No configurations found No tunnels exist - Cannot parse “%1$s” at %2$s + string + IP address + endpoint + IP network + number + Cannot parse %1$s “%2$s” Peer Allows an app to control WireGuard tunnels. Apps with this permission may enable and disable WireGuard tunnels at will, potentially misdirecting Internet traffic. control WireGuard tunnels @@ -97,13 +115,15 @@ Private key Public key Public key - Tip: generate with `qrencode -t ansiutf8 < tunnel.conf`. + Tip: generate with `qrencode -t ansiutf8 < tunnel.conf`. Bring up previously-enabled tunnels on boot Restore on boot Save Select all Set Exclusions Settings + Shell cannot read exit status + Shell expected 4 markers, received %i Shell failed to start: %i Error toggling WireGuard tunnel: %s wg and wg-quick are already installed @@ -120,10 +140,7 @@ Unable to configure tunnel (wg-quick returned %i) Unable to create tunnel: %s Successfully created tunnel “%s” - Tunnel %s already exists - Address is empty - Peer public key may not be empty - Forbidden characters in endpoint + Tunnel “%s” already exists Invalid name Add a tunnel using the blue button Tunnel Name @@ -132,14 +149,17 @@ Successfully renamed tunnel to “%s” Go userspace Kernel module + Unknown error %1$s backend v%2$s Checking %s backend version Unknown %s version - WireGuard for Android v%s" - VPN service not authorized by user + WireGuard for Android v%s + VPN service not authorized by user Unable to start Android VPN service Unable to export tunnels: %s - Saved to %s + Saved to “%s” Zip file will be saved to downloads folder - Export tunnels to zip file + Export tunnels to zip file + Incorrect key length + Bad characters in key