diff options
Diffstat (limited to 'app/src/main/java/com/wireguard/android/util')
11 files changed, 0 insertions, 903 deletions
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 481a6ffb..00000000 --- a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java +++ /dev/null @@ -1,160 +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 android.os.RemoteException; - -import androidx.annotation.Nullable; - -import com.wireguard.android.Application; -import com.wireguard.android.R; -import com.wireguard.android.backend.BackendException; -import com.wireguard.android.util.RootShell.RootShellException; -import com.wireguard.config.BadConfigException; -import com.wireguard.config.BadConfigException.Location; -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<BadConfigException.Reason, Integer> BCE_REASON_MAP = new EnumMap<>(Maps.of( - BadConfigException.Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key, - BadConfigException.Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number, - BadConfigException.Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value, - BadConfigException.Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute, - BadConfigException.Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section, - BadConfigException.Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value, - BadConfigException.Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error, - BadConfigException.Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute, - BadConfigException.Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section - )); - private static final Map<BackendException.Reason, Integer> BE_REASON_MAP = new EnumMap<>(Maps.of( - BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME, R.string.module_version_error, - BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE, R.string.tunnel_config_error, - BackendException.Reason.TUNNEL_MISSING_CONFIG, R.string.no_config_error, - BackendException.Reason.VPN_NOT_AUTHORIZED, R.string.vpn_not_authorized_error, - BackendException.Reason.UNABLE_TO_START_VPN, R.string.vpn_start_error, - 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, - 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 instanceof BackendException) { - final BackendException be = (BackendException) rootCause; - message = resources.getString(BE_REASON_MAP.get(be.getReason()), be.getFormat()); - } else if (rootCause instanceof RootShellException) { - final RootShellException rse = (RootShellException) rootCause; - message = resources.getString(RSE_REASON_MAP.get(rse.getReason()), rse.getFormat()); - } 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 || cause instanceof BackendException || - cause instanceof RootShellException) - break; - final Throwable nextCause = cause.getCause(); - if (nextCause instanceof RemoteException) - break; - cause = nextCause; - } - 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/Extensions.kt b/app/src/main/java/com/wireguard/android/util/Extensions.kt deleted file mode 100644 index 6b528a85..00000000 --- a/app/src/main/java/com/wireguard/android/util/Extensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util - -import android.content.Context -import android.util.TypedValue -import androidx.annotation.AttrRes - -fun Context.resolveAttribute(@AttrRes attrRes: Int): Int { - val typedValue = TypedValue() - theme.resolveAttribute(attrRes, typedValue, true) - return typedValue.data -} 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 bf094a5e..00000000 --- a/app/src/main/java/com/wireguard/android/util/ModuleLoader.java +++ /dev/null @@ -1,186 +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.util.RootShell.RootShellException; - -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.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 RootShell rootShell; - private final String userAgent; - private final File moduleDir; - private final File tmpDir; - - public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) { - moduleDir = new File(context.getCacheDir(), "kmod"); - tmpDir = new File(context.getCacheDir(), "tmp"); - this.rootShell = rootShell; - this.userAgent = userAgent; - } - - public boolean moduleMightExist() { - return moduleDir.exists() && moduleDir.isDirectory(); - } - - 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); - } - } - - @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, 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; - } -} 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> { -} |