Auto-format the source directories

Blame Jason for writing Java in vim.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Samuel Holland 2018-09-05 18:33:42 -05:00
parent 4e134772d7
commit a264f7ab36
42 changed files with 850 additions and 841 deletions

View File

@ -11,10 +11,10 @@
<permission <permission
android:name="${applicationId}.permission.CONTROL_TUNNELS" android:name="${applicationId}.permission.CONTROL_TUNNELS"
android:protectionLevel="dangerous" android:description="@string/permission_description"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/permission_label" android:label="@string/permission_label"
android:description="@string/permission_description" /> android:protectionLevel="dangerous" />
<application <application
android:name=".Application" android:name=".Application"

View File

@ -53,70 +53,18 @@ import java9.util.concurrent.CompletableFuture;
compress = true) compress = true)
public class Application extends android.app.Application { public class Application extends android.app.Application {
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf; @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker; @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
@Nullable private Backend backend;
@SuppressWarnings("NullableProblems") private RootShell rootShell; @SuppressWarnings("NullableProblems") private RootShell rootShell;
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences; @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller; @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager; @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
@Nullable private Backend backend;
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
public Application() { public Application() {
weakSelf = new WeakReference<>(this); weakSelf = new WeakReference<>(this);
} }
/* The ACRA password can be trivially reverse engineered and is open source anyway,
* so there's no point in trying to protect it. However, we do want to at least
* prevent innocent self-builders from uploading stuff to our crash reporter. So, we
* check the DN of the certs that signed the apk, without even bothering to try
* validating that they're authentic. It's a good enough heuristic.
*/
@SuppressWarnings("deprecation")
@Nullable
private static String getInstallSource(final Context context) {
if (BuildConfig.DEBUG)
return null;
try {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
for (final Signature sig : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
try {
for (final String category : ((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()))).getSubjectDN().getName().split(", *")) {
final String[] parts = category.split("=", 2);
if (!"O".equals(parts[0]))
continue;
switch (parts[1]) {
case "Google Inc.":
return "Play Store";
case "fdroid.org":
return "F-Droid";
}
}
} catch (final Exception ignored) { }
}
} catch (final Exception ignored) { }
return null;
}
@Override
protected void attachBaseContext(final Context context) {
super.attachBaseContext(context);
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(0);
}
final String installSource = getInstallSource(context);
if (installSource != null) {
ACRA.init(this);
ACRA.getErrorReporter().putCustomData("installSource", installSource);
}
}
public static Application get() { public static Application get() {
return weakSelf.get(); return weakSelf.get();
} }
@ -149,6 +97,40 @@ public class Application extends android.app.Application {
return get().futureBackend; return get().futureBackend;
} }
/* The ACRA password can be trivially reverse engineered and is open source anyway,
* so there's no point in trying to protect it. However, we do want to at least
* prevent innocent self-builders from uploading stuff to our crash reporter. So, we
* check the DN of the certs that signed the apk, without even bothering to try
* validating that they're authentic. It's a good enough heuristic.
*/
@SuppressWarnings("deprecation")
@Nullable
private static String getInstallSource(final Context context) {
if (BuildConfig.DEBUG)
return null;
try {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
for (final Signature sig : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
try {
for (final String category : ((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()))).getSubjectDN().getName().split(", *")) {
final String[] parts = category.split("=", 2);
if (!"O".equals(parts[0]))
continue;
switch (parts[1]) {
case "Google Inc.":
return "Play Store";
case "fdroid.org":
return "F-Droid";
}
}
} catch (final Exception ignored) {
}
}
} catch (final Exception ignored) {
}
return null;
}
public static RootShell getRootShell() { public static RootShell getRootShell() {
return get().rootShell; return get().rootShell;
} }
@ -165,6 +147,26 @@ public class Application extends android.app.Application {
return get().tunnelManager; return get().tunnelManager;
} }
@Override
protected void attachBaseContext(final Context context) {
super.attachBaseContext(context);
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(0);
}
final String installSource = getInstallSource(context);
if (installSource != null) {
ACRA.init(this);
ACRA.getErrorReporter().putCustomData("installSource", installSource);
}
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();

View File

@ -40,9 +40,9 @@ public class QuickTileService extends TileService {
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback(); private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback(); private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
@Nullable private Tunnel tunnel;
@Nullable private Icon iconOn;
@Nullable private Icon iconOff; @Nullable private Icon iconOff;
@Nullable private Icon iconOn;
@Nullable private Tunnel tunnel;
/* This works around an annoying unsolved frameworks bug some people are hitting. */ /* This works around an annoying unsolved frameworks bug some people are hitting. */
@Override @Override
@ -57,6 +57,22 @@ public class QuickTileService extends TileService {
return ret; return ret;
} }
@Override
public void onClick() {
if (tunnel != null) {
final Tile tile = getQsTile();
if (tile != null) {
tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
tile.updateTile();
}
tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
} else {
final Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAndCollapse(intent);
}
}
@Override @Override
public void onCreate() { public void onCreate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@ -79,22 +95,6 @@ public class QuickTileService extends TileService {
iconOff = Icon.createWithBitmap(b); iconOff = Icon.createWithBitmap(b);
} }
@Override
public void onClick() {
if (tunnel != null) {
final Tile tile = getQsTile();
if (tile != null) {
tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
tile.updateTile();
}
tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
} else {
final Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAndCollapse(intent);
}
}
@Override @Override
public void onStartListening() { public void onStartListening() {
Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback); Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback);

View File

@ -19,9 +19,9 @@ import java.lang.reflect.Field;
public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName(); private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName();
@Nullable private static Resources lastResources;
private static boolean lastDarkMode; private static boolean lastDarkMode;
@Nullable private static Resources lastResources;
private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) { private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) {
if (resources == lastResources && darkMode == lastDarkMode) if (resources == lastResources && darkMode == lastDarkMode)
return; return;
@ -33,7 +33,8 @@ public abstract class ThemeChangeAwareActivity extends AppCompatActivity impleme
f = o.getClass().getDeclaredField("mResourcesImpl"); f = o.getClass().getDeclaredField("mResourcesImpl");
f.setAccessible(true); f.setAccessible(true);
o = f.get(o); o = f.get(o);
} catch (final Exception ignored) { } } catch (final Exception ignored) {
}
f = o.getClass().getDeclaredField("mDrawableCache"); f = o.getClass().getDeclaredField("mDrawableCache");
f.setAccessible(true); f.setAccessible(true);
o = f.get(o); o = f.get(o);

View File

@ -53,14 +53,11 @@ public interface Backend {
Statistics getStatistics(Tunnel tunnel) throws Exception; Statistics getStatistics(Tunnel tunnel) throws Exception;
/** /**
* Set the state of a tunnel. * Determine type name of underlying backend.
* *
* @param tunnel The tunnel to control the state of. * @return Type name
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
* {@code TOGGLE}.
* @return The updated state of the tunnel.
*/ */
State setState(Tunnel tunnel, State state) throws Exception; String getTypeName();
/** /**
* Determine version of underlying backend. * Determine version of underlying backend.
@ -71,9 +68,12 @@ public interface Backend {
String getVersion() throws Exception; String getVersion() throws Exception;
/** /**
* Determine type name of underlying backend. * Set the state of a tunnel.
* *
* @return Type name * @param tunnel The tunnel to control the state of.
* @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
* {@code TOGGLE}.
* @return The updated state of the tunnel.
*/ */
String getTypeName(); State setState(Tunnel tunnel, State state) throws Exception;
} }

View File

