From 48a9fd46a679db5cc4baa0ffd03b14d006bab04a Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 26 Mar 2020 23:54:44 -0600 Subject: [PATCH] databinding: rewrite in kotlin Signed-off-by: Jason A. Donenfeld --- .../android/databinding/BindingAdapters.java | 150 ------------- .../android/databinding/BindingAdapters.kt | 137 ++++++++++++ .../databinding/ItemChangeListener.java | 143 ------------- .../android/databinding/ItemChangeListener.kt | 112 ++++++++++ .../wireguard/android/databinding/Keyed.kt | 12 ++ .../databinding/ObservableKeyedArrayList.kt | 32 +++ .../ObservableKeyedRecyclerViewAdapter.java | 162 -------------- .../ObservableKeyedRecyclerViewAdapter.kt | 103 +++++++++ .../ObservableSortedKeyedArrayList.kt | 106 ++++++++++ .../android/fragment/AppListDialogFragment.kt | 5 +- .../android/fragment/TunnelListFragment.kt | 33 +-- .../android/model/ApplicationData.kt | 6 +- .../android/model/ObservableTunnel.kt | 5 +- .../wireguard/android/model/TunnelManager.kt | 7 +- .../util/ObservableKeyedArrayList.java | 111 ---------- .../android/util/ObservableKeyedList.java | 21 -- .../util/ObservableSortedKeyedArrayList.java | 200 ------------------ .../util/ObservableSortedKeyedList.java | 19 -- .../main/java/com/wireguard/util/Keyed.java | 15 -- .../java/com/wireguard/util/KeyedList.java | 33 --- .../com/wireguard/util/SortedKeyedList.java | 32 --- .../res/layout/app_list_dialog_fragment.xml | 2 +- ui/src/main/res/layout/app_list_item.xml | 2 +- .../main/res/layout/tunnel_list_fragment.xml | 2 +- ui/src/main/res/layout/tunnel_list_item.xml | 2 +- 25 files changed, 533 insertions(+), 919 deletions(-) delete mode 100644 ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.java create mode 100644 ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt delete mode 100644 ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java create mode 100644 ui/src/main/java/com/wireguard/android/databinding/ItemChangeListener.kt create mode 100644 ui/src/main/java/com/wireguard/android/databinding/Keyed.kt create mode 100644 ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedArrayList.kt delete mode 100644 ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java create mode 100644 ui/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.kt create mode 100644 ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt delete mode 100644 ui/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java delete mode 100644 ui/src/main/java/com/wireguard/android/util/ObservableKeyedList.java delete mode 100644 ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java delete mode 100644 ui/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java delete mode 100644 ui/src/main/java/com/wireguard/util/Keyed.java delete mode 100644 ui/src/main/java/com/wireguard/util/KeyedList.java delete mode 100644 ui/src/main/java/com/wireguard/util/SortedKeyedList.java 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 - void setItems(final LinearLayout view, - @Nullable final ObservableList oldList, final int oldLayoutId, - @Nullable final ObservableList newList, final int newLayoutId) { - if (oldList == newList && oldLayoutId == newLayoutId) - return; - ItemChangeListener 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 - void setItems(final LinearLayout view, - @Nullable final Iterable oldList, final int oldLayoutId, - @Nullable final Iterable 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 > - void setItems(final RecyclerView view, - @Nullable final ObservableKeyedList oldList, final int oldLayoutId, - final RowConfigurationHandler oldRowConfigurationHandler, - @Nullable final ObservableKeyedList 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 adapter = - (ObservableKeyedRecyclerViewAdapter) 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 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 setItems(view: LinearLayout, + oldList: ObservableList?, oldLayoutId: Int, + newList: ObservableList?, newLayoutId: Int) { + if (oldList === newList && oldLayoutId == newLayoutId) + return + var listener: ItemChangeListener? = 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 = 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 setItems(view: LinearLayout, + oldList: Iterable?, oldLayoutId: Int, + newList: Iterable?, 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(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 > setItems(view: RecyclerView, + oldList: ObservableKeyedArrayList?, oldLayoutId: Int, + oldRowConfigurationHandler: RowConfigurationHandler<*, *>?, + newList: ObservableKeyedArrayList?, 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? + // 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?) { + 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 { - private final OnListChangedCallback callback = new OnListChangedCallback<>(this); - private final ViewGroup container; - private final int layoutId; - private final LayoutInflater layoutInflater; - @Nullable private ObservableList 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 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 - extends ObservableList.OnListChangedCallback> { - - private final WeakReference> weakListener; - - private OnListChangedCallback(final ItemChangeListener listener) { - weakListener = new WeakReference<>(listener); - } - - @Override - public void onChanged(final ObservableList sender) { - final ItemChangeListener 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 sender, final int positionStart, - final int itemCount) { - final ItemChangeListener 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 sender, final int positionStart, - final int itemCount) { - final ItemChangeListener 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 sender, final int fromPosition, - final int toPosition, final int itemCount) { - final ItemChangeListener 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 sender, final int positionStart, - final int itemCount) { - final ItemChangeListener 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(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? = null + + private fun getView(position: Int, convertView: View?): View { + var binding = if (convertView != null) DataBindingUtil.getBinding(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?) { + list?.removeOnListChangedCallback(callback) + list = newList + if (list != null) { + list!!.addOnListChangedCallback(callback) + callback.onChanged(list!!) + } else { + container.removeAllViews() + } + } + + private class OnListChangedCallback constructor(listener: ItemChangeListener) : ObservableList.OnListChangedCallback>() { + private val weakListener: WeakReference> = WeakReference(listener) + + override fun onChanged(sender: ObservableList) { + 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, 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, 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, fromPosition: Int, + toPosition: Int, itemCount: Int) { + val listener = weakListener.get() + if (listener != null) { + val views = arrayOfNulls(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, 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 { + 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> : ObservableArrayList() { + 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> extends Adapter { - - private final OnListChangedCallback callback = new OnListChangedCallback<>(this); - private final int layoutId; - private final LayoutInflater layoutInflater; - @Nullable private ObservableKeyedList list; - @Nullable private RowConfigurationHandler rowConfigurationHandler; - - ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId, - final ObservableKeyedList 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 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 { - void onConfigureRow(B binding, T item, int position); - } - - private static final class OnListChangedCallback> - extends ObservableList.OnListChangedCallback> { - - private final WeakReference> weakAdapter; - - private OnListChangedCallback(final ObservableKeyedRecyclerViewAdapter adapter) { - weakAdapter = new WeakReference<>(adapter); - } - - @Override - public void onChanged(final ObservableList sender) { - final ObservableKeyedRecyclerViewAdapter adapter = weakAdapter.get(); - if (adapter != null) - adapter.notifyDataSetChanged(); - else - sender.removeOnListChangedCallback(this); - } - - @Override - public void onItemRangeChanged(final ObservableList sender, final int positionStart, - final int itemCount) { - onChanged(sender); - } - - @Override - public void onItemRangeInserted(final ObservableList sender, final int positionStart, - final int itemCount) { - onChanged(sender); - } - - @Override - public void onItemRangeMoved(final ObservableList sender, final int fromPosition, - final int toPosition, final int itemCount) { - onChanged(sender); - } - - @Override - public void onItemRangeRemoved(final ObservableList 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> internal constructor(context: Context, private val layoutId: Int, + list: ObservableKeyedArrayList?) : RecyclerView.Adapter() { + private val callback = OnListChangedCallback(this) + private val layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var list: ObservableKeyedArrayList? = null + private var rowConfigurationHandler: RowConfigurationHandler? = 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?) { + list?.removeOnListChangedCallback(callback) + list = newList + list?.addOnListChangedCallback(callback) + notifyDataSetChanged() + } + + fun setRowConfigurationHandler(rowConfigurationHandler: RowConfigurationHandler<*, *>?) { + this.rowConfigurationHandler = rowConfigurationHandler as? RowConfigurationHandler + } + + interface RowConfigurationHandler { + fun onConfigureRow(binding: B, item: T, position: Int) + } + + private class OnListChangedCallback> constructor(adapter: ObservableKeyedRecyclerViewAdapter<*, E>) : ObservableList.OnListChangedCallback>() { + private val weakAdapter: WeakReference> = WeakReference(adapter) + + override fun onChanged(sender: ObservableList) { + val adapter = weakAdapter.get() + if (adapter != null) + adapter.notifyDataSetChanged() + else + sender.removeOnListChangedCallback(this) + } + + override fun onItemRangeChanged(sender: ObservableList, positionStart: Int, + itemCount: Int) { + onChanged(sender) + } + + override fun onItemRangeInserted(sender: ObservableList, positionStart: Int, + itemCount: Int) { + onChanged(sender) + } + + override fun onItemRangeMoved(sender: ObservableList, fromPosition: Int, + toPosition: Int, itemCount: Int) { + onChanged(sender) + } + + override fun onItemRangeRemoved(sender: ObservableList, 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>(private val comparator: Comparator?) : ObservableKeyedArrayList() { + @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): Boolean { + var didChange = false + for (e in elements) { + if (add(e)) + didChange = true + } + return didChange + } + + override fun addAll(index: Int, elements: Collection): 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> + -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> + 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 + 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 { + return this + } + + private class KeyList>(private val list: ObservableSortedKeyedArrayList) : AbstractList(), Set { + override fun get(index: Int): K = list[index].key + + override val size + get() = list.size + + override fun spliterator(): Spliterator = super.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 = ObservableKeyedArrayList() + private val appData: ObservableKeyedArrayList = ObservableKeyedArrayList() private var currentlyExcludedApps = emptyList() 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 { - actionModeListener.toggleItemChecked(position) + val parent = this + binding!!.rowConfigurationHandler = object : RowConfigurationHandler { + 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 { - 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, 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>() + val tunnels = CompletableFuture>() private val context: Context = get() private val delayedLoadRestoreTunnels = ArrayList>() - private val tunnelMap: ObservableSortedKeyedList = ObservableSortedKeyedArrayList(COMPARATOR) + private val tunnelMap: ObservableSortedKeyedArrayList = 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> - extends ObservableArrayList implements ObservableKeyedList { - @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 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 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 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 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 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> - extends KeyedList, ObservableList { -} 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> - extends ObservableKeyedArrayList implements ObservableSortedKeyedList { - @Nullable private final Comparator comparator; - private final transient KeyList keyList = new KeyList<>(this); - - @SuppressWarnings("WeakerAccess") - public ObservableSortedKeyedArrayList() { - comparator = null; - } - - public ObservableSortedKeyedArrayList(final Comparator comparator) { - this.comparator = comparator; - } - - public ObservableSortedKeyedArrayList(final Collection c) { - this(); - addAll(c); - } - - public ObservableSortedKeyedArrayList(final SortedKeyedList 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 c) { - boolean didChange = false; - for (final E e : c) - if (add(e)) - didChange = true; - return didChange; - } - - @Override - public boolean addAll(int index, final Collection c) { - for (final E e : c) - add(index++, e); - return true; - } - - @Nullable - @Override - public Comparator 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> list = - (List>) 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> list = - (List>) keyList; - index = Collections.binarySearch(list, key); - } - return index >= 0 ? index : -1; - } - - @Override - public Set 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 key = - (Comparable) 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 values() { - return this; - } - - private static final class KeyList> - extends AbstractList implements Set { - private final ObservableSortedKeyedArrayList list; - - private KeyList(final ObservableSortedKeyedArrayList 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 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> - extends ObservableKeyedList, SortedKeyedList { -} 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 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> extends List { - boolean containsAllKeys(Collection 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> extends KeyedList { - Comparator comparator(); - - @Nullable - K firstKey(); - - Set keySet(); - - @Nullable - K lastKey(); - - Collection 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 @@ + type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ApplicationData>" /> + type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, com.wireguard.android.model.ApplicationData>" /> + type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ObservableTunnel>" /> + type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ObservableTunnel>" />