mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f7bdd4f5f | |||
| 3cf6aad083 | |||
| b997a2581b | |||
| d7ea078cdf | |||
| d50e0f5fb9 | |||
| c696e9f275 | |||
| 707c8c19a8 | |||
| b37b48b8dc |
+7
-3
@@ -5,7 +5,7 @@ apply plugin: 'com.android.application'
|
||||
final def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
buildToolsVersion '28.0.0'
|
||||
buildToolsVersion '28.0.1'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -16,8 +16,8 @@ android {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 429
|
||||
versionName '0.0.20180706'
|
||||
versionCode 430
|
||||
versionName '0.0.20180710'
|
||||
}
|
||||
// If the keystore file exists
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
@@ -40,6 +40,10 @@ android {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WireGuard β</string>
|
||||
</resources>
|
||||
@@ -9,7 +9,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<permission
|
||||
android:name="com.wireguard.android.permission.CONTROL_TUNNELS"
|
||||
android:name="${applicationId}.permission.CONTROL_TUNNELS"
|
||||
android:protectionLevel="dangerous"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/permission_label"
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".model.TunnelManager$IntentReceiver"
|
||||
android:permission="com.wireguard.android.permission.CONTROL_TUNNELS">
|
||||
android:permission="${applicationId}.permission.CONTROL_TUNNELS">
|
||||
<intent-filter>
|
||||
<action android:name="com.wireguard.android.action.REFRESH_TUNNEL_STATES" />
|
||||
<action android:name="com.wireguard.android.action.SET_TUNNEL_UP" />
|
||||
|
||||
@@ -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<BackendCallback> haveBackendCallbacks = new ArrayList<>();
|
||||
private Collection<BackendCallback> haveBackendCallbacks = new ArrayList<>();
|
||||
private final Object haveBackendCallbacksLock = new Object();
|
||||
|
||||
public Application() {
|
||||
|
||||
@@ -21,7 +21,7 @@ public class BootShutdownReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
Application.onHaveBackend(backend -> {
|
||||
if (backend.getClass() != WgQuickBackend.class)
|
||||
if (!(backend instanceof WgQuickBackend))
|
||||
return;
|
||||
final String action = intent.getAction();
|
||||
if (action == null)
|
||||
|
||||
@@ -10,6 +10,10 @@ import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.databinding.Observable;
|
||||
import android.databinding.Observable.OnPropertyChangedCallback;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
@@ -21,6 +25,7 @@ import com.wireguard.android.activity.MainActivity;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.android.widget.SlashDrawable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -37,6 +42,26 @@ public class QuickTileService extends TileService {
|
||||
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
|
||||
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
|
||||
private Tunnel tunnel;
|
||||
private Icon iconOn;
|
||||
private Icon iconOff;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile));
|
||||
icon.setAnimationEnabled(false); /* Unfortunately we can't have animations, since Icons are marshaled. */
|
||||
icon.setSlashed(false);
|
||||
Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas c = new Canvas(b);
|
||||
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||
icon.draw(c);
|
||||
iconOn = Icon.createWithBitmap(b);
|
||||
icon.setSlashed(true);
|
||||
b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
c = new Canvas(b);
|
||||
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||
icon.draw(c);
|
||||
iconOff = Icon.createWithBitmap(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
@@ -99,10 +124,7 @@ public class QuickTileService extends TileService {
|
||||
return;
|
||||
tile.setLabel(label);
|
||||
if (tile.getState() != state) {
|
||||
// The icon must be changed every time the state changes, or the shade will not change.
|
||||
final Integer iconResource = state == Tile.STATE_ACTIVE ? R.drawable.ic_tile
|
||||
: R.drawable.ic_tile_disabled;
|
||||
tile.setIcon(Icon.createWithResource(this, iconResource));
|
||||
tile.setIcon(state == Tile.STATE_ACTIVE ? iconOn : iconOff);
|
||||
tile.setState(state);
|
||||
}
|
||||
tile.updateTile();
|
||||
|
||||
@@ -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.getClass() == GoBackend.class) {
|
||||
final Intent intent = GoBackend.VpnService.prepare(this);
|
||||
if (intent != null)
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -102,7 +102,7 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
Application.onHaveBackend(backend -> {
|
||||
for (final Preference pref : wgQuickOnlyPrefs) {
|
||||
if (backend.getClass() == WgQuickBackend.class)
|
||||
if (backend instanceof WgQuickBackend)
|
||||
pref.setVisible(true);
|
||||
else
|
||||
screen.removePreference(pref);
|
||||
|
||||
@@ -13,10 +13,10 @@ import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.InputFilter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
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;
|
||||
@@ -67,37 +67,11 @@ public final class BindingAdapters {
|
||||
listener.setList(newList);
|
||||
}
|
||||
|
||||
@BindingAdapter({"items", "layout"})
|
||||
public static <K, E extends Keyed<? extends K>>
|
||||
void setItems(final ListView view,
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
|
||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
||||
return;
|
||||
// The ListAdapter interface is not generic, so this cannot be checked.
|
||||
@SuppressWarnings("unchecked") ObservableKeyedListAdapter<K, E> adapter =
|
||||
(ObservableKeyedListAdapter<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 ObservableKeyedListAdapter<>(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({"items", "layout"})
|
||||
@BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
|
||||
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) {
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId, final RowConfigurationHandler oldRowConfigurationHandler,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId, final RowConfigurationHandler newRowConfigurationHandler) {
|
||||
if (view.getLayoutManager() == null)
|
||||
view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
|
||||
|
||||
@@ -118,6 +92,8 @@ public final class BindingAdapters {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,133 +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.databinding;
|
||||
|
||||
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 com.wireguard.android.BR;
|
||||
import com.wireguard.util.Keyed;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* A generic {@code ListAdapter} backed by a {@code ObservableKeyedList}.
|
||||
*/
|
||||
|
||||
class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAdapter {
|
||||
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
|
||||
private final int layoutId;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private ObservableKeyedList<K, E> list;
|
||||
|
||||
ObservableKeyedListAdapter(final Context context, final int layoutId,
|
||||
final ObservableKeyedList<K, E> list) {
|
||||
this.layoutId = layoutId;
|
||||
layoutInflater = LayoutInflater.from(context);
|
||||
setList(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return list != null ? list.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
|
||||
if (binding == null)
|
||||
binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false);
|
||||
binding.setVariable(BR.collection, list);
|
||||
binding.setVariable(BR.key, getKey(position));
|
||||
binding.setVariable(BR.item, getItem(position));
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
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<ObservableKeyedListAdapter<?, E>> weakAdapter;
|
||||
|
||||
private OnListChangedCallback(final ObservableKeyedListAdapter<?, E> adapter) {
|
||||
weakAdapter = new WeakReference<>(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(final ObservableList<E> sender) {
|
||||
final ObservableKeyedListAdapter 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -26,12 +26,13 @@ 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> {
|
||||
public 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;
|
||||
private RowConfigurationHandler rowConfigurationHandler;
|
||||
|
||||
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
|
||||
final ObservableKeyedList<K, E> list) {
|
||||
@@ -67,12 +68,17 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull 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) {
|
||||
rowConfigurationHandler.onConfigureRow(holder.binding, getItem(position), position);
|
||||
}
|
||||
}
|
||||
|
||||
void setList(final ObservableKeyedList<K, E> newList) {
|
||||
@@ -85,6 +91,10 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
|
||||
this.rowConfigurationHandler = rowConfigurationHandler;
|
||||
}
|
||||
|
||||
private static final class OnListChangedCallback<E extends Keyed<?>>
|
||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
||||
|
||||
@@ -138,4 +148,8 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
}
|
||||
}
|
||||
|
||||
public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
|
||||
void onConfigureRow(B binding, T item, int position);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,11 +7,26 @@
|
||||
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 android.widget.Toast;
|
||||
|
||||
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 +34,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 +64,62 @@ 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 String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||
final int messageResId = checked ? R.string.error_up : R.string.error_down;
|
||||
final String message = getContext().getString(messageResId, error);
|
||||
final View view = getView();
|
||||
if (view != null)
|
||||
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, message, throwable);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,29 +16,26 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
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;
|
||||
@@ -47,13 +44,13 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.IntStream;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
@@ -64,8 +61,7 @@ public class TunnelListFragment extends BaseFragment {
|
||||
private static final int REQUEST_IMPORT = 1;
|
||||
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
|
||||
|
||||
private final MultiChoiceModeListener actionModeListener = new ActionModeListener();
|
||||
private final ListViewCallbacks listViewCallbacks = new ListViewCallbacks();
|
||||
private final ActionModeListener actionModeListener = new ActionModeListener();
|
||||
private ActionMode actionMode;
|
||||
private TunnelListFragmentBinding binding;
|
||||
|
||||
@@ -182,20 +178,19 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
|
||||
binding.tunnelList.setMultiChoiceModeListener(actionModeListener);
|
||||
binding.tunnelList.setOnItemClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnItemLongClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnTouchListener(listViewCallbacks);
|
||||
|
||||
binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
|
||||
if (binding != null) {
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
@@ -276,38 +271,52 @@ public class TunnelListFragment extends BaseFragment {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setFragment(this);
|
||||
binding.setTunnels(Application.getTunnelManager().getTunnels());
|
||||
binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
|
||||
binding.setFragment(this);
|
||||
binding.getRoot().setOnClickListener(clicked -> {
|
||||
if (actionMode == null) {
|
||||
setSelectedTunnel(tunnel);
|
||||
} else {
|
||||
actionModeListener.toggleItemChecked(position);
|
||||
}
|
||||
});
|
||||
binding.getRoot().setOnLongClickListener(clicked -> {
|
||||
actionModeListener.toggleItemChecked(position);
|
||||
return true;
|
||||
});
|
||||
|
||||
binding.getRoot().setActivated(actionModeListener.checkedItems.contains(position));
|
||||
});
|
||||
}
|
||||
|
||||
private final class ActionModeListener implements MultiChoiceModeListener {
|
||||
private Resources resources;
|
||||
private AbsListView tunnelList;
|
||||
private final class ActionModeListener implements ActionMode.Callback {
|
||||
private final Set<Integer> checkedItems = new HashSet<>();
|
||||
|
||||
private IntStream getCheckedPositions() {
|
||||
final SparseBooleanArray checkedItemPositions = tunnelList.getCheckedItemPositions();
|
||||
return IntStream.range(0, checkedItemPositions.size())
|
||||
.filter(checkedItemPositions::valueAt)
|
||||
.map(checkedItemPositions::keyAt);
|
||||
}
|
||||
private Resources resources;
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_delete:
|
||||
// Must operate in two steps: positions change once we start deleting things.
|
||||
final List<Tunnel> tunnelsToDelete = getCheckedPositions()
|
||||
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
|
||||
.collect(Collectors.toList());
|
||||
List<Tunnel> tunnelsToDelete = new ArrayList<>();
|
||||
for (Integer position : checkedItems) {
|
||||
tunnelsToDelete.add(Application.getTunnelManager().getTunnels().get(position));
|
||||
}
|
||||
|
||||
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
|
||||
.map(Tunnel::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
|
||||
checkedItems.clear();
|
||||
mode.finish();
|
||||
return true;
|
||||
case R.id.menu_action_select_all:
|
||||
for (int i = 0; i < tunnelList.getAdapter().getCount(); ++i)
|
||||
tunnelList.setItemChecked(i, true);
|
||||
for (int i = 0; i < Application.getTunnelManager().getTunnels().size(); ++i) {
|
||||
setItemChecked(i, true);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -317,9 +326,9 @@ public class TunnelListFragment extends BaseFragment {
|
||||
@Override
|
||||
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
|
||||
actionMode = mode;
|
||||
if (getActivity() != null)
|
||||
if (getActivity() != null) {
|
||||
resources = getActivity().getResources();
|
||||
tunnelList = binding.tunnelList;
|
||||
}
|
||||
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
@@ -328,12 +337,31 @@ public class TunnelListFragment extends BaseFragment {
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
actionMode = null;
|
||||
resources = null;
|
||||
|
||||
checkedItems.clear();
|
||||
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
|
||||
final long id, final boolean checked) {
|
||||
updateTitle(mode);
|
||||
void toggleItemChecked(int position) {
|
||||
setItemChecked(position, !checkedItems.contains(position));
|
||||
}
|
||||
|
||||
void setItemChecked(int position, boolean checked) {
|
||||
if (checked) {
|
||||
checkedItems.add(position);
|
||||
} else {
|
||||
checkedItems.remove(position);
|
||||
}
|
||||
|
||||
if (actionMode == null && !checkedItems.isEmpty() && getActivity() != null) {
|
||||
((AppCompatActivity) getActivity()).startSupportActionMode(this);
|
||||
} else if (actionMode != null && checkedItems.isEmpty()) {
|
||||
actionMode.finish();
|
||||
}
|
||||
|
||||
binding.tunnelList.getAdapter().notifyItemChanged(position);
|
||||
|
||||
updateTitle(actionMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -342,8 +370,12 @@ public class TunnelListFragment extends BaseFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateTitle(final ActionMode mode) {
|
||||
final int count = (int) getCheckedPositions().count();
|
||||
private void updateTitle(@Nullable final ActionMode mode) {
|
||||
if (mode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int count = checkedItems.size();
|
||||
if (count == 0) {
|
||||
mode.setTitle("");
|
||||
} else {
|
||||
@@ -352,30 +384,4 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ListViewCallbacks
|
||||
implements OnItemClickListener, OnItemLongClickListener, OnTouchListener {
|
||||
@Override
|
||||
public void onItemClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
setSelectedTunnel((Tunnel) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
if (actionMode != null)
|
||||
return false;
|
||||
if (binding != null)
|
||||
binding.tunnelList.setItemChecked(position, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(final View view, final MotionEvent motionEvent) {
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright © 2018 The Android Open Source Project
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Path.Direction;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.FloatProperty;
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class SlashDrawable extends Drawable {
|
||||
|
||||
private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
|
||||
private static final long QS_ANIM_LENGTH = 350;
|
||||
|
||||
private final Path mPath = new Path();
|
||||
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
// These values are derived in un-rotated (vertical) orientation
|
||||
private static final float SLASH_WIDTH = 1.8384776f;
|
||||
private static final float SLASH_HEIGHT = 28f;
|
||||
private static final float CENTER_X = 10.65f;
|
||||
private static final float CENTER_Y = 11.869239f;
|
||||
private static final float SCALE = 24f;
|
||||
|
||||
// Bottom is derived during animation
|
||||
private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
|
||||
private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
|
||||
private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
|
||||
// Draw the slash washington-monument style; rotate to no-u-turn style
|
||||
private static final float DEFAULT_ROTATION = -45f;
|
||||
|
||||
private Drawable mDrawable;
|
||||
private final RectF mSlashRect = new RectF(0, 0, 0, 0);
|
||||
private float mRotation;
|
||||
private boolean mSlashed;
|
||||
private Mode mTintMode;
|
||||
private ColorStateList mTintList;
|
||||
private boolean mAnimationEnabled = true;
|
||||
|
||||
public SlashDrawable(final Drawable d) {
|
||||
setDrawable(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(final Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mDrawable.setBounds(bounds);
|
||||
}
|
||||
|
||||
public void setDrawable(final Drawable d) {
|
||||
mDrawable = d;
|
||||
mDrawable.setCallback(getCallback());
|
||||
mDrawable.setBounds(getBounds());
|
||||
if (mTintMode != null)
|
||||
mDrawable.setTintMode(mTintMode);
|
||||
if (mTintList != null)
|
||||
mDrawable.setTintList(mTintList);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setRotation(final float rotation) {
|
||||
if (mRotation == rotation)
|
||||
return;
|
||||
mRotation = rotation;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setAnimationEnabled(final boolean enabled) {
|
||||
mAnimationEnabled = enabled;
|
||||
}
|
||||
|
||||
// Animate this value on change
|
||||
private float mCurrentSlashLength;
|
||||
private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
|
||||
@Override
|
||||
public void setValue(final SlashDrawable object, final float value) {
|
||||
object.mCurrentSlashLength = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(final SlashDrawable object) {
|
||||
return object.mCurrentSlashLength;
|
||||
}
|
||||
};
|
||||
|
||||
public void setSlashed(final boolean slashed) {
|
||||
if (mSlashed == slashed) return;
|
||||
|
||||
mSlashed = slashed;
|
||||
|
||||
final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
|
||||
final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
|
||||
|
||||
if (mAnimationEnabled) {
|
||||
final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
|
||||
anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
|
||||
anim.setDuration(QS_ANIM_LENGTH);
|
||||
anim.start();
|
||||
} else {
|
||||
mCurrentSlashLength = end;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull final Canvas canvas) {
|
||||
canvas.save();
|
||||
final Matrix m = new Matrix();
|
||||
final int width = getBounds().width();
|
||||
final int height = getBounds().height();
|
||||
final float radiusX = scale(CORNER_RADIUS, width);
|
||||
final float radiusY = scale(CORNER_RADIUS, height);
|
||||
updateRect(
|
||||
scale(LEFT, width),
|
||||
scale(TOP, height),
|
||||
scale(RIGHT, width),
|
||||
scale(TOP + mCurrentSlashLength, height)
|
||||
);
|
||||
|
||||
mPath.reset();
|
||||
// Draw the slash vertically
|
||||
mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
|
||||
// Rotate -45 + desired rotation
|
||||
m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
|
||||
mPath.transform(m);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
|
||||
// Rotate back to vertical
|
||||
m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
|
||||
mPath.transform(m);
|
||||
|
||||
// Draw another rect right next to the first, for clipping
|
||||
m.setTranslate(mSlashRect.width(), 0);
|
||||
mPath.transform(m);
|
||||
mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
|
||||
m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
|
||||
mPath.transform(m);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||
canvas.clipPath(mPath, Region.Op.DIFFERENCE);
|
||||
else
|
||||
canvas.clipOutPath(mPath);
|
||||
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private float scale(final float frac, final int width) {
|
||||
return frac * width;
|
||||
}
|
||||
|
||||
private void updateRect(final float left, final float top, final float right, final float bottom) {
|
||||
mSlashRect.left = left;
|
||||
mSlashRect.top = top;
|
||||
mSlashRect.right = right;
|
||||
mSlashRect.bottom = bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTint(@ColorInt final int tintColor) {
|
||||
super.setTint(tintColor);
|
||||
mDrawable.setTint(tintColor);
|
||||
mPaint.setColor(tintColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTintList(@Nullable final ColorStateList tint) {
|
||||
mTintList = tint;
|
||||
super.setTintList(tint);
|
||||
setDrawableTintList(tint);
|
||||
mPaint.setColor(tint == null ? 0 : tint.getDefaultColor());
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private void setDrawableTintList(@Nullable final ColorStateList tint) {
|
||||
mDrawable.setTintList(tint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTintMode(@NonNull final Mode tintMode) {
|
||||
mTintMode = tintMode;
|
||||
super.setTintMode(tintMode);
|
||||
mDrawable.setTintMode(tintMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
|
||||
mDrawable.setAlpha(alpha);
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
|
||||
mDrawable.setColorFilter(colorFilter);
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="400dp"
|
||||
android:height="400dp"
|
||||
android:viewportHeight="400.0"
|
||||
android:viewportWidth="400.0">
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M197.7,0C191.5,0.1 185.2,0.5 178.7,1.1C191.1,4 202.2,6.6 213.4,9.2C213.3,9.8 213.1,10.5 213,11.2C198.1,13.2 183.9,7.7 169.3,5.7C174.6,8.8 180,11.7 185.5,14.2C191.2,16.7 197,18.8 202.8,21.2C195.4,27.5 188,28.8 178.7,26.7C173.7,25.6 168.3,25 163.1,25.2C157.8,25.5 152.4,26.8 147.5,30C152.7,32.7 157.5,34.8 162.1,37.5C163.9,38.6 166.1,40.4 166.6,42.4C167.8,46.9 168.2,51.8 168.9,56.5C160.4,57.5 145.4,66.1 142.4,71.7C155.5,74.3 169.7,71.2 182.2,79.6C178.1,82.8 168.5,86.6 165,89.3C169.3,90.4 179.4,89.9 183.3,89.6C186.6,89.4 188.2,89.3 189.5,90.4L228,120.6C232.1,123.8 248.4,139.3 252.7,149C256.3,157.3 256.8,164.4 256.8,166.1C256.8,170.7 256.2,177.9 253,186C252.7,186.9 252,188.3 251.2,189.8L280.8,219.4C288.3,207.7 293.4,194.5 296.3,180C299.6,163.2 299.4,146.5 291.7,130.5C285.8,118.3 276.1,109.4 265.8,101.3C255.1,93 243.7,85.4 233.1,77C230.2,74.8 228.3,70.8 226.9,67.3C226.4,65.8 228.2,61.7 229.4,61.5C236,60.3 242.6,59.7 249.3,59.4C256.9,59.2 264.6,59.4 272.3,59.5C274,59.5 276.2,59.3 277.2,60.3C281.2,64.2 284.3,61.6 287,59.1C289.4,56.9 291,54 292.9,51.6C291.7,51.4 289.4,50.9 287.1,50.8C279.4,50.6 271.7,50.7 264,50.5C262.6,50.4 261.3,49 259.9,48.2C261.3,47.6 262.8,46.6 264.2,46.6C277.5,46.5 290.8,46.5 304.1,46.5C304.2,39.6 294.9,30.1 286.7,27.5C286.6,28.5 286.5,29.3 286.5,30.3C278.3,30.5 270.3,30.3 263,26.4C261.1,25.4 259.8,23.1 258.3,21.5C256.3,19.3 254.7,16.6 252.3,15.2C247.4,12.3 242,10.3 236.9,7.8C224.3,1.7 211.3,-0.1 197.7,0zM249.6,29.4C250.3,29.4 250.9,29.6 251.6,30.2C252.6,31 253.6,31.9 254.8,33C253.3,33.8 252,34.5 250.8,35.1C249,36 247.6,35.4 246.5,34C245.6,32.8 245.5,31.7 246.8,30.7C247.7,30 248.6,29.4 249.6,29.4zM231.8,213C220.7,221.9 207.6,227.2 193.6,230.1C152.7,238.5 118.7,282.2 128.3,330.3C139.5,386.5 201.5,416.9 252.2,390.2C285,372.9 302.3,339.2 297.7,302.5C295.9,288.1 291,275.4 283.1,264.3L265.1,246.3C264.2,246.2 263.2,246.6 261.9,247.5C253.1,253.1 244.2,258.4 235.1,263.4C229.9,266.2 224.2,268.3 217.7,271.2C219.9,271.8 221,272.1 222.1,272.4C246.5,278.9 259.6,300.3 253.8,324.4C248.7,345.7 227,359.4 205.9,355.8C188.4,352.8 173.1,338.2 170.5,320.8C167.8,301.8 177.2,283.5 194.1,275.9C203.4,271.6 213,267.9 222.3,263.6C232.9,258.7 244.4,254.9 253.6,248.1C256.2,246.2 258.7,244.2 261,242.2L231.8,213z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m97.9,307.6c-7.8,2 -15.4,4.9 -23.4,7.5 3.9,-26.3 34.7,-50.6 60.8,-47.8 -8.1,10.9 -11.8,23.3 -12.7,35.6 -8.7,1.6 -16.8,2.7 -24.8,4.7"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M177,111.6C175.7,111.6 174.4,111.7 173.1,111.7L247,185.6C248.1,183.2 249,180.6 249.9,177.9C252.2,170.4 251.8,159.3 248,152.1C234.7,126.6 206.6,111.3 177,111.6zM139.7,120.9C137.9,121.8 136.1,122.9 134.3,124C81.2,156.4 84.3,231 132.9,260.7C136.6,263 138.3,262.7 140.8,259.4C148.4,249.2 157.9,241.1 169,234.9C171.7,233.3 174.5,231.9 178.1,230C154,225.8 141.1,214.8 139.6,197.8C137.9,179.4 146.9,164.5 163.2,158.3C167,156.9 171,156.2 174.9,156.1L139.7,120.9zM212.5,193.7C212.5,195.8 212.3,197.9 211.8,200.1C209.9,210.5 203.6,218.1 195.8,224.8C208.5,221.9 219.6,216.9 228.6,209.7L212.5,193.7z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1.3750788" />
|
||||
<path
|
||||
android:fillAlpha="1"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M78.4,59.6l262,262l-21.3,21.3l-262,-262z"
|
||||
android:strokeWidth="0.82064575" />
|
||||
</vector>
|
||||
@@ -2,4 +2,5 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/list_item_ripple"> <!-- TODO(msf): themeify this -->
|
||||
<item android:drawable="@drawable/list_item_background" />
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/white" />
|
||||
</ripple>
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.fragment.TunnelController" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel.State" />
|
||||
|
||||
<import type="com.wireguard.android.util.ClipboardUtils" />
|
||||
|
||||
<variable
|
||||
name="fragment"
|
||||
type="com.wireguard.android.fragment.TunnelDetailFragment" />
|
||||
|
||||
<variable
|
||||
name="tunnel"
|
||||
type="com.wireguard.android.model.Tunnel" />
|
||||
@@ -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}" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interface_name_label"
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
name="fragment"
|
||||
type="com.wireguard.android.fragment.TunnelListFragment" />
|
||||
|
||||
<variable
|
||||
name="rowConfigurationHandler"
|
||||
type="com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler" />
|
||||
|
||||
<variable
|
||||
name="tunnels"
|
||||
type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" />
|
||||
@@ -21,13 +25,14 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<ListView
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/tunnel_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="multipleChoiceModal"
|
||||
app:items="@{tunnels}"
|
||||
app:layout="@{@layout/tunnel_list_item}" />
|
||||
app:layout="@{@layout/tunnel_list_item}"
|
||||
app:configurationHandler="@{rowConfigurationHandler}" />
|
||||
|
||||
<com.wireguard.android.widget.fab.FloatingActionsMenu
|
||||
android:id="@+id/create_menu"
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.wireguard.android.fragment.TunnelController" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel" />
|
||||
|
||||
<import type="com.wireguard.android.model.Tunnel.State" />
|
||||
@@ -21,6 +19,10 @@
|
||||
<variable
|
||||
name="item"
|
||||
type="com.wireguard.android.model.Tunnel" />
|
||||
|
||||
<variable
|
||||
name="fragment"
|
||||
type="com.wireguard.android.fragment.TunnelListFragment" />
|
||||
</data>
|
||||
|
||||
<RelativeLayout
|
||||
@@ -49,6 +51,6 @@
|
||||
android:layout_alignBaseline="@+id/tunnel_name"
|
||||
android:layout_alignParentEnd="true"
|
||||
app:checked="@{item.state == State.UP}"
|
||||
app:onBeforeCheckedChanged="@{TunnelController::setTunnelState}" />
|
||||
app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
|
||||
+1
-1
Submodule app/tools/wireguard updated: 42f3f170d4...e80799ba76
Reference in New Issue
Block a user