@ -59,12 +59,6 @@ public final class GoBackend implements Backend {
private static native String wgVersion(); private static native String wgVersion();
@Override
public String getVersion() { return wgVersion(); }
@Override
public String getTypeName() { return "Go userspace"; }
@Override @Override
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
if (tunnel.getState() == State.UP) { if (tunnel.getState() == State.UP) {
@ -101,6 +95,16 @@ public final class GoBackend implements Backend {
return new Statistics(); return new Statistics();
} }
@Override
public String getTypeName() {
return "Go userspace";
}
@Override
public String getVersion() {
return wgVersion();
}
@Override @Override
public State setState(final Tunnel tunnel, State state) throws Exception { public State setState(final Tunnel tunnel, State state) throws Exception {
final State originalState = getState(tunnel); final State originalState = getState(tunnel);

View File

@ -40,18 +40,6 @@ public final class WgQuickBackend implements Backend {
localTemporaryDir = new File(context.getCacheDir(), "tmp"); localTemporaryDir = new File(context.getCacheDir(), "tmp");
} }
@Override
public String getVersion() throws Exception {
final List<String> output = new ArrayList<>();
if (Application.getRootShell()
.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
throw new Exception("Unable to determine kernel module version");
return output.get(0);
}
@Override
public String getTypeName() { return "Kernel module"; }
@Override @Override
public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception { public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
if (tunnel.getState() == State.UP) { if (tunnel.getState() == State.UP) {
@ -94,6 +82,20 @@ public final class WgQuickBackend implements Backend {
return new Statistics(); return new Statistics();
} }
@Override
public String getTypeName() {
return "Kernel module";
}
@Override
public String getVersion() throws Exception {
final List<String> output = new ArrayList<>();
if (Application.getRootShell()
.run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
throw new Exception("Unable to determine kernel module version");
return output.get(0);
}
@Override @Override
public State setState(final Tunnel tunnel, State state) throws Exception { public State setState(final Tunnel tunnel, State state) throws Exception {
final State originalState = getState(tunnel); final State originalState = getState(tunnel);

View File

@ -40,11 +40,6 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
setList(list); setList(list);
} }
@Override
public int getItemCount() {
return list != null ? list.size() : 0;
}
@Nullable @Nullable
private E getItem(final int position) { private E getItem(final int position) {
if (list == null || position < 0 || position >= list.size()) if (list == null || position < 0 || position >= list.size())
@ -52,6 +47,11 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
return list.get(position); return list.get(position);
} }
@Override
public int getItemCount() {
return list != null ? list.size() : 0;
}
@Override @Override
public long getItemId(final int position) { public long getItemId(final int position) {
final K key = getKey(position); final K key = getKey(position);
@ -64,11 +64,6 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
return item != null ? item.getKey() : null; return item != null ? item.getKey() : null;
} }
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void onBindViewHolder(final ViewHolder holder, final int position) { public void onBindViewHolder(final ViewHolder holder, final int position) {
@ -85,6 +80,11 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
} }
} }
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
}
void setList(@Nullable final ObservableKeyedList<K, E> newList) { void setList(@Nullable final ObservableKeyedList<K, E> newList) {
if (list != null) if (list != null)
list.removeOnListChangedCallback(callback); list.removeOnListChangedCallback(callback);
@ -99,6 +99,10 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
this.rowConfigurationHandler = rowConfigurationHandler; this.rowConfigurationHandler = rowConfigurationHandler;
} }
public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
void onConfigureRow(B binding, T item, int position);
}
private static final class OnListChangedCallback<E extends Keyed<?>> private static final class OnListChangedCallback<E extends Keyed<?>>
extends ObservableList.OnListChangedCallback<ObservableList<E>> { extends ObservableList.OnListChangedCallback<ObservableList<E>> {
@ -152,8 +156,4 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
} }
} }
public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
void onConfigureRow(B binding, T item, int position);
}
} }

View File

@ -34,9 +34,8 @@ import java.util.List;
public class AppListDialogFragment extends DialogFragment { public class AppListDialogFragment extends DialogFragment {
private static final String KEY_EXCLUDED_APPS = "excludedApps"; private static final String KEY_EXCLUDED_APPS = "excludedApps";
private List<String> currentlyExcludedApps;
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>(); private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
private List<String> currentlyExcludedApps;
public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) { public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) {
final Bundle extras = new Bundle(); final Bundle extras = new Bundle();
@ -47,39 +46,6 @@ public class AppListDialogFragment extends DialogFragment {
return fragment; return fragment;
} }
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
}
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.excluded_applications);
final 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());
alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> { });
binding.setFragment(this);
binding.setAppData(appData);
loadData();
final AlertDialog dialog = alertDialogBuilder.create();
dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> {
for (final ApplicationData app : appData)
app.setExcludedFromTunnel(false);
}));
return dialog;
}
private void loadData() { private void loadData() {
final Activity activity = getActivity(); final Activity activity = getActivity();
if (activity == null) { if (activity == null) {
@ -113,6 +79,40 @@ public class AppListDialogFragment extends DialogFragment {
})); }));
} }
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
}
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.excluded_applications);
final 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());
alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> {
});
binding.setFragment(this);
binding.setAppData(appData);
loadData();
final AlertDialog dialog = alertDialogBuilder.create();
dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> {
for (final ApplicationData app : appData)
app.setExcludedFromTunnel(false);
}));
return dialog;
}
void setExclusionsAndDismiss() { void setExclusionsAndDismiss() {
final List<String> excludedApps = new ArrayList<>(); final List<String> excludedApps = new ArrayList<>();
for (final ApplicationData data : appData) { for (final ApplicationData data : appData) {

View File

@ -33,9 +33,8 @@ import com.wireguard.android.util.ExceptionLoggers;
*/ */
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 static final int REQUEST_CODE_VPN_PERMISSION = 23491;
private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
@Nullable private BaseActivity activity; @Nullable private BaseActivity activity;
@Nullable private Tunnel pendingTunnel; @Nullable private Tunnel pendingTunnel;
@Nullable private Boolean pendingTunnelUp; @Nullable private Boolean pendingTunnelUp;
@ -45,6 +44,18 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
return activity != null ? activity.getSelectedTunnel() : null; return activity != null ? activity.getSelectedTunnel() : null;
} }
@Override
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final 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;
}
}
@Override @Override
public void onAttach(final Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);
@ -64,18 +75,6 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
super.onDetach(); super.onDetach();
} }
@Override
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final 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(@Nullable final Tunnel tunnel) { protected void setSelectedTunnel(@Nullable final Tunnel tunnel) {
if (activity != null) if (activity != null)
activity.setSelectedTunnel(tunnel); activity.setSelectedTunnel(tunnel);

View File

@ -13,7 +13,6 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import com.wireguard.android.Application; import com.wireguard.android.Application;
@ -27,9 +26,8 @@ import java.util.Objects;
public class ConfigNamingDialogFragment extends DialogFragment { public class ConfigNamingDialogFragment extends DialogFragment {
private static final String KEY_CONFIG_TEXT = "config_text"; private static final String KEY_CONFIG_TEXT = "config_text";
@Nullable private Config config;
@Nullable private ConfigNamingDialogFragmentBinding binding; @Nullable private ConfigNamingDialogFragmentBinding binding;
@Nullable private Config config;
@Nullable private InputMethodManager imm; @Nullable private InputMethodManager imm;
public static ConfigNamingDialogFragment newInstance(final String configText) { public static ConfigNamingDialogFragment newInstance(final String configText) {
@ -40,6 +38,26 @@ public class ConfigNamingDialogFragment extends DialogFragment {
return fragment; return fragment;
} }
private void createTunnelAndDismiss() {
if (binding != null) {
final String name = binding.tunnelNameText.getText().toString();
Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> {
if (tunnel != null) {
dismiss();
} else {
binding.tunnelNameTextLayout.setError(throwable.getMessage());
}
});
}
}
@Override
public void dismiss() {
setKeyboardVisible(false);
super.dismiss();
}
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -51,17 +69,6 @@ public class ConfigNamingDialogFragment extends DialogFragment {
} }
} }
@Override public void onResume() {
super.onResume();
final AlertDialog dialog = (AlertDialog) getDialog();
if (dialog != null) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
setKeyboardVisible(true);
}
}
@Override @Override
public Dialog onCreateDialog(final Bundle savedInstanceState) { public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Activity activity = getActivity(); final Activity activity = getActivity();
@ -81,23 +88,14 @@ public class ConfigNamingDialogFragment extends DialogFragment {
return alertDialogBuilder.create(); return alertDialogBuilder.create();
} }
@Override @Override public void onResume() {
public void dismiss() { super.onResume();
setKeyboardVisible(false);
super.dismiss();
}
private void createTunnelAndDismiss() { final AlertDialog dialog = (AlertDialog) getDialog();
if (binding != null) { if (dialog != null) {
final String name = binding.tunnelNameText.getText().toString(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> { setKeyboardVisible(true);
if (tunnel != null) {
dismiss();
} else {
binding.tunnelNameTextLayout.setError(throwable.getMessage());
}
});
} }
} }

View File

