Request VPN permissions on activation

Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
Eric Kuck 2018-07-08 15:42:48 -05:00 committed by Jason A. Donenfeld
parent d50e0f5fb9
commit d7ea078cdf
9 changed files with 107 additions and 95 deletions

View File

@ -25,7 +25,7 @@ import com.wireguard.android.util.ToolsInstaller;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
public class Application extends android.app.Application { public class Application extends android.app.Application {
@ -37,7 +37,7 @@ public class Application extends android.app.Application {
private ToolsInstaller toolsInstaller; private ToolsInstaller toolsInstaller;
private TunnelManager tunnelManager; private TunnelManager tunnelManager;
private Handler handler; private Handler handler;
private List<BackendCallback> haveBackendCallbacks = new ArrayList<>(); private Collection<BackendCallback> haveBackendCallbacks = new ArrayList<>();
private final Object haveBackendCallbacksLock = new Object(); private final Object haveBackendCallbacksLock = new Object();
public Application() { public Application() {

View File

@ -6,13 +6,11 @@
package com.wireguard.android.activity; package com.wireguard.android.activity;
import android.content.Intent;
import android.databinding.CallbackRegistry; import android.databinding.CallbackRegistry;
import android.databinding.CallbackRegistry.NotifierCallback; import android.databinding.CallbackRegistry.NotifierCallback;
import android.os.Bundle; import android.os.Bundle;
import com.wireguard.android.Application; import com.wireguard.android.Application;
import com.wireguard.android.backend.GoBackend;
import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.TunnelManager; import com.wireguard.android.model.TunnelManager;
@ -52,14 +50,6 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
// The selected tunnel must be set before the superclass method recreates fragments. // The selected tunnel must be set before the superclass method recreates fragments.
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Application.onHaveBackend(backend -> {
if (backend instanceof GoBackend) {
final Intent intent = GoBackend.VpnService.prepare(this);
if (intent != null)
startActivityForResult(intent, 0);
}
});
} }
@Override @Override

View File

@ -14,7 +14,6 @@ import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.wireguard.android.BR; import com.wireguard.android.BR;
@ -78,7 +77,7 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
holder.binding.executePendingBindings(); holder.binding.executePendingBindings();
if (rowConfigurationHandler != null) { if (rowConfigurationHandler != null) {
rowConfigurationHandler.onConfigureRow(holder.binding.getRoot(), getItem(position), position); rowConfigurationHandler.onConfigureRow(holder.binding, getItem(position), position);
} }
} }
@ -149,8 +148,8 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
} }
} }
public interface RowConfigurationHandler<T> { public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
void onConfigureRow(View view, T item, int position); void onConfigureRow(B binding, T item, int position);
} }
} }

View File

@ -7,11 +7,25 @@
package com.wireguard.android.fragment; package com.wireguard.android.fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.View;
import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.activity.BaseActivity; import com.wireguard.android.activity.BaseActivity;
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener; import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
import com.wireguard.android.backend.GoBackend;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.util.ExceptionLoggers;
/** /**
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when * Base class for fragments that need to know the currently-selected tunnel. Only does anything when
@ -19,7 +33,12 @@ import com.wireguard.android.model.Tunnel;
*/ */
public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener { public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
private BaseActivity activity; private BaseActivity activity;
private Tunnel pendingTunnel;
private Boolean pendingTunnelUp;
protected Tunnel getSelectedTunnel() { protected Tunnel getSelectedTunnel() {
return activity != null ? activity.getSelectedTunnel() : null; return activity != null ? activity.getSelectedTunnel() : null;
@ -44,8 +63,64 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
super.onDetach(); super.onDetach();
} }
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
if (pendingTunnel != null && pendingTunnelUp != null)
setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp);
pendingTunnel = null;
pendingTunnelUp = null;
}
}
protected void setSelectedTunnel(final Tunnel tunnel) { protected void setSelectedTunnel(final Tunnel tunnel) {
if (activity != null) if (activity != null)
activity.setSelectedTunnel(tunnel); activity.setSelectedTunnel(tunnel);
} }
public void setTunnelState(final View view, final boolean checked) {
final ViewDataBinding binding = DataBindingUtil.findBinding(view);
final Tunnel tunnel;
if (binding instanceof TunnelDetailFragmentBinding)
tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
else if (binding instanceof TunnelListItemBinding)
tunnel = ((TunnelListItemBinding) binding).getItem();
else
return;
Application.onHaveBackend(backend -> {
if (backend instanceof GoBackend) {
final Intent intent = GoBackend.VpnService.prepare(view.getContext());
if (intent != null) {
pendingTunnel = tunnel;
pendingTunnelUp = checked;
startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
return;
}
}
setTunnelStateWithPermissionsResult(tunnel, checked);
});
}
private void setTunnelStateWithPermissionsResult(@NonNull final Tunnel tunnel, final boolean checked) {
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
if (throwable == null)
return;
final View view = getView();
if (view == null) {
Log.e(TAG, "setTunnelStateWithPermissionsResult() with no view");
return;
}
final Context context = view.getContext();
final String error = ExceptionLoggers.unwrapMessage(throwable);
final int messageResId = checked ? R.string.error_up : R.string.error_down;
final String message = context.getString(messageResId, error);
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
Log.e(TAG, message, throwable);
});
}
} }

View File

