Compare commits

...

23 Commits

Author SHA1 Message Date
Jason A. Donenfeld ffb8bccbc5 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-18 18:40:16 +02:00
Jason A. Donenfeld 08170f7e55 TunnelListFragment: setOnScrollListener is old, but we support API 21, so we have to use it
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-18 18:32:42 +02:00
Jason A. Donenfeld 28d47b3470 AppListDialogFragment: getArguments is null before onCreate
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-18 18:08:08 +02:00
Jason A. Donenfeld 8e3586328c fab: use auto calculated fling threshold
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-18 02:32:05 +02:00
Jason A. Donenfeld f315654d40 fab: add fab sized padding at bottom of recycler view
This way we can keep scrolling when there are exactly the right number
of items, so that the toggle switch becomes visible.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-18 02:15:14 +02:00
Harsh Shandilya fdfab18d45 fab: make fab respond to recyclerview scroll events
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-18 02:15:06 +02:00
Harsh Shandilya d43e77867c fab: slide fab up when a snackbar shows
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-18 02:15:06 +02:00
Harsh Shandilya df03bdd7f9 android: QuickTileService: Do not use slashed icon on Android P
Android P features circle masked QS tiles which make use of colors
to denote STATE_ACTIVE/STATE_INACTIVE rather than a slash across
the drawable as seen on Android Oreo.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-16 17:31:14 +02:00
Jason A. Donenfeld ae5bf6fbb2 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-16 15:05:18 +02:00
Jason A. Donenfeld 04ff63f1b5 InetAddresses: unwrap reflection exceptions
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-16 15:05:18 +02:00
Harsh Shandilya 71cf39660f android: TunnelListFragment: Annotate parameter to match super method
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-16 03:26:02 +02:00
Jason A. Donenfeld 7364f2540e BaseFragment: do not allow tunnel to be null
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-13 04:13:35 +02:00
Jason A. Donenfeld 5d66f6b2e5 config: dns servers can be null
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-13 04:09:09 +02:00
Jason A. Donenfeld fec5fa8caf config: make loadData private
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-13 04:06:55 +02:00
Jason A. Donenfeld 6f48e138a4 TunnelEditorFragment: binding might be null
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-13 04:04:11 +02:00
Jason A. Donenfeld 21c15fe4ea QuickTileService: show intermediate state when changing
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-13 03:57:02 +02:00
Eric Kuck 67ea8b2936 global: Add nullity annotations
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-13 03:46:23 +02:00
Jason A. Donenfeld fbaa4d9ab1 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-12 02:14:25 +02:00
Harsh Shandilya 19b57c41b7 Address lint issues in TunnelListFragment
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2018-07-12 02:07:19 +02:00
Jason A. Donenfeld 26d762bc5c TunnelEditorFragment: add DNSes to allowedIPs when excluding rfc1918
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-12 02:04:52 +02:00
Jason A. Donenfeld eab0248aaa Clean up warnings
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-11 21:03:22 +02:00
Jason A. Donenfeld a5bbe171cb fab: remove asus hack
Let's hope Eric's changes make this no longer required.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-07-11 21:03:22 +02:00
Eric Kuck 5463086e75 fab: use support library's rendering
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
2018-07-11 21:03:22 +02:00
67 changed files with 811 additions and 974 deletions
+1
View File
@@ -15,3 +15,4 @@ build/
*.iml
*.jks
keystore.properties
package-info.java
+2
View File
@@ -94,6 +94,8 @@
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<arrangement>
<groups />
<rules>
+5 -2
View File
@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply from: 'nonnull.gradle'
// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
@@ -16,8 +17,8 @@ android {
applicationId 'com.wireguard.android'
minSdkVersion 21
targetSdkVersion 27
versionCode 430
versionName '0.0.20180710'
versionCode 434
versionName '0.0.20180718'
}
// If the keystore file exists
if (keystorePropertiesFile.exists()) {
@@ -56,6 +57,7 @@ ext {
databindingVersion = '3.1.3'
supportLibsVersion = '27.1.1'
streamsupportVersion = '1.6.0'
jsr305Version = '3.0.2'
}
dependencies {
@@ -67,6 +69,7 @@ dependencies {
implementation "com.android.support:support-annotations:$supportLibsVersion"
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
}
tasks.withType(JavaCompile) {
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
* SPDX-License-Identifier: Apache-2.0
*/
task generateNonNullJavaFiles(dependsOn: "assembleDebug", type: Copy) {
group = "Copying"
description = "Generate package-info.java classes"
def basePackage = "com" + File.separatorChar + "wireguard"
def mainSrcPhrase = "src" + File.separatorChar + "main" + File.separatorChar +
"java" + File.separatorChar
def mainTestSrcPhrase = "src" + File.separatorChar + "test" + File.separatorChar +
"java" + File.separatorChar
def mainAndroidTestSrcPhrase = "src" + File.separatorChar + "androidTest" + File.separatorChar +
"java" + File.separatorChar
def sourceDir = file( "${projectDir}" + File.separatorChar + "src" + File.separatorChar +
"main" + File.separatorChar + "java" + File.separatorChar +
basePackage )
def testSourceDir = file( "${projectDir}" + File.separatorChar + "src" + File.separatorChar +
"test" + File.separatorChar + "java" + File.separatorChar +
basePackage)
def androidTestSourceDir = file( "${projectDir}" + File.separatorChar + "src" + File
.separatorChar +
"androidTest" + File.separatorChar + "java" + File.separatorChar +
basePackage )
generateInfoFiles(sourceDir, mainSrcPhrase);
sourceDir.eachDirRecurse { dir ->
generateInfoFiles(dir, mainSrcPhrase)
}
if (file(testSourceDir).exists()) {
generateInfoFiles(testSourceDir, mainTestSrcPhrase);
testSourceDir.eachDirRecurse { dir ->
generateInfoFiles(dir, mainTestSrcPhrase)
}
}
if (file(androidTestSourceDir).exists()) {
generateInfoFiles(androidTestSourceDir, mainAndroidTestSrcPhrase);
androidTestSourceDir.eachDirRecurse { dir ->
generateInfoFiles(dir, mainAndroidTestSrcPhrase)
}
}
println "[SUCCESS] NonNull generator: package-info.java files checked"
}
private void generateInfoFiles(File dir, String mainSrcPhrase) {
def infoFileContentHeader = getFileContentHeader();
def infoFileContentFooter = getFileContentFooter();
def infoFilePath = dir.getAbsolutePath() + File.separatorChar + "package-info.java"
//file(infoFilePath).delete(); //do not use in production code
if (!file(infoFilePath).exists()) {
def infoFileContentPackage = getFileContentPackage(dir.getAbsolutePath(), mainSrcPhrase);
new File(infoFilePath).write(infoFileContentHeader +
infoFileContentPackage + infoFileContentFooter)
println "[dir] " + infoFilePath + " created";
}
}
def getFileContentPackage(String path, String mainSrcPhrase) {
def mainSrcPhraseIndex = path.indexOf(mainSrcPhrase)
def output = path.substring(mainSrcPhraseIndex)
// Win hotfix
if (System.properties['os.name'].toLowerCase().contains('windows')) {
output = output.replace("\\", "/")
mainSrcPhrase = mainSrcPhrase.replace("\\", "/")
}
return "package " + output.replaceAll(mainSrcPhrase, "").replaceAll(
"/", ".") + ";\n"
}
def getFileContentHeader() {
return "/**\n" +
" * Make all method parameters @NonNull by default.\n" +
" */\n" +
"@NonNullForAll\n"
}
def getFileContentFooter() {
return "\n" +
"import com.wireguard.util.NonNullForAll;\n"
}
@@ -10,6 +10,7 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatDelegate;
import com.wireguard.android.backend.Backend;
@@ -26,18 +27,19 @@ import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Executor;
public class Application extends android.app.Application {
private static WeakReference<Application> weakSelf;
private AsyncWorker asyncWorker;
private Backend backend;
private RootShell rootShell;
private SharedPreferences sharedPreferences;
private ToolsInstaller toolsInstaller;
private TunnelManager tunnelManager;
private Handler handler;
private Collection<BackendCallback> haveBackendCallbacks = new ArrayList<>();
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
@SuppressWarnings("NullableProblems") private RootShell rootShell;
@SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
@SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
@SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
@SuppressWarnings("NullableProblems") private Handler handler;
@Nullable private Backend backend;
@Nullable private Collection<BackendCallback> haveBackendCallbacks = new ArrayList<>();
private final Object haveBackendCallbacksLock = new Object();
public Application() {
@@ -65,9 +67,11 @@ public class Application extends android.app.Application {
if (app.backend == null)
app.backend = new GoBackend(app.getApplicationContext());
synchronized (app.haveBackendCallbacksLock) {
for (final BackendCallback callback : app.haveBackendCallbacks)
app.handler.post(() -> callback.callback(app.backend));
app.haveBackendCallbacks = null;
if (app.haveBackendCallbacks != null) {
for (final BackendCallback callback : app.haveBackendCallbacks)
app.handler.post(() -> callback.callback(app.backend));
app.haveBackendCallbacks = null;
}
}
}
return app.backend;
@@ -82,10 +86,12 @@ public class Application extends android.app.Application {
public static void onHaveBackend(final BackendCallback callback) {
final Application app = get();
synchronized (app.haveBackendCallbacksLock) {
if (app.haveBackendCallbacks == null)
if (app.haveBackendCallbacks == null) {
Objects.requireNonNull(app.backend, "Backend still null in onHaveBackend call");
callback.callback(app.backend);
else
} else {
app.haveBackendCallbacks.add(callback);
}
}
}
@@ -12,12 +12,11 @@ import android.databinding.Observable;
import android.databinding.Observable.OnPropertyChangedCallback;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;
@@ -41,10 +40,11 @@ public class QuickTileService extends TileService {
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
private Tunnel tunnel;
private Icon iconOn;
private Icon iconOff;
@Nullable private Tunnel tunnel;
@Nullable private Icon iconOn;
@Nullable private Icon iconOff;
@SuppressWarnings("deprecation")
@Override
public void onCreate() {
final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile));
@@ -55,17 +55,25 @@ public class QuickTileService extends TileService {
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
icon.draw(c);
iconOn = Icon.createWithBitmap(b);
icon.setSlashed(true);
b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
c = new Canvas(b);
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
icon.draw(c);
iconOff = Icon.createWithBitmap(b);
/* TODO(msf): Change this to an explicit test for P when we start targetting SDK 28 */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
iconOff = iconOn;
} else {
icon.setSlashed(true);
b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
c = new Canvas(b);
icon.setBounds(0, 0, c.getWidth(), c.getHeight());
icon.draw(c);
iconOff = Icon.createWithBitmap(b);
}
}
@Override
public void onClick() {
if (tunnel != null) {
final Tile tile = getQsTile();
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);
@@ -90,7 +98,7 @@ public class QuickTileService extends TileService {
}
private void onToggleFinished(@SuppressWarnings("unused") final State state,
final Throwable throwable) {
@Nullable final Throwable throwable) {
if (throwable == null)
return;
final String error = ExceptionLoggers.unwrapMessage(throwable);
@@ -9,6 +9,7 @@ package com.wireguard.android.activity;
import android.databinding.CallbackRegistry;
import android.databinding.CallbackRegistry.NotifierCallback;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.wireguard.android.Application;
import com.wireguard.android.model.Tunnel;
@@ -24,19 +25,19 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
private static final String KEY_SELECTED_TUNNEL = "selected_tunnel";
private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry();
private Tunnel selectedTunnel;
@Nullable private Tunnel selectedTunnel;
public void addOnSelectedTunnelChangedListener(
final OnSelectedTunnelChangedListener listener) {
public void addOnSelectedTunnelChangedListener(final OnSelectedTunnelChangedListener listener) {
selectionChangeRegistry.add(listener);
}
@Nullable
public Tunnel getSelectedTunnel() {
return selectedTunnel;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
protected void onCreate(@Nullable final Bundle savedInstanceState) {
// Restore the saved tunnel if there is one; otherwise grab it from the arguments.
String savedTunnelName = null;
if (savedInstanceState != null)
@@ -59,14 +60,14 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
super.onSaveInstanceState(outState);
}
protected abstract void onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
protected abstract void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel);
public void removeOnSelectedTunnelChangedListener(
final OnSelectedTunnelChangedListener listener) {
selectionChangeRegistry.remove(listener);
}
public void setSelectedTunnel(final Tunnel tunnel) {
public void setSelectedTunnel(@Nullable final Tunnel tunnel) {
final Tunnel oldTunnel = selectedTunnel;
if (Objects.equals(oldTunnel, tunnel))
return;
@@ -76,7 +77,7 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
}
public interface OnSelectedTunnelChangedListener {
void onSelectedTunnelChanged(Tunnel oldTunnel, Tunnel newTunnel);
void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel);
}
private static final class SelectionChangeNotifier
@@ -9,6 +9,7 @@ package com.wireguard.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@@ -22,6 +23,8 @@ import com.wireguard.android.fragment.TunnelEditorFragment;
import com.wireguard.android.fragment.TunnelListFragment;
import com.wireguard.android.model.Tunnel;
import java.util.List;
import java9.util.stream.Stream;
/**
@@ -33,6 +36,7 @@ import java9.util.stream.Stream;
public class MainActivity extends BaseActivity {
private static final String KEY_STATE = "fragment_state";
private static final String TAG = "WireGuard/" + MainActivity.class.getSimpleName();
private State state = State.EMPTY;
private boolean moveToState(final State nextState) {
@@ -70,13 +74,19 @@ public class MainActivity extends BaseActivity {
@Override
public void onBackPressed() {
TunnelListFragment fragment = null;
try {
fragment = ((TunnelListFragment) getSupportFragmentManager().getFragments().get(0));
} catch (final ClassCastException ignored) { }
if (fragment == null || !fragment.collapseActionMenu()) {
if (!moveToState(State.ofLayer(state.layer - 1)))
super.onBackPressed();
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
boolean handled = false;
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
handled = ((TunnelListFragment) fragments.get(0)).collapseActionMenu();
}
if (!handled) {
handled = moveToState(State.ofLayer(state.layer - 1));
}
if (!handled) {
super.onBackPressed();
}
}
@@ -84,7 +94,7 @@ public class MainActivity extends BaseActivity {
// calling View#performClick defeats the purpose of it.
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(final Bundle savedInstanceState) {
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null)
@@ -99,9 +109,10 @@ public class MainActivity extends BaseActivity {
final int actionBarId = getResources().getIdentifier("action_bar", "id", getPackageName());
if (actionBarId != 0 && findViewById(actionBarId) != null) {
findViewById(actionBarId).setOnTouchListener((v, e) -> {
try {
((TunnelListFragment) getSupportFragmentManager().getFragments().get(0)).collapseActionMenu();
} catch (final ClassCastException ignored) { }
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
((TunnelListFragment) fragments.get(0)).collapseActionMenu();
}
return false;
});
}
@@ -142,7 +153,7 @@ public class MainActivity extends BaseActivity {
}
@Override
protected void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
moveToState(newTunnel != null ? State.DETAIL : State.LIST);
}
@@ -157,10 +168,10 @@ public class MainActivity extends BaseActivity {
DETAIL(TunnelDetailFragment.class, 2),
EDITOR(TunnelEditorFragment.class, 3);
private final String fragment;
@Nullable private final String fragment;
private final int layer;
State(final Class<? extends Fragment> fragment, final int layer) {
State(@Nullable final Class<? extends Fragment> fragment, final int layer) {
this.fragment = fragment != null ? fragment.getName() : null;
this.layer = layer;
}
@@ -8,12 +8,13 @@ package com.wireguard.android.activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import android.util.SparseArray;
import android.view.MenuItem;
import com.wireguard.android.Application;
@@ -22,16 +23,14 @@ import com.wireguard.android.backend.WgQuickBackend;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Interface for changing application-global persistent settings.
*/
public class SettingsActivity extends ThemeChangeAwareActivity {
private final Map<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>();
private final SparseArray<PermissionRequestCallback> permissionRequestCallbacks = new SparseArray<>();
private int permissionRequestCounter;
public void ensurePermissions(final String[] permissions, final PermissionRequestCallback cb) {
@@ -54,7 +53,7 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
getSupportFragmentManager().beginTransaction()
@@ -76,8 +75,8 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
@Override
public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
final String[] permissions,
final int[] grantResults) {
final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode);
if (f != null) {
permissionRequestCallbacks.remove(requestCode);
@@ -8,6 +8,7 @@ package com.wireguard.android.activity;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.util.Log;
@@ -19,7 +20,7 @@ import java.lang.reflect.Field;
public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName();
private static Resources lastResources;
@Nullable private static Resources lastResources;
private static boolean lastDarkMode;
private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) {
if (resources == lastResources && darkMode == lastDarkMode)
@@ -51,7 +52,7 @@ public abstract class ThemeChangeAwareActivity extends AppCompatActivity impleme
@Override
protected void onCreate(final Bundle savedInstanceState) {
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@@ -7,6 +7,7 @@
package com.wireguard.android.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.wireguard.android.fragment.TunnelEditorFragment;
import com.wireguard.android.model.Tunnel;
@@ -18,7 +19,7 @@ import com.wireguard.android.model.Tunnel;
public class TunnelCreatorActivity extends BaseActivity {
@Override
@SuppressWarnings("UnnecessaryFullyQualifiedName")
protected void onCreate(final Bundle savedInstanceState) {
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
getSupportFragmentManager().beginTransaction()
@@ -28,7 +29,7 @@ public class TunnelCreatorActivity extends BaseActivity {
}
@Override
protected void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
finish();
}
}
@@ -10,6 +10,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelFileDescriptor;
import android.support.annotation.Nullable;
import android.support.v4.util.ArraySet;
import android.util.Log;
@@ -29,6 +30,7 @@ import com.wireguard.crypto.KeyEncoding;
import java.net.InetAddress;
import java.util.Collections;
import java.util.Formatter;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -40,7 +42,7 @@ public final class GoBackend implements Backend {
private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
private final Context context;
private Tunnel currentTunnel;
@Nullable private Tunnel currentTunnel;
private int currentTunnelHandle = -1;
public GoBackend(final Context context) {
@@ -114,12 +116,14 @@ public final class GoBackend implements Backend {
return getState(tunnel);
}
private void setStateInternal(final Tunnel tunnel, final Config config, final State state)
private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
throws Exception {
if (state == State.UP) {
Log.i(TAG, "Bringing tunnel up");
Objects.requireNonNull(config, "Trying to bring up a tunnel with no config");
if (VpnService.prepare(context) != null)
throw new Exception("VPN service not authorized by user");
@@ -245,7 +249,7 @@ public final class GoBackend implements Backend {
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
vpnService.complete(this);
if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) {
Log.d(TAG, "Service started by Always-on VPN feature");
@@ -7,6 +7,7 @@
package com.wireguard.android.backend;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import com.wireguard.android.Application;
@@ -21,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java9.util.stream.Collectors;
@@ -106,8 +108,9 @@ public final class WgQuickBackend implements Backend {
return getState(tunnel);
}
private void setStateInternal(final Tunnel tunnel, final Config config, final State state)
throws Exception {
private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception {
Objects.requireNonNull(config, "Trying to set state with a null config");
final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
stream.write(config.toString().getBytes(StandardCharsets.UTF_8));
@@ -9,6 +9,7 @@ package com.wireguard.android.databinding;
import android.databinding.DataBindingUtil;
import android.databinding.ObservableList;
import android.databinding.ViewDataBinding;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -16,6 +17,7 @@ import android.view.ViewGroup;
import com.wireguard.android.BR;
import java.lang.ref.WeakReference;
import java.util.Objects;
/**
* Helper class for binding an ObservableList to the children of a ViewGroup.
@@ -26,7 +28,7 @@ class ItemChangeListener<T> {
private final ViewGroup container;
private final int layoutId;
private final LayoutInflater layoutInflater;
private ObservableList<T> list;
@Nullable private ObservableList<T> list;
ItemChangeListener(final ViewGroup container, final int layoutId) {
this.container = container;
@@ -34,17 +36,21 @@ class ItemChangeListener<T> {
layoutInflater = LayoutInflater.from(container.getContext());
}
private View getView(final int position, final View convertView) {
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
if (binding == null)
private View getView(final int position, @Nullable final View convertView) {
ViewDataBinding binding = convertView != null ? DataBindingUtil.getBinding(convertView) : null;
if (binding == null) {
binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false);
}
Objects.requireNonNull(list, "Trying to get a view while list is still null");
binding.setVariable(BR.collection, list);
binding.setVariable(BR.item, list.get(position));
binding.executePendingBindings();
return binding.getRoot();
}
void setList(final ObservableList<T> newList) {
void setList(@Nullable final ObservableList<T> newList) {
if (list != null)
list.removeOnListChangedCallback(callback);
list = newList;
@@ -10,7 +10,7 @@ import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.ObservableList;
import android.databinding.ViewDataBinding;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
@@ -31,8 +31,8 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
private final int layoutId;
private final LayoutInflater layoutInflater;
private ObservableKeyedList<K, E> list;
private RowConfigurationHandler rowConfigurationHandler;
@Nullable private ObservableKeyedList<K, E> list;
@Nullable private RowConfigurationHandler rowConfigurationHandler;
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
final ObservableKeyedList<K, E> list) {
@@ -46,6 +46,7 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
return list != null ? list.size() : 0;
}
@Nullable
private E getItem(final int position) {
if (list == null || position < 0 || position >= list.size())
return null;
@@ -58,30 +59,34 @@ public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>>
return key != null ? key.hashCode() : -1;
}
@Nullable
private K getKey(final int position) {
final E item = getItem(position);
return item != null ? item.getKey() : null;
}
@NonNull @Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.binding.setVariable(BR.collection, list);
holder.binding.setVariable(BR.key, getKey(position));
holder.binding.setVariable(BR.item, getItem(position));
holder.binding.executePendingBindings();
if (rowConfigurationHandler != null) {
rowConfigurationHandler.onConfigureRow(holder.binding, getItem(position), position);
E item = getItem(position);
if (item != null) {
rowConfigurationHandler.onConfigureRow(holder.binding, item, position);
}
}
}
void setList(final ObservableKeyedList<K, E> newList) {
void setList(@Nullable final ObservableKeyedList<K, E> newList) {
if (list != null)
list.removeOnListChangedCallback(callback);
list = newList;
@@ -58,17 +58,12 @@ public class AppListDialogFragment extends DialogFragment {
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
}
@Override
public void onAttach(final Context context) {
super.onAttach(context);
}
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setTitle(R.string.excluded_applications);
AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
binding.executePendingBindings();
alertDialogBuilder.setView(binding.getRoot());
@@ -10,7 +10,7 @@ import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.util.Log;
@@ -37,10 +37,11 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
private BaseActivity activity;
private Tunnel pendingTunnel;
private Boolean pendingTunnelUp;
@Nullable private BaseActivity activity;
@Nullable private Tunnel pendingTunnel;
@Nullable private Boolean pendingTunnelUp;
@Nullable
protected Tunnel getSelectedTunnel() {
return activity != null ? activity.getSelectedTunnel() : null;
}
@@ -65,7 +66,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
@@ -76,7 +77,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
}
}
protected void setSelectedTunnel(final Tunnel tunnel) {
protected void setSelectedTunnel(@Nullable final Tunnel tunnel) {
if (activity != null)
activity.setSelectedTunnel(tunnel);
}
@@ -90,6 +91,8 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
tunnel = ((TunnelListItemBinding) binding).getItem();
else
return;
if (tunnel == null)
return;
Application.onHaveBackend(backend -> {
if (backend instanceof GoBackend) {
@@ -106,7 +109,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
});
}
private void setTunnelStateWithPermissionsResult(@NonNull final Tunnel tunnel, final boolean checked) {
private void setTunnelStateWithPermissionsResult(final Tunnel tunnel, final boolean checked) {
tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
if (throwable == null)
return;
@@ -7,7 +7,7 @@
package com.wireguard.android.fragment;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -24,14 +24,16 @@ import com.wireguard.config.Config;
*/
public class TunnelDetailFragment extends BaseFragment {
private TunnelDetailFragmentBinding binding;
@Nullable private TunnelDetailFragmentBinding binding;
private void onConfigLoaded(final String name, final Config config) {
binding.setConfig(new Config.Observable(config, name));
if (binding != null) {
binding.setConfig(new Config.Observable(config, name));
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@@ -42,8 +44,8 @@ public class TunnelDetailFragment extends BaseFragment {
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelDetailFragmentBinding.inflate(inflater, container, false);
binding.executePendingBindings();
@@ -57,7 +59,7 @@ public class TunnelDetailFragment extends BaseFragment {
}
@Override
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
if (binding == null)
return;
binding.setTunnel(newTunnel);
@@ -68,7 +70,11 @@ public class TunnelDetailFragment extends BaseFragment {
}
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
if (binding == null) {
return;
}
binding.setFragment(this);
onSelectedTunnelChanged(null, getSelectedTunnel());
super.onViewStateRestored(savedInstanceState);
@@ -8,8 +8,10 @@ package com.wireguard.android.fragment;
import android.app.Activity;
import android.content.Context;
import android.databinding.Observable;
import android.databinding.ObservableList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.FragmentManager;
import android.util.Log;
@@ -23,6 +25,7 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import com.wireguard.android.Application;
import com.wireguard.android.BR;
import com.wireguard.android.R;
import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
@@ -31,8 +34,10 @@ import com.wireguard.android.model.TunnelManager;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.config.Attribute;
import com.wireguard.config.Config;
import com.wireguard.config.Peer;
import java.util.List;
import java.util.Objects;
/**
* Fragment for editing a WireGuard configuration.
@@ -43,15 +48,17 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
private static final String KEY_ORIGINAL_NAME = "original_name";
private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
private TunnelEditorFragmentBinding binding;
private Tunnel tunnel;
@Nullable private TunnelEditorFragmentBinding binding;
@Nullable private Tunnel tunnel;
private void onConfigLoaded(final String name, final Config config) {
binding.setConfig(new Config.Observable(config, name));
if (binding != null) {
binding.setConfig(new Config.Observable(config, name));
}
}
private void onConfigSaved(final Tunnel savedTunnel,
final Throwable throwable) {
@Nullable final Throwable throwable) {
final String message;
if (throwable == null) {
message = getString(R.string.config_save_success, savedTunnel.getName());
@@ -69,7 +76,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
}
@Override
public void onCreate(final Bundle savedInstanceState) {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@@ -79,11 +86,54 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
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) {
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
@Override
public void onItemRangeRemoved(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
}
};
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);
config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
} 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
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
binding.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
binding.executePendingBindings();
return binding.getRoot();
}
@@ -152,14 +202,14 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
public void onSaveInstanceState(final Bundle outState) {
outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName());
super.onSaveInstanceState(outState);
}
@Override
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
tunnel = newTunnel;
if (binding == null)
return;
@@ -168,7 +218,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
tunnel.getConfigAsync().thenAccept(a -> onConfigLoaded(tunnel.getName(), a));
}
private void onTunnelCreated(final Tunnel newTunnel, final Throwable throwable) {
private void onTunnelCreated(final Tunnel newTunnel, @Nullable final Throwable throwable) {
final String message;
if (throwable == null) {
tunnel = newTunnel;
@@ -187,7 +237,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
}
private void onTunnelRenamed(final Tunnel renamedTunnel, final Config newConfig,
final Throwable throwable) {
@Nullable final Throwable throwable) {
final String message;
if (throwable == null) {
message = getString(R.string.tunnel_rename_success, renamedTunnel.getName());
@@ -206,7 +256,11 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
}
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
if (binding == null) {
return;
}
binding.setFragment(this);
if (savedInstanceState == null) {
@@ -226,15 +280,16 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
final FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
if (fragmentManager != null && binding != null) {
final String[] excludedApps = Attribute.stringToList(binding.getConfig().getInterfaceSection().getExcludedApplications());
final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
fragment.show(getFragmentManager(), null);
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));
}
@@ -26,6 +26,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import com.wireguard.android.Application;
import com.wireguard.android.R;
@@ -35,7 +37,7 @@ import com.wireguard.android.databinding.TunnelListFragmentBinding;
import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.widget.ToggleSwitch;
import com.wireguard.android.widget.CustomRecyclerViewScrollListener;
import com.wireguard.config.Config;
import java.io.BufferedReader;
@@ -46,7 +48,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -62,20 +63,20 @@ public class TunnelListFragment extends BaseFragment {
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
private final ActionModeListener actionModeListener = new ActionModeListener();
private ActionMode actionMode;
private TunnelListFragmentBinding binding;
@Nullable private ActionMode actionMode;
@Nullable private TunnelListFragmentBinding binding;
public boolean collapseActionMenu() {
if (binding.createMenu.isExpanded()) {
if (binding != null && binding.createMenu.isExpanded()) {
binding.createMenu.collapse();
return true;
}
return false;
}
private void importTunnel(final Uri uri) {
private void importTunnel(@Nullable final Uri uri) {
final Activity activity = getActivity();
if (activity == null)
if (activity == null || uri == null)
return;
final ContentResolver contentResolver = activity.getContentResolver();
@@ -167,10 +168,10 @@ public class TunnelListFragment extends BaseFragment {
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
switch (requestCode) {
case REQUEST_IMPORT:
if (resultCode == Activity.RESULT_OK)
if (resultCode == Activity.RESULT_OK && data != null)
importTunnel(data.getData());
return;
default:
@@ -178,10 +179,11 @@ public class TunnelListFragment extends BaseFragment {
}
}
@SuppressWarnings("deprecation")
@SuppressLint("ClickableViewAccessibility")
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
@@ -191,6 +193,23 @@ public class TunnelListFragment extends BaseFragment {
}
return false;
});
binding.tunnelList.setOnScrollListener(new CustomRecyclerViewScrollListener() {
@Override
public void show() {
binding.createMenu.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(2))
.start();
}
@Override
public void hide() {
binding.createMenu.animate()
.translationY(binding.createMenu.getHeight() + getResources().getDimension(R.dimen.fab_margin))
.setInterpolator(new AccelerateInterpolator(2))
.start();
}
});
binding.executePendingBindings();
return binding.getRoot();
}
@@ -218,16 +237,18 @@ public class TunnelListFragment extends BaseFragment {
@Override
public void onPause() {
binding.createMenu.collapse();
if (binding != null) {
binding.createMenu.collapse();
}
super.onPause();
}
@Override
public void onSelectedTunnelChanged(final Tunnel oldTunnel, final Tunnel newTunnel) {
public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
// Do nothing.
}
private void onTunnelDeletionFinished(final Integer count, final Throwable throwable) {
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
final String message;
if (throwable == null) {
message = getResources().getQuantityString(R.plurals.delete_success, count, count);
@@ -267,8 +288,13 @@ public class TunnelListFragment extends BaseFragment {
}
@Override
public void onViewStateRestored(final Bundle savedInstanceState) {
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (binding == null) {
return;
}
binding.setFragment(this);
binding.setTunnels(Application.getTunnelManager().getTunnels());
binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
@@ -290,16 +316,16 @@ public class TunnelListFragment extends BaseFragment {
}
private final class ActionModeListener implements ActionMode.Callback {
private final Set<Integer> checkedItems = new HashSet<>();
private final Collection<Integer> checkedItems = new HashSet<>();
private Resources resources;
@Nullable private Resources resources;
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_action_delete:
List<Tunnel> tunnelsToDelete = new ArrayList<>();
for (Integer position : checkedItems) {
final Collection<Tunnel> tunnelsToDelete = new ArrayList<>();
for (final Integer position : checkedItems) {
tunnelsToDelete.add(Application.getTunnelManager().getTunnels().get(position));
}
@@ -342,11 +368,11 @@ public class TunnelListFragment extends BaseFragment {
binding.tunnelList.getAdapter().notifyDataSetChanged();
}
void toggleItemChecked(int position) {
void toggleItemChecked(final int position) {
setItemChecked(position, !checkedItems.contains(position));
}
void setItemChecked(int position, boolean checked) {
void setItemChecked(final int position, final boolean checked) {
if (checked) {
checkedItems.add(position);
} else {
@@ -359,7 +385,9 @@ public class TunnelListFragment extends BaseFragment {
actionMode.finish();
}
binding.tunnelList.getAdapter().notifyItemChanged(position);
if (binding != null) {
binding.tunnelList.getAdapter().notifyItemChanged(position);
}
updateTitle(actionMode);
}
@@ -9,36 +9,32 @@ package com.wireguard.android.model;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import com.wireguard.android.BR;
import com.wireguard.util.Keyed;
public class ApplicationData extends BaseObservable implements Keyed<String> {
@NonNull private final Drawable icon;
@NonNull private final String name;
@NonNull private final String packageName;
private final Drawable icon;
private final String name;
private final String packageName;
private boolean excludedFromTunnel;
public ApplicationData(@NonNull final Drawable icon, @NonNull final String name, @NonNull final String packageName, final boolean excludedFromTunnel) {
public ApplicationData(final Drawable icon, final String name, final String packageName, final boolean excludedFromTunnel) {
this.icon = icon;
this.name = name;
this.packageName = packageName;
this.excludedFromTunnel = excludedFromTunnel;
}
@NonNull
public Drawable getIcon() {
return icon;
}
@NonNull
public String getName() {
return name;
}
@NonNull
public String getPackageName() {
return packageName;
}
@@ -8,13 +8,12 @@ package com.wireguard.android.model;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.wireguard.android.BR;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.util.Keyed;
import com.wireguard.config.Config;
import com.wireguard.util.Keyed;
import java.util.regex.Pattern;
@@ -30,20 +29,20 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
private final TunnelManager manager;
private Config config;
@Nullable private Config config;
private String name;
private State state;
private Statistics statistics;
@Nullable private Statistics statistics;
Tunnel(@NonNull final TunnelManager manager, @NonNull final String name,
@Nullable final Config config, @NonNull final State state) {
Tunnel(final TunnelManager manager, final String name,
@Nullable final Config config, final State state) {
this.manager = manager;
this.name = name;
this.config = config;
this.state = state;
}
public static boolean isNameInvalid(@NonNull final CharSequence name) {
public static boolean isNameInvalid(final CharSequence name) {
return !NAME_PATTERN.matcher(name).matches();
}
@@ -51,7 +50,7 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return manager.delete(this);
}
@Bindable
@Bindable @Nullable
public Config getConfig() {
if (config == null)
manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E);
@@ -83,7 +82,7 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return TunnelManager.getTunnelState(this);
}
@Bindable
@Bindable @Nullable
public Statistics getStatistics() {
// FIXME: Check age of statistics.
if (statistics == null)
@@ -118,25 +117,26 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
return state;
}
Statistics onStatisticsChanged(final Statistics statistics) {
@Nullable
Statistics onStatisticsChanged(@Nullable final Statistics statistics) {
this.statistics = statistics;
notifyPropertyChanged(BR.statistics);
return statistics;
}
public CompletionStage<Config> setConfig(@NonNull final Config config) {
public CompletionStage<Config> setConfig(final Config config) {
if (!config.equals(this.config))
return manager.setTunnelConfig(this, config);
return CompletableFuture.completedFuture(this.config);
}
public CompletionStage<String> setName(@NonNull final String name) {
public CompletionStage<String> setName(final String name) {
if (!name.equals(this.name))
return manager.setTunnelName(this, name);
return CompletableFuture.completedFuture(this.name);
}
public CompletionStage<State> setState(@NonNull final State state) {
public CompletionStage<State> setState(final State state) {
if (state != this.state)
return manager.setTunnelState(this, state);
return CompletableFuture.completedFuture(this.state);
@@ -152,6 +152,5 @@ public class Tunnel extends BaseObservable implements Keyed<String> {
}
}
public static class Statistics extends BaseObservable {
}
public static class Statistics extends BaseObservable { }
}
@@ -11,7 +11,7 @@ import android.content.Context;
import android.content.Intent;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.wireguard.android.Application;
import com.wireguard.android.BR;
@@ -47,9 +47,8 @@ public final class TunnelManager extends BaseObservable {
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
private final ConfigStore configStore;
private final ObservableSortedKeyedList<String, Tunnel> tunnels =
new ObservableSortedKeyedArrayList<>(COMPARATOR);
private Tunnel lastUsedTunnel;
private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
@Nullable private Tunnel lastUsedTunnel;
private boolean haveLoaded;
private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
@@ -57,13 +56,13 @@ public final class TunnelManager extends BaseObservable {
this.configStore = configStore;
}
private Tunnel addToList(final String name, 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);
tunnels.add(tunnel);
return tunnel;
}
public CompletionStage<Tunnel> create(@NonNull final String name, final Config config) {
public CompletionStage<Tunnel> create(final String name, @Nullable final Config config) {
if (Tunnel.isNameInvalid(name))
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
if (tunnels.containsKey(name)) {
@@ -102,7 +101,7 @@ public final class TunnelManager extends BaseObservable {
});
}
@Bindable
@Bindable @Nullable
public Tunnel getLastUsedTunnel() {
return lastUsedTunnel;
}
@@ -191,7 +190,7 @@ public final class TunnelManager extends BaseObservable {
Application.getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply();
}
private void setLastUsedTunnel(final Tunnel tunnel) {
private void setLastUsedTunnel(@Nullable final Tunnel tunnel) {
if (tunnel == lastUsedTunnel)
return;
lastUsedTunnel = tunnel;
@@ -256,7 +255,7 @@ public final class TunnelManager extends BaseObservable {
public static final class IntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
public void onReceive(final Context context, @Nullable final Intent intent) {
final TunnelManager manager = Application.getTunnelManager();
if (intent == null)
return;
@@ -10,6 +10,7 @@ import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
@@ -33,7 +34,7 @@ import java.io.InputStreamReader;
public class LogExporterPreference extends Preference {
private static final String TAG = "WireGuard/" + LogExporterPreference.class.getSimpleName();
private String exportedFilePath;
@Nullable private String exportedFilePath;
public LogExporterPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
@@ -73,7 +74,7 @@ public class LogExporterPreference extends Preference {
}).whenComplete(this::exportLogComplete);
}
private void exportLogComplete(final String filePath, final Throwable throwable) {
private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) {
if (throwable != null) {
final String error = ExceptionLoggers.unwrapMessage(throwable);
final String message = getContext().getString(R.string.log_export_error, error);
@@ -7,7 +7,7 @@
package com.wireguard.android.preference;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
@@ -43,7 +43,7 @@ public class ToolsInstallerPreference extends Preference {
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult);
}
private void onCheckResult(final int state, final Throwable throwable) {
private void onCheckResult(final int state, @Nullable final Throwable throwable) {
if (throwable != null || state == ToolsInstaller.ERROR)
setState(State.INITIAL);
else if ((state & ToolsInstaller.YES) == ToolsInstaller.YES)
@@ -62,7 +62,7 @@ public class ToolsInstallerPreference extends Preference {
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult);
}
private void onInstallResult(final Integer result, final Throwable throwable) {
private void onInstallResult(final Integer result, @Nullable final Throwable throwable) {
if (throwable != null)
setState(State.FAILURE);
else if ((result & (ToolsInstaller.YES | ToolsInstaller.MAGISK)) == (ToolsInstaller.YES | ToolsInstaller.MAGISK))
@@ -73,7 +73,7 @@ public class ToolsInstallerPreference extends Preference {
setState(State.FAILURE);
}
private void setState(@NonNull final State state) {
private void setState(final State state) {
if (this.state == state)
return;
this.state = state;
@@ -9,6 +9,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
@@ -17,7 +18,7 @@ import com.wireguard.android.BuildConfig;
import com.wireguard.android.R;
public class VersionPreference extends Preference {
private String versionSummary;
@Nullable private String versionSummary;
public VersionPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
@@ -33,7 +34,7 @@ public class VersionPreference extends Preference {
});
}
@Override
@Override @Nullable
public CharSequence getSummary() {
return versionSummary;
}
@@ -10,6 +10,7 @@ import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
@@ -40,7 +41,7 @@ import java9.util.concurrent.CompletableFuture;
public class ZipExporterPreference extends Preference {
private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
private String exportedFilePath;
@Nullable private String exportedFilePath;
public ZipExporterPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
@@ -79,7 +80,7 @@ public class ZipExporterPreference extends Preference {
}).whenComplete(this::exportZipComplete));
}
private void exportZipComplete(final String filePath, final Throwable throwable) {
private void exportZipComplete(@Nullable final String filePath, @Nullable final Throwable throwable) {
if (throwable != null) {
final String error = ExceptionLoggers.unwrapMessage(throwable);
final String message = getContext().getString(R.string.zip_export_error, error);
@@ -6,8 +6,9 @@
package com.wireguard.android.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java9.util.concurrent.CompletionException;
import java9.util.function.BiConsumer;
@@ -34,7 +35,6 @@ public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
return throwable;
}
@NonNull
public static String unwrapMessage(Throwable throwable) {
throwable = unwrap(throwable);
final String message = throwable.getMessage();
@@ -44,7 +44,7 @@ public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
}
@Override
public void accept(final Object result, final Throwable throwable) {
public void accept(final Object result, @Nullable final Throwable throwable) {
if (throwable != null)
Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable));
else if (priority <= Log.DEBUG)
@@ -7,7 +7,7 @@
package com.wireguard.android.util;
import android.databinding.ObservableArrayList;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.wireguard.util.Keyed;
@@ -25,28 +25,28 @@ import java.util.Objects;
public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
extends ObservableArrayList<E> implements ObservableKeyedList<K, E> {
@Override
public boolean add(final E e) {
public boolean add(@Nullable final E e) {
if (e == null)
throw new NullPointerException("Trying to add a null element");
return super.add(e);
}
@Override
public void add(final int index, final E e) {
public void add(final int index, @Nullable final E e) {
if (e == null)
throw new NullPointerException("Trying to add a null element");
super.add(index, e);
}
@Override
public boolean addAll(@NonNull final Collection<? extends E> c) {
public boolean addAll(final Collection<? extends E> c) {
if (c.contains(null))
throw new NullPointerException("Trying to add a collection with null element(s)");
return super.addAll(c);
}
@Override
public boolean addAll(final int index, @NonNull final Collection<? extends E> c) {
public boolean addAll(final int index, final Collection<? extends E> c) {
if (c.contains(null))
throw new NullPointerException("Trying to add a collection with null element(s)");
return super.addAll(index, c);
@@ -65,13 +65,13 @@ public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
return indexOfKey(key) >= 0;
}
@Override
@Override @Nullable
public E get(final K key) {
final int index = indexOfKey(key);
return index >= 0 ? get(index) : null;
}
@Override
@Override @Nullable
public E getLast(final K key) {
final int index = lastIndexOfKey(key);
return index >= 0 ? get(index) : null;
@@ -100,7 +100,7 @@ public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
}
@Override
public E set(final int index, final E e) {
public E set(final int index, @Nullable final E e) {
if (e == null)
throw new NullPointerException("Trying to set a null key");
return super.set(index, e);
@@ -6,7 +6,7 @@
package com.wireguard.android.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.wireguard.util.Keyed;
import com.wireguard.util.SortedKeyedList;
@@ -29,6 +29,7 @@ import java.util.Spliterator;
public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
@Nullable
private final Comparator<? super K> comparator;
private final transient KeyList<K, E> keyList = new KeyList<>(this);
@@ -75,7 +76,7 @@ public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
}
@Override
public boolean addAll(@NonNull final Collection<? extends E> c) {
public boolean addAll(final Collection<? extends E> c) {
boolean didChange = false;
for (final E e : c)
if (add(e))
@@ -84,12 +85,13 @@ public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
}
@Override
public boolean addAll(int index, @NonNull final Collection<? extends E> c) {
public boolean addAll(int index, final Collection<? extends E> c) {
for (final E e : c)
add(index++, e);
return true;
}
@Nullable
@Override
public Comparator<? super K> comparator() {
return comparator;
@@ -128,7 +130,6 @@ public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
}
@Override
@NonNull
public Set<K> keySet() {
return keyList;
}
@@ -168,7 +169,6 @@ public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
}
@Override
@NonNull
public Collection<E> values() {
return this;
}
@@ -7,6 +7,7 @@
package com.wireguard.android.util;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import com.wireguard.android.R;
@@ -34,10 +35,10 @@ public class RootShell {
private final File localTemporaryDir;
private final Object lock = new Object();
private final String preamble;
private Process process;
private BufferedReader stderr;
private OutputStreamWriter stdin;
private BufferedReader stdout;
@Nullable private Process process;
@Nullable private BufferedReader stderr;
@Nullable private OutputStreamWriter stdin;
@Nullable private BufferedReader stdout;
public RootShell(final Context context) {
deviceNotRootedMessage = context.getString(R.string.error_root);
@@ -80,7 +81,7 @@ public class RootShell {
* @param command Command to run as root.
* @return The exit value of the command.
*/
public int run(final Collection<String> output, final String command)
public int run(@Nullable final Collection<String> output, final String command)
throws IOException, NoRootException {
synchronized (lock) {
/* Start inside synchronized block to prevent a concurrent call to stop(). */
@@ -19,8 +19,7 @@ import java.util.zip.ZipFile;
public final class SharedLibraryLoader {
private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
private SharedLibraryLoader() {
}
private SharedLibraryLoader() { }
public static void loadSharedLibrary(final Context context, final String libName) {
Throwable noAbiException;
@@ -7,6 +7,7 @@
package com.wireguard.android.util;
import android.content.Context;
import android.support.annotation.Nullable;
import android.system.OsConstants;
import android.util.Log;
@@ -39,20 +40,21 @@ public final class ToolsInstaller {
new File("/system/xbin"),
new File("/system/bin"),
};
private static final File INSTALL_DIR = getInstallDir();
@Nullable private static final File INSTALL_DIR = getInstallDir();
private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
private final File localBinaryDir;
private final Object lock = new Object();
private final File nativeLibraryDir;
private Boolean areToolsAvailable;
private Boolean installAsMagiskModule;
@Nullable private Boolean areToolsAvailable;
@Nullable private Boolean installAsMagiskModule;
public ToolsInstaller(final Context context) {
localBinaryDir = new File(context.getCacheDir(), "bin");
nativeLibraryDir = new File(context.getApplicationInfo().nativeLibraryDir);
}
@Nullable
private static File getInstallDir() {
final String path = System.getenv("PATH");
if (path == null)
@@ -0,0 +1,43 @@
/*
* Copyright © 2018 Harsh Shandilya <msfjarvis@gmail.com>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget;
import android.support.v7.widget.RecyclerView;
import com.wireguard.android.R;
public abstract class CustomRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
private int scrollDist;
private boolean isVisible = true;
private static int flingThreshold;
@Override
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
if (flingThreshold == 0)
flingThreshold = recyclerView.getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal) / 2;
if (isVisible && scrollDist >= flingThreshold) {
hide();
scrollDist = 0;
isVisible = false;
} else if (!isVisible && scrollDist <= -flingThreshold) {
show();
scrollDist = 0;
isVisible = true;
}
if (isVisible ? dy > 0 : dy < 0) {
scrollDist += dy;
}
}
public abstract void show();
public abstract void hide();
}
@@ -6,6 +6,7 @@
package com.wireguard.android.widget;
import android.support.annotation.Nullable;
import android.text.InputFilter;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -25,7 +26,7 @@ public class KeyInputFilter implements InputFilter {
return new KeyInputFilter();
}
@Override
@Override @Nullable
public CharSequence filter(final CharSequence source,
final int sStart, final int sEnd,
final Spanned dest,
@@ -6,6 +6,7 @@
package com.wireguard.android.widget;
import android.support.annotation.Nullable;
import android.text.InputFilter;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -25,7 +26,7 @@ public class NameInputFilter implements InputFilter {
return new NameInputFilter();
}
@Override
@Override @Nullable
public CharSequence filter(final CharSequence source,
final int sStart, final int sEnd,
final Spanned dest,
@@ -25,11 +25,9 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.FloatProperty;
@TargetApi(Build.VERSION_CODES.N)
public class SlashDrawable extends Drawable {
@@ -53,26 +51,24 @@ public class SlashDrawable extends Drawable {
// Draw the slash washington-monument style; rotate to no-u-turn style
private static final float DEFAULT_ROTATION = -45f;
private Drawable mDrawable;
private final Drawable mDrawable;
private final RectF mSlashRect = new RectF(0, 0, 0, 0);
private float mRotation;
private boolean mSlashed;
private Mode mTintMode;
private ColorStateList mTintList;
private boolean mAnimationEnabled = true;
public SlashDrawable(final Drawable d) {
setDrawable(d);
mDrawable = d;
}
@Override
public int getIntrinsicHeight() {
return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
return mDrawable.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
return mDrawable.getIntrinsicWidth();
}
@Override
@@ -81,17 +77,6 @@ public class SlashDrawable extends Drawable {
mDrawable.setBounds(bounds);
}
public void setDrawable(final Drawable d) {
mDrawable = d;
mDrawable.setCallback(getCallback());
mDrawable.setBounds(getBounds());
if (mTintMode != null)
mDrawable.setTintMode(mTintMode);
if (mTintList != null)
mDrawable.setTintList(mTintList);
invalidateSelf();
}
public void setRotation(final float rotation) {
if (mRotation == rotation)
return;
@@ -117,6 +102,7 @@ public class SlashDrawable extends Drawable {
}
};
@SuppressWarnings("unchecked")
public void setSlashed(final boolean slashed) {
if (mSlashed == slashed) return;
@@ -136,8 +122,9 @@ public class SlashDrawable extends Drawable {
}
}
@SuppressWarnings("deprecation")
@Override
public void draw(@NonNull final Canvas canvas) {
public void draw(final Canvas canvas) {
canvas.save();
final Matrix m = new Matrix();
final int width = getBounds().width();
@@ -199,7 +186,6 @@ public class SlashDrawable extends Drawable {
@Override
public void setTintList(@Nullable final ColorStateList tint) {
mTintList = tint;
super.setTintList(tint);
setDrawableTintList(tint);
mPaint.setColor(tint == null ? 0 : tint.getDefaultColor());
@@ -211,8 +197,7 @@ public class SlashDrawable extends Drawable {
}
@Override
public void setTintMode(@NonNull final Mode tintMode) {
mTintMode = tintMode;
public void setTintMode(final Mode tintMode) {
super.setTintMode(tintMode);
mDrawable.setTintMode(tintMode);
}
@@ -7,22 +7,23 @@ package com.wireguard.android.widget;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.Switch;
public class ToggleSwitch extends Switch {
private boolean isRestoringState;
private OnBeforeCheckedChangeListener listener;
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
public ToggleSwitch(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Nullable private OnBeforeCheckedChangeListener listener;
public ToggleSwitch(final Context context) {
this(context, null);
}
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
public ToggleSwitch(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onRestoreInstanceState(final Parcelable state) {
isRestoringState = true;
@@ -1,97 +0,0 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import com.wireguard.android.R;
public class AddFloatingActionButton extends FloatingActionButton {
int mPlusColor;
public AddFloatingActionButton(final Context context) {
this(context, null);
}
public AddFloatingActionButton(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public AddFloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
void init(final Context context, final AttributeSet attributeSet) {
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0, 0);
mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_fab_plusIconColor, FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white));
attr.recycle();
super.init(context, attributeSet);
}
/**
* @return the current Color of plus icon.
*/
public int getPlusColor() {
return mPlusColor;
}
public void setPlusColor(final int color) {
if (mPlusColor != color) {
mPlusColor = color;
updateBackground();
}
}
public void setPlusColorResId(@ColorRes final int plusColor) {
setPlusColor(ContextCompat.getColor(getContext(), plusColor));
}
@Override
public void setIcon(@DrawableRes final int icon) {
throw new UnsupportedOperationException("Use FloatingActionButton if you want to use custom icon");
}
@Override
Drawable getIconDrawable() {
final float iconSize = getDimension(R.dimen.fab_icon_size);
final float iconHalfSize = iconSize / 2f;
final float plusSize = getDimension(R.dimen.fab_plus_icon_size);
final float plusHalfStroke = getDimension(R.dimen.fab_plus_icon_stroke) / 2f;
final float plusOffset = (iconSize - plusSize) / 2f;
final Shape shape = new Shape() {
@Override
public void draw(final Canvas canvas, final Paint paint) {
canvas.drawRect(plusOffset, iconHalfSize - plusHalfStroke, iconSize - plusOffset, iconHalfSize + plusHalfStroke, paint);
canvas.drawRect(iconHalfSize - plusHalfStroke, plusOffset, iconHalfSize + plusHalfStroke, iconSize - plusOffset, paint);
}
};
final ShapeDrawable drawable = new ShapeDrawable(shape);
final Paint paint = drawable.getPaint();
paint.setColor(mPlusColor);
paint.setStyle(Style.FILL);
paint.setAntiAlias(true);
return drawable;
}
}
@@ -1,446 +0,0 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable.ShaderFactory;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatImageButton;
import android.util.AttributeSet;
import android.widget.TextView;
import com.wireguard.android.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class FloatingActionButton extends AppCompatImageButton {
public static final int SIZE_NORMAL = 0;
public static final int SIZE_MINI = 1;
int mColorNormal;
int mColorPressed;
int mColorDisabled;
String mTitle;
boolean mStrokeVisible;
@DrawableRes
private int mIcon;
private Drawable mIconDrawable;
private int mSize;
private float mCircleSize;
private float mShadowRadius;
private float mShadowOffset;
private int mDrawableSize;
public FloatingActionButton(final Context context) {
this(context, null);
}
public FloatingActionButton(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public static int getColorFromTheme(final Context context, final int themeResource, @ColorRes final int fallback) {
final TypedArray a = context.obtainStyledAttributes(new int[]{themeResource});
try {
return a.getColor(0, ContextCompat.getColor(context, fallback));
} finally {
a.recycle();
}
}
void init(final Context context, final AttributeSet attributeSet) {
final TypedArray attr = context.obtainStyledAttributes(attributeSet,
R.styleable.FloatingActionButton, 0, 0);
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal,
getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed,
darkenOrLightenColor(mColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled,
ContextCompat.getColor(context, android.R.color.darker_gray)); //TODO(msf): load from theme
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true);
attr.recycle();
updateCircleSize();
mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
updateDrawableSize();
updateBackground();
}
private void updateDrawableSize() {
mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);
}
private void updateCircleSize() {
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
}
@FAB_SIZE
public int getSize() {
return mSize;
}
public void setSize(@FAB_SIZE final int size) {
if (size != SIZE_MINI && size != SIZE_NORMAL) {
throw new IllegalArgumentException("Use @FAB_SIZE constants only!");
}
if (mSize != size) {
mSize = size;
updateCircleSize();
updateDrawableSize();
updateBackground();
}
}
public void setIcon(@DrawableRes final int icon) {
if (mIcon != icon) {
mIcon = icon;
mIconDrawable = null;
updateBackground();
}
}
/**
* @return the current Color for normal state.
*/
public int getColorNormal() {
return mColorNormal;
}
public void setColorNormal(final int color) {
if (mColorNormal != color) {
mColorNormal = color;
updateBackground();
}
}
public void setColorNormalResId(@ColorRes final int colorNormal) {
setColorNormal(ContextCompat.getColor(getContext(), colorNormal));
}
/**
* @return the current color for pressed state.
*/
public int getColorPressed() {
return mColorPressed;
}
public void setColorPressed(final int color) {
if (mColorPressed != color) {
mColorPressed = color;
updateBackground();
}
}
public void setColorPressedResId(@ColorRes final int colorPressed) {
setColorPressed(ContextCompat.getColor(getContext(), colorPressed));
}
/**
* @return the current color for disabled state.
*/
public int getColorDisabled() {
return mColorDisabled;
}
public void setColorDisabled(final int color) {
if (mColorDisabled != color) {
mColorDisabled = color;
updateBackground();
}
}
public void setColorDisabledResId(@ColorRes final int colorDisabled) {
setColorDisabled(ContextCompat.getColor(getContext(), colorDisabled));
}
public boolean isStrokeVisible() {
return mStrokeVisible;
}
public void setStrokeVisible(final boolean visible) {
if (mStrokeVisible != visible) {
mStrokeVisible = visible;
updateBackground();
}
}
float getDimension(@DimenRes final int id) {
return getResources().getDimension(id);
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle() {
return mTitle;
}
public void setTitle(final String title) {
mTitle = title;
final TextView label = getLabelView();
if (label != null) {
label.setText(title);
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDrawableSize, mDrawableSize);
}
void updateBackground() {
final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
final float halfStrokeWidth = strokeWidth / 2f;
final LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{
//TODO(msf); replace these pngs with programatic elevation
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini, null),
createFillDrawable(strokeWidth),
createOuterStrokeDrawable(strokeWidth),
getIconDrawable()
});
final int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2;
final int circleInsetHorizontal = (int) (mShadowRadius);
final int circleInsetTop = (int) (mShadowRadius - mShadowOffset);
final int circleInsetBottom = (int) (mShadowRadius + mShadowOffset);
layerDrawable.setLayerInset(1,
circleInsetHorizontal,
circleInsetTop,
circleInsetHorizontal,
circleInsetBottom);
layerDrawable.setLayerInset(2,
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetTop - halfStrokeWidth),
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetBottom - halfStrokeWidth));
layerDrawable.setLayerInset(3,
circleInsetHorizontal + iconOffset,
circleInsetTop + iconOffset,
circleInsetHorizontal + iconOffset,
circleInsetBottom + iconOffset);
setBackground(layerDrawable);
}
Drawable getIconDrawable() {
if (mIconDrawable != null) {
return mIconDrawable;
} else if (mIcon != 0) {
return ContextCompat.getDrawable(getContext(), mIcon);
} else {
return new ColorDrawable(Color.TRANSPARENT);
}
}
public void setIconDrawable(@NonNull final Drawable iconDrawable) {
if (mIconDrawable != iconDrawable) {
mIcon = 0;
mIconDrawable = iconDrawable;
updateBackground();
}
}
private StateListDrawable createFillDrawable(final float strokeWidth) {
final StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled, strokeWidth));
drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed, strokeWidth));
drawable.addState(new int[]{}, createCircleDrawable(mColorNormal, strokeWidth));
return drawable;
}
private Drawable createCircleDrawable(final int color, final float strokeWidth) {
final int alpha = Color.alpha(color);
final int opaqueColor = opaque(color);
final ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = fillDrawable.getPaint();
paint.setAntiAlias(true);
paint.setColor(opaqueColor);
final Drawable[] layers = {
fillDrawable,
createInnerStrokesDrawable(opaqueColor, strokeWidth)
};
final LayerDrawable drawable = alpha == 255 || !mStrokeVisible
? new LayerDrawable(layers)
: new TranslucentLayerDrawable(alpha, layers);
final int halfStrokeWidth = (int) (strokeWidth / 2f);
drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
return drawable;
}
private static Drawable createOuterStrokeDrawable(final float strokeWidth) {
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAlpha(opacityToAlpha(0.02f));
return shapeDrawable;
}
private static int opacityToAlpha(final float opacity) {
return (int) (255f * opacity);
}
private static int darkenColor(final int argb) {
return adjustColorBrightness(argb, 0.9f);
}
private static int lightenColor(final int argb) {
return adjustColorBrightness(argb, 1.1f);
}
public static int darkenOrLightenColor(final int argb) {
final float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
final float factor;
if (hsv[2] < 0.2)
factor = 1.2f;
else
factor = 0.8f;
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private static int adjustColorBrightness(final int argb, final float factor) {
final float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private static int halfTransparent(final int argb) {
return Color.argb(
Color.alpha(argb) / 2,
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private static int opaque(final int argb) {
return Color.rgb(
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private Drawable createInnerStrokesDrawable(final int color, final float strokeWidth) {
if (!mStrokeVisible) {
return new ColorDrawable(Color.TRANSPARENT);
}
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final int bottomStrokeColor = darkenColor(color);
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
final int topStrokeColor = lightenColor(color);
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
shapeDrawable.setShaderFactory(new ShaderFactory() {
@Override
public Shader resize(int width, int height) {
return new LinearGradient(width / 2, 0, width / 2, height,
new int[]{topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor},
new float[]{0f, 0.2f, 0.5f, 0.8f, 1f},
TileMode.CLAMP
);
}
});
return shapeDrawable;
}
@Override
public void setVisibility(final int visibility) {
final TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_NORMAL, SIZE_MINI})
public @interface FAB_SIZE {
}
private static final class TranslucentLayerDrawable extends LayerDrawable {
private final int mAlpha;
private TranslucentLayerDrawable(final int alpha, final Drawable... layers) {
super(layers);
mAlpha = alpha;
}
@Override
public void draw(final Canvas canvas) {
final Rect bounds = getBounds();
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha);
super.draw(canvas);
canvas.restore();
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright © 2018 Harsh Shandilya <msfjarvis@gmail.com>
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.util.AttributeSet;
import android.view.View;
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) { }
@Override
public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
final View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
final View dependency) {
final float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
}
@@ -17,11 +17,12 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
@@ -34,7 +35,6 @@ import android.widget.TextView;
import com.wireguard.android.R;
@SuppressWarnings("ReturnOfInnerClass")
public class FloatingActionsMenu extends ViewGroup {
public static final int EXPAND_UP = 0;
public static final int EXPAND_DOWN = 1;
@@ -50,11 +50,6 @@ public class FloatingActionsMenu extends ViewGroup {
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 mAddButtonPlusColor;
private int mAddButtonColorNormal;
private int mAddButtonColorPressed;
private int mAddButtonSize;
private boolean mAddButtonStrokeVisible;
private int mExpandDirection;
private int mButtonSpacing;
private int mLabelsMargin;
@@ -62,33 +57,33 @@ public class FloatingActionsMenu extends ViewGroup {
private boolean mExpanded;
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
private AddFloatingActionButton mAddButton;
private RotatingDrawable mRotatingDrawable;
@Nullable private FloatingActionButton mAddButton;
@Nullable private RotatingDrawable mRotatingDrawable;
private int mMaxButtonWidth;
private int mMaxButtonHeight;
private int mLabelsStyle;
private int mLabelsPosition;
private int mButtonsCount;
private TouchDelegateGroup mTouchDelegateGroup;
private OnFloatingActionsMenuUpdateListener mListener;
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
private final Rect touchArea = new Rect(0, 0, 0, 0);
public FloatingActionsMenu(final Context context) {
this(context, null);
}
public FloatingActionsMenu(final Context context, final AttributeSet attrs) {
public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionsMenu(final Context context, final AttributeSet attrs, final int defStyle) {
public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(final Context context, final AttributeSet attributeSet) {
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset));
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);
@@ -96,14 +91,6 @@ public class FloatingActionsMenu extends ViewGroup {
setTouchDelegate(mTouchDelegateGroup);
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor,
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white));
mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal,
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright));
mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed,
FloatingActionButton.darkenOrLightenColor(mAddButtonColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states
mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL);
mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true);
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);
@@ -125,45 +112,30 @@ public class FloatingActionsMenu extends ViewGroup {
}
private void createAddButton(final Context context) {
mAddButton = new AddFloatingActionButton(context) {
@Override
void updateBackground() {
mPlusColor = mAddButtonPlusColor;
mColorNormal = mAddButtonColorNormal;
mColorPressed = mAddButtonColorPressed;
mStrokeVisible = mAddButtonStrokeVisible;
super.updateBackground();
}
final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_inverse, context.getTheme()));
mRotatingDrawable = rotatingDrawable;
@Override
Drawable getIconDrawable() {
final RotatingDrawable rotatingDrawable = new RotatingDrawable(super.getIconDrawable());
mRotatingDrawable = rotatingDrawable;
final TimeInterpolator interpolator = new OvershootInterpolator();
final TimeInterpolator interpolator = new OvershootInterpolator();
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
collapseAnimator.setInterpolator(interpolator);
expandAnimator.setInterpolator(interpolator);
collapseAnimator.setInterpolator(interpolator);
expandAnimator.setInterpolator(interpolator);
mExpandAnimation.play(expandAnimator);
mCollapseAnimation.play(collapseAnimator);
return rotatingDrawable;
}
};
mExpandAnimation.play(expandAnimator);
mCollapseAnimation.play(collapseAnimator);
mAddButton = new FloatingActionButton(context);
mAddButton.setImageDrawable(rotatingDrawable);
mAddButton.setId(R.id.fab_expand_menu_button);
mAddButton.setSize(mAddButtonSize);
mAddButton.setOnClickListener(v -> toggle());
addView(mAddButton, super.generateDefaultLayoutParams());
mButtonsCount++;
}
public void addButton(final FloatingActionButton button) {
public void addButton(final LabeledFloatingActionButton button) {
addView(button, mButtonsCount - 1);
mButtonsCount++;
@@ -172,7 +144,7 @@ public class FloatingActionsMenu extends ViewGroup {
}
}
public void removeButton(final FloatingActionButton button) {
public void removeButton(final LabeledFloatingActionButton button) {
removeView(button.getLabelView());
removeView(button);
button.setTag(R.id.fab_label, null);
@@ -257,9 +229,9 @@ public class FloatingActionsMenu extends ViewGroup {
final int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0;
// Ensure mAddButton is centered on the line where the buttons should be
final int buttonsHorizontalCenter = mLabelsPosition == LABELS_ON_LEFT_SIDE
final int buttonsHorizontalCenter = (mLabelsPosition == LABELS_ON_LEFT_SIDE
? r - l - mMaxButtonWidth / 2
: mMaxButtonWidth / 2;
: mMaxButtonWidth / 2);
final int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2;
mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight());
@@ -314,7 +286,7 @@ public class FloatingActionsMenu extends ViewGroup {
childY - mButtonSpacing / 2,
Math.max(childX + child.getMeasuredWidth(), labelRight),
childY + child.getMeasuredHeight() + mButtonSpacing / 2);
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(touchArea, child));
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(new Rect(touchArea), child));
label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
label.setAlpha(mExpanded ? 1f : 0f);
@@ -401,23 +373,21 @@ public class FloatingActionsMenu extends ViewGroup {
}
private void createLabels() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS"))
return;
final Context context = new ContextThemeWrapper(getContext(), mLabelsStyle);
for (int i = 0; i < mButtonsCount; i++) {
final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
final String title = button.getTitle();
if (button == mAddButton || title == null ||
button.getTag(R.id.fab_label) != null) continue;
if (button instanceof LabeledFloatingActionButton) {
final String title = ((LabeledFloatingActionButton) button).getTitle();
final AppCompatTextView label = new AppCompatTextView(context);
label.setTextAppearance(context, mLabelsStyle);
label.setText(button.getTitle());
addView(label);
final AppCompatTextView label = new AppCompatTextView(context);
label.setTextAppearance(context, mLabelsStyle);
label.setText(title);
addView(label);
button.setTag(R.id.fab_label, label);
button.setTag(R.id.fab_label, label);
}
}
}
@@ -560,7 +530,7 @@ public class FloatingActionsMenu extends ViewGroup {
}
@Override
public void writeToParcel(@NonNull final Parcel out, final int flags) {
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeInt(mExpanded ? 1 : 0);
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2014 Jerzy Chalupski
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget.fab;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.widget.TextView;
import com.wireguard.android.R;
public class LabeledFloatingActionButton extends FloatingActionButton {
@Nullable private final String title;
public LabeledFloatingActionButton(final Context context) {
this(context, null);
}
public LabeledFloatingActionButton(final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public LabeledFloatingActionButton(final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.LabeledFloatingActionButton, 0, 0);
title = attr.getString(R.styleable.LabeledFloatingActionButton_fab_title);
attr.recycle();
}
@Nullable
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
@Nullable
public String getTitle() {
return title;
}
@Override
public void setVisibility(final int visibility) {
final TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
}
@@ -7,7 +7,7 @@
package com.wireguard.android.widget.fab;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
@@ -18,14 +18,14 @@ import java.util.Collection;
public class TouchDelegateGroup extends TouchDelegate {
private static final Rect USELESS_HACKY_RECT = new Rect();
private final Collection<TouchDelegate> mTouchDelegates = new ArrayList<>();
private TouchDelegate mCurrentTouchDelegate;
@Nullable private TouchDelegate mCurrentTouchDelegate;
private boolean mEnabled;
public TouchDelegateGroup(final View uselessHackyView) {
super(USELESS_HACKY_RECT, uselessHackyView);
}
public void addTouchDelegate(@NonNull final TouchDelegate touchDelegate) {
public void addTouchDelegate(final TouchDelegate touchDelegate) {
mTouchDelegates.add(touchDelegate);
}
@@ -42,14 +42,15 @@ public class TouchDelegateGroup extends TouchDelegate {
}
@Override
public boolean onTouchEvent(@NonNull final MotionEvent event) {
if (!mEnabled) return false;
public boolean onTouchEvent(final MotionEvent event) {
if (!mEnabled)
return false;
TouchDelegate delegate = null;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (final TouchDelegate touchDelegate : mTouchDelegates) {
for (final TouchDelegate touchDelegate : mTouchDelegates) {
if (touchDelegate.onTouchEvent(event)) {
mCurrentTouchDelegate = touchDelegate;
return true;
@@ -6,6 +6,7 @@
package com.wireguard.config;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.util.HashMap;
@@ -59,13 +60,13 @@ public enum Attribute {
return KEY_MAP.get(SEPARATOR_PATTERN.split(line)[0].toLowerCase());
}
public static String[] stringToList(final String string) {
public static String[] stringToList(@Nullable final String string) {
if (TextUtils.isEmpty(string))
return EMPTY_LIST;
return LIST_SEPARATOR_PATTERN.split(string.trim());
}
public String composeWith(final Object value) {
public String composeWith(@Nullable final Object value) {
return String.format("%s = %s%n", token, value);
}
@@ -77,11 +78,13 @@ public enum Attribute {
return String.format("%s = %s%n", token, iterableToString(value));
}
@Nullable
public String parse(final CharSequence line) {
final Matcher matcher = pattern.matcher(line);
return matcher.matches() ? matcher.group(1) : null;
}
@Nullable
public String[] parseList(final CharSequence line) {
final Matcher matcher = pattern.matcher(line);
return matcher.matches() ? stringToList(matcher.group(1)) : null;
@@ -12,6 +12,7 @@ import android.databinding.ObservableArrayList;
import android.databinding.ObservableList;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import com.android.databinding.library.baseAdapters.BR;
@@ -96,13 +97,19 @@ public class Config {
return new Observable[size];
}
};
private String name;
private Interface.Observable observableInterface;
private ObservableList<Peer.Observable> observablePeers;
@Nullable private String name;
private final Interface.Observable observableInterface;
private final ObservableList<Peer.Observable> observablePeers;
public Observable(final Config parent, final String name) {
public Observable(@Nullable final Config parent, @Nullable final String name) {
this.name = name;
loadData(parent);
observableInterface = new Interface.Observable(parent == null ? null : parent.interfaceSection);
observablePeers = new ObservableArrayList<>();
if (parent != null) {
for (final Peer peer : parent.getPeers())
observablePeers.add(new Peer.Observable(peer));
}
}
private Observable(final Parcel in) {
@@ -144,15 +151,6 @@ public class Config {
return observablePeers;
}
protected void loadData(final Config parent) {
observableInterface = new Interface.Observable(parent == null ? null : parent.interfaceSection);
observablePeers = new ObservableArrayList<>();
if (parent != null) {
for (final Peer peer : parent.getPeers())
observablePeers.add(new Peer.Observable(peer));
}
}
public void setName(final String name) {
this.name = name;
notifyPropertyChanged(BR.name);
@@ -6,7 +6,6 @@
package com.wireguard.config;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
@@ -29,14 +28,13 @@ public final class InetAddresses {
// Prevent instantiation.
}
@NonNull
public static InetAddress parse(@Nullable final String address) {
if (address == null || address.isEmpty())
throw new IllegalArgumentException("Empty address");
try {
return (InetAddress) PARSER_METHOD.invoke(null, address);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
throw new RuntimeException(e.getCause() == null ? e : e.getCause());
}
}
}
@@ -6,8 +6,6 @@
package com.wireguard.config;
import android.support.annotation.NonNull;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Objects;
@@ -16,7 +14,7 @@ public class InetNetwork {
private final InetAddress address;
private final int mask;
public InetNetwork(@NonNull final String input) {
public InetNetwork(final String input) {
final int slash = input.lastIndexOf('/');
final int rawMask;
final String rawAddress;
@@ -40,7 +38,6 @@ public class InetNetwork {
return Objects.equals(address, other.address) && mask == other.mask;
}
@NonNull
public InetAddress getAddress() {
return address;
}
@@ -10,6 +10,7 @@ import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import com.wireguard.android.BR;
import com.wireguard.crypto.Keypair;
@@ -27,7 +28,7 @@ public class Interface {
private final List<InetNetwork> addressList;
private final List<InetAddress> dnsList;
private final List<String> excludedApplications;
private Keypair keypair;
@Nullable private Keypair keypair;
private int listenPort;
private int mtu;
@@ -37,7 +38,7 @@ public class Interface {
excludedApplications = new ArrayList<>();
}
private void addAddresses(final String[] addresses) {
private void addAddresses(@Nullable final String[] addresses) {
if (addresses != null && addresses.length > 0) {
for (final String addr : addresses) {
if (addr.isEmpty())
@@ -47,7 +48,7 @@ public class Interface {
}
}
private void addDnses(final String[] dnses) {
private void addDnses(@Nullable final String[] dnses) {
if (dnses != null && dnses.length > 0) {
for (final String dns : dnses) {
dnsList.add(InetAddresses.parse(dns));
@@ -55,12 +56,13 @@ public class Interface {
}
}
private void addExcludedApplications(final String[] applications) {
private void addExcludedApplications(@Nullable final String[] applications) {
if (applications != null && applications.length > 0) {
excludedApplications.addAll(Arrays.asList(applications));
}
}
@Nullable
private String getAddressString() {
if (addressList.isEmpty())
return null;
@@ -71,6 +73,7 @@ public class Interface {
return addressList.toArray(new InetNetwork[addressList.size()]);
}
@Nullable
private String getDnsString() {
if (dnsList.isEmpty())
return null;
@@ -88,6 +91,7 @@ public class Interface {
return dnsList.toArray(new InetAddress[dnsList.size()]);
}
@Nullable
private String getExcludedApplicationsString() {
if (excludedApplications.isEmpty())
return null;
@@ -102,6 +106,7 @@ public class Interface {
return listenPort;
}
@Nullable
private String getListenPortString() {
if (listenPort == 0)
return null;
@@ -112,18 +117,21 @@ public class Interface {
return mtu;
}
@Nullable
private String getMtuString() {
if (mtu == 0)
return null;
return Integer.toString(mtu);
}
@Nullable
public String getPrivateKey() {
if (keypair == null)
return null;
return keypair.getPrivateKey();
}
@Nullable
public String getPublicKey() {
if (keypair == null)
return null;
@@ -156,17 +164,17 @@ public class Interface {
}
}
private void setAddressString(final String addressString) {
private void setAddressString(@Nullable final String addressString) {
addressList.clear();
addAddresses(Attribute.stringToList(addressString));
}
private void setDnsString(final String dnsString) {
private void setDnsString(@Nullable final String dnsString) {
dnsList.clear();
addDnses(Attribute.stringToList(dnsString));
}
private void setExcludedApplicationsString(final String applicationsString) {
private void setExcludedApplicationsString(@Nullable final String applicationsString) {
excludedApplications.clear();
addExcludedApplications(Attribute.stringToList(applicationsString));
}
@@ -175,7 +183,7 @@ public class Interface {
this.listenPort = listenPort;
}
private void setListenPortString(final String port) {
private void setListenPortString(@Nullable final String port) {
if (port != null && !port.isEmpty())
setListenPort(Integer.parseInt(port, 10));
else
@@ -186,14 +194,14 @@ public class Interface {
this.mtu = mtu;
}
private void setMtuString(final String mtu) {
private void setMtuString(@Nullable final String mtu) {
if (mtu != null && !mtu.isEmpty())
setMtu(Integer.parseInt(mtu, 10));
else
setMtu(0);
}
private void setPrivateKey(String privateKey) {
private void setPrivateKey(@Nullable String privateKey) {
if (privateKey != null && privateKey.isEmpty())
privateKey = null;
keypair = privateKey == null ? null : new Keypair(privateKey);
@@ -229,15 +237,15 @@ public class Interface {
return new Observable[size];
}
};
private String addresses;
private String dnses;
private String excludedApplications;
private String listenPort;
private String mtu;
private String privateKey;
private String publicKey;
@Nullable private String addresses;
@Nullable private String dnses;
@Nullable private String excludedApplications;
@Nullable private String listenPort;
@Nullable private String mtu;
@Nullable private String privateKey;
@Nullable private String publicKey;
public Observable(final Interface parent) {
public Observable(@Nullable final Interface parent) {
if (parent != null)
loadData(parent);
}
@@ -276,16 +284,19 @@ public class Interface {
notifyPropertyChanged(BR.publicKey);
}
@Nullable
@Bindable
public String getAddresses() {
return addresses;
}
@Nullable
@Bindable
public String getDnses() {
return dnses;
}
@Nullable
@Bindable
public String getExcludedApplications() {
return excludedApplications;
@@ -296,27 +307,31 @@ public class Interface {
return Attribute.stringToList(excludedApplications).length;
}
@Nullable
@Bindable
public String getListenPort() {
return listenPort;
}
@Nullable
@Bindable
public String getMtu() {
return mtu;
}
@Nullable
@Bindable
public String getPrivateKey() {
return privateKey;
}
@Nullable
@Bindable
public String getPublicKey() {
return publicKey;
}
protected void loadData(final Interface parent) {
private void loadData(final Interface parent) {
addresses = parent.getAddressString();
dnses = parent.getDnsString();
excludedApplications = parent.getExcludedApplicationsString();
@@ -10,6 +10,7 @@ import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import com.android.databinding.library.baseAdapters.BR;
import com.wireguard.crypto.KeyEncoding;
@@ -21,26 +22,29 @@ import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java9.lang.Iterables;
/**
* Represents the configuration for a WireGuard peer (a [Peer] block).
*/
public class Peer {
private final List<InetNetwork> allowedIPsList;
private InetSocketAddress endpoint;
@Nullable private InetSocketAddress endpoint;
private int persistentKeepalive;
private String preSharedKey;
private String publicKey;
@Nullable private String preSharedKey;
@Nullable private String publicKey;
public Peer() {
allowedIPsList = new ArrayList<>();
}
private void addAllowedIPs(final String[] allowedIPs) {
private void addAllowedIPs(@Nullable final String[] allowedIPs) {
if (allowedIPs != null && allowedIPs.length > 0) {
for (final String allowedIP : allowedIPs) {
allowedIPsList.add(new InetNetwork(allowedIP));
@@ -52,16 +56,19 @@ public class Peer {
return allowedIPsList.toArray(new InetNetwork[allowedIPsList.size()]);
}
@Nullable
private String getAllowedIPsString() {
if (allowedIPsList.isEmpty())
return null;
return Attribute.iterableToString(allowedIPsList);
}
@Nullable
public InetSocketAddress getEndpoint() {
return endpoint;
}
@Nullable
private String getEndpointString() {
if (endpoint == null)
return null;
@@ -72,16 +79,19 @@ public class Peer {
return persistentKeepalive;
}
@Nullable
private String getPersistentKeepaliveString() {
if (persistentKeepalive == 0)
return null;
return Integer.valueOf(persistentKeepalive).toString();
}
@Nullable
public String getPreSharedKey() {
return preSharedKey;
}
@Nullable
public String getPublicKey() {
return publicKey;
}
@@ -127,16 +137,16 @@ public class Peer {
}
}
private void setAllowedIPsString(final String allowedIPsString) {
private void setAllowedIPsString(@Nullable final String allowedIPsString) {
allowedIPsList.clear();
addAllowedIPs(Attribute.stringToList(allowedIPsString));
}
private void setEndpoint(final InetSocketAddress endpoint) {
private void setEndpoint(@Nullable final InetSocketAddress endpoint) {
this.endpoint = endpoint;
}
private void setEndpointString(final String endpoint) {
private void setEndpointString(@Nullable final String endpoint) {
if (endpoint != null && !endpoint.isEmpty()) {
final InetSocketAddress constructedEndpoint;
if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1)
@@ -157,14 +167,14 @@ public class Peer {
this.persistentKeepalive = persistentKeepalive;
}
private void setPersistentKeepaliveString(final String persistentKeepalive) {
private void setPersistentKeepaliveString(@Nullable final String persistentKeepalive) {
if (persistentKeepalive != null && !persistentKeepalive.isEmpty())
setPersistentKeepalive(Integer.parseInt(persistentKeepalive, 10));
else
setPersistentKeepalive(0);
}
private void setPreSharedKey(String preSharedKey) {
private void setPreSharedKey(@Nullable String preSharedKey) {
if (preSharedKey != null && preSharedKey.isEmpty())
preSharedKey = null;
if (preSharedKey != null)
@@ -172,7 +182,7 @@ public class Peer {
this.preSharedKey = preSharedKey;
}
private void setPublicKey(String publicKey) {
private void setPublicKey(@Nullable String publicKey) {
if (publicKey != null && publicKey.isEmpty())
publicKey = null;
if (publicKey != null)
@@ -208,11 +218,13 @@ public class Peer {
return new Observable[size];
}
};
private String allowedIPs;
private String endpoint;
private String persistentKeepalive;
private String preSharedKey;
private String publicKey;
@Nullable private String allowedIPs;
@Nullable private String endpoint;
@Nullable private String persistentKeepalive;
@Nullable private String preSharedKey;
@Nullable private String publicKey;
private final List<String> interfaceDNSRoutes = new ArrayList<>();
private int numSiblings;
public Observable(final Peer parent) {
loadData(parent);
@@ -224,6 +236,8 @@ public class Peer {
persistentKeepalive = in.readString();
preSharedKey = in.readString();
publicKey = in.readString();
numSiblings = in.readInt();
in.readStringList(interfaceDNSRoutes);
}
public static Observable newInstance() {
@@ -251,57 +265,57 @@ public class Peer {
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 void toggleExcludePrivateIPs() {
final HashSet<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
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)
ips.removeAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
else if (hasDefaultRoute) {
ips.remove(DEFAULT_ROUTE_V4);
if ((!hasDefaultRoute && !hasDefaultRouteModRFC1918) || numSiblings > 0)
return;
Iterables.removeIf(ips, ip -> !ip.contains(":"));
if (hasDefaultRoute) {
ips.addAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
} else if (hasDefaultRouteModRFC1918) {
ips.removeAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.addAll(interfaceDNSRoutes);
} else if (hasDefaultRouteModRFC1918)
ips.add(DEFAULT_ROUTE_V4);
}
setAllowedIPs(Attribute.iterableToString(ips));
}
@Bindable
public boolean getCanToggleExcludePrivateIPs() {
final Collection<String> ips = Arrays.asList(Attribute.stringToList(allowedIPs));
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
public boolean getAllowedIPsContainsDefaultRoute() {
return Arrays.asList(Attribute.stringToList(allowedIPs)).contains(DEFAULT_ROUTE_V4);
}
@Bindable
public boolean getAllowedIPsContainsDefaultRouteModRFC1918() {
return Arrays.asList(Attribute.stringToList(allowedIPs)).containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
}
@Bindable
@Bindable @Nullable
public String getEndpoint() {
return endpoint;
}
@Bindable
@Bindable @Nullable
public String getPersistentKeepalive() {
return persistentKeepalive;
}
@Bindable
@Bindable @Nullable
public String getPreSharedKey() {
return preSharedKey;
}
@Bindable
@Bindable @Nullable
public String getPublicKey() {
return publicKey;
}
protected void loadData(final Peer parent) {
private void loadData(final Peer parent) {
allowedIPs = parent.getAllowedIPsString();
endpoint = parent.getEndpointString();
persistentKeepalive = parent.getPersistentKeepaliveString();
@@ -312,8 +326,8 @@ public class Peer {
public void setAllowedIPs(final String allowedIPs) {
this.allowedIPs = allowedIPs;
notifyPropertyChanged(BR.allowedIPs);
notifyPropertyChanged(BR.allowedIPsContainsDefaultRoute);
notifyPropertyChanged(BR.allowedIPsContainsDefaultRouteModRFC1918);
notifyPropertyChanged(BR.canToggleExcludePrivateIPs);
notifyPropertyChanged(BR.isExcludePrivateIPsOn);
}
public void setEndpoint(final String endpoint) {
@@ -336,6 +350,27 @@ public class Peer {
notifyPropertyChanged(BR.publicKey);
}
public void setInterfaceDNSRoutes(@Nullable final String dnsServers) {
final Collection<String> ips = new HashSet<>(Arrays.asList(Attribute.stringToList(allowedIPs)));
final boolean modifyAllowedIPs = ips.containsAll(DEFAULT_ROUTE_MOD_RFC1918_V4);
ips.removeAll(interfaceDNSRoutes);
interfaceDNSRoutes.clear();
for (final String dnsServer : Attribute.stringToList(dnsServers)) {
if (!dnsServer.contains(":"))
interfaceDNSRoutes.add(dnsServer + "/32");
}
ips.addAll(interfaceDNSRoutes);
if (modifyAllowedIPs)
setAllowedIPs(Attribute.iterableToString(ips));
}
public void setNumSiblings(final int num) {
numSiblings = num;
notifyPropertyChanged(BR.canToggleExcludePrivateIPs);
notifyPropertyChanged(BR.isExcludePrivateIPsOn);
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(allowedIPs);
@@ -343,6 +378,8 @@ public class Peer {
dest.writeString(persistentKeepalive);
dest.writeString(preSharedKey);
dest.writeString(publicKey);
dest.writeInt(numSiblings);
dest.writeStringList(interfaceDNSRoutes);
}
}
}
@@ -5,6 +5,8 @@
package com.wireguard.crypto;
import android.support.annotation.Nullable;
import java.util.Arrays;
/**
@@ -93,7 +95,7 @@ public final class Curve25519 {
* if the base point of the curve should be used.
*/
public static void eval(final byte[] result, final int offset,
final byte[] privateKey, final byte[] publicKey) {
final byte[] privateKey, @Nullable final byte[] publicKey) {
final Curve25519 state = new Curve25519();
try {
// Unpack the public key value. If null, use 9 as the base point.
@@ -0,0 +1,25 @@
/*
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
/**
* This annotation can be applied to a package, class or method to indicate that all
* class fields and method parameters and return values in that element are nonnull
* by default unless overridden.
*/
@Documented
@Nonnull
@TypeQualifierDefault({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NonNullForAll { }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="?android:attr/colorBackground"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
@@ -103,10 +103,10 @@
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/allowed_ips_label"
android:layout_alignParentEnd="true"
android:checked="@{item.allowedIPsContainsDefaultRouteModRFC1918}"
android:checked="@{item.isExcludePrivateIPsOn}"
android:onClick="@{() -> item.toggleExcludePrivateIPs()}"
android:text="@string/exclude_private_ips"
android:visibility="@{(item.allowedIPsContainsDefaultRoute || item.allowedIPsContainsDefaultRouteModRFC1918) ? View.VISIBLE : View.GONE}" />
android:visibility="@{item.canToggleExcludePrivateIPs ? View.VISIBLE : View.GONE}" />
<EditText
android:id="@+id/allowed_ips_text"
@@ -23,12 +23,15 @@
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground">
android:background="?android:attr/colorBackground"
android:clipChildren="false">
<android.support.v7.widget.RecyclerView
android:id="@+id/tunnel_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/design_fab_size_normal"
android:clipToPadding="false"
android:choiceMode="multipleChoiceModal"
app:items="@{tunnels}"
app:layout="@{@layout/tunnel_list_item}"
@@ -39,27 +42,28 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:layout_margin="@dimen/fab_margin"
app:fab_labelStyle="@style/fab_label"
android:clipChildren="false"
app:fab_labelsPosition="left"
app:layout_dodgeInsetEdges="bottom">
app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior" >
<com.wireguard.android.widget.fab.FloatingActionButton
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/create_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestCreateConfig}"
app:fab_icon="@drawable/ic_action_edit_inverse"
app:fab_size="mini"
app:fabSize="mini"
app:srcCompat="@drawable/ic_action_edit_inverse"
app:fab_title="@string/create_empty" />
<com.wireguard.android.widget.fab.FloatingActionButton
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
android:id="@+id/create_from_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{fragment::onRequestImportConfig}"
app:fab_icon="@drawable/ic_action_open_inverse"
app:fab_size="mini"
app:srcCompat="@drawable/ic_action_open_inverse"
app:fabSize="mini"
app:fab_title="@string/create_from_file" />
</com.wireguard.android.widget.fab.FloatingActionsMenu>
</android.support.design.widget.CoordinatorLayout>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>
+2 -30
View File
@@ -3,46 +3,18 @@
<item name="fab_expand_menu_button" type="id"/>
<item name="fab_label" type="id"/>
<dimen name="fab_size_normal">56dp</dimen>
<dimen name="fab_size_mini">40dp</dimen>
<dimen name="fab_icon_size">24dp</dimen>
<dimen name="fab_plus_icon_size">14dp</dimen>
<dimen name="fab_plus_icon_stroke">2dp</dimen>
<dimen name="fab_shadow_offset">3dp</dimen>
<dimen name="fab_shadow_radius">9dp</dimen>
<dimen name="fab_stroke_width">1dp</dimen>
<dimen name="fab_actions_spacing">16dp</dimen>
<dimen name="fab_actions_spacing">24dp</dimen>
<dimen name="fab_labels_margin">8dp</dimen>
<declare-styleable name="FloatingActionButton">
<attr name="fab_colorPressed" format="color"/>
<attr name="fab_colorDisabled" format="color"/>
<attr name="fab_colorNormal" format="color"/>
<attr name="fab_icon" format="reference"/>
<attr name="fab_size" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<declare-styleable name="LabeledFloatingActionButton">
<attr name="fab_title" format="string"/>
<attr name="fab_stroke_visible" format="boolean"/>
</declare-styleable>
<declare-styleable name="AddFloatingActionButton">
<attr name="fab_plusIconColor" format="color"/>
</declare-styleable>
<declare-styleable name="FloatingActionsMenu">
<attr name="fab_addButtonColorPressed" format="color"/>
<attr name="fab_addButtonColorNormal" format="color"/>
<attr name="fab_addButtonSize" format="enum">
<enum name="normal" value="0"/>
<enum name="mini" value="1"/>
</attr>
<attr name="fab_addButtonPlusIconColor" format="color"/>
<attr name="fab_addButtonStrokeVisible" format="boolean"/>
<attr name="fab_labelStyle" format="reference"/>
<attr name="fab_labelsPosition" format="enum">
<enum name="left" value="0"/>