@ -48,8 +48,59 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
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();
private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>();
@Nullable private TunnelEditorFragmentBinding binding; @Nullable private TunnelEditorFragmentBinding binding;
private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(final Observable sender, final int propertyId) {
if (binding == null)
return;
final Config.Observable config = binding.getConfig();
if (config == null)
return;
if (propertyId == BR.config) {
config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config);
config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection());
config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers());
} else if (propertyId == BR.dnses || propertyId == BR.peers)
;
else
return;
final int numSiblings = config.getPeers().size() - 1;
for (final Peer.Observable peer : config.getPeers()) {
peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses());
peer.setNumSiblings(numSiblings);
}
}
};
private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() {
@Override
public void onChanged(final ObservableList<Peer.Observable> sender) {
}
@Override
public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
}
@Override
public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
if (binding != null)
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
@Override
public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) {
}
@Override
public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
if (binding != null)
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
};
@Nullable private Tunnel tunnel; @Nullable private Tunnel tunnel;
private void onConfigLoaded(final String name, final Config config) { private void onConfigLoaded(final String name, final Config config) {
@ -87,54 +138,6 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
inflater.inflate(R.menu.config_editor, menu); inflater.inflate(R.menu.config_editor, menu);
} }
private final ObservableList.OnListChangedCallback<? extends ObservableList<Peer.Observable>> breakObjectListOrientedLayeringHandler = new ObservableList.OnListChangedCallback<ObservableList<Peer.Observable>>() {
@Override
public void onChanged(final ObservableList<Peer.Observable> sender) { }
@Override
public void onItemRangeChanged(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) { }
@Override
public void onItemRangeMoved(final ObservableList<Peer.Observable> sender, final int fromPosition, final int toPosition, final int itemCount) { }
@Override
public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
if (binding != null)
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
@Override
public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
if (binding != null)
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
};
private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>();
private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(final Observable sender, final int propertyId) {
if (binding == null)
return;
final Config.Observable config = binding.getConfig();
if (config == null)
return;
if (propertyId == BR.config) {
config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config);
config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection());
config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers());
} else if (propertyId == BR.dnses || propertyId == BR.peers)
;
else
return;
final int numSiblings = config.getPeers().size() - 1;
for (final Peer.Observable peer : config.getPeers()) {
peer.setInterfaceDNSRoutes(config.getInterfaceSection().getDnses());
peer.setNumSiblings(numSiblings);
}
}
};
@Override @Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) { @Nullable final Bundle savedInstanceState) {
@ -159,6 +162,12 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
super.onDestroyView(); super.onDestroyView();
} }
@Override
public void onExcludedAppsSelected(final List<String> excludedApps) {
Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded");
binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps));
}
private void onFinished() { private void onFinished() {
// Hide the keyboard; it rarely goes away on its own. // Hide the keyboard; it rarely goes away on its own.
final Activity activity = getActivity(); final Activity activity = getActivity();
@ -217,6 +226,15 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
} }
} }
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null && binding != null) {
final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
fragment.show(fragmentManager, null);
}
}
@Override @Override
public void onSaveInstanceState(final Bundle outState) { public void onSaveInstanceState(final Bundle outState) {
outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig()); outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
@ -294,19 +312,4 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
} }
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null && binding != null) {
final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
fragment.show(fragmentManager, null);
}
}
@Override
public void onExcludedAppsSelected(final List<String> excludedApps) {
Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded");
binding.getConfig().getInterfaceSection().setExcludedApplications(Attribute.iterableToString(excludedApps));
}
} }

View File

@ -38,13 +38,11 @@ import com.wireguard.android.databinding.TunnelListFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding; 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.util.ObservableSortedKeyedList;
import com.wireguard.android.widget.MultiselectableRelativeLayout; import com.wireguard.android.widget.MultiselectableRelativeLayout;
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
import com.wireguard.config.Config; import com.wireguard.config.Config;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -185,6 +183,19 @@ public class TunnelListFragment extends BaseFragment {
}); });
} }
@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS");
if (checkedItems != null) {
for (final Integer i : checkedItems)
actionModeListener.setItemChecked(i, true);
}
}
}
@Override @Override
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
switch (requestCode) { switch (requestCode) {
@ -228,6 +239,14 @@ public class TunnelListFragment extends BaseFragment {
super.onDestroyView(); super.onDestroyView();
} }
@Override
public void onPause() {
if (binding != null) {
binding.createMenu.collapse();
}
super.onPause();
}
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) { public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class)); startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
if (binding != null) if (binding != null)
@ -255,15 +274,10 @@ public class TunnelListFragment extends BaseFragment {
} }
@Override @Override
public void onPause() { public void onSaveInstanceState(final Bundle outState) {
if (binding != null) { super.onSaveInstanceState(outState);
binding.createMenu.collapse();
}
super.onPause();
}
private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) { outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
return (MultiselectableRelativeLayout)binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
} }
@Override @Override
@ -317,26 +331,6 @@ public class TunnelListFragment extends BaseFragment {
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show(); Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
} }
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
}
@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS");
if (checkedItems != null) {
for (final Integer i : checkedItems)
actionModeListener.setItemChecked(i, true);
}
}
}
@Override @Override
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) { public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
@ -368,11 +362,19 @@ public class TunnelListFragment extends BaseFragment {
}); });
} }
private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) {
return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
}
private final class ActionModeListener implements ActionMode.Callback { private final class ActionModeListener implements ActionMode.Callback {
private final Collection<Integer> checkedItems = new HashSet<>(); private final Collection<Integer> checkedItems = new HashSet<>();
@Nullable private Resources resources; @Nullable private Resources resources;
public ArrayList<Integer> getCheckedItems() {
return new ArrayList<>(checkedItems);
}
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@ -425,12 +427,10 @@ public class TunnelListFragment extends BaseFragment {
binding.tunnelList.getAdapter().notifyDataSetChanged(); binding.tunnelList.getAdapter().notifyDataSetChanged();
} }
void toggleItemChecked(final int position) { @Override
setItemChecked(position, !checkedItems.contains(position)); public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
} updateTitle(mode);
return false;
public ArrayList<Integer> getCheckedItems() {
return new ArrayList<>(checkedItems);
} }
void setItemChecked(final int position, final boolean checked) { void setItemChecked(final int position, final boolean checked) {
@ -454,10 +454,8 @@ public class TunnelListFragment extends BaseFragment {
updateTitle(actionMode); updateTitle(actionMode);
} }
@Override void toggleItemChecked(final int position) {
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { setItemChecked(position, !checkedItems.contains(position));
updateTitle(mode);
return false;
} }
private void updateTitle(@Nullable final ActionMode mode) { private void updateTitle(@Nullable final ActionMode mode) {

View File

@ -30,6 +30,11 @@ public class ApplicationData extends BaseObservable implements Keyed<String> {
return icon; return icon;
} }
@Override
public String getKey() {
return name;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -47,9 +52,4 @@ public class ApplicationData extends BaseObservable implements Keyed<String> {
this.excludedFromTunnel = excludedFromTunnel; this.excludedFromTunnel = excludedFromTunnel;
notifyPropertyChanged(BR.excludedFromTunnel); notifyPropertyChanged(BR.excludedFromTunnel);
} }
@Override
public String getKey() {
return name;
}
} }

View File

@ -151,5 +151,6 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
} }
} }
public static class Statistics extends BaseObservable { } public static class Statistics extends BaseObservable {
}
} }

View File

@ -44,19 +44,28 @@ public final class TunnelManager extends BaseObservable {
private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel"; private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel";
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot"; private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
private static final String KEY_RUNNING_TUNNELS = "enabled_configs"; private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
private final ConfigStore configStore; private final ConfigStore configStore;
private final Context context = Application.get(); private final Context context = Application.get();
private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
@Nullable private Tunnel lastUsedTunnel;
private boolean haveLoaded;
private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>(); private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
private boolean haveLoaded;
@Nullable private Tunnel lastUsedTunnel;
public TunnelManager(final ConfigStore configStore) { public TunnelManager(final ConfigStore configStore) {
this.configStore = configStore; this.configStore = configStore;
} }
static CompletionStage<State> getTunnelState(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel))
.thenApply(tunnel::onStateChanged);
}
static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel))
.thenApply(tunnel::onStatisticsChanged);
}
private Tunnel addToList(final String name, @Nullable final Config config, final State state) { private Tunnel addToList(final String name, @Nullable final Config config, final State state) {
final Tunnel tunnel = new Tunnel(this, name, config, state); final Tunnel tunnel = new Tunnel(this, name, config, state);
tunnels.add(tunnel); tunnels.add(tunnel);
@ -112,16 +121,6 @@ public final class TunnelManager extends BaseObservable {
.thenApply(tunnel::onConfigChanged); .thenApply(tunnel::onConfigChanged);
} }
static CompletionStage<State> getTunnelState(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel))
.thenApply(tunnel::onStateChanged);
}
static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel))
.thenApply(tunnel::onStatisticsChanged);
}
public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() { public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() {
return completableTunnels; return completableTunnels;
} }

