diff options
author | Samuel Holland <samuel@sholland.org> | 2017-07-29 06:09:55 -0500 |
---|---|---|
committer | Samuel Holland <samuel@sholland.org> | 2017-07-29 06:09:55 -0500 |
commit | 7ceafaf2bb57cbfae7f724a9fe0c07ae4faff415 (patch) | |
tree | 5a74c80dd665f9854a16b4313c32bd913a1b8959 /app | |
parent | 85f1d4f1d3554be5c8103cacfe7121145d90c90c (diff) |
ProfileList: Add minimal activity
For now, it simply reads the files in the app's data directory with
file names ending in ".conf" and displays them in a list.
This includes the generic list data binding setup for future use.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app')
6 files changed, 246 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69b413fe..82186447 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,13 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light.DarkActionBar"> + <activity android:name=".ProfileListActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/app/src/main/java/com/wireguard/android/BindingAdapters.java b/app/src/main/java/com/wireguard/android/BindingAdapters.java new file mode 100644 index 00000000..aa5b8c1e --- /dev/null +++ b/app/src/main/java/com/wireguard/android/BindingAdapters.java @@ -0,0 +1,38 @@ +package com.wireguard.android; + +import android.databinding.BindingAdapter; +import android.databinding.ObservableList; +import android.widget.ListView; + +/** + * Static methods for use by generated code in the Android data binding library. + */ + +public final class BindingAdapters { + @BindingAdapter({"items", "layout"}) + public static <T> void listBinding(ListView view, ObservableList<T> oldList, int oldLayoutId, + ObservableList<T> newList, int newLayoutId) { + // Remove any existing binding when there is no new list. + if (newList == null) { + view.setAdapter(null); + return; + } + // The ListAdapter interface is not generic, so this cannot be checked. + @SuppressWarnings("unchecked") + ObservableListAdapter<T> adapter = (ObservableListAdapter<T>) view.getAdapter(); + // If the layout changes, any existing adapter must be replaced. + if (newLayoutId != oldLayoutId) + adapter = null; + // Add a new binding if there was none, or if it must be replaced due to a layout change. + if (adapter == null) { + adapter = new ObservableListAdapter<>(view.getContext(), newLayoutId, newList); + view.setAdapter(adapter); + } else if (newList != oldList) { + // Changing the list only requires modifying the existing adapter. + adapter.setList(newList); + } + } + + private BindingAdapters() { + } +} diff --git a/app/src/main/java/com/wireguard/android/ObservableListAdapter.java b/app/src/main/java/com/wireguard/android/ObservableListAdapter.java new file mode 100644 index 00000000..3b1cf5f8 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ObservableListAdapter.java @@ -0,0 +1,91 @@ +package com.wireguard.android; + +import android.content.Context; +import android.databinding.DataBindingUtil; +import android.databinding.ObservableList; +import android.databinding.ViewDataBinding; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; + +/** + * A generic ListAdapter backed by an ObservableList. + */ + +class ObservableListAdapter<T> extends BaseAdapter implements ListAdapter { + private final int layoutId; + private final LayoutInflater layoutInflater; + private ObservableList<T> list; + private final OnListChangedCallback<ObservableList<T>> callback = new OnListChangedCallback<>(); + + ObservableListAdapter(Context context, int layoutId, ObservableList<T> list) { + this.layoutInflater = LayoutInflater.from(context); + this.layoutId = layoutId; + setList(list); + } + + @Override + public int getCount() { + return list != null ? list.size() : 0; + } + + @Override + public T getItem(int position) { + return list != null ? list.get(position) : null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewDataBinding binding = DataBindingUtil.getBinding(convertView); + if (binding == null) + binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false); + binding.setVariable(BR.item, getItem(position)); + binding.executePendingBindings(); + return binding.getRoot(); + } + + public void setList(ObservableList<T> newList) { + if (list != null) + list.removeOnListChangedCallback(callback); + list = newList; + if (list != null) { + list.addOnListChangedCallback(callback); + } + } + + private class OnListChangedCallback<L extends ObservableList<T>> + extends ObservableList.OnListChangedCallback<L> { + @Override + public void onChanged(L sender) { + ObservableListAdapter.this.notifyDataSetChanged(); + } + + @Override + public void onItemRangeChanged(L sender, int positionStart, int itemCount) { + ObservableListAdapter.this.notifyDataSetChanged(); + } + + @Override + public void onItemRangeInserted(L sender, int positionStart, int itemCount) { + ObservableListAdapter.this.notifyDataSetChanged(); + } + + @Override + public void onItemRangeMoved(L sender, int fromPosition, int toPosition, + int itemCount) { + ObservableListAdapter.this.notifyDataSetChanged(); + } + + @Override + public void onItemRangeRemoved(L sender, int positionStart, int itemCount) { + ObservableListAdapter.this.notifyDataSetChanged(); + } + } +} diff --git a/app/src/main/java/com/wireguard/android/ProfileListActivity.java b/app/src/main/java/com/wireguard/android/ProfileListActivity.java new file mode 100644 index 00000000..afa8a123 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ProfileListActivity.java @@ -0,0 +1,64 @@ +package com.wireguard.android; + +import android.app.Activity; +import android.databinding.DataBindingUtil; +import android.databinding.ObservableArrayList; +import android.databinding.ObservableList; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; + +import com.wireguard.android.databinding.ProfileListActivityBinding; +import com.wireguard.config.Profile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; + +public class ProfileListActivity extends Activity { + private final ObservableList<Profile> profiles = new ObservableArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final ProfileListActivityBinding binding = + DataBindingUtil.setContentView(this, R.layout.profile_list_activity); + binding.setProfiles(profiles); + new ProfileLoader().execute(getFilesDir().listFiles()); + } + + private class ProfileLoader extends AsyncTask<File, Profile, ArrayList<Profile>> { + private static final String TAG = "WGProfileLoader"; + + @Override + protected ArrayList<Profile> doInBackground(File... files) { + final ArrayList<Profile> loadedProfiles = new ArrayList<>(); + for (File file : files) { + final String fileName = file.getName(); + final int suffixStart = fileName.lastIndexOf(".conf"); + if (suffixStart <= 0) { + Log.w(TAG, "Ignoring stray file " + fileName); + continue; + } + final Profile profile = new Profile(fileName.substring(0, suffixStart)); + try { + final FileInputStream inputStream = openFileInput(fileName); + profile.fromStream(inputStream); + loadedProfiles.add(profile); + } catch (IOException e) { + Log.w(TAG, "Failed to load profile from " + fileName, e); + } + if (isCancelled()) + break; + } + return loadedProfiles; + } + + @Override + protected void onPostExecute(ArrayList<Profile> loadedProfiles) { + // FIXME: This should replace an existing profile if the name matches. + profiles.addAll(loadedProfiles); + } + } +} diff --git a/app/src/main/res/layout/profile_list_activity.xml b/app/src/main/res/layout/profile_list_activity.xml new file mode 100644 index 00000000..4e7b251f --- /dev/null +++ b/app/src/main/res/layout/profile_list_activity.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <data> + + <variable + name="profiles" + type="android.databinding.ObservableList<com.wireguard.config.Profile>" /> + </data> + + <ListView + android:layout_width="match_parent" + android:layout_height="match_parent" + app:items="@{profiles}" + app:layout="@{@layout/profile_list_item}" /> +</layout> diff --git a/app/src/main/res/layout/profile_list_item.xml b/app/src/main/res/layout/profile_list_item.xml new file mode 100644 index 00000000..aeafaf0f --- /dev/null +++ b/app/src/main/res/layout/profile_list_item.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> + + <data> + + <variable + name="item" + type="com.wireguard.config.Profile" /> + </data> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp"> + + <TextView + android:id="@+id/profile_name" + style="?android:attr/textAppearanceMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@{item.name}" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/profile_name" + android:text="@{item.toString()}" /> + </RelativeLayout> +</layout> |