global: format code
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
		
							parent
							
								
									40ebf8006e
								
							
						
					
					
						commit
						2e55e5fd05
					
				| @ -9,6 +9,21 @@ import com.wireguard.util.NonNullForAll; | ||||
| 
 | ||||
| @NonNullForAll | ||||
| public final class BackendException extends Exception { | ||||
|     private final Object[] format; | ||||
|     private final Reason reason; | ||||
|     public BackendException(final Reason reason, final Object... format) { | ||||
|         this.reason = reason; | ||||
|         this.format = format; | ||||
|     } | ||||
| 
 | ||||
|     public Object[] getFormat() { | ||||
|         return format; | ||||
|     } | ||||
| 
 | ||||
|     public Reason getReason() { | ||||
|         return reason; | ||||
|     } | ||||
| 
 | ||||
|     public enum Reason { | ||||
|         UNKNOWN_KERNEL_MODULE_NAME, | ||||
|         WG_QUICK_CONFIG_ERROR_CODE, | ||||
| @ -18,16 +33,4 @@ public final class BackendException extends Exception { | ||||
|         TUN_CREATION_ERROR, | ||||
|         GO_ACTIVATION_ERROR_CODE | ||||
|     } | ||||
|     private final Reason reason; | ||||
|     private final Object[] format; | ||||
|     public BackendException(final Reason reason, final Object ...format) { | ||||
|         this.reason = reason; | ||||
|         this.format = format; | ||||
|     } | ||||
|     public Reason getReason() { | ||||
|         return reason; | ||||
|     } | ||||
|     public Object[] getFormat() { | ||||
|         return format; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -34,25 +34,21 @@ import java9.util.concurrent.CompletableFuture; | ||||
| @NonNullForAll | ||||
| public final class GoBackend implements Backend { | ||||
|     private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName(); | ||||
|     private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); | ||||
|     public interface AlwaysOnCallback { | ||||
|         void alwaysOnTriggered(); | ||||
|     } | ||||
|     @Nullable private static AlwaysOnCallback alwaysOnCallback; | ||||
|     public static void setAlwaysOnCallback(AlwaysOnCallback cb) { | ||||
|         alwaysOnCallback = cb; | ||||
|     } | ||||
| 
 | ||||
|     private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>(); | ||||
|     private final Context context; | ||||
|     @Nullable private Tunnel currentTunnel; | ||||
|     @Nullable private Config currentConfig; | ||||
|     @Nullable private Tunnel currentTunnel; | ||||
|     private int currentTunnelHandle = -1; | ||||
| 
 | ||||
|     public GoBackend(final Context context) { | ||||
|         SharedLibraryLoader.loadSharedLibrary(context, "wg-go"); | ||||
|         this.context = context; | ||||
|     } | ||||
| 
 | ||||
|     public static void setAlwaysOnCallback(AlwaysOnCallback cb) { | ||||
|         alwaysOnCallback = cb; | ||||
|     } | ||||
| 
 | ||||
|     private static native String wgGetConfig(int handle); | ||||
| 
 | ||||
|     private static native int wgGetSocketV4(int handle); | ||||
| @ -143,7 +139,7 @@ public final class GoBackend implements Backend { | ||||
|                 setStateInternal(currentTunnel, null, State.DOWN); | ||||
|             try { | ||||
|                 setStateInternal(tunnel, config, state); | ||||
|             } catch(final Exception e) { | ||||
|             } catch (final Exception e) { | ||||
|                 if (originalTunnel != null) | ||||
|                     setStateInternal(originalTunnel, originalConfig, State.UP); | ||||
|                 throw e; | ||||
| @ -209,7 +205,7 @@ public final class GoBackend implements Backend { | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) | ||||
|                 builder.setMetered(false); | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) | ||||
| 	        service.setUnderlyingNetworks(null); | ||||
|                 service.setUnderlyingNetworks(null); | ||||
| 
 | ||||
|             builder.setBlocking(true); | ||||
|             try (final ParcelFileDescriptor tun = builder.establish()) { | ||||
| @ -246,13 +242,13 @@ public final class GoBackend implements Backend { | ||||
|         context.startService(new Intent(context, VpnService.class)); | ||||
|     } | ||||
| 
 | ||||
|     public interface AlwaysOnCallback { | ||||
|         void alwaysOnTriggered(); | ||||
|     } | ||||
| 
 | ||||
|     public static class VpnService extends android.net.VpnService { | ||||
|         @Nullable private GoBackend owner; | ||||
| 
 | ||||
|         public void setOwner(final GoBackend owner) { | ||||
|             this.owner = owner; | ||||
|         } | ||||
| 
 | ||||
|         public Builder getBuilder() { | ||||
|             return new Builder(); | ||||
|         } | ||||
| @ -290,5 +286,9 @@ public final class GoBackend implements Backend { | ||||
|             } | ||||
|             return super.onStartCommand(intent, flags, startId); | ||||
|         } | ||||
| 
 | ||||
|         public void setOwner(final GoBackend owner) { | ||||
|             this.owner = owner; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -16,10 +16,11 @@ import java.util.Map; | ||||
| 
 | ||||
| @NonNullForAll | ||||
| public class Statistics { | ||||
|     private long lastTouched = SystemClock.elapsedRealtime(); | ||||
|     private final Map<Key, Pair<Long, Long>> peerBytes = new HashMap<>(); | ||||
|     private long lastTouched = SystemClock.elapsedRealtime(); | ||||
| 
 | ||||
|     Statistics() { } | ||||
|     Statistics() { | ||||
|     } | ||||
| 
 | ||||
|     void add(final Key key, final long rx, final long tx) { | ||||
|         peerBytes.put(key, Pair.create(rx, tx)); | ||||
| @ -30,10 +31,6 @@ public class Statistics { | ||||
|         return SystemClock.elapsedRealtime() - lastTouched > 900; | ||||
|     } | ||||
| 
 | ||||
|     public Key[] peers() { | ||||
|         return peerBytes.keySet().toArray(new Key[0]); | ||||
|     } | ||||
| 
 | ||||
|     public long peerRx(final Key peer) { | ||||
|         if (!peerBytes.containsKey(peer)) | ||||
|             return 0; | ||||
| @ -46,6 +43,10 @@ public class Statistics { | ||||
|         return peerBytes.get(peer).second; | ||||
|     } | ||||
| 
 | ||||
|     public Key[] peers() { | ||||
|         return peerBytes.keySet().toArray(new Key[0]); | ||||
|     } | ||||
| 
 | ||||
|     public long totalRx() { | ||||
|         long rx = 0; | ||||
|         for (final Pair<Long, Long> val : peerBytes.values()) { | ||||
|  | ||||
| @ -15,16 +15,6 @@ import java.util.regex.Pattern; | ||||
| 
 | ||||
| @NonNullForAll | ||||
| public interface Tunnel { | ||||
|     enum State { | ||||
|         DOWN, | ||||
|         TOGGLE, | ||||
|         UP; | ||||
| 
 | ||||
|         public static State of(final boolean running) { | ||||
|             return running ? UP : DOWN; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     int NAME_MAX_LENGTH = 15; | ||||
|     Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}"); | ||||
| 
 | ||||
| @ -46,4 +36,14 @@ public interface Tunnel { | ||||
|      * @return The new state of the tunnel. | ||||
|      */ | ||||
|     void onStateChange(State newState); | ||||
| 
 | ||||
|     enum State { | ||||
|         DOWN, | ||||
|         TOGGLE, | ||||
|         UP; | ||||
| 
 | ||||
|         public static State of(final boolean running) { | ||||
|             return running ? UP : DOWN; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -42,11 +42,10 @@ import java9.util.stream.Stream; | ||||
| @NonNullForAll | ||||
| public final class WgQuickBackend implements Backend { | ||||
|     private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName(); | ||||
| 
 | ||||
|     private final RootShell rootShell; | ||||
|     private final ToolsInstaller toolsInstaller; | ||||
|     private final File localTemporaryDir; | ||||
|     private final RootShell rootShell; | ||||
|     private final Map<Tunnel, Config> runningConfigs = new HashMap<>(); | ||||
|     private final ToolsInstaller toolsInstaller; | ||||
|     private boolean multipleTunnels; | ||||
| 
 | ||||
|     public WgQuickBackend(final Context context, final RootShell rootShell, final ToolsInstaller toolsInstaller) { | ||||
| @ -55,10 +54,6 @@ public final class WgQuickBackend implements Backend { | ||||
|         this.toolsInstaller = toolsInstaller; | ||||
|     } | ||||
| 
 | ||||
|     public void setMultipleTunnels(boolean on) { | ||||
|         multipleTunnels = on; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Set<String> getRunningTunnelNames() { | ||||
|         final List<String> output = new ArrayList<>(); | ||||
| @ -110,6 +105,10 @@ public final class WgQuickBackend implements Backend { | ||||
|         return output.get(0); | ||||
|     } | ||||
| 
 | ||||
|     public void setMultipleTunnels(boolean on) { | ||||
|         multipleTunnels = on; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public State setState(final Tunnel tunnel, State state, @Nullable final Config config) throws Exception { | ||||
|         final State originalState = getState(tunnel); | ||||
| @ -135,7 +134,8 @@ public final class WgQuickBackend implements Backend { | ||||
|                         for (final Pair<Tunnel, Config> entry : rewind) { | ||||
|                             setStateInternal(entry.first, entry.second, State.UP); | ||||
|                         } | ||||
|                     } catch (final Exception ignored) { } | ||||
|                     } catch (final Exception ignored) { | ||||
|                     } | ||||
|                     throw e; | ||||
|                 } | ||||
|             } | ||||
| @ -153,7 +153,8 @@ public final class WgQuickBackend implements Backend { | ||||
|                             setStateInternal(entry.getKey(), entry.getValue(), State.UP); | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (final Exception ignored) { } | ||||
|                 } catch (final Exception ignored) { | ||||
|                 } | ||||
|                 throw e; | ||||
|             } | ||||
|         } else if (state == State.DOWN) { | ||||
|  | ||||
| @ -39,15 +39,14 @@ import androidx.annotation.Nullable; | ||||
| 
 | ||||
| @NonNullForAll | ||||
| public class ModuleLoader { | ||||
|     private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If"; | ||||
|     private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig"; | ||||
|     private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s"; | ||||
|     private static final String MODULE_NAME = "wireguard-%s.ko"; | ||||
| 
 | ||||
|     private final RootShell rootShell; | ||||
|     private final String userAgent; | ||||
|     private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If"; | ||||
|     private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s"; | ||||
|     private final File moduleDir; | ||||
|     private final RootShell rootShell; | ||||
|     private final File tmpDir; | ||||
|     private final String userAgent; | ||||
| 
 | ||||
|     public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) { | ||||
|         moduleDir = new File(context.getCacheDir(), "kmod"); | ||||
| @ -56,27 +55,75 @@ public class ModuleLoader { | ||||
|         this.userAgent = userAgent; | ||||
|     } | ||||
| 
 | ||||
|     public boolean moduleMightExist() { | ||||
|         return moduleDir.exists() && moduleDir.isDirectory(); | ||||
|     public static boolean isModuleLoaded() { | ||||
|         return new File("/sys/module/wireguard").exists(); | ||||
|     } | ||||
| 
 | ||||
|     public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException { | ||||
|         final List<String> output = new ArrayList<>(); | ||||
|         rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1"); | ||||
|         if (output.size() != 1 || output.get(0).length() != 64) | ||||
|             throw new InvalidParameterException("Invalid sha256 of /proc/version"); | ||||
|         final String moduleName = String.format(MODULE_NAME, output.get(0)); | ||||
|         HttpURLConnection connection = (HttpURLConnection) new URL(MODULE_LIST_URL).openConnection(); | ||||
|         connection.setRequestProperty("User-Agent", userAgent); | ||||
|         connection.connect(); | ||||
|         if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) | ||||
|             throw new IOException("Hash list could not be found"); | ||||
|         byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */]; | ||||
|         int len; | ||||
|         try (final InputStream inputStream = connection.getInputStream()) { | ||||
|             len = inputStream.read(input); | ||||
|         } | ||||
|         if (len <= 0) | ||||
|             throw new IOException("Hash list was empty"); | ||||
|         final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8)); | ||||
|         if (modules == null) | ||||
|             throw new InvalidParameterException("The signature did not verify or invalid hash list format"); | ||||
|         if (!modules.containsKey(moduleName)) | ||||
|             return OsConstants.ENOENT; | ||||
|         connection = (HttpURLConnection) new URL(String.format(MODULE_URL, moduleName)).openConnection(); | ||||
|         connection.setRequestProperty("User-Agent", userAgent); | ||||
|         connection.connect(); | ||||
|         if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) | ||||
|             throw new IOException("Module file could not be found, despite being on hash list"); | ||||
| 
 | ||||
|         tmpDir.mkdirs(); | ||||
|         moduleDir.mkdir(); | ||||
|         File tempFile = null; | ||||
|         try { | ||||
|             tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir); | ||||
|             MessageDigest digest = MessageDigest.getInstance("SHA-256"); | ||||
|             try (final InputStream inputStream = connection.getInputStream(); | ||||
|                  final FileOutputStream outputStream = new FileOutputStream(tempFile)) { | ||||
|                 int total = 0; | ||||
|                 while ((len = inputStream.read(input)) > 0) { | ||||
|                     total += len; | ||||
|                     if (total > 1024 * 1024 * 15 /* 15 MiB */) | ||||
|                         throw new IOException("File too big"); | ||||
|                     outputStream.write(input, 0, len); | ||||
|                     digest.update(input, 0, len); | ||||
|                 } | ||||
|                 outputStream.getFD().sync(); | ||||
|             } | ||||
|             if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes)) | ||||
|                 throw new IOException("Incorrect file hash"); | ||||
| 
 | ||||
|             if (!tempFile.renameTo(new File(moduleDir, moduleName))) | ||||
|                 throw new IOException("Unable to rename to final destination"); | ||||
|         } finally { | ||||
|             if (tempFile != null) | ||||
|                 tempFile.delete(); | ||||
|         } | ||||
|         return OsConstants.EXIT_SUCCESS; | ||||
|     } | ||||
| 
 | ||||
|     public void loadModule() throws IOException, RootShellException { | ||||
|         rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath())); | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isModuleLoaded() { | ||||
|         return new File("/sys/module/wireguard").exists(); | ||||
|     } | ||||
| 
 | ||||
|     private static final class Sha256Digest { | ||||
|         private byte[] bytes; | ||||
|         private Sha256Digest(final String hex) { | ||||
|             if (hex.length() != 64) | ||||
|                 throw new InvalidParameterException("SHA256 hashes must be 32 bytes long"); | ||||
|             bytes = new byte[32]; | ||||
|             for (int i = 0; i < 32; ++i) | ||||
|                 bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); | ||||
|         } | ||||
|     public boolean moduleMightExist() { | ||||
|         return moduleDir.exists() && moduleDir.isDirectory(); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
| @ -127,62 +174,15 @@ public class ModuleLoader { | ||||
|         return hashes; | ||||
|     } | ||||
| 
 | ||||
|     public Integer download() throws IOException, RootShellException, NoSuchAlgorithmException { | ||||
|         final List<String> output = new ArrayList<>(); | ||||
|         rootShell.run(output, "sha256sum /proc/version|cut -d ' ' -f 1"); | ||||
|         if (output.size() != 1 || output.get(0).length() != 64) | ||||
|             throw new InvalidParameterException("Invalid sha256 of /proc/version"); | ||||
|         final String moduleName = String.format(MODULE_NAME, output.get(0)); | ||||
|         HttpURLConnection connection = (HttpURLConnection)new URL(MODULE_LIST_URL).openConnection(); | ||||
|         connection.setRequestProperty("User-Agent", userAgent); | ||||
|         connection.connect(); | ||||
|         if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) | ||||
|             throw new IOException("Hash list could not be found"); | ||||
|         byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */]; | ||||
|         int len; | ||||
|         try (final InputStream inputStream = connection.getInputStream()) { | ||||
|             len = inputStream.read(input); | ||||
|         } | ||||
|         if (len <= 0) | ||||
|             throw new IOException("Hash list was empty"); | ||||
|         final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8)); | ||||
|         if (modules == null) | ||||
|             throw new InvalidParameterException("The signature did not verify or invalid hash list format"); | ||||
|         if (!modules.containsKey(moduleName)) | ||||
|             return OsConstants.ENOENT; | ||||
|         connection = (HttpURLConnection)new URL(String.format(MODULE_URL, moduleName)).openConnection(); | ||||
|         connection.setRequestProperty("User-Agent", userAgent); | ||||
|         connection.connect(); | ||||
|         if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) | ||||
|             throw new IOException("Module file could not be found, despite being on hash list"); | ||||
|     private static final class Sha256Digest { | ||||
|         private byte[] bytes; | ||||
| 
 | ||||
|         tmpDir.mkdirs(); | ||||
|         moduleDir.mkdir(); | ||||
|         File tempFile = null; | ||||
|         try { | ||||
|             tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir); | ||||
|             MessageDigest digest = MessageDigest.getInstance("SHA-256"); | ||||
|             try (final InputStream inputStream = connection.getInputStream(); | ||||
|                  final FileOutputStream outputStream = new FileOutputStream(tempFile)) { | ||||
|                 int total = 0; | ||||
|                 while ((len = inputStream.read(input)) > 0) { | ||||
|                     total += len; | ||||
|                     if (total > 1024 * 1024 * 15 /* 15 MiB */) | ||||
|                         throw new IOException("File too big"); | ||||
|                     outputStream.write(input, 0, len); | ||||
|                     digest.update(input, 0, len); | ||||
|                 } | ||||
|                 outputStream.getFD().sync(); | ||||
|             } | ||||
|             if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes)) | ||||
|                 throw new IOException("Incorrect file hash"); | ||||
| 
 | ||||
|             if (!tempFile.renameTo(new File(moduleDir, moduleName))) | ||||
|                 throw new IOException("Unable to rename to final destination"); | ||||
|         } finally { | ||||
|             if (tempFile != null) | ||||
|                 tempFile.delete(); | ||||
|         private Sha256Digest(final String hex) { | ||||
|             if (hex.length() != 64) | ||||
|                 throw new InvalidParameterException("SHA256 hashes must be 32 bytes long"); | ||||
|             bytes = new byte[32]; | ||||
|             for (int i = 0; i < 32; ++i) | ||||
|                 bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); | ||||
|         } | ||||
|         return OsConstants.EXIT_SUCCESS; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -187,6 +187,25 @@ public class RootShell { | ||||
|     } | ||||
| 
 | ||||
|     public static class RootShellException extends Exception { | ||||
|         private final Object[] format; | ||||
|         private final Reason reason; | ||||
|         public RootShellException(final Reason reason, final Object... format) { | ||||
|             this.reason = reason; | ||||
|             this.format = format; | ||||
|         } | ||||
| 
 | ||||
|         public Object[] getFormat() { | ||||
|             return format; | ||||
|         } | ||||
| 
 | ||||
|         public Reason getReason() { | ||||
|             return reason; | ||||
|         } | ||||
| 
 | ||||
|         public boolean isIORelated() { | ||||
|             return reason != Reason.NO_ROOT_ACCESS; | ||||
|         } | ||||
| 
 | ||||
|         public enum Reason { | ||||
|             NO_ROOT_ACCESS, | ||||
|             SHELL_MARKER_COUNT_ERROR, | ||||
| @ -195,20 +214,5 @@ public class RootShell { | ||||
|             CREATE_BIN_DIR_ERROR, | ||||
|             CREATE_TEMP_DIR_ERROR | ||||
|         } | ||||
|         private final Reason reason; | ||||
|         private final Object[] format; | ||||
|         public RootShellException(final Reason reason, final Object ...format) { | ||||
|             this.reason = reason; | ||||
|             this.format = format; | ||||
|         } | ||||
|         public boolean isIORelated() { | ||||
|             return reason != Reason.NO_ROOT_ACCESS; | ||||
|         } | ||||
|         public Reason getReason() { | ||||
|             return reason; | ||||
|         } | ||||
|         public Object[] getFormat() { | ||||
|             return format; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -40,9 +40,9 @@ public final class ToolsInstaller { | ||||
|     private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName(); | ||||
| 
 | ||||
|     private final Context context; | ||||
|     private final RootShell rootShell; | ||||
|     private final File localBinaryDir; | ||||
|     private final Object lock = new Object(); | ||||
|     private final RootShell rootShell; | ||||
|     @Nullable private Boolean areToolsAvailable; | ||||
|     @Nullable private Boolean installAsMagiskModule; | ||||
| 
 | ||||
| @ -107,6 +107,29 @@ public final class ToolsInstaller { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public boolean extract() throws IOException { | ||||
|         localBinaryDir.mkdirs(); | ||||
|         final File files[] = new File[EXECUTABLES.length]; | ||||
|         final File tempFiles[] = new File[EXECUTABLES.length]; | ||||
|         boolean allExist = true; | ||||
|         for (int i = 0; i < files.length; ++i) { | ||||
|             files[i] = new File(localBinaryDir, EXECUTABLES[i]); | ||||
|             tempFiles[i] = new File(localBinaryDir, EXECUTABLES[i] + ".tmp"); | ||||
|             allExist &= files[i].exists(); | ||||
|         } | ||||
|         if (allExist) | ||||
|             return false; | ||||
|         for (int i = 0; i < files.length; ++i) { | ||||
|             if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], tempFiles[i])) | ||||
|                 throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]); | ||||
|             if (!tempFiles[i].setExecutable(true, false)) | ||||
|                 throw new IOException("Unable to mark " + tempFiles[i].getAbsolutePath() + " as executable"); | ||||
|             if (!tempFiles[i].renameTo(files[i])) | ||||
|                 throw new IOException("Unable to rename " + tempFiles[i].getAbsolutePath() + " to " + files[i].getAbsolutePath()); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public int install() throws RootShellException, IOException { | ||||
|         if (!context.getPackageName().startsWith("com.wireguard.")) | ||||
|             throw new SecurityException("The tools may only be installed system-wide from the main WireGuard app."); | ||||
| @ -161,29 +184,6 @@ public final class ToolsInstaller { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public boolean extract() throws IOException { | ||||
|         localBinaryDir.mkdirs(); | ||||
|         final File files[] = new File[EXECUTABLES.length]; | ||||
|         final File tempFiles[] = new File[EXECUTABLES.length]; | ||||
|         boolean allExist = true; | ||||
|         for (int i = 0; i < files.length; ++i) { | ||||
|             files[i] = new File(localBinaryDir, EXECUTABLES[i]); | ||||
|             tempFiles[i] = new File(localBinaryDir, EXECUTABLES[i] + ".tmp"); | ||||
|             allExist &= files[i].exists(); | ||||
|         } | ||||
|         if (allExist) | ||||
|             return false; | ||||
|         for (int i = 0; i < files.length; ++i) { | ||||
|             if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], tempFiles[i])) | ||||
|                 throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]); | ||||
|             if (!tempFiles[i].setExecutable(true, false)) | ||||
|                 throw new IOException("Unable to mark " + tempFiles[i].getAbsolutePath() + " as executable"); | ||||
|             if (!tempFiles[i].renameTo(files[i])) | ||||
|                 throw new IOException("Unable to rename " + tempFiles[i].getAbsolutePath() + " to " + files[i].getAbsolutePath()); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private boolean willInstallAsMagiskModule() { | ||||
|         synchronized (lock) { | ||||
|             if (installAsMagiskModule == null) { | ||||
|  | ||||
| @ -35,7 +35,8 @@ public final class InetAddresses { | ||||
|         PARSER_METHOD = m; | ||||
|     } | ||||
| 
 | ||||
|     private InetAddresses() { } | ||||
|     private InetAddresses() { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Parses a numeric IPv4 or IPv6 address without performing any DNS lookups. | ||||
|  | ||||
| @ -10,6 +10,7 @@ import com.wireguard.util.NonNullForAll; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  */ | ||||
| @NonNullForAll | ||||
| public class ParseException extends Exception { | ||||
|  | ||||
| @ -204,6 +204,16 @@ public final class Key { | ||||
|         return new Key(publicKey); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(final Object obj) { | ||||
|         if (obj == this) | ||||
|             return true; | ||||
|         if (obj == null || obj.getClass() != getClass()) | ||||
|             return false; | ||||
|         final Key other = (Key) obj; | ||||
|         return MessageDigest.isEqual(key, other.key); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the key as an array of bytes. | ||||
|      * | ||||
| @ -214,6 +224,14 @@ public final class Key { | ||||
|         return Arrays.copyOf(key, key.length); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         int ret = 0; | ||||
|         for (int i = 0; i < key.length / 4; ++i) | ||||
|             ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encodes the key to base64. | ||||
|      * | ||||
| @ -250,24 +268,6 @@ public final class Key { | ||||
|         return new String(output); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         int ret = 0; | ||||
|         for (int i = 0; i < key.length / 4; ++i) | ||||
|             ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(final Object obj) { | ||||
|         if (obj == this) | ||||
|             return true; | ||||
|         if (obj == null || obj.getClass() != getClass()) | ||||
|             return false; | ||||
|         final Key other = (Key) obj; | ||||
|         return MessageDigest.isEqual(key, other.key); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The supported formats for encoding a WireGuard key. | ||||
|      */ | ||||
|  | ||||
| @ -37,17 +37,16 @@ import java9.util.concurrent.CompletableFuture; | ||||
| 
 | ||||
| @NonNullForAll | ||||
| public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener { | ||||
|     private static final String TAG = "WireGuard/" + Application.class.getSimpleName(); | ||||
|     public static final String USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT); | ||||
| 
 | ||||
|     private static final String TAG = "WireGuard/" + Application.class.getSimpleName(); | ||||
|     @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf; | ||||
|     private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>(); | ||||
|     @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker; | ||||
|     @Nullable private Backend backend; | ||||
|     @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader; | ||||
|     @SuppressWarnings("NullableProblems") private RootShell rootShell; | ||||
|     @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences; | ||||
|     @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller; | ||||
|     @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader; | ||||
|     @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager; | ||||
| 
 | ||||
|     public Application() { | ||||
| @ -102,6 +101,10 @@ public class Application extends android.app.Application implements SharedPrefer | ||||
|         return get().futureBackend; | ||||
|     } | ||||
| 
 | ||||
|     public static ModuleLoader getModuleLoader() { | ||||
|         return get().moduleLoader; | ||||
|     } | ||||
| 
 | ||||
|     public static RootShell getRootShell() { | ||||
|         return get().rootShell; | ||||
|     } | ||||
| @ -114,10 +117,6 @@ public class Application extends android.app.Application implements SharedPrefer | ||||
|         return get().toolsInstaller; | ||||
|     } | ||||
| 
 | ||||
|     public static ModuleLoader getModuleLoader() { | ||||
|         return get().moduleLoader; | ||||
|     } | ||||
| 
 | ||||
|     public static TunnelManager getTunnelManager() { | ||||
|         return get().tunnelManager; | ||||
|     } | ||||
| @ -167,15 +166,15 @@ public class Application extends android.app.Application implements SharedPrefer | ||||
|         sharedPreferences.registerOnSharedPreferenceChangeListener(this); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { | ||||
|         if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend) | ||||
|             ((WgQuickBackend) backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onTerminate() { | ||||
|         sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); | ||||
|         super.onTerminate(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { | ||||
|         if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend) | ||||
|             ((WgQuickBackend)backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -34,8 +34,20 @@ import androidx.databinding.DataBindingUtil; | ||||
| @NonNullForAll | ||||
| public class TunnelDetailFragment extends BaseFragment { | ||||
|     @Nullable private TunnelDetailFragmentBinding binding; | ||||
|     @Nullable private Timer timer; | ||||
|     @Nullable private State lastState = State.TOGGLE; | ||||
|     @Nullable private Timer timer; | ||||
| 
 | ||||
|     private String formatBytes(final long bytes) { | ||||
|         if (bytes < 1024) | ||||
|             return requireContext().getString(R.string.transfer_bytes, bytes); | ||||
|         else if (bytes < 1024 * 1024) | ||||
|             return requireContext().getString(R.string.transfer_kibibytes, bytes / 1024.0); | ||||
|         else if (bytes < 1024 * 1024 * 1024) | ||||
|             return requireContext().getString(R.string.transfer_mibibytes, bytes / (1024.0 * 1024.0)); | ||||
|         else if (bytes < 1024 * 1024 * 1024 * 1024) | ||||
|             return requireContext().getString(R.string.transfer_gibibytes, bytes / (1024.0 * 1024.0 * 1024.0)); | ||||
|         return requireContext().getString(R.string.transfer_tibibytes, bytes / (1024.0 * 1024.0 * 1024.0) / 1024.0); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
| @ -48,27 +60,6 @@ public class TunnelDetailFragment extends BaseFragment { | ||||
|         inflater.inflate(R.menu.tunnel_detail, menu); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStop() { | ||||
|         super.onStop(); | ||||
|         if (timer != null) { | ||||
|             timer.cancel(); | ||||
|             timer = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         timer = new Timer(); | ||||
|         timer.scheduleAtFixedRate(new TimerTask() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 updateStats(); | ||||
|             } | ||||
|         }, 0, 1000); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, | ||||
|                              @Nullable final Bundle savedInstanceState) { | ||||
| @ -86,6 +77,18 @@ public class TunnelDetailFragment extends BaseFragment { | ||||
|         super.onDestroyView(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         timer = new Timer(); | ||||
|         timer.scheduleAtFixedRate(new TimerTask() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 updateStats(); | ||||
|             } | ||||
|         }, 0, 1000); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { | ||||
|         if (binding == null) | ||||
| @ -99,6 +102,15 @@ public class TunnelDetailFragment extends BaseFragment { | ||||
|         updateStats(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStop() { | ||||
|         super.onStop(); | ||||
|         if (timer != null) { | ||||
|             timer.cancel(); | ||||
|             timer = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onViewStateRestored(@Nullable final Bundle savedInstanceState) { | ||||
|         if (binding == null) { | ||||
| @ -110,18 +122,6 @@ public class TunnelDetailFragment extends BaseFragment { | ||||
|         super.onViewStateRestored(savedInstanceState); | ||||
|     } | ||||
| 
 | ||||
|     private String formatBytes(final long bytes) { | ||||
|         if (bytes < 1024) | ||||
|             return requireContext().getString(R.string.transfer_bytes, bytes); | ||||
|         else if (bytes < 1024*1024) | ||||
|             return requireContext().getString(R.string.transfer_kibibytes, bytes/1024.0); | ||||
|         else if (bytes < 1024*1024*1024) | ||||
|             return requireContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0)); | ||||
|         else if (bytes < 1024*1024*1024*1024) | ||||
|             return requireContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0)); | ||||
|         return requireContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0); | ||||
|     } | ||||
| 
 | ||||
|     private void updateStats() { | ||||
|         if (binding == null || !isResumed()) | ||||
|             return; | ||||
|  | ||||
| @ -262,14 +262,6 @@ public class TunnelListFragment extends BaseFragment { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void showSnackbar(final CharSequence message) { | ||||
|         if (binding != null) { | ||||
|             final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG); | ||||
|             snackbar.setAnchorView(binding.createFab); | ||||
|             snackbar.show(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) { | ||||
|         final String message; | ||||
|         if (throwable == null) { | ||||
| @ -337,6 +329,14 @@ public class TunnelListFragment extends BaseFragment { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void showSnackbar(final CharSequence message) { | ||||
|         if (binding != null) { | ||||
|             final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG); | ||||
|             snackbar.setAnchorView(binding.createFab); | ||||
|             snackbar.show(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private MultiselectableRelativeLayout viewForTunnel(final ObservableTunnel tunnel, final List tunnels) { | ||||
|         return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; | ||||
|     } | ||||
|  | ||||
| @ -27,12 +27,12 @@ import java9.util.concurrent.CompletionStage; | ||||
| public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel { | ||||
|     private final TunnelManager manager; | ||||
|     @Nullable private Config config; | ||||
|     private State state; | ||||
|     private String name; | ||||
|     private State state; | ||||
|     @Nullable private Statistics statistics; | ||||
| 
 | ||||
|     ObservableTunnel(final TunnelManager manager, final String name, | ||||
|            @Nullable final Config config, final State state) { | ||||
|                      @Nullable final Config config, final State state) { | ||||
|         this.name = name; | ||||
|         this.manager = manager; | ||||
|         this.config = config; | ||||
| @ -103,6 +103,11 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T | ||||
|         return name; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStateChange(final State newState) { | ||||
|         onStateChanged(newState); | ||||
|     } | ||||
| 
 | ||||
|     State onStateChanged(final State state) { | ||||
|         if (state != State.UP) | ||||
|             onStatisticsChanged(null); | ||||
| @ -111,11 +116,6 @@ public class ObservableTunnel extends BaseObservable implements Keyed<String>, T | ||||
|         return state; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStateChange(final State newState) { | ||||
|         onStateChanged(newState); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     Statistics onStatisticsChanged(@Nullable final Statistics statistics) { | ||||
|         this.statistics = statistics; | ||||
|  | ||||
| @ -47,8 +47,7 @@ public class LogExporterPreference extends Preference { | ||||
|                 final Process process = Runtime.getRuntime().exec(new String[]{ | ||||
|                         "logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"}); | ||||
|                 try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); | ||||
|                      final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) | ||||
|                 { | ||||
|                      final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { | ||||
|                     String line; | ||||
|                     while ((line = stdout.readLine()) != null) { | ||||
|                         outputFile.getOutputStream().write(line.getBytes()); | ||||
|  | ||||
| @ -28,14 +28,6 @@ import androidx.preference.Preference; | ||||
| public class VersionPreference extends Preference { | ||||
|     @Nullable private String versionSummary; | ||||
| 
 | ||||
|     private String getBackendPrettyName(final Context context, final Backend backend) { | ||||
|         if (backend instanceof GoBackend) | ||||
|             return context.getString(R.string.type_name_kernel_module); | ||||
|         if (backend instanceof WgQuickBackend) | ||||
|             return context.getString(R.string.type_name_go_userspace); | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     public VersionPreference(final Context context, final AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
| 
 | ||||
| @ -50,6 +42,14 @@ public class VersionPreference extends Preference { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private String getBackendPrettyName(final Context context, final Backend backend) { | ||||
|         if (backend instanceof GoBackend) | ||||
|             return context.getString(R.string.type_name_kernel_module); | ||||
|         if (backend instanceof WgQuickBackend) | ||||
|             return context.getString(R.string.type_name_go_userspace); | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public CharSequence getSummary() { | ||||
|  | ||||
| @ -26,30 +26,6 @@ import java.io.OutputStream; | ||||
| @NonNullForAll | ||||
| public class DownloadsFileSaver { | ||||
| 
 | ||||
|     public static class DownloadsFile { | ||||
|         private Context context; | ||||
|         private OutputStream outputStream; | ||||
|         private String fileName; | ||||
|         private Uri uri; | ||||
| 
 | ||||
|         private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) { | ||||
|             this.context = context; | ||||
|             this.outputStream = outputStream; | ||||
|             this.fileName = fileName; | ||||
|             this.uri = uri; | ||||
|         } | ||||
| 
 | ||||
|         public OutputStream getOutputStream() { return outputStream; } | ||||
|         public String getFileName() { return fileName; } | ||||
| 
 | ||||
|         public void delete() { | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) | ||||
|                 context.getContentResolver().delete(uri, null, null); | ||||
|             else | ||||
|                 new File(fileName).delete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | ||||
|             final ContentResolver contentResolver = context.getContentResolver(); | ||||
| @ -89,12 +65,40 @@ public class DownloadsFileSaver { | ||||
|             } | ||||
|             return new DownloadsFile(context, contentStream, path, contentUri); | ||||
|         } else { | ||||
|             @SuppressWarnings("deprecation") | ||||
|             final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | ||||
|             @SuppressWarnings("deprecation") final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | ||||
|             final File file = new File(path, name); | ||||
|             if (!path.isDirectory() && !path.mkdirs()) | ||||
|                 throw new IOException(context.getString(R.string.create_output_dir_error)); | ||||
|             return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class DownloadsFile { | ||||
|         private Context context; | ||||
|         private String fileName; | ||||
|         private OutputStream outputStream; | ||||
|         private Uri uri; | ||||
| 
 | ||||
|         private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) { | ||||
|             this.context = context; | ||||
|             this.outputStream = outputStream; | ||||
|             this.fileName = fileName; | ||||
|             this.uri = uri; | ||||
|         } | ||||
| 
 | ||||
|         public void delete() { | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) | ||||
|                 context.getContentResolver().delete(uri, null, null); | ||||
|             else | ||||
|                 new File(fileName).delete(); | ||||
|         } | ||||
| 
 | ||||
|         public String getFileName() { | ||||
|             return fileName; | ||||
|         } | ||||
| 
 | ||||
|         public OutputStream getOutputStream() { | ||||
|             return outputStream; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -51,14 +51,6 @@ public final class ErrorMessages { | ||||
|             BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error, | ||||
|             BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error | ||||
|     )); | ||||
|     private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of( | ||||
|             RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root, | ||||
|             RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error, | ||||
|             RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error, | ||||
|             RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error, | ||||
|             RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error, | ||||
|             RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error | ||||
|     )); | ||||
|     private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of( | ||||
|             Format.BASE64, R.string.key_length_explanation_base64, | ||||
|             Format.BINARY, R.string.key_length_explanation_binary, | ||||
| @ -74,6 +66,14 @@ public final class ErrorMessages { | ||||
|             InetNetwork.class, R.string.parse_error_inet_network, | ||||
|             Integer.class, R.string.parse_error_integer | ||||
|     ); | ||||
|     private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of( | ||||
|             RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root, | ||||
|             RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error, | ||||
|             RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error, | ||||
|             RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error, | ||||
|             RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error, | ||||
|             RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error | ||||
|     )); | ||||
| 
 | ||||
|     private ErrorMessages() { | ||||
|         // Prevent instantiation | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user