View File

@ -50,7 +50,8 @@ public class VersionPreference extends Preference {
intent.setData(Uri.parse("https://www.wireguard.com/")); intent.setData(Uri.parse("https://www.wireguard.com/"));
try { try {
getContext().startActivity(intent); getContext().startActivity(intent);
} catch (final ActivityNotFoundException ignored) { } } catch (final ActivityNotFoundException ignored) {
}
} }
} }

View File

@ -19,7 +19,8 @@ import java.util.zip.ZipFile;
public final class SharedLibraryLoader { public final class SharedLibraryLoader {
private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName(); private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
private SharedLibraryLoader() { } private SharedLibraryLoader() {
}
public static void loadSharedLibrary(final Context context, final String libName) { public static void loadSharedLibrary(final Context context, final String libName) {
Throwable noAbiException; Throwable noAbiException;

View File

@ -26,11 +26,10 @@ import java.util.List;
public final class ToolsInstaller { public final class ToolsInstaller {
public static final int ERROR = 0x0; public static final int ERROR = 0x0;
public static final int YES = 0x1;
public static final int NO = 0x2;
public static final int MAGISK = 0x4; public static final int MAGISK = 0x4;
public static final int NO = 0x2;
public static final int SYSTEM = 0x8; public static final int SYSTEM = 0x8;
public static final int YES = 0x1;
private static final String[][] EXECUTABLES = { private static final String[][] EXECUTABLES = {
{"libwg.so", "wg"}, {"libwg.so", "wg"},
{"libwg-quick.so", "wg-quick"}, {"libwg-quick.so", "wg-quick"},
@ -107,34 +106,8 @@ public final class ToolsInstaller {
} }
} }
private boolean willInstallAsMagiskModule() { public int install() throws NoRootException {
synchronized (lock) { return willInstallAsMagiskModule() ? installMagisk() : installSystem();
if (installAsMagiskModule == null) {
try {
installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
} catch (final Exception ignored) {
installAsMagiskModule = false;
}
}
return installAsMagiskModule;
}
}
private int installSystem() throws NoRootException {
if (INSTALL_DIR == null)
return OsConstants.ENOENT;
final StringBuilder script = new StringBuilder("set -ex; ");
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
for (final String[] names : EXECUTABLES) {
final File destination = new File(INSTALL_DIR, names[1]);
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
new File(nativeLibraryDir, names[0]), destination, destination, destination));
}
try {
return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
} catch (final IOException ignored) {
return ERROR;
}
} }
private int installMagisk() throws NoRootException { private int installMagisk() throws NoRootException {
@ -158,8 +131,21 @@ public final class ToolsInstaller {
} }
} }
public int install() throws NoRootException { private int installSystem() throws NoRootException {
return willInstallAsMagiskModule() ? installMagisk() : installSystem(); if (INSTALL_DIR == null)
return OsConstants.ENOENT;
final StringBuilder script = new StringBuilder("set -ex; ");
script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
for (final String[] names : EXECUTABLES) {
final File destination = new File(INSTALL_DIR, names[1]);
script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
new File(nativeLibraryDir, names[0]), destination, destination, destination));
}
try {
return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
} catch (final IOException ignored) {
return ERROR;
}
} }
public int symlink() throws NoRootException { public int symlink() throws NoRootException {
@ -184,4 +170,17 @@ public final class ToolsInstaller {
return OsConstants.EXIT_FAILURE; return OsConstants.EXIT_FAILURE;
} }
} }
private boolean willInstallAsMagiskModule() {
synchronized (lock) {
if (installAsMagiskModule == null) {
try {
installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.core/mirror -a -d /sbin/.core/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
} catch (final Exception ignored) {
installAsMagiskModule = false;
}
}
return installAsMagiskModule;
}
}
} }

View File

@ -12,23 +12,25 @@ import android.widget.RelativeLayout;
import com.wireguard.android.R; import com.wireguard.android.R;
public class MultiselectableRelativeLayout extends RelativeLayout { public class MultiselectableRelativeLayout extends RelativeLayout {
private static final int[] STATE_MULTISELECTED = {R.attr.state_multiselected};
private boolean multiselected;
public MultiselectableRelativeLayout(final Context context) { public MultiselectableRelativeLayout(final Context context) {
super(context); super(context);
} }
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) { public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) { public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
} }
private static final int[] STATE_MULTISELECTED = { R.attr.state_multiselected };
private boolean multiselected;
@Override @Override
protected int[] onCreateDrawableState(final int extraSpace) { protected int[] onCreateDrawableState(final int extraSpace) {
if (multiselected) { if (multiselected) {

View File

@ -31,95 +31,43 @@ import android.util.FloatProperty;
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
public class SlashDrawable extends Drawable { 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_X = 10.65f;
private static final float CENTER_Y = 11.869239f; private static final float CENTER_Y = 11.869239f;
private static final float SCALE = 24f; private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
// 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 // Draw the slash washington-monument style; rotate to no-u-turn style
private static final float DEFAULT_ROTATION = -45f; private static final float DEFAULT_ROTATION = -45f;
private static final long QS_ANIM_LENGTH = 350;
private final Drawable mDrawable; private static final float SCALE = 24f;
private final RectF mSlashRect = new RectF(0, 0, 0, 0); private static final float SLASH_HEIGHT = 28f;
private float mRotation; // These values are derived in un-rotated (vertical) orientation
private boolean mSlashed; private static final float SLASH_WIDTH = 1.8384776f;
private boolean mAnimationEnabled = true; // Bottom is derived during animation
private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
public SlashDrawable(final Drawable d) { private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
mDrawable = d; private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
}
@Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
@Override
protected void onBoundsChange(final Rect bounds) {
super.onBoundsChange(bounds);
mDrawable.setBounds(bounds);
}
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") { private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
@Override
public void setValue(final SlashDrawable object, final float value) {
object.mCurrentSlashLength = value;
}
@Override @Override
public Float get(final SlashDrawable object) { public Float get(final SlashDrawable object) {
return object.mCurrentSlashLength; return object.mCurrentSlashLength;
} }
};
@SuppressWarnings("unchecked") @Override
public void setSlashed(final boolean slashed) { public void setValue(final SlashDrawable object, final float value) {
if (mSlashed == slashed) return; object.mCurrentSlashLength = value;
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();
} }
};
private final Drawable mDrawable;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
private final RectF mSlashRect = new RectF(0, 0, 0, 0);
private boolean mAnimationEnabled = true;
// Animate this value on change
private float mCurrentSlashLength;
private float mRotation;
private boolean mSlashed;
public SlashDrawable(final Drawable d) {
mDrawable = d;
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -166,15 +114,76 @@ public class SlashDrawable extends Drawable {
canvas.restore(); canvas.restore();
} }
@Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
protected void onBoundsChange(final Rect bounds) {
super.onBoundsChange(bounds);
mDrawable.setBounds(bounds);
}
private float scale(final float frac, final int width) { private float scale(final float frac, final int width) {
return frac * width; return frac * width;
} }
private void updateRect(final float left, final float top, final float right, final float bottom) { @Override
mSlashRect.left = left; public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
mSlashRect.top = top; mDrawable.setAlpha(alpha);
mSlashRect.right = right; mPaint.setAlpha(alpha);
mSlashRect.bottom = bottom; }
public void setAnimationEnabled(final boolean enabled) {
mAnimationEnabled = enabled;
}
@Override
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
mDrawable.setColorFilter(colorFilter);
mPaint.setColorFilter(colorFilter);
}
private void setDrawableTintList(@Nullable final ColorStateList tint) {
mDrawable.setTintList(tint);
}
public void setRotation(final float rotation) {
if (mRotation == rotation)
return;
mRotation = rotation;
invalidateSelf();
}
@SuppressWarnings("unchecked")
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 @Override
@ -192,30 +201,16 @@ public class SlashDrawable extends Drawable {
invalidateSelf(); invalidateSelf();
} }
private void setDrawableTintList(@Nullable final ColorStateList tint) {
mDrawable.setTintList(tint);
}
@Override @Override
public void setTintMode(final Mode tintMode) { public void setTintMode(final Mode tintMode) {
super.setTintMode(tintMode); super.setTintMode(tintMode);
mDrawable.setTintMode(tintMode); mDrawable.setTintMode(tintMode);
} }
@Override private void updateRect(final float left, final float top, final float right, final float bottom) {
public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) { mSlashRect.left = left;
mDrawable.setAlpha(alpha); mSlashRect.top = top;
mPaint.setAlpha(alpha); mSlashRect.right = right;
} mSlashRect.bottom = bottom;
@Override
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
mDrawable.setColorFilter(colorFilter);
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
} }
} }

