diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java index f41bd3a7..f3045831 100644 --- a/app/src/main/java/com/wireguard/android/Application.java +++ b/app/src/main/java/com/wireguard/android/Application.java @@ -25,7 +25,7 @@ import com.wireguard.android.util.ToolsInstaller; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.List; +import java.util.Collection; import java.util.concurrent.Executor; public class Application extends android.app.Application { @@ -37,7 +37,7 @@ public class Application extends android.app.Application { private ToolsInstaller toolsInstaller; private TunnelManager tunnelManager; private Handler handler; - private List haveBackendCallbacks = new ArrayList<>(); + private Collection haveBackendCallbacks = new ArrayList<>(); private final Object haveBackendCallbacksLock = new Object(); public Application() { diff --git a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java b/app/src/main/java/com/wireguard/android/activity/BaseActivity.java index e5aaadf2..9b535d79 100644 --- a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/BaseActivity.java @@ -6,13 +6,11 @@ package com.wireguard.android.activity; -import android.content.Intent; import android.databinding.CallbackRegistry; import android.databinding.CallbackRegistry.NotifierCallback; import android.os.Bundle; import com.wireguard.android.Application; -import com.wireguard.android.backend.GoBackend; import com.wireguard.android.model.Tunnel; 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. super.onCreate(savedInstanceState); - - Application.onHaveBackend(backend -> { - if (backend instanceof GoBackend) { - final Intent intent = GoBackend.VpnService.prepare(this); - if (intent != null) - startActivityForResult(intent, 0); - } - }); } @Override diff --git a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java index 79168e48..d45e60a5 100644 --- a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java +++ b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java @@ -14,7 +14,6 @@ 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.View; import android.view.ViewGroup; import com.wireguard.android.BR; @@ -78,7 +77,7 @@ public class ObservableKeyedRecyclerViewAdapter> holder.binding.executePendingBindings(); 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> } } - public interface RowConfigurationHandler { - void onConfigureRow(View view, T item, int position); + public interface RowConfigurationHandler { + void onConfigureRow(B binding, T item, int position); } } diff --git a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java index e476a7f2..510b1cae 100644 --- a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java @@ -7,11 +7,25 @@ package com.wireguard.android.fragment; 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.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.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.State; +import com.wireguard.android.util.ExceptionLoggers; /** * 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 { + private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName(); + private static final int REQUEST_CODE_VPN_PERMISSION = 23491; + private BaseActivity activity; + private Tunnel pendingTunnel; + private Boolean pendingTunnelUp; protected Tunnel getSelectedTunnel() { return activity != null ? activity.getSelectedTunnel() : null; @@ -44,8 +63,64 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC 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) { if (activity != null) 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); + }); + } + } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java b/app/src/main/java/com/wireguard/android/fragment/TunnelController.java deleted file mode 100644 index dcaa92e1..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelController.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2018 Samuel Holland - * Copyright © 2018 Jason A. Donenfeld . 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); - }); - } -} diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java index 570e358a..5c35686f 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java @@ -69,7 +69,9 @@ public class TunnelDetailFragment extends BaseFragment { @Override public void onViewStateRestored(final Bundle savedInstanceState) { + binding.setFragment(this); onSelectedTunnelChanged(null, getSelectedTunnel()); super.onViewStateRestored(savedInstanceState); } + } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java index 66c1d7b9..88d9c0b7 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java @@ -32,8 +32,10 @@ import com.wireguard.android.R; import com.wireguard.android.activity.TunnelCreatorActivity; import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter; import com.wireguard.android.databinding.TunnelListFragmentBinding; +import com.wireguard.android.databinding.TunnelListItemBinding; import com.wireguard.android.model.Tunnel; import com.wireguard.android.util.ExceptionLoggers; +import com.wireguard.android.widget.ToggleSwitch; import com.wireguard.config.Config; import java.io.BufferedReader; @@ -269,23 +271,21 @@ public class TunnelListFragment extends BaseFragment { super.onViewStateRestored(savedInstanceState); binding.setFragment(this); binding.setTunnels(Application.getTunnelManager().getTunnels()); - binding.setRowConfigurationHandler(new ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler() { - @Override - public void onConfigureRow(View view, Tunnel tunnel, int position) { - view.setOnClickListener(clicked -> { - if (actionMode == null) { - setSelectedTunnel(tunnel); - } else { - actionModeListener.toggleItemChecked(position); - } - }); - view.setOnLongClickListener(clicked -> { + binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler) (binding, tunnel, position) -> { + binding.setFragment(this); + binding.getRoot().setOnClickListener(clicked -> { + if (actionMode == null) { + setSelectedTunnel(tunnel); + } else { 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)); }); } diff --git a/app/src/main/res/layout/tunnel_detail_fragment.xml b/app/src/main/res/layout/tunnel_detail_fragment.xml index 3fa3c757..ed43cc01 100644 --- a/app/src/main/res/layout/tunnel_detail_fragment.xml +++ b/app/src/main/res/layout/tunnel_detail_fragment.xml @@ -5,12 +5,14 @@ - - + + @@ -63,7 +65,7 @@ android:layout_alignBaseline="@+id/interface_title" android:layout_alignParentEnd="true" app:checked="@{tunnel.state == State.UP}" - app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" /> + app:onBeforeCheckedChanged="@{fragment::setTunnelState}" /> - - @@ -21,6 +19,10 @@ + + + app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />