AppListDialogFragment: add implementation for excluding applications
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
5729947d6c
commit
500a705531
@ -171,6 +171,9 @@ public final class GoBackend implements Backend {
|
|||||||
configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
builder.setConfigureIntent(PendingIntent.getActivity(context, 0, configureIntent, 0));
|
builder.setConfigureIntent(PendingIntent.getActivity(context, 0, configureIntent, 0));
|
||||||
|
|
||||||
|
for (final String excludedApplication : config.getInterface().getExcludedApplications())
|
||||||
|
builder.addDisallowedApplication(excludedApplication);
|
||||||
|
|
||||||
for (final InetNetwork addr : config.getInterface().getAddresses())
|
for (final InetNetwork addr : config.getInterface().getAddresses())
|
||||||
builder.addAddress(addr.getAddress(), addr.getMask());
|
builder.addAddress(addr.getAddress(), addr.getMask());
|
||||||
|
|
||||||
@ -250,5 +253,6 @@ public final class GoBackend implements Backend {
|
|||||||
}
|
}
|
||||||
return super.onStartCommand(intent, flags, startId);
|
return super.onStartCommand(intent, flags, startId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,18 @@ package com.wireguard.android.databinding;
|
|||||||
import android.databinding.BindingAdapter;
|
import android.databinding.BindingAdapter;
|
||||||
import android.databinding.ObservableList;
|
import android.databinding.ObservableList;
|
||||||
import android.databinding.adapters.ListenerUtil;
|
import android.databinding.adapters.ListenerUtil;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.android.util.ObservableKeyedList;
|
import com.wireguard.android.util.ObservableKeyedList;
|
||||||
import com.wireguard.android.widget.ToggleSwitch;
|
import com.wireguard.android.widget.ToggleSwitch;
|
||||||
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
|
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
|
||||||
|
import com.wireguard.util.Keyed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static methods for use by generated code in the Android data binding library.
|
* Static methods for use by generated code in the Android data binding library.
|
||||||
@ -91,9 +93,39 @@ public final class BindingAdapters {
|
|||||||
adapter.setList(newList);
|
adapter.setList(newList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAdapter({"items", "layout"})
|
||||||
|
public static <K, E extends Keyed<? extends K>>
|
||||||
|
void setItems(final RecyclerView view,
|
||||||
|
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
||||||
|
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||||
|
adapter.setList(newList);
|
||||||
|
}
|
||||||
|
|
||||||
@BindingAdapter("onBeforeCheckedChanged")
|
@BindingAdapter("onBeforeCheckedChanged")
|
||||||
public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
|
public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
|
||||||
final OnBeforeCheckedChangeListener listener) {
|
final OnBeforeCheckedChangeListener listener) {
|
||||||
view.setOnBeforeCheckedChangeListener(listener);
|
view.setOnBeforeCheckedChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
package com.wireguard.android.databinding;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.databinding.DataBindingUtil;
|
||||||
|
import android.databinding.ObservableList;
|
||||||
|
import android.databinding.ViewDataBinding;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.RecyclerView.Adapter;
|
||||||
|
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 java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
private ObservableKeyedList<K, E> list;
|
||||||
|
|
||||||
|
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
|
||||||
|
final ObservableKeyedList<K, E> list) {
|
||||||
|
this.layoutId = layoutId;
|
||||||
|
layoutInflater = LayoutInflater.from(context);
|
||||||
|
setList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return list != null ? list.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private E getItem(final int position) {
|
||||||
|
if (list == null || position < 0 || position >= list.size())
|
||||||
|
return null;
|
||||||
|
return list.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(final int position) {
|
||||||
|
final K key = getKey(position);
|
||||||
|
return key != null ? key.hashCode() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private K getKey(final int position) {
|
||||||
|
final E item = getItem(position);
|
||||||
|
return item != null ? item.getKey() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull @Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setList(final ObservableKeyedList<K, E> newList) {
|
||||||
|
if (list != null)
|
||||||
|
list.removeOnListChangedCallback(callback);
|
||||||
|
list = newList;
|
||||||
|
if (list != null) {
|
||||||
|
list.addOnListChangedCallback(callback);
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ViewDataBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package com.wireguard.android.fragment;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.wireguard.android.Application;
|
||||||
|
import com.wireguard.android.R;
|
||||||
|
import com.wireguard.android.activity.BaseActivity;
|
||||||
|
import com.wireguard.android.databinding.AppListDialogFragmentBinding;
|
||||||
|
import com.wireguard.android.model.ApplicationData;
|
||||||
|
import com.wireguard.android.model.Tunnel;
|
||||||
|
import com.wireguard.android.util.ExceptionLoggers;
|
||||||
|
import com.wireguard.android.util.ObservableKeyedArrayList;
|
||||||
|
import com.wireguard.android.util.ObservableKeyedList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AppListDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
|
private static final String KEY_EXCLUDED_APPS = "excludedApps";
|
||||||
|
|
||||||
|
private List<String> currentlyExcludedApps;
|
||||||
|
private Tunnel tunnel;
|
||||||
|
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
|
||||||
|
|
||||||
|
public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(String[] excludedApps, T target) {
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
extras.putStringArray(KEY_EXCLUDED_APPS, excludedApps);
|
||||||
|
AppListDialogFragment fragment = new AppListDialogFragment();
|
||||||
|
fragment.setTargetFragment(target, 0);
|
||||||
|
fragment.setArguments(extras);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(final Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof BaseActivity) {
|
||||||
|
tunnel = ((BaseActivity) context).getSelectedTunnel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||||
|
alertDialogBuilder.setTitle(R.string.excluded_applications);
|
||||||
|
|
||||||
|
AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
|
||||||
|
binding.executePendingBindings();
|
||||||
|
alertDialogBuilder.setView(binding.getRoot());
|
||||||
|
|
||||||
|
alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss());
|
||||||
|
alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||||
|
|
||||||
|
binding.setFragment(this);
|
||||||
|
binding.setAppData(appData);
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
return alertDialogBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final PackageManager pm = activity.getPackageManager();
|
||||||
|
Application.getAsyncWorker().supplyAsync(() -> {
|
||||||
|
Intent launcherIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||||
|
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(launcherIntent, 0);
|
||||||
|
|
||||||
|
List<ApplicationData> appData = new ArrayList<>();
|
||||||
|
for (ResolveInfo resolveInfo : resolveInfos) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
appData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(appData, (lhs, rhs) -> lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()));
|
||||||
|
return appData;
|
||||||
|
}).whenComplete(((data, throwable) -> {
|
||||||
|
if (data != null) {
|
||||||
|
appData.clear();
|
||||||
|
appData.addAll(data);
|
||||||
|
} else {
|
||||||
|
final String error = throwable != null ? ExceptionLoggers.unwrapMessage(throwable) : "Unknown";
|
||||||
|
final String message = activity.getString(R.string.error_fetching_apps, error);
|
||||||
|
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
|
||||||
|
dismissAllowingStateLoss();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExclusionsAndDismiss() {
|
||||||
|
final List<String> excludedApps = new ArrayList<>();
|
||||||
|
for (ApplicationData data : appData) {
|
||||||
|
if (data.isExcludedFromTunnel()) {
|
||||||
|
excludedApps.add(data.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((AppExclusionListener) getTargetFragment()).onExcludedAppsSelected(excludedApps);
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AppExclusionListener {
|
||||||
|
void onExcludedAppsSelected(List<String> excludedApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,6 +11,7 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -24,16 +25,20 @@ import android.widget.Toast;
|
|||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
|
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
|
||||||
|
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
|
||||||
import com.wireguard.android.model.Tunnel;
|
import com.wireguard.android.model.Tunnel;
|
||||||
import com.wireguard.android.model.TunnelManager;
|
import com.wireguard.android.model.TunnelManager;
|
||||||
import com.wireguard.android.util.ExceptionLoggers;
|
import com.wireguard.android.util.ExceptionLoggers;
|
||||||
|
import com.wireguard.config.Attribute;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment for editing a WireGuard configuration.
|
* Fragment for editing a WireGuard configuration.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class TunnelEditorFragment extends BaseFragment {
|
public class TunnelEditorFragment extends BaseFragment implements AppExclusionListener {
|
||||||
private static final String KEY_LOCAL_CONFIG = "local_config";
|
private static final String KEY_LOCAL_CONFIG = "local_config";
|
||||||
private static final String KEY_ORIGINAL_NAME = "original_name";
|
private static final String KEY_ORIGINAL_NAME = "original_name";
|
||||||
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
|
||||||
@ -202,6 +207,8 @@ public class TunnelEditorFragment extends BaseFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewStateRestored(final Bundle savedInstanceState) {
|
public void onViewStateRestored(final Bundle savedInstanceState) {
|
||||||
|
binding.setFragment(this);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
onSelectedTunnelChanged(null, getSelectedTunnel());
|
onSelectedTunnelChanged(null, getSelectedTunnel());
|
||||||
} else {
|
} else {
|
||||||
@ -216,4 +223,23 @@ public class TunnelEditorFragment extends BaseFragment {
|
|||||||
|
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
|
||||||
|
FragmentManager fragmentManager = getFragmentManager();
|
||||||
|
if (fragmentManager != null) {
|
||||||
|
String[] excludedApps = excludedApplications();
|
||||||
|
AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
|
||||||
|
fragment.show(getFragmentManager(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExcludedAppsSelected(List<String> excludedApps) {
|
||||||
|
binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] excludedApplications() {
|
||||||
|
return Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.wireguard.android.model;
|
||||||
|
|
||||||
|
import android.databinding.BaseObservable;
|
||||||
|
import android.databinding.Bindable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.wireguard.android.BR;
|
||||||
|
import com.wireguard.util.Keyed;
|
||||||
|
|
||||||
|
public class ApplicationData extends BaseObservable implements Keyed<String> {
|
||||||
|
|
||||||
|
@NonNull private final Drawable icon;
|
||||||
|
@NonNull private final String name;
|
||||||
|
@NonNull private final String packageName;
|
||||||
|
private boolean excludedFromTunnel;
|
||||||
|
|
||||||
|
public ApplicationData(@NonNull Drawable icon, @NonNull String name, @NonNull String packageName, boolean excludedFromTunnel) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.name = name;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.excludedFromTunnel = excludedFromTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Drawable getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getPackageName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bindable
|
||||||
|
public boolean isExcludedFromTunnel() {
|
||||||
|
return excludedFromTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcludedFromTunnel(boolean excludedFromTunnel) {
|
||||||
|
this.excludedFromTunnel = excludedFromTunnel;
|
||||||
|
notifyPropertyChanged(BR.excludedFromTunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,11 @@ import java.util.regex.Pattern;
|
|||||||
* The set of valid attributes for an interface or peer in a WireGuard configuration file.
|
* The set of valid attributes for an interface or peer in a WireGuard configuration file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum Attribute {
|
public enum Attribute {
|
||||||
ADDRESS("Address"),
|
ADDRESS("Address"),
|
||||||
ALLOWED_IPS("AllowedIPs"),
|
ALLOWED_IPS("AllowedIPs"),
|
||||||
DNS("DNS"),
|
DNS("DNS"),
|
||||||
|
EXCLUDED_APPLICATIONS("ExcludedApplications"),
|
||||||
ENDPOINT("Endpoint"),
|
ENDPOINT("Endpoint"),
|
||||||
LISTEN_PORT("ListenPort"),
|
LISTEN_PORT("ListenPort"),
|
||||||
MTU("MTU"),
|
MTU("MTU"),
|
||||||
@ -59,7 +60,7 @@ enum Attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String[] stringToList(final String string) {
|
public static String[] stringToList(final String string) {
|
||||||
if (string == null)
|
if (TextUtils.isEmpty(string))
|
||||||
return EMPTY_LIST;
|
return EMPTY_LIST;
|
||||||
return LIST_SEPARATOR_PATTERN.split(string.trim());
|
return LIST_SEPARATOR_PATTERN.split(string.trim());
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import com.wireguard.crypto.Keypair;
|
|||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +26,7 @@ import java.util.List;
|
|||||||
public class Interface {
|
public class Interface {
|
||||||
private final List<InetNetwork> addressList;
|
private final List<InetNetwork> addressList;
|
||||||
private final List<InetAddress> dnsList;
|
private final List<InetAddress> dnsList;
|
||||||
|
private final List<String> excludedApplications;
|
||||||
private Keypair keypair;
|
private Keypair keypair;
|
||||||
private int listenPort;
|
private int listenPort;
|
||||||
private int mtu;
|
private int mtu;
|
||||||
@ -32,6 +34,7 @@ public class Interface {
|
|||||||
public Interface() {
|
public Interface() {
|
||||||
addressList = new ArrayList<>();
|
addressList = new ArrayList<>();
|
||||||
dnsList = new ArrayList<>();
|
dnsList = new ArrayList<>();
|
||||||
|
excludedApplications = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAddresses(final String[] addresses) {
|
private void addAddresses(final String[] addresses) {
|
||||||
@ -52,6 +55,12 @@ public class Interface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addExcludedApplications(final String[] applications) {
|
||||||
|
if (applications != null && applications.length > 0) {
|
||||||
|
excludedApplications.addAll(Arrays.asList(applications));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getAddressString() {
|
private String getAddressString() {
|
||||||
if (addressList.isEmpty())
|
if (addressList.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
@ -79,6 +88,16 @@ public class Interface {
|
|||||||
return dnsList.toArray(new InetAddress[dnsList.size()]);
|
return dnsList.toArray(new InetAddress[dnsList.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getExcludedApplicationsString() {
|
||||||
|
if (excludedApplications.isEmpty())
|
||||||
|
return null;
|
||||||
|
return Attribute.iterableToString(excludedApplications);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getExcludedApplications() {
|
||||||
|
return excludedApplications.toArray(new String[excludedApplications.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
public int getListenPort() {
|
public int getListenPort() {
|
||||||
return listenPort;
|
return listenPort;
|
||||||
}
|
}
|
||||||
@ -120,6 +139,9 @@ public class Interface {
|
|||||||
case DNS:
|
case DNS:
|
||||||
addDnses(key.parseList(line));
|
addDnses(key.parseList(line));
|
||||||
break;
|
break;
|
||||||
|
case EXCLUDED_APPLICATIONS:
|
||||||
|
addExcludedApplications(key.parseList(line));
|
||||||
|
break;
|
||||||
case LISTEN_PORT:
|
case LISTEN_PORT:
|
||||||
setListenPortString(key.parse(line));
|
setListenPortString(key.parse(line));
|
||||||
break;
|
break;
|
||||||
@ -144,6 +166,11 @@ public class Interface {
|
|||||||
addDnses(Attribute.stringToList(dnsString));
|
addDnses(Attribute.stringToList(dnsString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setExcludedApplicationsString(final String applicationsString) {
|
||||||
|
excludedApplications.clear();
|
||||||
|
addExcludedApplications(Attribute.stringToList(applicationsString));
|
||||||
|
}
|
||||||
|
|
||||||
private void setListenPort(final int listenPort) {
|
private void setListenPort(final int listenPort) {
|
||||||
this.listenPort = listenPort;
|
this.listenPort = listenPort;
|
||||||
}
|
}
|
||||||
@ -179,6 +206,8 @@ public class Interface {
|
|||||||
sb.append(Attribute.ADDRESS.composeWith(addressList));
|
sb.append(Attribute.ADDRESS.composeWith(addressList));
|
||||||
if (!dnsList.isEmpty())
|
if (!dnsList.isEmpty())
|
||||||
sb.append(Attribute.DNS.composeWith(getDnsStrings()));
|
sb.append(Attribute.DNS.composeWith(getDnsStrings()));
|
||||||
|
if (!excludedApplications.isEmpty())
|
||||||
|
sb.append(Attribute.EXCLUDED_APPLICATIONS.composeWith(excludedApplications));
|
||||||
if (listenPort != 0)
|
if (listenPort != 0)
|
||||||
sb.append(Attribute.LISTEN_PORT.composeWith(listenPort));
|
sb.append(Attribute.LISTEN_PORT.composeWith(listenPort));
|
||||||
if (mtu != 0)
|
if (mtu != 0)
|
||||||
@ -202,6 +231,7 @@ public class Interface {
|
|||||||
};
|
};
|
||||||
private String addresses;
|
private String addresses;
|
||||||
private String dnses;
|
private String dnses;
|
||||||
|
private String excludedApplications;
|
||||||
private String listenPort;
|
private String listenPort;
|
||||||
private String mtu;
|
private String mtu;
|
||||||
private String privateKey;
|
private String privateKey;
|
||||||
@ -219,11 +249,13 @@ public class Interface {
|
|||||||
privateKey = in.readString();
|
privateKey = in.readString();
|
||||||
listenPort = in.readString();
|
listenPort = in.readString();
|
||||||
mtu = in.readString();
|
mtu = in.readString();
|
||||||
|
excludedApplications = in.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commitData(final Interface parent) {
|
public void commitData(final Interface parent) {
|
||||||
parent.setAddressString(addresses);
|
parent.setAddressString(addresses);
|
||||||
parent.setDnsString(dnses);
|
parent.setDnsString(dnses);
|
||||||
|
parent.setExcludedApplicationsString(excludedApplications);
|
||||||
parent.setPrivateKey(privateKey);
|
parent.setPrivateKey(privateKey);
|
||||||
parent.setListenPortString(listenPort);
|
parent.setListenPortString(listenPort);
|
||||||
parent.setMtuString(mtu);
|
parent.setMtuString(mtu);
|
||||||
@ -254,6 +286,11 @@ public class Interface {
|
|||||||
return dnses;
|
return dnses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bindable
|
||||||
|
public String getExcludedApplications() {
|
||||||
|
return excludedApplications;
|
||||||
|
}
|
||||||
|
|
||||||
@Bindable
|
@Bindable
|
||||||
public String getListenPort() {
|
public String getListenPort() {
|
||||||
return listenPort;
|
return listenPort;
|
||||||
@ -277,6 +314,7 @@ public class Interface {
|
|||||||
protected void loadData(final Interface parent) {
|
protected void loadData(final Interface parent) {
|
||||||
addresses = parent.getAddressString();
|
addresses = parent.getAddressString();
|
||||||
dnses = parent.getDnsString();
|
dnses = parent.getDnsString();
|
||||||
|
excludedApplications = parent.getExcludedApplicationsString();
|
||||||
publicKey = parent.getPublicKey();
|
publicKey = parent.getPublicKey();
|
||||||
privateKey = parent.getPrivateKey();
|
privateKey = parent.getPrivateKey();
|
||||||
listenPort = parent.getListenPortString();
|
listenPort = parent.getListenPortString();
|
||||||
@ -293,6 +331,11 @@ public class Interface {
|
|||||||
notifyPropertyChanged(BR.dnses);
|
notifyPropertyChanged(BR.dnses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExcludedApplications(final String excludedApplications) {
|
||||||
|
this.excludedApplications = excludedApplications;
|
||||||
|
notifyPropertyChanged(BR.excludedApplications);
|
||||||
|
}
|
||||||
|
|
||||||
public void setListenPort(final String listenPort) {
|
public void setListenPort(final String listenPort) {
|
||||||
this.listenPort = listenPort;
|
this.listenPort = listenPort;
|
||||||
notifyPropertyChanged(BR.listenPort);
|
notifyPropertyChanged(BR.listenPort);
|
||||||
@ -324,6 +367,7 @@ public class Interface {
|
|||||||
dest.writeString(privateKey);
|
dest.writeString(privateKey);
|
||||||
dest.writeString(listenPort);
|
dest.writeString(listenPort);
|
||||||
dest.writeString(mtu);
|
dest.writeString(mtu);
|
||||||
|
dest.writeString(excludedApplications);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
app/src/main/res/layout/app_list_dialog_fragment.xml
Normal file
43
app/src/main/res/layout/app_list_dialog_fragment.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
|
||||||
|
<import type="com.wireguard.android.model.ApplicationData" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="fragment"
|
||||||
|
type="com.wireguard.android.fragment.AppListDialogFragment" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="appData"
|
||||||
|
type="com.wireguard.android.util.ObservableKeyedList<String, ApplicationData>" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="200dp" >
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="@{appData.isEmpty() ? View.VISIBLE : View.GONE}"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/app_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:items="@{appData}"
|
||||||
|
app:layout="@{@layout/app_list_item}" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</layout>
|
56
app/src/main/res/layout/app_list_item.xml
Normal file
56
app/src/main/res/layout/app_list_item.xml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<import type="com.wireguard.android.model.ApplicationData" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="collection"
|
||||||
|
type="com.wireguard.android.util.ObservableKeyedList<String, com.wireguard.android.model.ApplicationData>" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="key"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="item"
|
||||||
|
type="com.wireguard.android.model.ApplicationData" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/list_item_background_anim"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/app_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@{item.icon}" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/app_name"
|
||||||
|
style="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:text="@{key}" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/excluded_checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="@={item.excludedFromTunnel}" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
<import type="com.wireguard.config.Peer" />
|
<import type="com.wireguard.config.Peer" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="fragment"
|
||||||
|
type="com.wireguard.android.fragment.TunnelEditorFragment" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="config"
|
name="config"
|
||||||
type="com.wireguard.config.Config.Observable" />
|
type="com.wireguard.config.Config.Observable" />
|
||||||
@ -211,6 +215,15 @@
|
|||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:text="@={config.interfaceSection.mtu}"
|
android:text="@={config.interfaceSection.mtu}"
|
||||||
android:textAlignment="center" />
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="-8dp"
|
||||||
|
android:layout_below="@+id/dns_servers_text"
|
||||||
|
android:onClick="@{fragment::onRequestSetExcludedApplications}"
|
||||||
|
android:text="@{fragment.excludedApplications().length == 0 ? @string/set_excluded_applications : String.format(@string/x_excluded_applications, fragment.excludedApplications().length)}" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</android.support.v7.widget.CardView>
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<string name="addresses">Addresses</string>
|
<string name="addresses">Addresses</string>
|
||||||
<string name="allowed_ips">Allowed IPs</string>
|
<string name="allowed_ips">Allowed IPs</string>
|
||||||
<string name="app_name">WireGuard</string>
|
<string name="app_name">WireGuard</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
<string name="config_save_error">Unable to save configuration for “%s”: %s</string>
|
<string name="config_save_error">Unable to save configuration for “%s”: %s</string>
|
||||||
<string name="config_save_success">Successfully saved configuration for “%s”</string>
|
<string name="config_save_success">Successfully saved configuration for “%s”</string>
|
||||||
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
||||||
@ -37,9 +38,11 @@
|
|||||||
<string name="dns_servers">DNS servers</string>
|
<string name="dns_servers">DNS servers</string>
|
||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
<string name="endpoint">Endpoint</string>
|
<string name="endpoint">Endpoint</string>
|
||||||
|
<string name="error_fetching_apps">Error fetching apps list: %s</string>
|
||||||
<string name="error_down">Error bringing down tunnel: %s</string>
|
<string name="error_down">Error bringing down tunnel: %s</string>
|
||||||
<string name="error_root">Please obtain root access and try again</string>
|
<string name="error_root">Please obtain root access and try again</string>
|
||||||
<string name="error_up">Error bringing up tunnel: %s</string>
|
<string name="error_up">Error bringing up tunnel: %s</string>
|
||||||
|
<string name="excluded_applications">Excluded Applications</string>
|
||||||
<string name="generate">Generate</string>
|
<string name="generate">Generate</string>
|
||||||
<string name="hint_automatic">(auto)</string>
|
<string name="hint_automatic">(auto)</string>
|
||||||
<string name="hint_generated">(generated)</string>
|
<string name="hint_generated">(generated)</string>
|
||||||
@ -66,6 +69,8 @@
|
|||||||
<string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string>
|
<string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string>
|
||||||
<string name="restore_on_boot_title">Restore on boot</string>
|
<string name="restore_on_boot_title">Restore on boot</string>
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
|
<string name="set_excluded_applications">Set Excluded Applications</string>
|
||||||
|
<string name="set_exclusions">Set Exclusions</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="toggle_error">Error toggling WireGuard tunnel: %s</string>
|
<string name="toggle_error">Error toggling WireGuard tunnel: %s</string>
|
||||||
<string name="tools_installer_already">wg and wg-quick are already installed</string>
|
<string name="tools_installer_already">wg and wg-quick are already installed</string>
|
||||||
@ -85,6 +90,7 @@
|
|||||||
<string name="version_summary">%s backend v%s</string>
|
<string name="version_summary">%s backend v%s</string>
|
||||||
<string name="version_summary_checking">Checking %s backend version</string>
|
<string name="version_summary_checking">Checking %s backend version</string>
|
||||||
<string name="version_summary_unknown">Unknown %s version</string>
|
<string name="version_summary_unknown">Unknown %s version</string>
|
||||||
|
<string name="x_excluded_applications">%d Excluded Applications</string>
|
||||||
<string name="zip_exporter_title">Export tunnels to zip file</string>
|
<string name="zip_exporter_title">Export tunnels to zip file</string>
|
||||||
<string name="zip_export_error">Unable to export tunnels: %s</string>
|
<string name="zip_export_error">Unable to export tunnels: %s</string>
|
||||||
<string name="zip_export_success">Saved to %s</string>
|
<string name="zip_export_success">Saved to %s</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user