View File

@ -18,19 +18,13 @@ import android.view.View;
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> { public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
private static final long ANIMATION_DURATION = 250;
private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) { public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
@Override
public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
final View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
private static final long ANIMATION_DURATION = 250;
private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) { private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) {
final float origin = child.getBehaviorYTranslation(); final float origin = child.getBehaviorYTranslation();
if (Math.abs(destination - origin) < fullSpan / 2) { if (Math.abs(destination - origin) < fullSpan / 2) {
@ -51,6 +45,12 @@ public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<Flo
animator.start(); animator.start();
} }
@Override
public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
final View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override @Override
public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child, public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
final View dependency) { final View dependency) {

View File

@ -37,39 +37,38 @@ import android.widget.TextView;
import com.wireguard.android.R; import com.wireguard.android.R;
public class FloatingActionsMenu extends ViewGroup { public class FloatingActionsMenu extends ViewGroup {
public static final int EXPAND_UP = 0;
public static final int EXPAND_DOWN = 1; public static final int EXPAND_DOWN = 1;
public static final int EXPAND_LEFT = 2; public static final int EXPAND_LEFT = 2;
public static final int EXPAND_RIGHT = 3; public static final int EXPAND_RIGHT = 3;
public static final int EXPAND_UP = 0;
public static final int LABELS_ON_LEFT_SIDE = 0; public static final int LABELS_ON_LEFT_SIDE = 0;
public static final int LABELS_ON_RIGHT_SIDE = 1; public static final int LABELS_ON_RIGHT_SIDE = 1;
private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator();
private static final int ANIMATION_DURATION = 300; private static final int ANIMATION_DURATION = 300;
private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS");
private static final float COLLAPSED_PLUS_ROTATION = 0f; private static final float COLLAPSED_PLUS_ROTATION = 0f;
private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f);
private static final float EXPANDED_PLUS_ROTATION = 90f + 45f; private static final float EXPANDED_PLUS_ROTATION = 90f + 45f;
private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator(); private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator();
private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f);
private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator();
private int mExpandDirection;
private int mButtonSpacing;
private int mLabelsMargin;
private int mLabelsVerticalOffset;
private boolean mExpanded;
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
@Nullable private FloatingActionButton mAddButton; private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
@Nullable private RotatingDrawable mRotatingDrawable;
private int mMaxButtonWidth;
private int mMaxButtonHeight;
private int mLabelsStyle;
private int mLabelsPosition;
private int mButtonsCount;
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
private final Rect touchArea = new Rect(0, 0, 0, 0); private final Rect touchArea = new Rect(0, 0, 0, 0);
private float scrollYTranslation;
private float behaviorYTranslation; private float behaviorYTranslation;
@Nullable private FloatingActionButton mAddButton;
private int mButtonSpacing;
private int mButtonsCount;
private int mExpandDirection;
private boolean mExpanded;
private int mLabelsMargin;
private int mLabelsPosition;
private int mLabelsStyle;
private int mLabelsVerticalOffset;
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
private int mMaxButtonHeight;
private int mMaxButtonWidth;
@Nullable private RotatingDrawable mRotatingDrawable;
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
private float scrollYTranslation;
public FloatingActionsMenu(final Context context) { public FloatingActionsMenu(final Context context) {
this(context, null); this(context, null);
@ -85,51 +84,39 @@ public class FloatingActionsMenu extends ViewGroup {
init(context, attrs); init(context, attrs);
} }
private void init(final Context context, @Nullable final AttributeSet attributeSet) { private static int adjustForOvershoot(final int dimension) {
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing)); return dimension * 12 / 10;
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
mTouchDelegateGroup = new TouchDelegateGroup(this);
setTouchDelegate(mTouchDelegateGroup);
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
attr.recycle();
if (mLabelsStyle != 0 && expandsHorizontally()) {
throw new IllegalStateException("Action labels in horizontal expand orientation is not supported.");
} }
createAddButton(context); public void addButton(final LabeledFloatingActionButton button) {
addView(button, mButtonsCount - 1);
mButtonsCount++;
if (mLabelsStyle != 0) {
createLabels();
}
} }
public float getScrollYTranslation() { public void collapse() {
return scrollYTranslation; collapse(false);
} }
public void setScrollYTranslation(final float scrollYTranslation) { private void collapse(final boolean immediately) {
this.scrollYTranslation = scrollYTranslation; if (mExpanded) {
setTranslationY(behaviorYTranslation + scrollYTranslation); mExpanded = false;
mTouchDelegateGroup.setEnabled(false);
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
mCollapseAnimation.start();
mExpandAnimation.cancel();
if (mListener != null) {
mListener.onMenuCollapsed();
}
}
} }
public float getBehaviorYTranslation() { public void collapseImmediately() {
return behaviorYTranslation; collapse(true);
}
public void setBehaviorYTranslation(final float behaviorYTranslation) {
this.behaviorYTranslation = behaviorYTranslation;
setTranslationY(behaviorYTranslation + scrollYTranslation);
}
public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
mListener = listener;
}
private boolean expandsHorizontally() {
return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
} }
private void createAddButton(final Context context) { private void createAddButton(final Context context) {
@ -156,87 +143,103 @@ public class FloatingActionsMenu extends ViewGroup {
mButtonsCount++; mButtonsCount++;
} }
public void addButton(final LabeledFloatingActionButton button) { private void createLabels() {
addView(button, mButtonsCount - 1); final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
mButtonsCount++;
for (int i = 0; i < mButtonsCount; i++) {
final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
if (button instanceof LabeledFloatingActionButton) {
final String title = ((LabeledFloatingActionButton) button).getTitle();
final AppCompatTextView label = new AppCompatTextView(context);
if (!BROKEN_LABEL_STYLE)
label.setTextAppearance(context, mLabelsStyle);
label.setText(title);
addView(label);
button.setTag(R.id.fab_label, label);
}
}
}
public void expand() {
if (!mExpanded) {
mExpanded = true;
mTouchDelegateGroup.setEnabled(true);
mCollapseAnimation.cancel();
mExpandAnimation.start();
if (mListener != null) {
mListener.onMenuExpanded();
}
}
}
private boolean expandsHorizontally() {
return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(super.generateDefaultLayoutParams());
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
return new LayoutParams(super.generateLayoutParams(attrs));
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) {
return new LayoutParams(super.generateLayoutParams(p));
}
public float getBehaviorYTranslation() {
return behaviorYTranslation;
}
public float getScrollYTranslation() {
return scrollYTranslation;
}
private void init(final Context context, @Nullable final AttributeSet attributeSet) {
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing));
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
mTouchDelegateGroup = new TouchDelegateGroup(this);
setTouchDelegate(mTouchDelegateGroup);
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
attr.recycle();
if (mLabelsStyle != 0 && expandsHorizontally()) {
throw new IllegalStateException("Action labels in horizontal expand orientation is not supported.");
}
createAddButton(context);
}
public boolean isExpanded() {
return mExpanded;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
bringChildToFront(mAddButton);
mButtonsCount = getChildCount();
if (mLabelsStyle != 0) { if (mLabelsStyle != 0) {
createLabels(); createLabels();
} }
} }
public void removeButton(final LabeledFloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null);
mButtonsCount--;
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
mMaxButtonWidth = 0;
mMaxButtonHeight = 0;
int maxLabelWidth = 0;
for (int i = 0; i < mButtonsCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
height += child.getMeasuredHeight();
break;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += child.getMeasuredWidth();
mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
break;
}
if (!expandsHorizontally()) {
final TextView label = (TextView) child.getTag(R.id.fab_label);
if (label != null) {
maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
}
}
}
if (expandsHorizontally()) {
height = mMaxButtonHeight;
} else {
width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
}
switch (mExpandDirection) {
case EXPAND_UP:
case EXPAND_DOWN:
height += mButtonSpacing * (mButtonsCount - 1);
height = adjustForOvershoot(height);
break;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += mButtonSpacing * (mButtonsCount - 1);
width = adjustForOvershoot(width);
break;
}
setMeasuredDimension(width, height);
}
private static int adjustForOvershoot(final int dimension) {
return dimension * 12 / 10;
}
@Override @Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) { protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
switch (mExpandDirection) { switch (mExpandDirection) {
@ -367,115 +370,64 @@ public class FloatingActionsMenu extends ViewGroup {
} }
@Override @Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() { protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
return new LayoutParams(super.generateDefaultLayoutParams()); measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override int width = 0;
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { int height = 0;
return new LayoutParams(super.generateLayoutParams(attrs));
}
@Override mMaxButtonWidth = 0;
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) { mMaxButtonHeight = 0;
return new LayoutParams(super.generateLayoutParams(p)); int maxLabelWidth = 0;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
bringChildToFront(mAddButton);
mButtonsCount = getChildCount();
if (mLabelsStyle != 0) {
createLabels();
}
}
private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS");
private void createLabels() {
final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
for (int i = 0; i < mButtonsCount; i++) { for (int i = 0; i < mButtonsCount; i++) {
final FloatingActionButton button = (FloatingActionButton) getChildAt(i); final View child = getChildAt(i);
if (button instanceof LabeledFloatingActionButton) { if (child.getVisibility() == GONE) {
final String title = ((LabeledFloatingActionButton) button).getTitle(); continue;
}
final AppCompatTextView label = new AppCompatTextView(context); switch (mExpandDirection) {
if (!BROKEN_LABEL_STYLE) case EXPAND_UP:
label.setTextAppearance(context, mLabelsStyle); case EXPAND_DOWN:
label.setText(title); mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
addView(label); height += child.getMeasuredHeight();
break;
case EXPAND_LEFT:
case EXPAND_RIGHT:
width += child.getMeasuredWidth();
mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
break;
}
button.setTag(R.id.fab_label, label); if (!expandsHorizontally()) {
final TextView label = (TextView) child.getTag(R.id.fab_label);
if (label != null) {
maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
} }
} }
} }
public void collapse() { if (expandsHorizontally()) {
collapse(false); height = mMaxButtonHeight;
}
public void collapseImmediately() {
collapse(true);
}
private void collapse(final boolean immediately) {
if (mExpanded) {
mExpanded = false;
mTouchDelegateGroup.setEnabled(false);
mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
mCollapseAnimation.start();
mExpandAnimation.cancel();
if (mListener != null) {
mListener.onMenuCollapsed();
}
}
}
public void toggle() {
if (mExpanded) {
collapse();
} else { } else {
expand(); width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
}
} }
public void expand() { switch (mExpandDirection) {
if (!mExpanded) { case EXPAND_UP:
mExpanded = true; case EXPAND_DOWN:
mTouchDelegateGroup.setEnabled(true); height += mButtonSpacing * (mButtonsCount - 1);
mCollapseAnimation.cancel(); height = adjustForOvershoot(height);
mExpandAnimation.start(); break;
case EXPAND_LEFT:
if (mListener != null) { case EXPAND_RIGHT:
mListener.onMenuExpanded(); width += mButtonSpacing * (mButtonsCount - 1);
} width = adjustForOvershoot(width);
} break;
} }
public boolean isExpanded() { setMeasuredDimension(width, height);
return mExpanded;
}
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
mAddButton.setEnabled(enabled);
}
@Override
public Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.mExpanded = mExpanded;
return savedState;
} }
@Override @Override
@ -495,10 +447,55 @@ public class FloatingActionsMenu extends ViewGroup {
} }
} }
public interface OnFloatingActionsMenuUpdateListener { @Override
void onMenuExpanded(); public Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.mExpanded = mExpanded;
return savedState;
}
public void removeButton(final LabeledFloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null);
mButtonsCount--;
}
public void setBehaviorYTranslation(final float behaviorYTranslation) {
this.behaviorYTranslation = behaviorYTranslation;
setTranslationY(behaviorYTranslation + scrollYTranslation);
}
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
mAddButton.setEnabled(enabled);
}
public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
mListener = listener;
}
public void setScrollYTranslation(final float scrollYTranslation) {
this.scrollYTranslation = scrollYTranslation;
setTranslationY(behaviorYTranslation + scrollYTranslation);
}
public void toggle() {
if (mExpanded) {
collapse();
} else {
expand();
}
}
public interface OnFloatingActionsMenuUpdateListener {
void onMenuCollapsed(); void onMenuCollapsed();
void onMenuExpanded();
} }
private static class RotatingDrawable extends LayerDrawable { private static class RotatingDrawable extends LayerDrawable {
@ -508,6 +505,14 @@ public class FloatingActionsMenu extends ViewGroup {
super(new Drawable[]{drawable}); super(new Drawable[]{drawable});
} }
@Override
public void draw(final Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
super.draw(canvas);
canvas.restore();
}
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
public float getRotation() { public float getRotation() {
return mRotation; return mRotation;
@ -519,14 +524,6 @@ public class FloatingActionsMenu extends ViewGroup {
mRotation = rotation; mRotation = rotation;
invalidateSelf(); invalidateSelf();
} }
@Override
public void draw(final Canvas canvas) {
canvas.save();
canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
super.draw(canvas);
canvas.restore();
}
} }
public static class SavedState extends BaseSavedState { public static class SavedState extends BaseSavedState {
@ -562,10 +559,10 @@ public class FloatingActionsMenu extends ViewGroup {
private class LayoutParams extends ViewGroup.LayoutParams { private class LayoutParams extends ViewGroup.LayoutParams {
private final ObjectAnimator mExpandDir = new ObjectAnimator();
private final ObjectAnimator mExpandAlpha = new ObjectAnimator();
private final ObjectAnimator mCollapseDir = new ObjectAnimator();
private final ObjectAnimator mCollapseAlpha = new ObjectAnimator(); private final ObjectAnimator mCollapseAlpha = new ObjectAnimator();
private final ObjectAnimator mCollapseDir = new ObjectAnimator();
private final ObjectAnimator mExpandAlpha = new ObjectAnimator();
private final ObjectAnimator mExpandDir = new ObjectAnimator();
private boolean animationsSetToPlay; private boolean animationsSetToPlay;
LayoutParams(final ViewGroup.LayoutParams source) { LayoutParams(final ViewGroup.LayoutParams source) {
@ -596,6 +593,20 @@ public class FloatingActionsMenu extends ViewGroup {
} }
} }
private void addLayerTypeListener(final Animator animator, final View view) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null);
}
@Override
public void onAnimationStart(final Animator animation) {
view.setLayerType(LAYER_TYPE_HARDWARE, null);
}
});
}
public void setAnimationsTarget(final View view) { public void setAnimationsTarget(final View view) {
mCollapseAlpha.setTarget(view); mCollapseAlpha.setTarget(view);
mCollapseDir.setTarget(view); mCollapseDir.setTarget(view);
@ -614,19 +625,5 @@ public class FloatingActionsMenu extends ViewGroup {
animationsSetToPlay = true; animationsSetToPlay = true;
} }
} }
private void addLayerTypeListener(final Animator animator, final View view) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null);
}
@Override
public void onAnimationStart(final Animator animation) {
view.setLayerType(LAYER_TYPE_HARDWARE, null);
}
});
}
} }
} }

