summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-03-26 23:54:44 -0600
committerJason A. Donenfeld <Jason@zx2c4.com>2020-03-27 03:08:47 -0600
commit48a9fd46a679db5cc4baa0ffd03b14d006bab04a (patch)
treeb0e1647a9bba066d6c6870a397d513912782fcad
parent8669c01eaa47a39d2c36147d4dbe2e987e0520c9 (diff)
databinding: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.java150
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt137
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java143
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt112
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/Keyed.kt12
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt32
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java162
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt103
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt106
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt5
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt31
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ApplicationData.kt6
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt5
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelManager.kt7
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java111
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ObservableKeyedList.java21
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java200
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java19
-rw-r--r--ui/src/main/java/com/wireguard/util/Keyed.java15
-rw-r--r--ui/src/main/java/com/wireguard/util/KeyedList.java33
-rw-r--r--ui/src/main/java/com/wireguard/util/SortedKeyedList.java32
-rw-r--r--ui/src/main/res/layout/app_list_dialog_fragment.xml2
-rw-r--r--ui/src/main/res/layout/app_list_item.xml2
-rw-r--r--ui/src/main/res/layout/tunnel_list_fragment.xml2
-rw-r--r--ui/src/main/res/layout/tunnel_list_item.xml2
25 files changed, 532 insertions, 918 deletions
diff --git a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.java b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
deleted file mode 100644
index 6dfd7856..00000000
--- a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import android.text.InputFilter;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.wireguard.android.BR;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler;
-import com.wireguard.android.util.ObservableKeyedList;
-import com.wireguard.android.widget.ToggleSwitch;
-import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
-import com.wireguard.config.Attribute;
-import com.wireguard.config.InetNetwork;
-import com.wireguard.util.Keyed;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-import androidx.databinding.BindingAdapter;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-import androidx.databinding.adapters.ListenerUtil;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import java9.util.Optional;
-
-/**
- * Static methods for use by generated code in the Android data binding library.
- */
-
-@SuppressWarnings("unused")
-@NonNullForAll
-public final class BindingAdapters {
- private BindingAdapters() {
- // Prevent instantiation.
- }
-
- @BindingAdapter("checked")
- public static void setChecked(final ToggleSwitch view, final boolean checked) {
- view.setCheckedInternal(checked);
- }
-
- @BindingAdapter("filter")
- public static void setFilter(final TextView view, final InputFilter filter) {
- view.setFilters(new InputFilter[]{filter});
- }
-
- @BindingAdapter({"items", "layout"})
- public static <E>
- void setItems(final LinearLayout view,
- @Nullable final ObservableList<E> oldList, final int oldLayoutId,
- @Nullable final ObservableList<E> newList, final int newLayoutId) {
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- ItemChangeListener<E> listener = ListenerUtil.getListener(view, R.id.item_change_listener);
- // If the layout changes, any existing listener must be replaced.
- if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
- listener.setList(null);
- listener = null;
- // Stop tracking the old listener.
- ListenerUtil.trackListener(view, null, R.id.item_change_listener);
- }
- // Avoid adding a listener when there is no new list or layout.
- if (newList == null || newLayoutId == 0)
- return;
- if (listener == null) {
- listener = new ItemChangeListener<>(view, newLayoutId);
- ListenerUtil.trackListener(view, listener, R.id.item_change_listener);
- }
- // Either the list changed, or this is an entirely new listener because the layout changed.
- listener.setList(newList);
- }
-
- @BindingAdapter({"items", "layout"})
- public static <E>
- void setItems(final LinearLayout view,
- @Nullable final Iterable<E> oldList, final int oldLayoutId,
- @Nullable final Iterable<E> newList, final int newLayoutId) {
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- view.removeAllViews();
- if (newList == null)
- return;
- final LayoutInflater layoutInflater = LayoutInflater.from(view.getContext());
- for (final E item : newList) {
- final ViewDataBinding binding =
- DataBindingUtil.inflate(layoutInflater, newLayoutId, view, false);
- binding.setVariable(BR.collection, newList);
- binding.setVariable(BR.item, item);
- binding.executePendingBindings();
- view.addView(binding.getRoot());
- }
- }
-
- @BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
- public static <K, E extends Keyed<? extends K>>
- void setItems(final RecyclerView view,
- @Nullable final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
- final RowConfigurationHandler oldRowConfigurationHandler,
- @Nullable final ObservableKeyedList<K, E> newList, final int newLayoutId,
- final RowConfigurationHandler newRowConfigurationHandler) {
- if (view.getLayoutManager() == null)
- view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
-
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- // The ListAdapter interface is not generic, so this cannot be checked.
- @SuppressWarnings("unchecked") ObservableKeyedRecyclerViewAdapter<K, E> adapter =
- (ObservableKeyedRecyclerViewAdapter<K, E>) view.getAdapter();
- // If the layout changes, any existing adapter must be replaced.
- if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
- adapter.setList(null);
- adapter = null;
- }
- // Avoid setting an adapter when there is no new list or layout.
- if (newList == null || newLayoutId == 0)
- return;
- if (adapter == null) {
- adapter = new ObservableKeyedRecyclerViewAdapter<>(view.getContext(), newLayoutId, newList);
- view.setAdapter(adapter);
- }
-
- adapter.setRowConfigurationHandler(newRowConfigurationHandler);
- // Either the list changed, or this is an entirely new listener because the layout changed.
- adapter.setList(newList);
- }
-
- @BindingAdapter("onBeforeCheckedChanged")
- public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
- final OnBeforeCheckedChangeListener listener) {
- view.setOnBeforeCheckedChangeListener(listener);
- }
-
- @BindingAdapter("android:text")
- public static void setText(final TextView view, final Optional<?> text) {
- view.setText(text.map(Object::toString).orElse(""));
- }
-
- @BindingAdapter("android:text")
- public static void setText(final TextView view, @Nullable final Iterable<InetNetwork> networks) {
- view.setText(networks != null ? Attribute.join(networks) : "");
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
new file mode 100644
index 00000000..87248b57
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+import android.text.InputFilter
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ObservableList
+import androidx.databinding.ViewDataBinding
+import androidx.databinding.adapters.ListenerUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.wireguard.android.BR
+import com.wireguard.android.R
+import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
+import com.wireguard.android.widget.ToggleSwitch
+import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener
+import com.wireguard.config.Attribute
+import com.wireguard.config.InetNetwork
+import java9.util.Optional
+
+/**
+ * Static methods for use by generated code in the Android data binding library.
+ */
+object BindingAdapters {
+ @JvmStatic
+ @BindingAdapter("checked")
+ fun setChecked(view: ToggleSwitch, checked: Boolean) {
+ view.setCheckedInternal(checked)
+ }
+
+ @JvmStatic
+ @BindingAdapter("filter")
+ fun setFilter(view: TextView, filter: InputFilter) {
+ view.filters = arrayOf(filter)
+ }
+
+ @JvmStatic
+ @BindingAdapter("items", "layout")
+ fun <E> setItems(view: LinearLayout,
+ oldList: ObservableList<E>?, oldLayoutId: Int,
+ newList: ObservableList<E>?, newLayoutId: Int) {
+ if (oldList === newList && oldLayoutId == newLayoutId)
+ return
+ var listener: ItemChangeListener<E>? = ListenerUtil.getListener(view, R.id.item_change_listener)
+ // If the layout changes, any existing listener must be replaced.
+ if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
+ listener.setList(null)
+ listener = null
+ // Stop tracking the old listener.
+ ListenerUtil.trackListener<Any?>(view, null, R.id.item_change_listener)
+ }
+ // Avoid adding a listener when there is no new list or layout.
+ if (newList == null || newLayoutId == 0)
+ return
+ if (listener == null) {
+ listener = ItemChangeListener(view, newLayoutId)
+ ListenerUtil.trackListener(view, listener, R.id.item_change_listener)
+ }
+ // Either the list changed, or this is an entirely new listener because the layout changed.
+ listener.setList(newList)
+ }
+
+ @JvmStatic
+ @BindingAdapter("items", "layout")
+ fun <E> setItems(view: LinearLayout,
+ oldList: Iterable<E>?, oldLayoutId: Int,
+ newList: Iterable<E>?, newLayoutId: Int) {
+ if (oldList === newList && oldLayoutId == newLayoutId)
+ return
+ view.removeAllViews()
+ if (newList == null)
+ return
+ val layoutInflater = LayoutInflater.from(view.context)
+ for (item in newList) {
+ val binding = DataBindingUtil.inflate<ViewDataBinding>(layoutInflater, newLayoutId, view, false)
+ binding.setVariable(BR.collection, newList)
+ binding.setVariable(BR.item, item)
+ binding.executePendingBindings()
+ view.addView(binding.root)
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter(requireAll = false, value = ["items", "layout", "configurationHandler"])
+ fun <K, E : Keyed<out K>> setItems(view: RecyclerView,
+ oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int,
+ oldRowConfigurationHandler: RowConfigurationHandler<*, *>?,
+ newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int,
+ newRowConfigurationHandler: RowConfigurationHandler<*, *>?) {
+ if (view.layoutManager == null)
+ view.layoutManager = LinearLayoutManager(view.context, RecyclerView.VERTICAL, false)
+ if (oldList === newList && oldLayoutId == newLayoutId)
+ return
+ // The ListAdapter interface is not generic, so this cannot be checked.
+ var adapter = view.adapter as ObservableKeyedRecyclerViewAdapter<K, E>?
+ // If the layout changes, any existing adapter must be replaced.
+ if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
+ adapter.setList(null)
+ adapter = null
+ }
+ // Avoid setting an adapter when there is no new list or layout.
+ if (newList == null || newLayoutId == 0)
+ return
+ if (adapter == null) {
+ adapter = ObservableKeyedRecyclerViewAdapter(view.context, newLayoutId, newList)
+ view.adapter = adapter
+ }
+ adapter.setRowConfigurationHandler(newRowConfigurationHandler)
+ // Either the list changed, or this is an entirely new listener because the layout changed.
+ adapter.setList(newList)
+ }
+
+ @JvmStatic
+ @BindingAdapter("onBeforeCheckedChanged")
+ fun setOnBeforeCheckedChanged(view: ToggleSwitch,
+ listener: OnBeforeCheckedChangeListener?) {
+ view.setOnBeforeCheckedChangeListener(listener)
+ }
+
+ @JvmStatic
+ @BindingAdapter("android:text")
+ fun setText(view: TextView, text: Optional<*>) {
+ view.text = text.map { obj: Any -> obj.toString() }.orElse("")
+ }
+
+ @JvmStatic
+ @BindingAdapter("android:text")
+ fun setText(view: TextView, networks: Iterable<InetNetwork?>?) {
+ view.text = if (networks != null) Attribute.join(networks) else ""
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java b/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java
deleted file mode 100644
index efccace1..00000000
--- a/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.wireguard.android.BR;
-import com.wireguard.util.NonNullForAll;
-
-import java.lang.ref.WeakReference;
-import java.util.Objects;
-
-import androidx.annotation.Nullable;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-
-/**
- * Helper class for binding an ObservableList to the children of a ViewGroup.
- */
-
-@NonNullForAll
-class ItemChangeListener<T> {
- private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this);
- private final ViewGroup container;
- private final int layoutId;
- private final LayoutInflater layoutInflater;
- @Nullable private ObservableList<T> list;
-
- ItemChangeListener(final ViewGroup container, final int layoutId) {
- this.container = container;
- this.layoutId = layoutId;
- layoutInflater = LayoutInflater.from(container.getContext());
- }
-
- private View getView(final int position, @Nullable final View convertView) {
- ViewDataBinding binding = convertView != null ? DataBindingUtil.getBinding(convertView) : null;
- if (binding == null) {
- binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false);
- }
-
- Objects.requireNonNull(list, "Trying to get a view while list is still null");
-
- binding.setVariable(BR.collection, list);
- binding.setVariable(BR.item, list.get(position));
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- void setList(@Nullable final ObservableList<T> newList) {
- if (list != null)
- list.removeOnListChangedCallback(callback);
- list = newList;
- if (list != null) {
- list.addOnListChangedCallback(callback);
- callback.onChanged(list);
- } else {
- container.removeAllViews();
- }
- }
-
- private static final class OnListChangedCallback<T>
- extends ObservableList.OnListChangedCallback<ObservableList<T>> {
-
- private final WeakReference<ItemChangeListener<T>> weakListener;
-
- private OnListChangedCallback(final ItemChangeListener<T> listener) {
- weakListener = new WeakReference<>(listener);
- }
-
- @Override
- public void onChanged(final ObservableList<T> sender) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- // TODO: recycle views
- listener.container.removeAllViews();
- for (int i = 0; i < sender.size(); ++i)
- listener.container.addView(listener.getView(i, null));
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- for (int i = positionStart; i < positionStart + itemCount; ++i) {
- final View child = listener.container.getChildAt(i);
- listener.container.removeViewAt(i);
- listener.container.addView(listener.getView(i, child));
- }
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- for (int i = positionStart; i < positionStart + itemCount; ++i)
- listener.container.addView(listener.getView(i, null));
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeMoved(final ObservableList<T> sender, final int fromPosition,
- final int toPosition, final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- final View[] views = new View[itemCount];
- for (int i = 0; i < itemCount; ++i)
- views[i] = listener.container.getChildAt(fromPosition + i);
- listener.container.removeViews(fromPosition, itemCount);
- for (int i = 0; i < itemCount; ++i)
- listener.container.addView(views[i], toPosition + i);
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- listener.container.removeViews(positionStart, itemCount);
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt b/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt
new file mode 100644
index 00000000..131f3877
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ObservableList
+import androidx.databinding.ViewDataBinding
+import com.wireguard.android.BR
+import java.lang.ref.WeakReference
+
+/**
+ * Helper class for binding an ObservableList to the children of a ViewGroup.
+ */
+internal class ItemChangeListener<T>(private val container: ViewGroup, private val layoutId: Int) {
+ private val callback = OnListChangedCallback(this)
+ private val layoutInflater: LayoutInflater = LayoutInflater.from(container.context)
+ private var list: ObservableList<T>? = null
+
+ private fun getView(position: Int, convertView: View?): View {
+ var binding = if (convertView != null) DataBindingUtil.getBinding<ViewDataBinding>(convertView) else null
+ if (binding == null) {
+ binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false)
+ }
+ require(list != null) { "Trying to get a view while list is still null" }
+ binding!!.setVariable(BR.collection, list)
+ binding.setVariable(BR.item, list!![position])
+ binding.executePendingBindings()
+ return binding.root
+ }
+
+ fun setList(newList: ObservableList<T>?) {
+ list?.removeOnListChangedCallback(callback)
+ list = newList
+ if (list != null) {
+ list!!.addOnListChangedCallback(callback)
+ callback.onChanged(list!!)
+ } else {
+ container.removeAllViews()
+ }
+ }
+
+ private class OnListChangedCallback<T> constructor(listener: ItemChangeListener<T>) : ObservableList.OnListChangedCallback<ObservableList<T>>() {
+ private val weakListener: WeakReference<ItemChangeListener<T>> = WeakReference(listener)
+
+ override fun onChanged(sender: ObservableList<T>) {
+ val listener = weakListener.get()
+ if (listener != null) {
+ // TODO: recycle views
+ listener.container.removeAllViews()
+ for (i in sender.indices)
+ listener.container.addView(listener.getView(i, null))
+ } else {
+ sender.removeOnListChangedCallback(this)
+ }
+ }
+
+ override fun onItemRangeChanged(sender: ObservableList<T>, positionStart: Int,
+ itemCount: Int) {
+ val listener = weakListener.get()
+ if (listener != null) {
+ for (i in positionStart until positionStart + itemCount) {
+ val child = listener.container.getChildAt(i)
+ listener.container.removeViewAt(i)
+ listener.container.addView(listener.getView(i, child))
+ }
+ } else {
+ sender.removeOnListChangedCallback(this)
+ }
+ }
+
+ override fun onItemRangeInserted(sender: ObservableList<T>, positionStart: Int,
+ itemCount: Int) {
+ val listener = weakListener.get()
+ if (listener != null) {
+ for (i in positionStart until positionStart + itemCount)
+ listener.container.addView(listener.getView(i, null))
+ } else {
+ sender.removeOnListChangedCallback(this)
+ }
+ }
+
+ override fun onItemRangeMoved(sender: ObservableList<T>, fromPosition: Int,
+ toPosition: Int, itemCount: Int) {
+ val listener = weakListener.get()
+ if (listener != null) {
+ val views = arrayOfNulls<View>(itemCount)
+ for (i in 0 until itemCount) views[i] = listener.container.getChildAt(fromPosition + i)
+ listener.container.removeViews(fromPosition, itemCount)
+ for (i in 0 until itemCount) listener.container.addView(views[i], toPosition + i)
+ } else {
+ sender.removeOnListChangedCallback(this)
+ }
+ }
+
+ override fun onItemRangeRemoved(sender: ObservableList<T>, positionStart: Int,
+ itemCount: Int) {
+ val listener = weakListener.get()
+ if (listener != null) {
+ listener.container.removeViews(positionStart, itemCount)
+ } else {
+ sender.removeOnListChangedCallback(this)
+ }
+ }
+
+ }
+
+}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt b/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
new file mode 100644
index 00000000..dd03e4c9
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+/**
+ * Interface for objects that have a identifying key of the given type.
+ */
+interface Keyed<K> {
+ val key: K
+}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt
new file mode 100644
index 00000000..c00f553c
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+import androidx.databinding.ObservableArrayList
+
+/**
+ * ArrayList that allows looking up elements by some key property. As the key property must always
+ * be retrievable, this list cannot hold `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.
+ */
+open class ObservableKeyedArrayList<K, E : Keyed<out K>> : ObservableArrayList<E>() {
+ fun containsKey(key: K) = indexOfKey(key) >= 0
+
+ operator fun get(key: K): E? {
+ val index = indexOfKey(key)
+ return if (index >= 0) get(index) else null
+ }
+
+ open fun indexOfKey(key: K): Int {
+ val iterator = listIterator()
+ while (iterator.hasNext()) {
+ val index = iterator.nextIndex()
+ if (iterator.next()!!.key == key)
+ return index
+ }
+ return -1
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
deleted file mode 100644
index c8222cde..00000000
--- a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.wireguard.android.BR;
-import com.wireguard.android.util.ObservableKeyedList;
-import com.wireguard.util.Keyed;
-import com.wireguard.util.NonNullForAll;
-
-import java.lang.ref.WeakReference;
-
-import androidx.annotation.Nullable;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-
-/**
- * A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
- */
-
-@NonNullForAll
-public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extends Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder> {
-
- private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
- private final int layoutId;
- private final LayoutInflater layoutInflater;
- @Nullable private ObservableKeyedList<K, E> list;
- @Nullable private RowConfigurationHandler rowConfigurationHandler;
-
- ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
- final ObservableKeyedList<K, E> list) {
- this.layoutId = layoutId;
- layoutInflater = LayoutInflater.from(context);
- setList(list);
- }
-
- @Nullable
- private E getItem(final int position) {
- if (list == null || position < 0 || position >= list.size())
- return null;
- return list.get(position);
- }
-
- @Override
- public int getItemCount() {
- return list != null ? list.size() : 0;
- }
-
- @Override
- public long getItemId(final int position) {
- final K key = getKey(position);
- return key != null ? key.hashCode() : -1;
- }
-
- @Nullable
- private K getKey(final int position) {
- final E item = getItem(position);
- return item != null ? item.getKey() : null;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void onBindViewHolder(final ViewHolder holder, final int position) {
- holder.binding.setVariable(BR.collection, list);
- holder.binding.setVariable(BR.key, getKey(position));
- holder.binding.setVariable(BR.item, getItem(position));
- holder.binding.executePendingBindings();
-
- if (rowConfigurationHandler != null) {
- final E item = getItem(position);
- if (item != null) {
- rowConfigurationHandler.onConfigureRow(holder.binding, item, position);
- }
- }
- }
-
- @Override
- public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
- return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
- }
-
- void setList(@Nullable final ObservableKeyedList<K, E> newList) {
- if (list != null)
- list.removeOnListChangedCallback(callback);
- list = newList;
- if (list != null) {
- list.addOnListChangedCallback(callback);
- }
- notifyDataSetChanged();
- }
-
- void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
- this.rowConfigurationHandler = rowConfigurationHandler;
- }
-
- public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
- void onConfigureRow(B binding, T item, int position);
- }
-
- private static final class OnListChangedCallback<E extends Keyed<?>>
- extends ObservableList.OnListChangedCallback<ObservableList<E>> {
-
- private final WeakReference<ObservableKeyedRecyclerViewAdapter<?, E>> weakAdapter;
-
- private OnListChangedCallback(final ObservableKeyedRecyclerViewAdapter<?, E> adapter) {
- weakAdapter = new WeakReference<>(adapter);
- }
-
- @Override
- public void onChanged(final ObservableList<E> sender) {
- final ObservableKeyedRecyclerViewAdapter adapter = weakAdapter.get();
- if (adapter != null)
- adapter.notifyDataSetChanged();
- else
- sender.removeOnListChangedCallback(this);
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeMoved(final ObservableList<E> sender, final int fromPosition,
- final int toPosition, final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- final ViewDataBinding binding;
-
- public ViewHolder(final ViewDataBinding binding) {
- super(binding.getRoot());
-
- this.binding = binding;
- }
- }
-
-}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt
new file mode 100644
index 00000000..72c2495b
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ObservableList
+import androidx.databinding.ViewDataBinding
+import androidx.recyclerview.widget.RecyclerView
+import com.wireguard.android.BR
+import java.lang.ref.WeakReference
+
+/**
+ * A generic `RecyclerView.Adapter` backed by a `ObservableKeyedArrayList`.
+ */
+class ObservableKeyedRecyclerViewAdapter<K, E : Keyed<out K>> internal constructor(context: Context, private val layoutId: Int,
+ list: ObservableKeyedArrayList<K, E>?) : RecyclerView.Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder>() {
+ private val callback = OnListChangedCallback(this)
+ private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
+ private var list: ObservableKeyedArrayList<K, E>? = null
+ private var rowConfigurationHandler: RowConfigurationHandler<ViewDataBinding, Any>? = null
+
+ private fun getItem(position: Int): E? = if (list == null || position < 0 || position >= list!!.size) null else list?.get(position)
+
+ override fun getItemCount() = list?.size ?: 0
+
+ override fun getItemId(position: Int) = (getKey(position)?.hashCode() ?: -1).toLong()
+
+ private fun getKey(position: Int): K? = getItem(position)?.key
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.binding.setVariable(BR.collection, list)
+ holder.binding.setVariable(BR.key, getKey(position))
+ holder.binding.setVariable(BR.item, getItem(position))
+ holder.binding.executePendingBindings()
+ if (rowConfigurationHandler != null) {
+ val item = getItem(position)
+ if (item != null) {
+ rowConfigurationHandler?.onConfigureRow(holder.binding, item, position)
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false))
+
+ fun setList(newList: ObservableKeyedArrayList<K, E>?) {
+ list?.removeOnListChangedCallback(callback)
+ list = newList
+ list?.addOnListChangedCallback(callback)
+ notifyDataSetChanged()
+ }
+
+ fun setRowConfigurationHandler(rowConfigurationHandler: RowConfigurationHandler<*, *>?) {
+ this.rowConfigurationHandler = rowConfigurationHandler as? RowConfigurationHandler<ViewDataBinding, Any>
+ }
+
+ interface RowConfigurationHandler<B : ViewDataBinding, T> {
+ fun onConfigureRow(binding: B, item: T, position: Int)
+ }
+
+ private class OnListChangedCallback<E : Keyed<*>> constructor(adapter: ObservableKeyedRecyclerViewAdapter<*, E>) : ObservableList.OnListChangedCallback<ObservableList<E>>() {
+ private val weakAdapter: WeakReference<ObservableKeyedRecyclerViewAdapter<*, E>> = WeakReference(adapter)
+
+ override fun onChanged(sender: ObservableList<E>) {
+ val adapter = weakAdapter.get()
+ if (adapter != null)
+ adapter.notifyDataSetChanged()
+ else
+ sender.removeOnListChangedCallback(this)
+ }
+
+ override fun onItemRangeChanged(sender: ObservableList<E>, positionStart: Int,
+ itemCount: Int) {
+ onChanged(sender)
+ }
+
+ override fun onItemRangeInserted(sender: ObservableList<E>, positionStart: Int,
+ itemCount: Int) {
+ onChanged(sender)
+ }
+
+ override fun onItemRangeMoved(sender: ObservableList<E>, fromPosition: Int,
+ toPosition: Int, itemCount: Int) {
+ onChanged(sender)
+ }
+
+ override fun onItemRangeRemoved(sender: ObservableList<E>, positionStart: Int,
+ itemCount: Int) {
+ onChanged(sender)
+ }
+
+ }
+
+ class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
+
+ init {
+ setList(list)
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt b/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
new file mode 100644
index 00000000..9261d202
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.databinding
+
+import java.util.AbstractList
+import java.util.Collections
+import java.util.Comparator
+import java.util.NoSuchElementException
+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.
+ */
+class ObservableSortedKeyedArrayList<K, E : Keyed<out K>>(private val comparator: Comparator<in K>?) : ObservableKeyedArrayList<K, E>() {
+ @Transient
+ private val keyList = KeyList(this)
+
+ override fun add(element: E): Boolean {
+ val insertionPoint = getInsertionPoint(element)
+ if (insertionPoint < 0) {
+ // Skipping insertion is non-destructive if the new and existing objects are the same.
+ if (element === get(-insertionPoint - 1)) return false
+ throw IllegalArgumentException("Element with same key already exists in list")
+ }
+ super.add(insertionPoint, element)
+ return true
+ }
+
+ override fun add(index: Int, element: E) {
+ val insertionPoint = getInsertionPoint(element)
+ require(insertionPoint >= 0) { "Element with same key already exists in list" }
+ if (insertionPoint != index) throw IndexOutOfBoundsException("Wrong index given for element")
+ super.add(index, element)
+ }
+
+ override fun addAll(elements: Collection<E>): Boolean {
+ var didChange = false
+ for (e in elements) {
+ if (add(e))
+ didChange = true
+ }
+ return didChange
+ }
+
+ override fun addAll(index: Int, elements: Collection<E>): Boolean {
+ var i = index
+ for (e in elements)
+ add(i++, e)
+ return true
+ }
+
+ private fun getInsertionPoint(e: E): Int {
+ return if (comparator != null) {
+ -Collections.binarySearch(keyList, e.key, comparator) - 1
+ } else {
+ val list = keyList as List<Comparable<K>>
+ -Collections.binarySearch(list, e.key) - 1
+ }
+ }
+
+ override fun indexOfKey(key: K): Int {
+ val index: Int
+ index = if (comparator != null) {
+ Collections.binarySearch(keyList, key, comparator)
+ } else {
+ val list = keyList as List<Comparable<K>>
+ Collections.binarySearch(list, key)
+ }
+ return if (index >= 0) index else -1
+ }
+
+ override fun set(index: Int, element: E): E {
+ val order: Int
+ order = if (comparator != null) {
+ comparator.compare(element.key, get(index).key)
+ } else {
+ val key = element.key as Comparable<K>
+ key.compareTo(get(index).key)
+ }
+ if (order != 0) {
+ // Allow replacement if the new key would be inserted adjacent to the replaced element.
+ val insertionPoint = getInsertionPoint(element)
+ if (insertionPoint < index || insertionPoint > index + 1)
+ throw IndexOutOfBoundsException("Wrong index given for element")
+ }
+ return super.set(index, element)
+ }
+
+ fun values(): Collection<E> {
+ return this
+ }
+
+ private class KeyList<K, E : Keyed<out K>>(private val list: ObservableSortedKeyedArrayList<K, E>) : AbstractList<K>(), Set<K> {
+ override fun get(index: Int): K = list[index].key
+
+ override val size
+ get() = list.size
+
+ override fun spliterator(): Spliterator<K> = super<AbstractList>.spliterator()
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
index 34981bd3..3bab3648 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -17,11 +17,10 @@ import com.wireguard.android.R
import com.wireguard.android.databinding.AppListDialogFragmentBinding
import com.wireguard.android.model.ApplicationData
import com.wireguard.android.util.ErrorMessages
-import com.wireguard.android.util.ObservableKeyedArrayList
-import com.wireguard.android.util.ObservableKeyedList
+import com.wireguard.android.databinding.ObservableKeyedArrayList
class AppListDialogFragment : DialogFragment() {
- private val appData: ObservableKeyedList<String, ApplicationData> = ObservableKeyedArrayList()
+ private val appData: ObservableKeyedArrayList<String, ApplicationData> = ObservableKeyedArrayList()
private var currentlyExcludedApps = emptyList<String>()
private fun loadData() {
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
index ca4b1eee..c23699eb 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
@@ -267,23 +267,26 @@ class TunnelListFragment : BaseFragment() {
binding ?: return
binding!!.fragment = this
Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
- binding!!.rowConfigurationHandler = RowConfigurationHandler { binding: TunnelListItemBinding, tunnel: ObservableTunnel, position ->
- binding.fragment = this
- binding.root.setOnClickListener {
- if (actionMode == null) {
- selectedTunnel = tunnel
- } else {
+ val parent = this
+ binding!!.rowConfigurationHandler = object : RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel> {
+ override fun onConfigureRow(binding: TunnelListItemBinding, item: ObservableTunnel, position: Int) {
+ binding.fragment = parent
+ binding.root.setOnClickListener {
+ if (actionMode == null) {
+ selectedTunnel = item
+ } else {
+ actionModeListener.toggleItemChecked(position)
+ }
+ }
+ binding.root.setOnLongClickListener {
actionModeListener.toggleItemChecked(position)
+ true
}
+ if (actionMode != null)
+ (binding.root as MultiselectableRelativeLayout).setMultiSelected(actionModeListener.checkedItems.contains(position))
+ else
+ (binding.root as MultiselectableRelativeLayout).setSingleSelected(selectedTunnel == item)
}
- binding.root.setOnLongClickListener {
- actionModeListener.toggleItemChecked(position)
- true
- }
- if (actionMode != null)
- (binding.root as MultiselectableRelativeLayout).setMultiSelected(actionModeListener.checkedItems.contains(position))
- else
- (binding.root as MultiselectableRelativeLayout).setSingleSelected(selectedTunnel == tunnel)
}
}
diff --git a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
index 6d536e71..e931cdd2 100644
--- a/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ApplicationData.kt
@@ -8,12 +8,10 @@ import android.graphics.drawable.Drawable
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.wireguard.android.BR
-import com.wireguard.util.Keyed
+import com.wireguard.android.databinding.Keyed
class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isExcludedFromTunnel: Boolean) : BaseObservable(), Keyed<String> {
- override fun getKey(): String {
- return name
- }
+ override val key = name
@get:Bindable
var isExcludedFromTunnel = isExcludedFromTunnel
diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
index 24b24ed5..457f50e7 100644
--- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
@@ -11,7 +11,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.util.ExceptionLoggers
import com.wireguard.config.Config
-import com.wireguard.util.Keyed
+import com.wireguard.android.databinding.Keyed
import java9.util.concurrent.CompletableFuture
import java9.util.concurrent.CompletionStage
@@ -24,8 +24,7 @@ class ObservableTunnel internal constructor(
config: Config?,
state: Tunnel.State
) : BaseObservable(), Keyed<String>, Tunnel {
- override fun getKey() = name
-
+ override val key = name
@Bindable
override fun getName() = name
diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
index 74ecbc66..43e94fdc 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
@@ -22,8 +22,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.configStore.ConfigStore
import com.wireguard.android.util.ExceptionLoggers
-import com.wireguard.android.util.ObservableSortedKeyedArrayList
-import com.wireguard.android.util.ObservableSortedKeyedList
+import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
import com.wireguard.config.Config
import java9.util.Comparators
import java9.util.concurrent.CompletableFuture
@@ -34,10 +33,10 @@ import java.util.ArrayList
* Maintains and mediates changes to the set of available WireGuard tunnels,
*/
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
- val tunnels = CompletableFuture<ObservableSortedKeyedList<String, ObservableTunnel>>()
+ val tunnels = CompletableFuture<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
private val context: Context = get()
private val delayedLoadRestoreTunnels = ArrayList<CompletableFuture<Void>>()
- private val tunnelMap: ObservableSortedKeyedList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(COMPARATOR)
+ private val tunnelMap: ObservableSortedKeyedArrayList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(COMPARATOR)
private var haveLoaded = false
private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel? {
diff --git a/ui/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java b/ui/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
deleted file mode 100644
index e233588c..00000000
--- a/ui/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
+++ /dev/null
@@ -1,111 +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.NonNullForAll;
-
-import java.util.Collection;
-import java.util.ListIterator;
-import java.util.Objects;
-
-import androidx.annotation.Nullable;
-import androidx.databinding.ObservableArrayList;
-
-/**
- * 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.
- */
-
-@NonNullForAll
-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/ui/src/main/java/com/wireguard/android/util/ObservableKeyedList.java b/ui/src/main/java/com/wireguard/android/util/ObservableKeyedList.java
deleted file mode 100644
index b191655e..00000000
--- a/ui/src/main/java/com/wireguard/android/util/ObservableKeyedList.java
+++ /dev/null
@@ -1,21 +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.KeyedList;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.databinding.ObservableList;
-
-/**
- * A list that is both keyed and observable.
- */
-
-@NonNullForAll
-public interface ObservableKeyedList<K, E extends Keyed<? extends K>>
- extends KeyedList<K, E>, ObservableList<E> {
-}
diff --git a/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java b/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
deleted file mode 100644
index 3af354b2..00000000
--- a/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
+++ /dev/null
@@ -1,200 +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.NonNullForAll;
-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;
-
-import androidx.annotation.Nullable;
-
-/**
- * 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.
- */
-
-@NonNullForAll
-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/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java b/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java
deleted file mode 100644
index 84e72e1e..00000000
--- a/ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.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 com.wireguard.util.Keyed;
-import com.wireguard.util.NonNullForAll;
-import com.wireguard.util.SortedKeyedList;
-
-/**
- * A list that is both sorted/keyed and observable.
- */
-
-@NonNullForAll
-public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>>
- extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> {
-}
diff --git a/ui/src/main/java/com/wireguard/util/Keyed.java b/ui/src/main/java/com/wireguard/util/Keyed.java
deleted file mode 100644
index 0cc85d50..00000000
--- a/ui/src/main/java/com/wireguard/util/Keyed.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.util;
-
-/**
- * Interface for objects that have a identifying key of the given type.
- */
-
-@NonNullForAll
-public interface Keyed<K> {
- K getKey();
-}
diff --git a/ui/src/main/java/com/wireguard/util/KeyedList.java b/ui/src/main/java/com/wireguard/util/KeyedList.java
deleted file mode 100644
index 48bba484..00000000
--- a/ui/src/main/java/com/wireguard/util/KeyedList.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.util;
-
-import java.util.Collection;
-import java.util.List;
-
-import androidx.annotation.Nullable;
-
-/**
- * A list containing elements that can be looked up by key. A {@code KeyedList} cannot contain
- * {@code null} elements.
- */
-
-@NonNullForAll
-public interface KeyedList<K, E extends Keyed<? extends K>> extends List<E> {
- boolean containsAllKeys(Collection<K> keys);
-
- boolean containsKey(K key);
-
- @Nullable
- E get(K key);
-
- @Nullable
- E getLast(K key);
-
- int indexOfKey(K key);
-
- int lastIndexOfKey(K key);
-}
diff --git a/ui/src/main/java/com/wireguard/util/SortedKeyedList.java b/ui/src/main/java/com/wireguard/util/SortedKeyedList.java
deleted file mode 100644
index 80c50bed..00000000
--- a/ui/src/main/java/com/wireguard/util/SortedKeyedList.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.util;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Set;
-
-import androidx.annotation.Nullable;
-
-/**
- * A keyed list where all elements are sorted by the comparator returned by {@code comparator()}
- * applied to their keys.
- */
-
-@NonNullForAll
-public interface SortedKeyedList<K, E extends Keyed<? extends K>> extends KeyedList<K, E> {
- Comparator<? super K> comparator();
-
- @Nullable
- K firstKey();
-
- Set<K> keySet();
-
- @Nullable
- K lastKey();
-
- Collection<E> values();
-}
diff --git a/ui/src/main/res/layout/app_list_dialog_fragment.xml b/ui/src/main/res/layout/app_list_dialog_fragment.xml
index c91161e6..7a9b1eb1 100644
--- a/ui/src/main/res/layout/app_list_dialog_fragment.xml
+++ b/ui/src/main/res/layout/app_list_dialog_fragment.xml
@@ -15,7 +15,7 @@
<variable
name="appData"
- type="com.wireguard.android.util.ObservableKeyedList&lt;String, ApplicationData&gt;" />
+ type="com.wireguard.android.databinding.ObservableKeyedArrayList&lt;String, ApplicationData&gt;" />
</data>
<FrameLayout
diff --git a/ui/src/main/res/layout/app_list_item.xml b/ui/src/main/res/layout/app_list_item.xml
index a76073c0..5ce7a8a5 100644
--- a/ui/src/main/res/layout/app_list_item.xml
+++ b/ui/src/main/res/layout/app_list_item.xml
@@ -8,7 +8,7 @@
<variable
name="collection"
- type="com.wireguard.android.util.ObservableKeyedList&lt;String, com.wireguard.android.model.ApplicationData&gt;" />
+ type="com.wireguard.android.databinding.ObservableKeyedArrayList&lt;String, com.wireguard.android.model.ApplicationData&gt;" />
<variable
name="key"
diff --git a/ui/src/main/res/layout/tunnel_list_fragment.xml b/ui/src/main/res/layout/tunnel_list_fragment.xml
index c790c30c..7b7cb4b5 100644
--- a/ui/src/main/res/layout/tunnel_list_fragment.xml
+++ b/ui/src/main/res/layout/tunnel_list_fragment.xml
@@ -17,7 +17,7 @@
<variable
name="tunnels"
- type="com.wireguard.android.util.ObservableKeyedList&lt;String, ObservableTunnel&gt;" />
+ type="com.wireguard.android.databinding.ObservableKeyedArrayList&lt;String, ObservableTunnel&gt;" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
diff --git a/ui/src/main/res/layout/tunnel_list_item.xml b/ui/src/main/res/layout/tunnel_list_item.xml
index 04c0f51e..9c9517a7 100644
--- a/ui/src/main/res/layout/tunnel_list_item.xml
+++ b/ui/src/main/res/layout/tunnel_list_item.xml
@@ -11,7 +11,7 @@
<variable
name="collection"
- type="com.wireguard.android.util.ObservableKeyedList&lt;String, ObservableTunnel&gt;" />
+ type="com.wireguard.android.databinding.ObservableKeyedArrayList&lt;String, ObservableTunnel&gt;" />
<variable
name="key"