@ -1,58 +0,0 @@
/*
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.fragment;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.View;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.Tunnel.State;
import com.wireguard.android.util.ExceptionLoggers;
/**
* Helper method shared by TunnelListFragment and TunnelDetailFragment.
*/
public final class TunnelController {
private static final String TAG = "WireGuard/" + TunnelController.class.getSimpleName();
private TunnelController() {
// Prevent instantiation.
}
public static void setTunnelState(final View view, final boolean checked) {
final ViewDataBinding binding = DataBindingUtil.findBinding(view);
final Tunnel tunnel;
if (binding instanceof TunnelDetailFragmentBinding)
tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
else if (binding instanceof TunnelListItemBinding)
tunnel = ((TunnelListItemBinding) binding).getItem();
else
tunnel = null;
if (tunnel == null) {
Log.e(TAG, "setChecked() from a null tunnel", new IllegalStateException("No tunnel"));
return;
}
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
if (throwable == null)
return;
final Context context = view.getContext();
final String error = ExceptionLoggers.unwrapMessage(throwable);
final int messageResId = checked ? R.string.error_up : R.string.error_down;
final String message = context.getString(messageResId, error);
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
Log.e(TAG, message, throwable);
});
}
}

View File

@ -69,7 +69,9 @@ public class TunnelDetailFragment extends BaseFragment {
@Override @Override
public void onViewStateRestored(final Bundle savedInstanceState) { public void onViewStateRestored(final Bundle savedInstanceState) {
binding.setFragment(this);
onSelectedTunnelChanged(null, getSelectedTunnel()); onSelectedTunnelChanged(null, getSelectedTunnel());
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
} }
} }

View File

@ -32,8 +32,10 @@ import com.wireguard.android.R;
import com.wireguard.android.activity.TunnelCreatorActivity; import com.wireguard.android.activity.TunnelCreatorActivity;
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter; import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter;
import com.wireguard.android.databinding.TunnelListFragmentBinding; import com.wireguard.android.databinding.TunnelListFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.Tunnel;
import com.wireguard.android.util.ExceptionLoggers; import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.widget.ToggleSwitch;
import com.wireguard.config.Config; import com.wireguard.config.Config;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -269,23 +271,21 @@ public class TunnelListFragment extends BaseFragment {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
binding.setFragment(this); binding.setFragment(this);
binding.setTunnels(Application.getTunnelManager().getTunnels()); binding.setTunnels(Application.getTunnelManager().getTunnels());
binding.setRowConfigurationHandler(new ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<Tunnel>() { binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
@Override binding.setFragment(this);
public void onConfigureRow(View view, Tunnel tunnel, int position) { binding.getRoot().setOnClickListener(clicked -> {
view.setOnClickListener(clicked -> { if (actionMode == null) {
if (actionMode == null) { setSelectedTunnel(tunnel);
setSelectedTunnel(tunnel); } else {
} else {
actionModeListener.toggleItemChecked(position);
}
});
view.setOnLongClickListener(clicked -> {
actionModeListener.toggleItemChecked(position); actionModeListener.toggleItemChecked(position);
return true; }
}); });
binding.getRoot().setOnLongClickListener(clicked -> {
actionModeListener.toggleItemChecked(position);
return true;
});
view.setActivated(actionModeListener.checkedItems.contains(position)); binding.getRoot().setActivated(actionModeListener.checkedItems.contains(position));
}
}); });
} }

View File

@ -5,12 +5,14 @@
<data> <data>
<import type="com.wireguard.android.fragment.TunnelController" />
<import type="com.wireguard.android.model.Tunnel.State" /> <import type="com.wireguard.android.model.Tunnel.State" />
<import type="com.wireguard.android.util.ClipboardUtils" /> <import type="com.wireguard.android.util.ClipboardUtils" />
<variable
name="fragment"
type="com.wireguard.android.fragment.TunnelDetailFragment" />
<variable <variable
name="tunnel" name="tunnel"
type="com.wireguard.android.model.Tunnel" /> type="com.wireguard.android.model.Tunnel" />
@ -63,7 +65,7 @@
android:layout_alignBaseline="@+id/interface_title" android:layout_alignBaseline="@+id/interface_title"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
app:checked="@{tunnel.state == State.UP}" app:checked="@{tunnel.state == State.UP}"
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" /> app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
<TextView <TextView
android:id="@+id/interface_name_label" android:id="@+id/interface_name_label"

View File

@ -4,8 +4,6 @@
<data> <data>
<import type="com.wireguard.android.fragment.TunnelController" />
<import type="com.wireguard.android.model.Tunnel" /> <import type="com.wireguard.android.model.Tunnel" />
<import type="com.wireguard.android.model.Tunnel.State" /> <import type="com.wireguard.android.model.Tunnel.State" />
@ -21,6 +19,10 @@
<variable <variable
name="item" name="item"
type="com.wireguard.android.model.Tunnel" /> type="com.wireguard.android.model.Tunnel" />
<variable
name="fragment"
type="com.wireguard.android.fragment.TunnelListFragment" />
</data> </data>
<RelativeLayout <RelativeLayout
@ -49,6 +51,6 @@
android:layout_alignBaseline="@+id/tunnel_name" android:layout_alignBaseline="@+id/tunnel_name"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
app:checked="@{item.state == State.UP}" app:checked="@{item.state == State.UP}"
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" /> app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
</RelativeLayout> </RelativeLayout>
</layout> </layout>