diff options
Diffstat (limited to 'app/src/main/java/com/wireguard/android/util')
14 files changed, 0 insertions, 1388 deletions
diff --git a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java b/app/src/main/java/com/wireguard/android/util/AsyncWorker.java deleted file mode 100644 index 1d041851..00000000 --- a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.os.Handler; - -import java.util.concurrent.Executor; - -import java9.util.concurrent.CompletableFuture; -import java9.util.concurrent.CompletionStage; - -/** - * Helper class for running asynchronous tasks and ensuring they are completed on the main thread. - */ - -public class AsyncWorker { - private final Executor executor; - private final Handler handler; - - public AsyncWorker(final Executor executor, final Handler handler) { - this.executor = executor; - this.handler = handler; - } - - public CompletionStage<Void> runAsync(final AsyncRunnable<?> runnable) { - final CompletableFuture<Void> future = new CompletableFuture<>(); - executor.execute(() -> { - try { - runnable.run(); - handler.post(() -> future.complete(null)); - } catch (final Throwable t) { - handler.post(() -> future.completeExceptionally(t)); - } - }); - return future; - } - - public <T> CompletionStage<T> supplyAsync(final AsyncSupplier<T, ?> supplier) { - final CompletableFuture<T> future = new CompletableFuture<>(); - executor.execute(() -> { - try { - final T result = supplier.get(); - handler.post(() -> future.complete(result)); - } catch (final Throwable t) { - handler.post(() -> future.completeExceptionally(t)); - } - }); - return future; - } - - @FunctionalInterface - public interface AsyncRunnable<E extends Throwable> { - void run() throws E; - } - - @FunctionalInterface - public interface AsyncSupplier<T, E extends Throwable> { - T get() throws E; - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java b/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java deleted file mode 100644 index 0df5e96a..00000000 --- a/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import com.google.android.material.snackbar.Snackbar; -import android.view.View; -import android.widget.TextView; - -/** - * Standalone utilities for interacting with the system clipboard. - */ - -public final class ClipboardUtils { - private ClipboardUtils() { - // Prevent instantiation - } - - public static void copyTextView(final View view) { - if (!(view instanceof TextView)) - return; - final CharSequence text = ((TextView) view).getText(); - if (text == null || text.length() == 0) - return; - final Object service = view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - if (!(service instanceof ClipboardManager)) - return; - final CharSequence description = view.getContentDescription(); - ((ClipboardManager) service).setPrimaryClip(ClipData.newPlainText(description, text)); - Snackbar.make(view, description + " copied to clipboard", Snackbar.LENGTH_LONG).show(); - } -} diff --git a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java b/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java deleted file mode 100644 index 7db46fa9..00000000 --- a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright © 2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.MediaStore; -import android.provider.MediaStore.MediaColumns; - -import com.wireguard.android.R; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -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(); - if (overwriteExisting) - contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), new String[]{name}); - final ContentValues contentValues = new ContentValues(); - contentValues.put(MediaColumns.DISPLAY_NAME, name); - contentValues.put(MediaColumns.MIME_TYPE, mimeType); - final Uri contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues); - if (contentUri == null) - throw new IOException(context.getString(R.string.create_downloads_file_error)); - final OutputStream contentStream = contentResolver.openOutputStream(contentUri); - if (contentStream == null) - throw new IOException(context.getString(R.string.create_downloads_file_error)); - @SuppressWarnings("deprecation") - Cursor cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DATA}, null, null, null); - String path = null; - if (cursor != null) { - try { - if (cursor.moveToFirst()) - path = cursor.getString(0); - } finally { - cursor.close(); - } - } - if (path == null) { - path = "Download/"; - cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DISPLAY_NAME}, null, null, null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) - path += cursor.getString(0); - } finally { - cursor.close(); - } - } - } - return new DownloadsFile(context, contentStream, path, contentUri); - } else { - @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); - } - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java deleted file mode 100644 index ee9cb12a..00000000 --- a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.res.Resources; -import androidx.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<Reason, Integer> 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<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, - Format.HEX, R.string.key_length_explanation_hex - )); - private static final Map<Type, Integer> 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<Class, Integer> 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 deleted file mode 100644 index 5c7a38c0..00000000 --- a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import androidx.annotation.Nullable; -import android.util.Log; - -import java9.util.function.BiConsumer; - -/** - * Helpers for logging exceptions from asynchronous tasks. These can be passed to - * {@code CompletionStage.whenComplete()} at the end of an asynchronous future chain. - */ - -public enum ExceptionLoggers implements BiConsumer<Object, Throwable> { - D(Log.DEBUG), - E(Log.ERROR); - - private static final String TAG = "WireGuard/" + ExceptionLoggers.class.getSimpleName(); - private final int priority; - - ExceptionLoggers(final int priority) { - this.priority = priority; - } - - @Override - public void accept(final Object result, @Nullable final Throwable throwable) { - if (throwable != null) - Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable)); - else if (priority <= Log.DEBUG) - Log.println(priority, TAG, "Future completed successfully"); - } -} diff --git a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java b/app/src/main/java/com/wireguard/android/util/FragmentUtils.java deleted file mode 100644 index 5fb9a3bc..00000000 --- a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package com.wireguard.android.util; - -import android.content.Context; -import androidx.preference.Preference; -import android.view.ContextThemeWrapper; - -import com.wireguard.android.activity.SettingsActivity; - -public final class FragmentUtils { - private FragmentUtils() { - // Prevent instantiation - } - - public static SettingsActivity getPrefActivity(final Preference preference) { - final Context context = preference.getContext(); - if (context instanceof ContextThemeWrapper) { - if (context instanceof SettingsActivity) { - return ((SettingsActivity) context); - } - } - return null; - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ModuleLoader.java b/app/src/main/java/com/wireguard/android/util/ModuleLoader.java deleted file mode 100644 index 524d10a6..00000000 --- a/app/src/main/java/com/wireguard/android/util/ModuleLoader.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright © 2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.Context; -import android.system.OsConstants; -import android.util.Base64; - -import com.wireguard.android.Application; -import com.wireguard.android.util.RootShell.NoRootException; - -import net.i2p.crypto.eddsa.EdDSAEngine; -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.InvalidParameterException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Signature; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -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 File moduleDir; - private final File tmpDir; - - public ModuleLoader(final Context context) { - moduleDir = new File(context.getCacheDir(), "kmod"); - tmpDir = new File(context.getCacheDir(), "tmp"); - } - - public boolean moduleMightExist() { - return moduleDir.exists() && moduleDir.isDirectory(); - } - - public void loadModule() throws IOException, NoRootException { - Application.getRootShell().run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath())); - } - - public 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); - } - } - - @Nullable - private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) { - final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT); - - if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd') - return null; - - final String[] lines = signifyDigest.split("\n", 3); - if (lines.length != 3) - return null; - if (!lines[0].startsWith("untrusted comment: ")) - return null; - - final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT); - if (signatureBytes == null || signatureBytes.length != 64 + 10) - return null; - for (int i = 0; i < 10; ++i) { - if (signatureBytes[i] != publicKeyBytes[i]) - return null; - } - - try { - EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); - Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm())); - byte[] rawPublicKeyBytes = new byte[32]; - System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32); - signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec))); - signature.update(lines[2].getBytes(StandardCharsets.UTF_8)); - if (!signature.verify(signatureBytes, 10, 64)) - return null; - } catch (final Exception ignored) { - return null; - } - - Map<String, Sha256Digest> hashes = new HashMap<>(); - for (final String line : lines[2].split("\n")) { - final String[] components = line.split(" ", 2); - if (components.length != 2) - return null; - try { - hashes.put(components[1], new Sha256Digest(components[0])); - } catch (final Exception ignored) { - return null; - } - } - return hashes; - } - - public Integer download() throws IOException, NoRootException, NoSuchAlgorithmException { - final List<String> output = new ArrayList<>(); - Application.getRootShell().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", Application.USER_AGENT); - 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", Application.USER_AGENT); - 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 OutputStream 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); - } - } - 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; - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java deleted file mode 100644 index 0ba02184..00000000 --- a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import androidx.databinding.ObservableArrayList; -import androidx.annotation.Nullable; - -import com.wireguard.util.Keyed; - -import java.util.Collection; -import java.util.ListIterator; -import java.util.Objects; - -/** - * ArrayList that allows looking up elements by some key property. As the key property must always - * be retrievable, this list cannot hold {@code null} elements. Because this class places no - * restrictions on the order or duplication of keys, lookup by key, as well as all list modification - * operations, require O(n) time. - */ - -public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>> - extends ObservableArrayList<E> implements ObservableKeyedList<K, E> { - @Override - public boolean add(@Nullable final E e) { - if (e == null) - throw new NullPointerException("Trying to add a null element"); - return super.add(e); - } - - @Override - public void add(final int index, @Nullable final E e) { - if (e == null) - throw new NullPointerException("Trying to add a null element"); - super.add(index, e); - } - - @Override - public boolean addAll(final Collection<? extends E> c) { - if (c.contains(null)) - throw new NullPointerException("Trying to add a collection with null element(s)"); - return super.addAll(c); - } - - @Override - public boolean addAll(final int index, final Collection<? extends E> c) { - if (c.contains(null)) - throw new NullPointerException("Trying to add a collection with null element(s)"); - return super.addAll(index, c); - } - - @Override - public boolean containsAllKeys(final Collection<K> keys) { - for (final K key : keys) - if (!containsKey(key)) - return false; - return true; - } - - @Override - public boolean containsKey(final K key) { - return indexOfKey(key) >= 0; - } - - @Nullable - @Override - public E get(final K key) { - final int index = indexOfKey(key); - return index >= 0 ? get(index) : null; - } - - @Nullable - @Override - public E getLast(final K key) { - final int index = lastIndexOfKey(key); - return index >= 0 ? get(index) : null; - } - - @Override - public int indexOfKey(final K key) { - final ListIterator<E> iterator = listIterator(); - while (iterator.hasNext()) { - final int index = iterator.nextIndex(); - if (Objects.equals(iterator.next().getKey(), key)) - return index; - } - return -1; - } - - @Override - public int lastIndexOfKey(final K key) { - final ListIterator<E> iterator = listIterator(size()); - while (iterator.hasPrevious()) { - final int index = iterator.previousIndex(); - if (Objects.equals(iterator.previous().getKey(), key)) - return index; - } - return -1; - } - - @Override - public E set(final int index, @Nullable final E e) { - if (e == null) - throw new NullPointerException("Trying to set a null key"); - return super.set(index, e); - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java b/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java deleted file mode 100644 index be8ceb9b..00000000 --- a/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import androidx.databinding.ObservableList; - -import com.wireguard.util.Keyed; -import com.wireguard.util.KeyedList; - -/** - * A list that is both keyed and observable. - */ - -public interface ObservableKeyedList<K, E extends Keyed<? extends K>> - extends KeyedList<K, E>, ObservableList<E> { -} diff --git a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java deleted file mode 100644 index 1d585856..00000000 --- a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import androidx.annotation.Nullable; - -import com.wireguard.util.Keyed; -import com.wireguard.util.SortedKeyedList; - -import java.util.AbstractList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.Spliterator; - -/** - * KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses - * binary search to improve lookup and replacement times to O(log(n)). However, due to the - * array-based nature of this class, insertion and removal of elements with anything but the largest - * key still require O(n) time. - */ - -public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>> - extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> { - @Nullable private final Comparator<? super K> comparator; - private final transient KeyList<K, E> keyList = new KeyList<>(this); - - @SuppressWarnings("WeakerAccess") - public ObservableSortedKeyedArrayList() { - comparator = null; - } - - public ObservableSortedKeyedArrayList(final Comparator<? super K> comparator) { - this.comparator = comparator; - } - - public ObservableSortedKeyedArrayList(final Collection<? extends E> c) { - this(); - addAll(c); - } - - public ObservableSortedKeyedArrayList(final SortedKeyedList<K, E> other) { - this(other.comparator()); - addAll(other); - } - - @Override - public boolean add(final E e) { - final int insertionPoint = getInsertionPoint(e); - if (insertionPoint < 0) { - // Skipping insertion is non-destructive if the new and existing objects are the same. - if (e == get(-insertionPoint - 1)) - return false; - throw new IllegalArgumentException("Element with same key already exists in list"); - } - super.add(insertionPoint, e); - return true; - } - - @Override - public void add(final int index, final E e) { - final int insertionPoint = getInsertionPoint(e); - if (insertionPoint < 0) - throw new IllegalArgumentException("Element with same key already exists in list"); - if (insertionPoint != index) - throw new IndexOutOfBoundsException("Wrong index given for element"); - super.add(index, e); - } - - @Override - public boolean addAll(final Collection<? extends E> c) { - boolean didChange = false; - for (final E e : c) - if (add(e)) - didChange = true; - return didChange; - } - - @Override - public boolean addAll(int index, final Collection<? extends E> c) { - for (final E e : c) - add(index++, e); - return true; - } - - @Nullable - @Override - public Comparator<? super K> comparator() { - return comparator; - } - - @Override - public K firstKey() { - if (isEmpty()) - // The parameter in the exception is only to shut - // lint up, we never care for the exception message. - throw new NoSuchElementException("Empty set"); - return get(0).getKey(); - } - - private int getInsertionPoint(final E e) { - if (comparator != null) { - return -Collections.binarySearch(keyList, e.getKey(), comparator) - 1; - } else { - @SuppressWarnings("unchecked") final List<Comparable<? super K>> list = - (List<Comparable<? super K>>) keyList; - return -Collections.binarySearch(list, e.getKey()) - 1; - } - } - - @Override - public int indexOfKey(final K key) { - final int index; - if (comparator != null) { - index = Collections.binarySearch(keyList, key, comparator); - } else { - @SuppressWarnings("unchecked") final List<Comparable<? super K>> list = - (List<Comparable<? super K>>) keyList; - index = Collections.binarySearch(list, key); - } - return index >= 0 ? index : -1; - } - - @Override - public Set<K> keySet() { - return keyList; - } - - @Override - public int lastIndexOfKey(final K key) { - // There can never be more than one element with the same key in the list. - return indexOfKey(key); - } - - @Override - public K lastKey() { - if (isEmpty()) - // The parameter in the exception is only to shut - // lint up, we never care for the exception message. - throw new NoSuchElementException("Empty set"); - return get(size() - 1).getKey(); - } - - @Override - public E set(final int index, final E e) { - final int order; - if (comparator != null) { - order = comparator.compare(e.getKey(), get(index).getKey()); - } else { - @SuppressWarnings("unchecked") final Comparable<? super K> key = - (Comparable<? super K>) e.getKey(); - order = key.compareTo(get(index).getKey()); - } - if (order != 0) { - // Allow replacement if the new key would be inserted adjacent to the replaced element. - final int insertionPoint = getInsertionPoint(e); - if (insertionPoint < index || insertionPoint > index + 1) - throw new IndexOutOfBoundsException("Wrong index given for element"); - } - return super.set(index, e); - } - - @Override - public Collection<E> values() { - return this; - } - - private static final class KeyList<K, E extends Keyed<? extends K>> - extends AbstractList<K> implements Set<K> { - private final ObservableSortedKeyedArrayList<K, E> list; - - private KeyList(final ObservableSortedKeyedArrayList<K, E> list) { - this.list = list; - } - - @Override - public K get(final int index) { - return list.get(index).getKey(); - } - - @Override - public int size() { - return list.size(); - } - - @Override - @SuppressWarnings("EmptyMethod") - public Spliterator<K> spliterator() { - return super.spliterator(); - } - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java deleted file mode 100644 index d796704e..00000000 --- a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import com.wireguard.util.Keyed; -import com.wireguard.util.SortedKeyedList; - -/** - * A list that is both sorted/keyed and observable. - */ - -public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>> - extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> { -} diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java deleted file mode 100644 index 1fe5667a..00000000 --- a/app/src/main/java/com/wireguard/android/util/RootShell.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.Context; -import androidx.annotation.Nullable; -import android.util.Log; - -import com.wireguard.android.BuildConfig; -import com.wireguard.android.R; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.UUID; - -/** - * Helper class for running commands as root. - */ - -public class RootShell { - private static final String SU = "su"; - private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName(); - - private final Context context; - private final String deviceNotRootedMessage; - private final File localBinaryDir; - private final File localTemporaryDir; - private final Object lock = new Object(); - private final String preamble; - @Nullable private Process process; - @Nullable private BufferedReader stderr; - @Nullable private OutputStreamWriter stdin; - @Nullable private BufferedReader stdout; - - public RootShell(final Context context) { - deviceNotRootedMessage = context.getString(R.string.error_root); - localBinaryDir = new File(context.getCodeCacheDir(), "bin"); - localTemporaryDir = new File(context.getCacheDir(), "tmp"); - preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n", - BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir); - this.context = context; - } - - private static boolean isExecutableInPath(final String name) { - final String path = System.getenv("PATH"); - if (path == null) - return false; - for (final String dir : path.split(":")) - if (new File(dir, name).canExecute()) - return true; - return false; - } - - private boolean isRunning() { - synchronized (lock) { - try { - // Throws an exception if the process hasn't finished yet. - if (process != null) - process.exitValue(); - return false; - } catch (final IllegalThreadStateException ignored) { - // The existing process is still running. - return true; - } - } - } - - /** - * Run a command in a root shell. - * - * @param output Lines read from stdout are appended to this list. Pass null if the - * output from the shell is not important. - * @param command Command to run as root. - * @return The exit value of the command. - */ - public int run(@Nullable final Collection<String> output, final String command) - throws IOException, NoRootException { - synchronized (lock) { - /* Start inside synchronized block to prevent a concurrent call to stop(). */ - start(); - final String marker = UUID.randomUUID().toString(); - final String script = "echo " + marker + "; echo " + marker + " >&2; (" + command + - "); ret=$?; echo " + marker + " $ret; echo " + marker + " $ret >&2\n"; - Log.v(TAG, "executing: " + command); - stdin.write(script); - stdin.flush(); - String line; - int errnoStdout = Integer.MIN_VALUE; - int errnoStderr = Integer.MAX_VALUE; - int markersSeen = 0; - while ((line = stdout.readLine()) != null) { - if (line.startsWith(marker)) { - ++markersSeen; - if (line.length() > marker.length() + 1) { - errnoStdout = Integer.valueOf(line.substring(marker.length() + 1)); - break; - } - } else if (markersSeen > 0) { - if (output != null) - output.add(line); - Log.v(TAG, "stdout: " + line); - } - } - while ((line = stderr.readLine()) != null) { - if (line.startsWith(marker)) { - ++markersSeen; - if (line.length() > marker.length() + 1) { - errnoStderr = Integer.valueOf(line.substring(marker.length() + 1)); - break; - } - } else if (markersSeen > 2) { - Log.v(TAG, "stderr: " + line); - } - } - if (markersSeen != 4) - throw new IOException(context.getString(R.string.shell_marker_count_error, markersSeen)); - if (errnoStdout != errnoStderr) - throw new IOException(context.getString(R.string.shell_exit_status_read_error)); - Log.v(TAG, "exit: " + errnoStdout); - return errnoStdout; - } - } - - public void start() throws IOException, NoRootException { - if (!isExecutableInPath(SU)) - throw new NoRootException(deviceNotRootedMessage); - synchronized (lock) { - if (isRunning()) - return; - if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs()) - throw new FileNotFoundException(context.getString(R.string.create_bin_dir_error)); - if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs()) - throw new FileNotFoundException(context.getString(R.string.create_temp_dir_error)); - try { - final ProcessBuilder builder = new ProcessBuilder().command(SU); - builder.environment().put("LC_ALL", "C"); - try { - process = builder.start(); - } catch (final IOException e) { - // A failure at this stage means the device isn't rooted. - throw new NoRootException(deviceNotRootedMessage, e); - } - stdin = new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8); - stdout = new BufferedReader(new InputStreamReader(process.getInputStream(), - StandardCharsets.UTF_8)); - stderr = new BufferedReader(new InputStreamReader(process.getErrorStream(), - StandardCharsets.UTF_8)); - stdin.write(preamble); - stdin.flush(); - // Check that the shell started successfully. - final String uid = stdout.readLine(); - if (!"0".equals(uid)) { - Log.w(TAG, "Root check did not return correct UID: " + uid); - throw new NoRootException(deviceNotRootedMessage); - } - if (!isRunning()) { - String line; - while ((line = stderr.readLine()) != null) { - Log.w(TAG, "Root check returned an error: " + line); - if (line.contains("Permission denied")) - throw new NoRootException(deviceNotRootedMessage); - } - throw new IOException(context.getString(R.string.shell_start_error, process.exitValue())); - } - } catch (final IOException | NoRootException e) { - stop(); - throw e; - } - } - } - - public void stop() { - synchronized (lock) { - if (process != null) { - process.destroy(); - process = null; - } - } - } - - public static class NoRootException extends Exception { - public NoRootException(final String message, final Throwable cause) { - super(message, cause); - } - - public NoRootException(final String message) { - super(message); - } - } -} diff --git a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java deleted file mode 100644 index e3923d19..00000000 --- a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.Context; -import android.os.Build; -import android.util.Log; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public final class SharedLibraryLoader { - private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName(); - - private SharedLibraryLoader() { - } - - public static boolean extractLibrary(final Context context, final String libName, final File destination) throws IOException { - final Collection<String> apks = new HashSet<>(); - if (context.getApplicationInfo().sourceDir != null) - apks.add(context.getApplicationInfo().sourceDir); - if (context.getApplicationInfo().splitSourceDirs != null) - apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs)); - - for (final String abi : Build.SUPPORTED_ABIS) { - for (final String apk : apks) { - final ZipFile zipFile; - try { - zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ); - } catch (final IOException e) { - throw new RuntimeException(e); - } - - final String mappedLibName = System.mapLibraryName(libName); - final byte[] buffer = new byte[1024 * 32]; - final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName; - final ZipEntry zipEntry = zipFile.getEntry(libZipPath); - if (zipEntry == null) - continue; - Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath()); - try (final FileOutputStream out = new FileOutputStream(destination); - final InputStream in = zipFile.getInputStream(zipEntry)) { - int len; - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - } - } - return true; - } - } - return false; - } - - public static void loadSharedLibrary(final Context context, final String libName) { - Throwable noAbiException; - try { - System.loadLibrary(libName); - return; - } catch (final UnsatisfiedLinkError e) { - Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e); - noAbiException = e; - } - File f = null; - try { - f = File.createTempFile("lib", ".so", context.getCodeCacheDir()); - if (extractLibrary(context, libName, f)) { - System.load(f.getAbsolutePath()); - return; - } - } catch (final Exception e) { - Log.d(TAG, "Failed to load library apk:/" + libName, e); - noAbiException = e; - } finally { - if (f != null) - // noinspection ResultOfMethodCallIgnored - f.delete(); - } - if (noAbiException instanceof RuntimeException) - throw (RuntimeException) noAbiException; - throw new RuntimeException(noAbiException); - } -} diff --git a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java deleted file mode 100644 index defdefd8..00000000 --- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util; - -import android.content.Context; -import androidx.annotation.Nullable; -import android.system.OsConstants; -import android.util.Log; - -import com.wireguard.android.Application; -import com.wireguard.android.BuildConfig; -import com.wireguard.android.R; -import com.wireguard.android.util.RootShell.NoRootException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -/** - * Helper to install WireGuard tools to the system partition. - */ - -public final class ToolsInstaller { - public static final int ERROR = 0x0; - public static final int MAGISK = 0x4; - public static final int NO = 0x2; - public static final int SYSTEM = 0x8; - public static final int YES = 0x1; - private static final String[] EXECUTABLES = {"wg", "wg-quick"}; - private static final File[] INSTALL_DIRS = { - new File("/system/xbin"), - new File("/system/bin"), - }; - @Nullable private static final File INSTALL_DIR = getInstallDir(); - private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName(); - - private final Context context; - private final File localBinaryDir; - private final Object lock = new Object(); - @Nullable private Boolean areToolsAvailable; - @Nullable private Boolean installAsMagiskModule; - - public ToolsInstaller(final Context context) { - localBinaryDir = new File(context.getCodeCacheDir(), "bin"); - this.context = context; - } - - @Nullable - private static File getInstallDir() { - final String path = System.getenv("PATH"); - if (path == null) - return INSTALL_DIRS[0]; - final List<String> paths = Arrays.asList(path.split(":")); - for (final File dir : INSTALL_DIRS) { - if (paths.contains(dir.getPath()) && dir.isDirectory()) - return dir; - } - return null; - } - - public int areInstalled() throws NoRootException { - if (INSTALL_DIR == null) - return ERROR; - final StringBuilder script = new StringBuilder(); - for (final String name : EXECUTABLES) { - script.append(String.format("cmp -s '%s' '%s' && ", - new File(localBinaryDir, name).getAbsolutePath(), - new File(INSTALL_DIR, name).getAbsolutePath())); - } - script.append("exit ").append(OsConstants.EALREADY).append(';'); - try { - final int ret = Application.getRootShell().run(null, script.toString()); - if (ret == OsConstants.EALREADY) - return willInstallAsMagiskModule() ? YES | MAGISK : YES | SYSTEM; - else - return willInstallAsMagiskModule() ? NO | MAGISK : NO | SYSTEM; - } catch (final IOException ignored) { - return ERROR; - } - } - - public void ensureToolsAvailable() throws FileNotFoundException { - synchronized (lock) { - if (areToolsAvailable == null) { - try { - Log.d(TAG, extract() ? "Tools are now extracted into our private binary dir" : - "Tools were already extracted into our private binary dir"); - areToolsAvailable = true; - } catch (final IOException e) { - Log.e(TAG, "The wg and wg-quick tools are not available", e); - areToolsAvailable = false; - } - } - if (!areToolsAvailable) - throw new FileNotFoundException( - context.getString(R.string.tools_unavailable_error)); - } - } - - public int install() throws NoRootException, IOException { - return willInstallAsMagiskModule() ? installMagisk() : installSystem(); - } - - private int installMagisk() throws NoRootException, IOException { - extract(); - final StringBuilder script = new StringBuilder("set -ex; "); - - script.append("trap 'rm -rf /sbin/.magisk/img/wireguard' INT TERM EXIT; "); - script.append(String.format("rm -rf /sbin/.magisk/img/wireguard/; mkdir -p /sbin/.magisk/img/wireguard%s; ", INSTALL_DIR)); - script.append(String.format("printf 'name=WireGuard Command Line Tools\nversion=%s\nversionCode=%s\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.magisk/img/wireguard/module.prop; ", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); - script.append("touch /sbin/.magisk/img/wireguard/auto_mount; "); - for (final String name : EXECUTABLES) { - final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, name); - script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ", - new File(localBinaryDir, name), destination, destination, destination)); - } - script.append("trap - INT TERM EXIT;"); - - try { - return Application.getRootShell().run(null, script.toString()) == 0 ? YES | MAGISK : ERROR; - } catch (final IOException ignored) { - return ERROR; - } - } - - private int installSystem() throws NoRootException, IOException { - if (INSTALL_DIR == null) - return OsConstants.ENOENT; - extract(); - final StringBuilder script = new StringBuilder("set -ex; "); - script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; "); - for (final String name : EXECUTABLES) { - final File destination = new File(INSTALL_DIR, name); - script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ", - new File(localBinaryDir, name), destination, destination, destination)); - } - try { - return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR; - } catch (final IOException ignored) { - return ERROR; - } - } - - public boolean extract() throws IOException { - localBinaryDir.mkdirs(); - final File files[] = new File[EXECUTABLES.length]; - boolean allExist = true; - for (int i = 0; i < files.length; ++i) { - files[i] = new File(localBinaryDir, EXECUTABLES[i]); - allExist &= files[i].exists(); - } - if (allExist) - return false; - for (int i = 0; i < files.length; ++i) { - if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], files[i])) - throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]); - if (!files[i].setExecutable(true, false)) - throw new IOException("Unable to mark " + files[i].getAbsolutePath() + " as executable"); - } - return true; - } - - private boolean willInstallAsMagiskModule() { - synchronized (lock) { - if (installAsMagiskModule == null) { - try { - installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.magisk/mirror -a -d /sbin/.magisk/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS; - } catch (final Exception ignored) { - installAsMagiskModule = false; - } - } - return installAsMagiskModule; - } - } -} |