View File

@ -29,13 +29,6 @@ public class TouchDelegateGroup extends TouchDelegate {
mTouchDelegates.add(touchDelegate); mTouchDelegates.add(touchDelegate);
} }
public void removeTouchDelegate(final TouchDelegate touchDelegate) {
mTouchDelegates.remove(touchDelegate);
if (mCurrentTouchDelegate == touchDelegate) {
mCurrentTouchDelegate = null;
}
}
public void clearTouchDelegates() { public void clearTouchDelegates() {
mTouchDelegates.clear(); mTouchDelegates.clear();
mCurrentTouchDelegate = null; mCurrentTouchDelegate = null;
@ -72,6 +65,13 @@ public class TouchDelegateGroup extends TouchDelegate {
return delegate != null && delegate.onTouchEvent(event); return delegate != null && delegate.onTouchEvent(event);
} }
public void removeTouchDelegate(final TouchDelegate touchDelegate) {
mTouchDelegates.remove(touchDelegate);
if (mCurrentTouchDelegate == touchDelegate) {
mCurrentTouchDelegate = null;
}
}
public void setEnabled(final boolean enabled) { public void setEnabled(final boolean enabled) {
mEnabled = enabled; mEnabled = enabled;
} }

View File

@ -105,9 +105,9 @@ public class Config {
return new Observable[size]; return new Observable[size];
} }
}; };
@Nullable private String name;
private final Interface.Observable observableInterface; private final Interface.Observable observableInterface;
private final ObservableList<Peer.Observable> observablePeers; private final ObservableList<Peer.Observable> observablePeers;
@Nullable private String name;
public Observable(@Nullable final Config parent, @Nullable final String name) { public Observable(@Nullable final Config parent, @Nullable final String name) {
this.name = name; this.name = name;

View File

@ -28,12 +28,12 @@ import java.util.List;
public class Interface { public class Interface {
private final List<InetNetwork> addressList; private final List<InetNetwork> addressList;
private final Context context = Application.get();
private final List<InetAddress> dnsList; private final List<InetAddress> dnsList;
private final List<String> excludedApplications; private final List<String> excludedApplications;
@Nullable private Keypair keypair; @Nullable private Keypair keypair;
private int listenPort; private int listenPort;
private int mtu; private int mtu;
private final Context context = Application.get();
public Interface() { public Interface() {
addressList = new ArrayList<>(); addressList = new ArrayList<>();
@ -94,6 +94,10 @@ public class Interface {
return dnsList.toArray(new InetAddress[dnsList.size()]); return dnsList.toArray(new InetAddress[dnsList.size()]);
} }
public String[] getExcludedApplications() {
return excludedApplications.toArray(new String[excludedApplications.size()]);
}
@Nullable @Nullable
private String getExcludedApplicationsString() { private String getExcludedApplicationsString() {
if (excludedApplications.isEmpty()) if (excludedApplications.isEmpty())
@ -101,10 +105,6 @@ public class Interface {
return Attribute.iterableToString(excludedApplications); return Attribute.iterableToString(excludedApplications);
} }
public String[] getExcludedApplications() {
return excludedApplications.toArray(new String[excludedApplications.size()]);
}
public int getListenPort() { public int getListenPort() {
return listenPort; return listenPort;
} }

View File

@ -37,11 +37,11 @@ import java9.lang.Iterables;
public class Peer { public class Peer {
private final List<InetNetwork> allowedIPsList; private final List<InetNetwork> allowedIPsList;
private final Context context = Application.get();
@Nullable private InetEndpoint endpoint; @Nullable private InetEndpoint endpoint;
private int persistentKeepalive; private int persistentKeepalive;
@Nullable private String preSharedKey; @Nullable private String preSharedKey;
@Nullable private String publicKey; @Nullable private String publicKey;
private final Context context = Application.get();
public Peer() { public Peer() {
allowedIPsList = new ArrayList<>(); allowedIPsList = new ArrayList<>();
@ -201,13 +201,15 @@ public class Peer {
return new Observable[size]; return new Observable[size];
} }
}; };
private static final List<String> DEFAULT_ROUTE_MOD_RFC1918_V4 = Arrays.asList("0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0";
private final List<String> interfaceDNSRoutes = new ArrayList<>();
@Nullable private String allowedIPs; @Nullable private String allowedIPs;
@Nullable private String endpoint; @Nullable private String endpoint;
private int numSiblings;
@Nullable private String persistentKeepalive; @Nullable private String persistentKeepalive;
@Nullable private String preSharedKey; @Nullable private String preSharedKey;
@Nullable private String publicKey; @Nullable private String publicKey;
private final List<String> interfaceDNSRoutes = new ArrayList<>();
private int numSiblings;
public Observable(final Peer parent) { public Observable(final Peer parent) {
loadData(parent); loadData(parent);
@ -244,22 +246,9 @@ public class Peer {
return 0; return 0;
} }
private static final String DEFAULT_ROUTE_V4 = "0.0.0.0/0"; @Bindable @Nullable
private static final List<String> DEFAULT_ROUTE_MOD_RFC1918_V4 = Arrays.asList("0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"); public String getAllowedIPs() {
return allowedIPs;
public void toggleExcludePrivateIPs() {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean hasDefaultRoute = ips.contains(DEFAULT_ROUTE_V4);
final boolean hasDefaultRouteModRFC1918 = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0)
return;
Iterables.removeIf(ips, ip -> !ip.contains(":"));
if (hasDefaultRoute) {
ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.addAll(interfaceDNSRoutes);
} else if (hasDefaultRouteModRFC1918)
ips.add(DEFAULT_ROUTE_V4);
setAllowedIPs(Attribute.iterableToString(ips));
} }
@Bindable @Bindable
@ -268,21 +257,16 @@ public class Peer {
return numSiblings == 0 && (ips.contains(DEFAULT_ROUTE_V4) || ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4)); return numSiblings == 0 && (ips.contains(DEFAULT_ROUTE_V4) || ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4));
} }
@Bindable
public boolean getIsExcludePrivateIPsOn() {
return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
}
@Bindable @Nullable
public String getAllowedIPs() {
return allowedIPs;
}
@Bindable @Nullable @Bindable @Nullable
public String getEndpoint() { public String getEndpoint() {
return endpoint; return endpoint;
} }
@Bindable
public boolean getIsExcludePrivateIPsOn() {
return numSiblings == 0 && Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
}
@Bindable @Nullable @Bindable @Nullable
public String getPersistentKeepalive() { public String getPersistentKeepalive() {
return persistentKeepalive; return persistentKeepalive;
@ -318,21 +302,6 @@ public class Peer {
notifyPropertyChanged(BR.endpoint); notifyPropertyChanged(BR.endpoint);
} }
public void setPersistentKeepalive(final String persistentKeepalive) {
this.persistentKeepalive = persistentKeepalive;
notifyPropertyChanged(BR.persistentKeepalive);
}
public void setPreSharedKey(final String preSharedKey) {
this.preSharedKey = preSharedKey;
notifyPropertyChanged(BR.preSharedKey);
}
public void setPublicKey(final String publicKey) {
this.publicKey = publicKey;
notifyPropertyChanged(BR.publicKey);
}
public void setInterfaceDNSRoutes(@Nullable final String dnsServers) { public void setInterfaceDNSRoutes(@Nullable final String dnsServers) {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs))); final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4); final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
@ -354,6 +323,36 @@ public class Peer {
notifyPropertyChanged(BR.isExcludePrivateIPsOn); notifyPropertyChanged(BR.isExcludePrivateIPsOn);
} }
public void setPersistentKeepalive(final String persistentKeepalive) {
this.persistentKeepalive = persistentKeepalive;
notifyPropertyChanged(BR.persistentKeepalive);
}
public void setPreSharedKey(final String preSharedKey) {
this.preSharedKey = preSharedKey;
notifyPropertyChanged(BR.preSharedKey);
}
public void setPublicKey(final String publicKey) {
this.publicKey = publicKey;
notifyPropertyChanged(BR.publicKey);
}
public void toggleExcludePrivateIPs() {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean hasDefaultRoute = ips.contains(DEFAULT_ROUTE_V4);
final boolean hasDefaultRouteModRFC1918 = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0)
return;
Iterables.removeIf(ips, ip -> !ip.contains(":"));
if (hasDefaultRoute) {
ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.addAll(interfaceDNSRoutes);
} else if (hasDefaultRouteModRFC1918)
ips.add(DEFAULT_ROUTE_V4);
setAllowedIPs(Attribute.iterableToString(ips));
}
@Override @Override
public void writeToParcel(final Parcel dest, final int flags) { public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(allowedIPs); dest.writeString(allowedIPs);

View File

@ -22,4 +22,5 @@ import javax.annotation.meta.TypeQualifierDefault;
@Nonnull @Nonnull
@TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface NonNullForAll { } public @interface NonNullForAll {
}

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="24dp" android:width="24dp"
android:height="24dp"> android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path <path
android:fillColor="?android:attr/colorForeground" android:fillColor="?android:attr/colorForeground"
android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" /> android:pathData="M3 5L5 5 5 3C3.9 3 3 3.9 3 5Zm0 8l2 0 0 -2 -2 0 0 2zm4 8l2 0 0 -2 -2 0 0 2zM3 9L5 9 5 7 3 7 3 9Zm10 -6l-2 0 0 2 2 0 0 -2zm6 0l0 2 2 0C21 3.9 20.1 3 19 3ZM5 21L5 19 3 19c0 1.1 0.9 2 2 2zm-2 -4l2 0 0 -2 -2 0 0 2zM9 3L7 3 7 5 9 5 9 3Zm2 18l2 0 0 -2 -2 0 0 2zm8 -8l2 0 0 -2 -2 0 0 2zm0 8c1.1 0 2 -0.9 2 -2l-2 0 0 2zm0 -12l2 0 0 -2 -2 0 0 2zm0 8l2 0 0 -2 -2 0 0 2zm-4 4l2 0 0 -2 -2 0 0 2zm0 -16l2 0 0 -2 -2 0 0 2zM7 17L17 17 17 7 7 7 7 17Zm2 -8l6 0 0 6 -6 0 0 -6z" />

View File

@ -3,10 +3,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item> <item>
<selector> <selector>
<item app:state_multiselected="true" android:state_activated="true"> <item
android:state_activated="true"
app:state_multiselected="true">
<color android:color="?attr/colorControlActivated" /> <color android:color="?attr/colorControlActivated" />
</item> </item>
<item app:state_multiselected="false" android:state_activated="true"> <item
android:state_activated="true"
app:state_multiselected="false">
<color android:color="?attr/colorControlHighlight" /> <color android:color="?attr/colorControlHighlight" />
</item> </item>
</selector> </selector>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
@ -23,10 +22,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/list_item_background" android:background="@drawable/list_item_background"
android:padding="16dp"
android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}"> android:onClick="@{(view) -> item.setExcludedFromTunnel(!item.excludedFromTunnel)}"
android:orientation="horizontal"
android:padding="16dp">
<ImageView <ImageView
android:id="@+id/app_icon" android:id="@+id/app_icon"

View File

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
<import type="com.wireguard.android.widget.NameInputFilter" /> <import type="com.wireguard.android.widget.NameInputFilter" />
</data> </data>

View File

@ -100,8 +100,8 @@
app:filter="@{KeyInputFilter.newInstance()}" /> app:filter="@{KeyInputFilter.newInstance()}" />
<Button <Button
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:id="@+id/generate_private_key_button" android:id="@+id/generate_private_key_button"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="96dp" android:layout_width="96dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignBottom="@id/private_key_text" android:layout_alignBottom="@id/private_key_text"
@ -217,12 +217,12 @@
android:textAlignment="center" /> android:textAlignment="center" />
<Button <Button
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:id="@+id/set_excluded_applications" android:id="@+id/set_excluded_applications"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="-8dp"
android:layout_below="@+id/dns_servers_text" android:layout_below="@+id/dns_servers_text"
android:layout_marginLeft="-8dp"
android:onClick="@{fragment::onRequestSetExcludedApplications}" android:onClick="@{fragment::onRequestSetExcludedApplications}"
android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" /> android:text="@{@plurals/set_excluded_applications(config.interfaceSection.excludedApplicationsCount, config.interfaceSection.excludedApplicationsCount)}" />
</RelativeLayout> </RelativeLayout>

View File

@ -5,6 +5,7 @@
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
<import type="com.wireguard.android.widget.KeyInputFilter" /> <import type="com.wireguard.android.widget.KeyInputFilter" />
<variable <variable

View File

@ -30,44 +30,46 @@
android:id="@+id/tunnel_list" android:id="@+id/tunnel_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}"
android:clipToPadding="false"
android:choiceMode="multipleChoiceModal" android:choiceMode="multipleChoiceModal"
android:clipToPadding="false"
android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}"
android:visibility="@{tunnels.size() > 0 ? android.view.View.VISIBLE : android.view.View.GONE}" android:visibility="@{tunnels.size() > 0 ? android.view.View.VISIBLE : android.view.View.GONE}"
app:configurationHandler="@{rowConfigurationHandler}"
app:items="@{tunnels}" app:items="@{tunnels}"
app:layout="@{@layout/tunnel_list_item}" app:layout="@{@layout/tunnel_list_item}" />
app:configurationHandler="@{rowConfigurationHandler}" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}" android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}">
android:layout_gravity="center">
<android.support.v7.widget.AppCompatImageView <android.support.v7.widget.AppCompatImageView
android:id="@+id/logo_placeholder" android:id="@+id/logo_placeholder"
android:layout_width="140dp" android:layout_width="140dp"
android:layout_height="140dp" android:layout_height="140dp"
android:layout_gravity="center" android:layout_gravity="center"
android:alpha="0.3333333"
android:layout_marginTop="-70dp"
android:layout_marginBottom="20dp" android:layout_marginBottom="20dp"
android:layout_marginTop="-70dp"
android:alpha="0.3333333"
android:src="@mipmap/ic_launcher" /> android:src="@mipmap/ic_launcher" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:textSize="20sp" android:text="@string/tunnel_list_placeholder"
android:text="@string/tunnel_list_placeholder" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
<com.wireguard.android.widget.fab.FloatingActionsMenu <com.wireguard.android.widget.fab.FloatingActionsMenu
android:id="@+id/create_menu" android:id="@+id/create_menu"
android:clipChildren="false"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin" android:layout_margin="@dimen/fab_margin"
android:clipChildren="false"
app:fab_labelStyle="@style/fab_label" app:fab_labelStyle="@style/fab_label"
app:fab_labelsPosition="@integer/label_position" app:fab_labelsPosition="@integer/label_position"
app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior"> app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior">
@ -77,18 +79,18 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestImportConfig}" android:onClick="@{fragment::onRequestImportConfig}"
app:srcCompat="@drawable/ic_action_open_white"
app:fabSize="mini" app:fabSize="mini"
app:fab_title="@string/create_from_file" /> app:fab_title="@string/create_from_file"
app:srcCompat="@drawable/ic_action_open_white" />
<com.wireguard.android.widget.fab.LabeledFloatingActionButton <com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/scan_qr_code" android:id="@+id/scan_qr_code"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestScanQRCode}" android:onClick="@{fragment::onRequestScanQRCode}"
app:srcCompat="@drawable/ic_action_scan_qr_code_white"
app:fabSize="mini" app:fabSize="mini"
app:fab_title="@string/create_from_qrcode" /> app:fab_title="@string/create_from_qrcode"
app:srcCompat="@drawable/ic_action_scan_qr_code_white" />
<com.wireguard.android.widget.fab.LabeledFloatingActionButton <com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/create_empty" android:id="@+id/create_empty"
@ -96,8 +98,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestCreateConfig}" android:onClick="@{fragment::onRequestCreateConfig}"
app:fabSize="mini" app:fabSize="mini"
app:srcCompat="@drawable/ic_action_edit_white" app:fab_title="@string/create_empty"
app:fab_title="@string/create_empty" /> app:srcCompat="@drawable/ic_action_edit_white" />
</com.wireguard.android.widget.fab.FloatingActionsMenu> </com.wireguard.android.widget.fab.FloatingActionsMenu>
</android.support.design.widget.CoordinatorLayout> </android.support.design.widget.CoordinatorLayout>

View File

@ -5,7 +5,7 @@
android:id="@+id/menu_settings" android:id="@+id/menu_settings"
android:alphabeticShortcut="s" android:alphabeticShortcut="s"
android:icon="@drawable/ic_settings" android:icon="@drawable/ic_settings"
android:orderInCategory="1000"
android:title="@string/settings" android:title="@string/settings"
app:showAsAction="always" app:showAsAction="always" />
android:orderInCategory="1000" />
</menu> </menu>

View File

@ -12,7 +12,7 @@
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="dark_theme" android:key="dark_theme"
android:summaryOn="@string/dark_theme_summary_on"
android:summaryOff="@string/dark_theme_summary_off" android:summaryOff="@string/dark_theme_summary_off"
android:summaryOn="@string/dark_theme_summary_on"
android:title="@string/dark_theme_title" /> android:title="@string/dark_theme_title" />
</PreferenceScreen> </PreferenceScreen>