mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d580200989 | |||
| c2cdde73d1 | |||
| bc74d4d7f8 | |||
| 535c611f2d | |||
| 6efbf65405 | |||
| abb121224d | |||
| 8ad4657d6f | |||
| 6d3f1e00a5 | |||
| c38f6c471d | |||
| 85462de254 | |||
| 61d4f17f5d | |||
| 09cf73cd3c | |||
| ffa3cefa67 | |||
| 8ec2cc8582 | |||
| 75dfa0643b | |||
| d5cde43158 | |||
| 6493a9a1f2 | |||
| f35e059194 | |||
| 3cb3e9d8b7 | |||
| 5dfc5659ad | |||
| c4102992ae | |||
| 219f4e8016 | |||
| 6558140a7c | |||
| db7b61ab80 | |||
| bce5d852e1 | |||
| b960b4a6cd | |||
| 1b10e75168 | |||
| 07359e392c | |||
| e5a5bad240 | |||
| ca92ac60b7 | |||
| e29c21f8df | |||
| 6ceeac93bf | |||
| 9f861096ac | |||
| 520df16885 | |||
| c905ef6083 | |||
| 9652fe99df | |||
| 62d8beff96 | |||
| 7d438e9dbc | |||
| b364221c93 | |||
| b8052bd8fb | |||
| e437ac389e | |||
| 2db2a0921d | |||
| d7889c4e88 | |||
| 559add3f71 | |||
| d615304e83 | |||
| 8e0835e570 | |||
| bb20c89cd5 | |||
| 996cbb5f2b | |||
| 8028d708cb | |||
| 7689905c78 | |||
| 284e42647c | |||
| 3a16c08821 | |||
| 76fb6a318e | |||
| c633f96374 | |||
| 3357be9557 | |||
| 6696e838da | |||
| d0d56f3a1b | |||
| fbf32a6c29 | |||
| 3e37289b68 | |||
| 2d6a45f824 | |||
| ed802336e6 | |||
| b1d1e3b436 | |||
| d33e322b67 | |||
| 60a6e29350 | |||
| 3b0e0c2f16 | |||
| ffb8bccbc5 | |||
| 08170f7e55 | |||
| 28d47b3470 | |||
| 8e3586328c | |||
| f315654d40 | |||
| fdfab18d45 | |||
| d43e77867c | |||
| df03bdd7f9 |
+20
-5
@@ -11,14 +11,15 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion 28
|
||||
dataBinding.enabled true
|
||||
defaultConfig {
|
||||
applicationId 'com.wireguard.android'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 433
|
||||
versionName '0.0.20180716'
|
||||
targetSdkVersion 28
|
||||
versionCode 439
|
||||
versionName '0.0.20180826'
|
||||
buildConfigField 'int', 'MIN_SDK_VERSION', "$minSdkVersion.apiLevel"
|
||||
}
|
||||
// If the keystore file exists
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
@@ -40,10 +41,20 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) signingConfig signingConfigs.release
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${android.defaultConfig.applicationId}"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${android.defaultConfig.applicationId}${applicationIdSuffix}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
@@ -58,6 +69,8 @@ ext {
|
||||
supportLibsVersion = '27.1.1'
|
||||
streamsupportVersion = '1.6.0'
|
||||
jsr305Version = '3.0.2'
|
||||
zxingEmbeddedVersion = '3.6.0'
|
||||
acraVersion = '5.2.0-rc2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -67,9 +80,11 @@ dependencies {
|
||||
implementation "com.android.support:design:$supportLibsVersion"
|
||||
implementation "com.android.support:preference-v14:$supportLibsVersion"
|
||||
implementation "com.android.support:support-annotations:$supportLibsVersion"
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion"
|
||||
implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion"
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
implementation "ch.acra:acra-http:$acraVersion"
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package="com.wireguard.android"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
@@ -50,6 +51,11 @@
|
||||
android:label="@string/create_activity_title"
|
||||
android:parentActivityName=".activity.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<receiver android:name=".BootShutdownReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
|
||||
package com.wireguard.android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -16,20 +21,36 @@ import android.support.v7.app.AppCompatDelegate;
|
||||
import com.wireguard.android.backend.Backend;
|
||||
import com.wireguard.android.backend.GoBackend;
|
||||
import com.wireguard.android.backend.WgQuickBackend;
|
||||
import com.wireguard.android.configStore.ConfigStore;
|
||||
import com.wireguard.android.configStore.FileConfigStore;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
import com.wireguard.android.util.AsyncWorker;
|
||||
import com.wireguard.android.util.RootShell;
|
||||
import com.wireguard.android.util.ToolsInstaller;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.annotation.AcraCore;
|
||||
import org.acra.annotation.AcraHttpSender;
|
||||
import org.acra.data.StringFormat;
|
||||
import org.acra.sender.HttpSender;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
@AcraCore(reportFormat = StringFormat.JSON,
|
||||
buildConfigClass = BuildConfig.class,
|
||||
logcatArguments = {"-b", "all", "-d", "-v", "threadtime", "*:V"},
|
||||
excludeMatchingSharedPreferencesKeys = {"last_used_tunnel", "enabled_configs"})
|
||||
@AcraHttpSender(uri = "https://crashreport.zx2c4.com/android/report",
|
||||
basicAuthLogin = "6RCovLxEVCTXGiW5",
|
||||
basicAuthPassword = "O7I3sVa5ULVdiC51",
|
||||
httpMethod = HttpSender.Method.POST,
|
||||
compress = true)
|
||||
public class Application extends android.app.Application {
|
||||
@SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
|
||||
@SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
|
||||
@@ -37,15 +58,65 @@ public class Application extends android.app.Application {
|
||||
@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();
|
||||
private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
|
||||
|
||||
public Application() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
/* The ACRA password can be trivially reverse engineered and is open source anyway,
|
||||
* so there's no point in trying to protect it. However, we do want to at least
|
||||
* prevent innocent self-builders from uploading stuff to our crash reporter. So, we
|
||||
* check the DN of the certs that signed the apk, without even bothering to try
|
||||
* validating that they're authentic. It's a good enough heuristic.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Nullable
|
||||
private static String getInstallSource(final Context context) {
|
||||
if (BuildConfig.DEBUG)
|
||||
return null;
|
||||
try {
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
for (final Signature sig : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures) {
|
||||
try {
|
||||
for (final String category : ((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()))).getSubjectDN().getName().split(", *")) {
|
||||
final String[] parts = category.split("=", 2);
|
||||
if (!"O".equals(parts[0]))
|
||||
continue;
|
||||
switch (parts[1]) {
|
||||
case "Google Inc.":
|
||||
return "Play Store";
|
||||
case "fdroid.org":
|
||||
return "F-Droid";
|
||||
}
|
||||
}
|
||||
} catch (final Exception ignored) { }
|
||||
}
|
||||
} catch (final Exception ignored) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context context) {
|
||||
super.attachBaseContext(context);
|
||||
|
||||
if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
|
||||
final Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_HOME);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
final String installSource = getInstallSource(context);
|
||||
if (installSource != null) {
|
||||
ACRA.init(this);
|
||||
ACRA.getErrorReporter().putCustomData("installSource", installSource);
|
||||
}
|
||||
}
|
||||
|
||||
public static Application get() {
|
||||
return weakSelf.get();
|
||||
}
|
||||
@@ -56,43 +127,26 @@ public class Application extends android.app.Application {
|
||||
|
||||
public static Backend getBackend() {
|
||||
final Application app = get();
|
||||
synchronized (app) {
|
||||
synchronized (app.futureBackend) {
|
||||
if (app.backend == null) {
|
||||
Backend backend = null;
|
||||
if (new File("/sys/module/wireguard").exists()) {
|
||||
try {
|
||||
app.rootShell.start();
|
||||
app.backend = new WgQuickBackend(app.getApplicationContext());
|
||||
} catch (final Exception ignored) { }
|
||||
}
|
||||
if (app.backend == null)
|
||||
app.backend = new GoBackend(app.getApplicationContext());
|
||||
synchronized (app.haveBackendCallbacksLock) {
|
||||
if (app.haveBackendCallbacks != null) {
|
||||
for (final BackendCallback callback : app.haveBackendCallbacks)
|
||||
app.handler.post(() -> callback.callback(app.backend));
|
||||
app.haveBackendCallbacks = null;
|
||||
backend = new WgQuickBackend(app.getApplicationContext());
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (backend == null)
|
||||
backend = new GoBackend(app.getApplicationContext());
|
||||
app.backend = backend;
|
||||
}
|
||||
return app.backend;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BackendCallback {
|
||||
void callback(final Backend backend);
|
||||
}
|
||||
|
||||
public static void onHaveBackend(final BackendCallback callback) {
|
||||
final Application app = get();
|
||||
synchronized (app.haveBackendCallbacksLock) {
|
||||
if (app.haveBackendCallbacks == null) {
|
||||
Objects.requireNonNull(app.backend, "Backend still null in onHaveBackend call");
|
||||
callback.callback(app.backend);
|
||||
} else {
|
||||
app.haveBackendCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
public static CompletableFuture<Backend> getBackendAsync() {
|
||||
return get().futureBackend;
|
||||
}
|
||||
|
||||
public static RootShell getRootShell() {
|
||||
@@ -115,11 +169,7 @@ public class Application extends android.app.Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
final Executor executor = AsyncTask.SERIAL_EXECUTOR;
|
||||
final ConfigStore configStore = new FileConfigStore(getApplicationContext());
|
||||
|
||||
asyncWorker = new AsyncWorker(executor, handler);
|
||||
asyncWorker = new AsyncWorker(AsyncTask.SERIAL_EXECUTOR, new Handler(Looper.getMainLooper()));
|
||||
rootShell = new RootShell(getApplicationContext());
|
||||
toolsInstaller = new ToolsInstaller(getApplicationContext());
|
||||
|
||||
@@ -128,8 +178,16 @@ public class Application extends android.app.Application {
|
||||
sharedPreferences.getBoolean("dark_theme", false) ?
|
||||
AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
|
||||
|
||||
tunnelManager = new TunnelManager(configStore);
|
||||
asyncWorker.runAsync(Application::getBackend);
|
||||
tunnelManager = new TunnelManager(new FileConfigStore(getApplicationContext()));
|
||||
tunnelManager.onCreate();
|
||||
|
||||
asyncWorker.supplyAsync(Application::getBackend).thenAccept(backend -> {
|
||||
futureBackend.complete(backend);
|
||||
if (ACRA.isInitialised()) {
|
||||
ACRA.getErrorReporter().putCustomData("backend", backend.getClass().getSimpleName());
|
||||
asyncWorker.supplyAsync(backend::getVersion).thenAccept(version ->
|
||||
ACRA.getErrorReporter().putCustomData("backendVersion", version));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class BootShutdownReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
Application.onHaveBackend(backend -> {
|
||||
Application.getBackendAsync().thenAccept(backend -> {
|
||||
if (!(backend instanceof WgQuickBackend))
|
||||
return;
|
||||
final String action = intent.getAction();
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -44,10 +45,26 @@ public class QuickTileService extends TileService {
|
||||
@Nullable private Icon iconOn;
|
||||
@Nullable private Icon iconOff;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||
@Override
|
||||
@Nullable
|
||||
public IBinder onBind(final Intent intent) {
|
||||
IBinder ret = null;
|
||||
try {
|
||||
ret = super.onBind(intent);
|
||||
} catch (final Exception e) {
|
||||
Log.d(TAG, "Failed to bind to TileService", e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
iconOff = iconOn = Icon.createWithResource(this, R.drawable.ic_tile);
|
||||
return;
|
||||
}
|
||||
final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile, Application.get().getTheme()));
|
||||
icon.setAnimationEnabled(false); /* Unfortunately we can't have animations, since Icons are marshaled. */
|
||||
icon.setSlashed(false);
|
||||
Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
@@ -67,8 +84,10 @@ public class QuickTileService extends TileService {
|
||||
public void onClick() {
|
||||
if (tunnel != null) {
|
||||
final Tile tile = getQsTile();
|
||||
tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
|
||||
tile.updateTile();
|
||||
if (tile != null) {
|
||||
tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
|
||||
tile.updateTile();
|
||||
}
|
||||
tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
|
||||
} else {
|
||||
final Intent intent = new Intent(this, MainActivity.class);
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.support.annotation.Nullable;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.model.TunnelManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -39,15 +38,17 @@ public abstract class BaseActivity extends ThemeChangeAwareActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
// Restore the saved tunnel if there is one; otherwise grab it from the arguments.
|
||||
String savedTunnelName = null;
|
||||
final String savedTunnelName;
|
||||
if (savedInstanceState != null)
|
||||
savedTunnelName = savedInstanceState.getString(KEY_SELECTED_TUNNEL);
|
||||
else if (getIntent() != null)
|
||||
savedTunnelName = getIntent().getStringExtra(KEY_SELECTED_TUNNEL);
|
||||
if (savedTunnelName != null) {
|
||||
final TunnelManager tunnelManager = Application.getTunnelManager();
|
||||
selectedTunnel = tunnelManager.getTunnels().get(savedTunnelName);
|
||||
}
|
||||
else
|
||||
savedTunnelName = null;
|
||||
|
||||
if (savedTunnelName != null)
|
||||
Application.getTunnelManager().getTunnels()
|
||||
.thenAccept(tunnels -> setSelectedTunnel(tunnels.get(savedTunnelName)));
|
||||
|
||||
// The selected tunnel must be set before the superclass method recreates fragments.
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@@ -10,12 +10,13 @@ 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;
|
||||
import android.util.Log;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.fragment.TunnelDetailFragment;
|
||||
@@ -23,71 +24,46 @@ 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;
|
||||
|
||||
/**
|
||||
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
||||
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
||||
* editing the configuration and interface state of WireGuard tunnels.
|
||||
*/
|
||||
|
||||
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) {
|
||||
if (state == nextState)
|
||||
return false;
|
||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
Log.i(TAG, "Moving from " + state.name() + " to " + nextState.name());
|
||||
if (nextState.layer > state.layer + 1) {
|
||||
moveToState(State.ofLayer(state.layer + 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
} else if (nextState.layer == state.layer + 1) {
|
||||
final Fragment fragment = Fragment.instantiate(this, nextState.fragment);
|
||||
final FragmentTransaction transaction = fragmentManager.beginTransaction()
|
||||
.replace(R.id.master_fragment, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||
if (state.layer > 0)
|
||||
transaction.addToBackStack(null);
|
||||
transaction.commit();
|
||||
} else if (nextState.layer == state.layer - 1) {
|
||||
if (fragmentManager.getBackStackEntryCount() == 0)
|
||||
return false;
|
||||
fragmentManager.popBackStack();
|
||||
} else if (nextState.layer < state.layer - 1) {
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
moveToState(nextState);
|
||||
return true;
|
||||
}
|
||||
state = nextState;
|
||||
if (state.layer <= State.LIST.layer)
|
||||
setSelectedTunnel(null);
|
||||
updateActionBar();
|
||||
return true;
|
||||
}
|
||||
public class MainActivity extends BaseActivity
|
||||
implements FragmentManager.OnBackStackChangedListener {
|
||||
@Nullable private ActionBar actionBar;
|
||||
private boolean isTwoPaneLayout;
|
||||
@Nullable private TunnelListFragment listFragment;
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
|
||||
|
||||
boolean handled = false;
|
||||
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
|
||||
handled = ((TunnelListFragment) fragments.get(0)).collapseActionMenu();
|
||||
final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
|
||||
// If the action menu is visible and expanded, collapse it instead of navigating back.
|
||||
if (isTwoPaneLayout || backStackEntries == 0) {
|
||||
if (listFragment != null && listFragment.collapseActionMenu())
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
handled = moveToState(State.ofLayer(state.layer - 1));
|
||||
// If the two-pane layout does not have an editor open, going back should exit the app.
|
||||
if (isTwoPaneLayout && backStackEntries <= 1) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
super.onBackPressed();
|
||||
// Deselect the current tunnel on navigating back from the detail pane to the one-pane list.
|
||||
if (!isTwoPaneLayout && backStackEntries == 1) {
|
||||
setSelectedTunnel(null);
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override public void onBackStackChanged() {
|
||||
if (actionBar == null)
|
||||
return;
|
||||
// Do not show the home menu when the two-pane layout is at the detail view (see above).
|
||||
final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
|
||||
final int minBackStackEntries = isTwoPaneLayout ? 2 : 1;
|
||||
actionBar.setDisplayHomeAsUpEnabled(backStackEntries >= minBackStackEntries);
|
||||
}
|
||||
|
||||
// We use onTouchListener here to avoid the UI click sound, hence
|
||||
@@ -97,25 +73,14 @@ public class MainActivity extends BaseActivity {
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null)
|
||||
state = State.valueOf(savedInstanceState.getString(KEY_STATE));
|
||||
if (state == State.EMPTY) {
|
||||
State initialState = getSelectedTunnel() != null ? State.DETAIL : State.LIST;
|
||||
if (getIntent() != null && getIntent().getStringExtra(KEY_STATE) != null)
|
||||
initialState = State.valueOf(getIntent().getStringExtra(KEY_STATE));
|
||||
moveToState(initialState);
|
||||
}
|
||||
updateActionBar();
|
||||
final int actionBarId = getResources().getIdentifier("action_bar", "id", getPackageName());
|
||||
if (actionBarId != 0 && findViewById(actionBarId) != null) {
|
||||
findViewById(actionBarId).setOnTouchListener((v, e) -> {
|
||||
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
|
||||
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
|
||||
((TunnelListFragment) fragments.get(0)).collapseActionMenu();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
actionBar = getSupportActionBar();
|
||||
isTwoPaneLayout = findViewById(R.id.master_detail_wrapper) instanceof LinearLayout;
|
||||
listFragment = (TunnelListFragment) getSupportFragmentManager().findFragmentByTag("LIST");
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
onBackStackChanged();
|
||||
final View actionBarView = findViewById(R.id.action_bar);
|
||||
if (actionBarView != null)
|
||||
actionBarView.setOnTouchListener((v, e) -> listFragment != null && listFragment.collapseActionMenu());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,11 +94,14 @@ public class MainActivity extends BaseActivity {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// The back arrow in the action bar should act the same as the back button.
|
||||
moveToState(State.ofLayer(state.layer - 1));
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.menu_action_edit:
|
||||
if (getSelectedTunnel() != null)
|
||||
moveToState(State.EDITOR);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_container, new TunnelEditorFragment())
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
return true;
|
||||
case R.id.menu_action_save:
|
||||
// This menu item is handled by the editor fragment.
|
||||
@@ -147,37 +115,26 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final Bundle outState) {
|
||||
outState.putString(KEY_STATE, state.name());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
|
||||
moveToState(newTunnel != null ? State.DETAIL : State.LIST);
|
||||
}
|
||||
|
||||
private void updateActionBar() {
|
||||
if (getSupportActionBar() != null)
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(state.layer > State.LIST.layer);
|
||||
}
|
||||
|
||||
private enum State {
|
||||
EMPTY(null, 0),
|
||||
LIST(TunnelListFragment.class, 1),
|
||||
DETAIL(TunnelDetailFragment.class, 2),
|
||||
EDITOR(TunnelEditorFragment.class, 3);
|
||||
|
||||
@Nullable private final String fragment;
|
||||
private final int layer;
|
||||
|
||||
State(@Nullable final Class<? extends Fragment> fragment, final int layer) {
|
||||
this.fragment = fragment != null ? fragment.getName() : null;
|
||||
this.layer = layer;
|
||||
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel,
|
||||
@Nullable final Tunnel newTunnel) {
|
||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
final int backStackEntries = fragmentManager.getBackStackEntryCount();
|
||||
if (newTunnel == null) {
|
||||
// Clear everything off the back stack (all editors and detail fragments).
|
||||
fragmentManager.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
private static State ofLayer(final int layer) {
|
||||
return Stream.of(State.values()).filter(s -> s.layer == layer).findFirst().get();
|
||||
if (backStackEntries == 2) {
|
||||
// Pop the editor off the back stack to reveal the detail fragment. Use the immediate
|
||||
// method to avoid the editor picking up the new tunnel while it is still visible.
|
||||
fragmentManager.popBackStackImmediate();
|
||||
} else if (backStackEntries == 0) {
|
||||
// Create and show a new detail fragment.
|
||||
fragmentManager.beginTransaction()
|
||||
.add(R.id.detail_container, new TunnelDetailFragment())
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class SettingsActivity extends ThemeChangeAwareActivity {
|
||||
for (final Preference pref : wgQuickOnlyPrefs)
|
||||
pref.setVisible(false);
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
Application.onHaveBackend(backend -> {
|
||||
Application.getBackendAsync().thenAccept(backend -> {
|
||||
for (final Preference pref : wgQuickOnlyPrefs) {
|
||||
if (backend instanceof WgQuickBackend)
|
||||
pref.setVisible(true);
|
||||
|
||||
@@ -240,10 +240,13 @@ public final class GoBackend implements Backend {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
for (final Tunnel tunnel : Application.getTunnelManager().getTunnels()) {
|
||||
if (tunnel != null && tunnel.getState() != State.DOWN)
|
||||
tunnel.setState(State.DOWN);
|
||||
}
|
||||
Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
|
||||
for (final Tunnel tunnel : tunnels) {
|
||||
if (tunnel != null && tunnel.getState() != State.DOWN)
|
||||
tunnel.setState(State.DOWN);
|
||||
}
|
||||
});
|
||||
|
||||
vpnService = vpnService.newIncompleteFuture();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ package com.wireguard.android.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -22,10 +21,8 @@ import android.widget.Toast;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.BaseActivity;
|
||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding;
|
||||
import com.wireguard.android.model.ApplicationData;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.android.util.ObservableKeyedArrayList;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
@@ -39,7 +36,7 @@ public class AppListDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String KEY_EXCLUDED_APPS = "excludedApps";
|
||||
|
||||
private final List<String> currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
|
||||
private List<String> currentlyExcludedApps;
|
||||
private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
|
||||
|
||||
public static <T extends Fragment & AppExclusionListener> AppListDialogFragment newInstance(final String[] excludedApps, final T target) {
|
||||
@@ -51,6 +48,13 @@ public class AppListDialogFragment extends DialogFragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
currentlyExcludedApps = Arrays.asList(getArguments().getStringArray(KEY_EXCLUDED_APPS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
@@ -94,7 +94,7 @@ public abstract class BaseFragment extends Fragment implements OnSelectedTunnelC
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
Application.onHaveBackend(backend -> {
|
||||
Application.getBackendAsync().thenAccept(backend -> {
|
||||
if (backend instanceof GoBackend) {
|
||||
final Intent intent = GoBackend.VpnService.prepare(view.getContext());
|
||||
if (intent != null) {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright © 2018 Eric Kuck <eric@bluelinelabs.com>.
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConfigNamingDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String KEY_CONFIG_TEXT = "config_text";
|
||||
|
||||
@Nullable private Config config;
|
||||
@Nullable private ConfigNamingDialogFragmentBinding binding;
|
||||
@Nullable private InputMethodManager imm;
|
||||
|
||||
public static ConfigNamingDialogFragment newInstance(final String configText) {
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(KEY_CONFIG_TEXT, configText);
|
||||
final ConfigNamingDialogFragment fragment = new ConfigNamingDialogFragment();
|
||||
fragment.setArguments(extras);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
config = Config.from(getArguments().getString(KEY_CONFIG_TEXT));
|
||||
} catch (final IOException exception) {
|
||||
throw new RuntimeException("Invalid config passed to " + getClass().getSimpleName(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final AlertDialog dialog = (AlertDialog) getDialog();
|
||||
if (dialog != null) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
|
||||
|
||||
setKeyboardVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
|
||||
alertDialogBuilder.setTitle(R.string.import_from_qrcode);
|
||||
|
||||
binding = ConfigNamingDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
|
||||
binding.executePendingBindings();
|
||||
alertDialogBuilder.setView(binding.getRoot());
|
||||
|
||||
alertDialogBuilder.setPositiveButton(R.string.create_tunnel, null);
|
||||
alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dismiss());
|
||||
|
||||
return alertDialogBuilder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
setKeyboardVisible(false);
|
||||
super.dismiss();
|
||||
}
|
||||
|
||||
private void createTunnelAndDismiss() {
|
||||
if (binding != null) {
|
||||
final String name = binding.tunnelNameText.getText().toString();
|
||||
|
||||
Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> {
|
||||
if (tunnel != null) {
|
||||
dismiss();
|
||||
} else {
|
||||
binding.tunnelNameTextLayout.setError(throwable.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeyboardVisible(final boolean visible) {
|
||||
Objects.requireNonNull(imm);
|
||||
|
||||
if (visible) {
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
} else if (binding != null) {
|
||||
imm.hideSoftInputFromWindow(binding.tunnelNameText.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,8 @@ import com.wireguard.config.Attribute;
|
||||
import com.wireguard.config.Config;
|
||||
import com.wireguard.config.Peer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -96,14 +98,17 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(final ObservableList<Peer.Observable> sender, final int positionStart, final int itemCount) {
|
||||
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
|
||||
if (binding != null)
|
||||
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);
|
||||
if (binding != null)
|
||||
breakObjectOrientedLayeringHandler.onPropertyChanged(binding.getConfig(), BR.peers);
|
||||
}
|
||||
};
|
||||
|
||||
private final Collection<Object> breakObjectOrientedLayeringHandlerReceivers = new ArrayList<>();
|
||||
private final Observable.OnPropertyChangedCallback breakObjectOrientedLayeringHandler = new Observable.OnPropertyChangedCallback() {
|
||||
@Override
|
||||
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
||||
@@ -114,8 +119,11 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
||||
return;
|
||||
if (propertyId == BR.config) {
|
||||
config.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
|
||||
breakObjectOrientedLayeringHandlerReceivers.add(config);
|
||||
config.getInterfaceSection().addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
|
||||
breakObjectOrientedLayeringHandlerReceivers.add(config.getInterfaceSection());
|
||||
config.getPeers().addOnListChangedCallback(breakObjectListOrientedLayeringHandler);
|
||||
breakObjectOrientedLayeringHandlerReceivers.add(config.getPeers());
|
||||
} else if (propertyId == BR.dnses || propertyId == BR.peers)
|
||||
;
|
||||
else
|
||||
@@ -134,13 +142,21 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
|
||||
binding.addOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
|
||||
breakObjectOrientedLayeringHandlerReceivers.add(binding);
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
for (final Object o : breakObjectOrientedLayeringHandlerReceivers) {
|
||||
if (o instanceof Observable)
|
||||
((Observable)o).removeOnPropertyChangedCallback(breakObjectOrientedLayeringHandler);
|
||||
else if (o instanceof ObservableList)
|
||||
((ObservableList)o).removeOnListChangedCallback(breakObjectListOrientedLayeringHandler);
|
||||
}
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@@ -158,6 +174,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
||||
}
|
||||
// Tell the activity to finish itself or go back to the detail view.
|
||||
getActivity().runOnUiThread(() -> {
|
||||
// TODO(smaeul): Remove this hack when fixing the Config ViewModel
|
||||
// The selected tunnel has to actually change, but we have to remember this one.
|
||||
final Tunnel savedTunnel = tunnel;
|
||||
if (savedTunnel == getSelectedTunnel())
|
||||
|
||||
@@ -18,8 +18,10 @@ import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -27,6 +29,8 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity;
|
||||
@@ -35,9 +39,13 @@ 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.util.ObservableSortedKeyedList;
|
||||
import com.wireguard.android.widget.MultiselectableRelativeLayout;
|
||||
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
|
||||
import com.wireguard.config.Config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -71,6 +79,20 @@ public class TunnelListFragment extends BaseFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void importTunnel(@NonNull final String configText) {
|
||||
try {
|
||||
// Ensure the config text is parseable before proceeding…
|
||||
Config.from(configText);
|
||||
|
||||
// Config text is valid, now create the tunnel…
|
||||
final FragmentManager fragmentManager = getFragmentManager();
|
||||
if (fragmentManager != null)
|
||||
ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null);
|
||||
} catch (final Exception exception) {
|
||||
onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
|
||||
}
|
||||
}
|
||||
|
||||
private void importTunnel(@Nullable final Uri uri) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null || uri == null)
|
||||
@@ -171,11 +193,18 @@ public class TunnelListFragment extends BaseFragment {
|
||||
if (resultCode == Activity.RESULT_OK && data != null)
|
||||
importTunnel(data.getData());
|
||||
return;
|
||||
case IntentIntegrator.REQUEST_CODE:
|
||||
final IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
|
||||
if (result != null && result.getContents() != null) {
|
||||
importTunnel(result.getContents());
|
||||
}
|
||||
return;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@@ -189,6 +218,7 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
binding.tunnelList.setOnScrollListener(new FloatingActionsMenuRecyclerViewScrollListener(binding.createMenu));
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
@@ -214,6 +244,17 @@ public class TunnelListFragment extends BaseFragment {
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
public void onRequestScanQRCode(@SuppressWarnings("unused") final View view) {
|
||||
final IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(this);
|
||||
intentIntegrator.setOrientationLocked(false);
|
||||
intentIntegrator.setBeepEnabled(false);
|
||||
intentIntegrator.setPrompt(getString(R.string.qrcode_hint));
|
||||
intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE));
|
||||
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (binding != null) {
|
||||
@@ -222,9 +263,20 @@ public class TunnelListFragment extends BaseFragment {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) {
|
||||
return (MultiselectableRelativeLayout)binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
|
||||
// Do nothing.
|
||||
if (binding == null)
|
||||
return;
|
||||
Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
|
||||
if (newTunnel != null)
|
||||
viewForTunnel(newTunnel, tunnels).setSingleSelected(true);
|
||||
if (oldTunnel != null)
|
||||
viewForTunnel(oldTunnel, tunnels).setSingleSelected(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
|
||||
@@ -266,6 +318,26 @@ public class TunnelListFragment extends BaseFragment {
|
||||
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
final Collection<Integer> checkedItems = savedInstanceState.getIntegerArrayList("CHECKED_ITEMS");
|
||||
if (checkedItems != null) {
|
||||
for (final Integer i : checkedItems)
|
||||
actionModeListener.setItemChecked(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
@@ -275,7 +347,7 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
|
||||
binding.setFragment(this);
|
||||
binding.setTunnels(Application.getTunnelManager().getTunnels());
|
||||
Application.getTunnelManager().getTunnels().thenAccept(binding::setTunnels);
|
||||
binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
|
||||
binding.setFragment(this);
|
||||
binding.getRoot().setOnClickListener(clicked -> {
|
||||
@@ -290,7 +362,10 @@ public class TunnelListFragment extends BaseFragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
binding.getRoot().setActivated(actionModeListener.checkedItems.contains(position));
|
||||
if (actionMode != null)
|
||||
((MultiselectableRelativeLayout)binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position));
|
||||
else
|
||||
((MultiselectableRelativeLayout)binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,25 +378,29 @@ public class TunnelListFragment extends BaseFragment {
|
||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_delete:
|
||||
final Collection<Tunnel> tunnelsToDelete = new ArrayList<>();
|
||||
for (final Integer position : checkedItems) {
|
||||
tunnelsToDelete.add(Application.getTunnelManager().getTunnels().get(position));
|
||||
}
|
||||
final Iterable<Integer> copyCheckedItems = new HashSet<>(checkedItems);
|
||||
Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
|
||||
final Collection<Tunnel> tunnelsToDelete = new ArrayList<>();
|
||||
for (final Integer position : copyCheckedItems)
|
||||
tunnelsToDelete.add(tunnels.get(position));
|
||||
|
||||
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
|
||||
.map(Tunnel::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
|
||||
.map(Tunnel::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
|
||||
});
|
||||
checkedItems.clear();
|
||||
mode.finish();
|
||||
return true;
|
||||
case R.id.menu_action_select_all:
|
||||
for (int i = 0; i < Application.getTunnelManager().getTunnels().size(); ++i) {
|
||||
setItemChecked(i, true);
|
||||
}
|
||||
Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
|
||||
for (int i = 0; i < tunnels.size(); ++i) {
|
||||
setItemChecked(i, true);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -335,6 +414,7 @@ public class TunnelListFragment extends BaseFragment {
|
||||
resources = getActivity().getResources();
|
||||
}
|
||||
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
|
||||
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -342,7 +422,6 @@ public class TunnelListFragment extends BaseFragment {
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
actionMode = null;
|
||||
resources = null;
|
||||
|
||||
checkedItems.clear();
|
||||
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
@@ -351,6 +430,10 @@ public class TunnelListFragment extends BaseFragment {
|
||||
setItemChecked(position, !checkedItems.contains(position));
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getCheckedItems() {
|
||||
return new ArrayList<>(checkedItems);
|
||||
}
|
||||
|
||||
void setItemChecked(final int position, final boolean checked) {
|
||||
if (checked) {
|
||||
checkedItems.add(position);
|
||||
@@ -358,15 +441,16 @@ public class TunnelListFragment extends BaseFragment {
|
||||
checkedItems.remove(position);
|
||||
}
|
||||
|
||||
final RecyclerView.Adapter adapter = binding == null ? null : binding.tunnelList.getAdapter();
|
||||
|
||||
if (actionMode == null && !checkedItems.isEmpty() && getActivity() != null) {
|
||||
((AppCompatActivity) getActivity()).startSupportActionMode(this);
|
||||
} else if (actionMode != null && checkedItems.isEmpty()) {
|
||||
actionMode.finish();
|
||||
}
|
||||
|
||||
if (binding != null) {
|
||||
binding.tunnelList.getAdapter().notifyItemChanged(position);
|
||||
}
|
||||
if (adapter != null)
|
||||
adapter.notifyItemChanged(position);
|
||||
|
||||
updateTitle(actionMode);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ import android.support.annotation.Nullable;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.configStore.ConfigStore;
|
||||
import com.wireguard.android.model.Tunnel.State;
|
||||
import com.wireguard.android.model.Tunnel.Statistics;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
import com.wireguard.android.util.ObservableSortedKeyedArrayList;
|
||||
import com.wireguard.android.util.ObservableSortedKeyedList;
|
||||
import com.wireguard.config.Config;
|
||||
@@ -47,6 +47,8 @@ public final class TunnelManager extends BaseObservable {
|
||||
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
||||
|
||||
private final ConfigStore configStore;
|
||||
private final Context context = Application.get();
|
||||
private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
|
||||
private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
||||
@Nullable private Tunnel lastUsedTunnel;
|
||||
private boolean haveLoaded;
|
||||
@@ -64,9 +66,9 @@ public final class TunnelManager extends BaseObservable {
|
||||
|
||||
public CompletionStage<Tunnel> create(final String name, @Nullable final Config config) {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)));
|
||||
if (tunnels.containsKey(name)) {
|
||||
final String message = "Tunnel " + name + " already exists";
|
||||
final String message = context.getString(R.string.tunnel_error_already_exists, name);
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
||||
}
|
||||
return Application.getAsyncWorker().supplyAsync(() -> configStore.create(name, config))
|
||||
@@ -121,8 +123,8 @@ public final class TunnelManager extends BaseObservable {
|
||||
.thenApply(tunnel::onStatisticsChanged);
|
||||
}
|
||||
|
||||
public ObservableKeyedList<String, Tunnel> getTunnels() {
|
||||
return tunnels;
|
||||
public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() {
|
||||
return completableTunnels;
|
||||
}
|
||||
|
||||
public void onCreate() {
|
||||
@@ -152,6 +154,8 @@ public final class TunnelManager extends BaseObservable {
|
||||
f.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
|
||||
completableTunnels.complete(tunnels);
|
||||
}
|
||||
|
||||
public void refreshTunnelStates() {
|
||||
@@ -210,9 +214,9 @@ public final class TunnelManager extends BaseObservable {
|
||||
|
||||
CompletionStage<String> setTunnelName(final Tunnel tunnel, final String name) {
|
||||
if (Tunnel.isNameInvalid(name))
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException("Invalid name"));
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)));
|
||||
if (tunnels.containsKey(name)) {
|
||||
final String message = "Tunnel " + name + " already exists";
|
||||
final String message = context.getString(R.string.tunnel_error_already_exists, name);
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
||||
}
|
||||
final State originalState = tunnel.getState();
|
||||
@@ -285,10 +289,12 @@ public final class TunnelManager extends BaseObservable {
|
||||
final String tunnelName = intent.getStringExtra("tunnel");
|
||||
if (tunnelName == null)
|
||||
return;
|
||||
final Tunnel tunnel = manager.getTunnels().get(tunnelName);
|
||||
if (tunnel == null)
|
||||
return;
|
||||
manager.setTunnelState(tunnel, state);
|
||||
manager.getTunnels().thenAccept(tunnels -> {
|
||||
final Tunnel tunnel = tunnels.get(tunnelName);
|
||||
if (tunnel == null)
|
||||
return;
|
||||
manager.setTunnelState(tunnel, state);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class VersionPreference extends Preference {
|
||||
public VersionPreference(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
Application.onHaveBackend(backend -> {
|
||||
Application.getBackendAsync().thenAccept(backend -> {
|
||||
versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypeName().toLowerCase());
|
||||
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> {
|
||||
versionSummary = exception == null
|
||||
|
||||
@@ -48,7 +48,10 @@ public class ZipExporterPreference extends Preference {
|
||||
}
|
||||
|
||||
private void exportZip() {
|
||||
final List<Tunnel> tunnels = new ArrayList<>(Application.getTunnelManager().getTunnels());
|
||||
Application.getTunnelManager().getTunnels().thenAccept(this::exportZip);
|
||||
}
|
||||
|
||||
private void exportZip(final List<Tunnel> tunnels) {
|
||||
final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
|
||||
for (final Tunnel tunnel : tunnels)
|
||||
futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
|
||||
|
||||
@@ -11,7 +11,11 @@ import android.view.ContextThemeWrapper;
|
||||
|
||||
import com.wireguard.android.activity.SettingsActivity;
|
||||
|
||||
public class FragmentUtils {
|
||||
public final class FragmentUtils {
|
||||
|
||||
private FragmentUtils() {
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
public static SettingsActivity getPrefActivity(final Preference preference) {
|
||||
final Context context = preference.getContext();
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.wireguard.android.BuildConfig;
|
||||
import com.wireguard.android.R;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -45,8 +46,8 @@ public class RootShell {
|
||||
final File cacheDir = context.getCacheDir();
|
||||
localBinaryDir = new File(cacheDir, "bin");
|
||||
localTemporaryDir = new File(cacheDir, "tmp");
|
||||
preamble = String.format("export CALLING_PACKAGE=com.wireguard.android PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
|
||||
localBinaryDir, localTemporaryDir);
|
||||
preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
|
||||
BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir);
|
||||
}
|
||||
|
||||
private static boolean isExecutableInPath(final String name) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
|
||||
public class MultiselectableRelativeLayout extends RelativeLayout {
|
||||
public MultiselectableRelativeLayout(final Context context) {
|
||||
super(context);
|
||||
}
|
||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
private static final int[] STATE_MULTISELECTED = { R.attr.state_multiselected };
|
||||
|
||||
private boolean multiselected;
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(final int extraSpace) {
|
||||
if (multiselected) {
|
||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
|
||||
mergeDrawableStates(drawableState, STATE_MULTISELECTED);
|
||||
return drawableState;
|
||||
}
|
||||
return super.onCreateDrawableState(extraSpace);
|
||||
}
|
||||
|
||||
public void setMultiSelected(final boolean on) {
|
||||
if (!multiselected) {
|
||||
multiselected = true;
|
||||
refreshDrawableState();
|
||||
}
|
||||
setActivated(on);
|
||||
}
|
||||
|
||||
public void setSingleSelected(final boolean on) {
|
||||
if (multiselected) {
|
||||
multiselected = false;
|
||||
refreshDrawableState();
|
||||
}
|
||||
setActivated(on);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
|
||||
|
||||
public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
||||
final View dependency) {
|
||||
return dependency instanceof Snackbar.SnackbarLayout;
|
||||
}
|
||||
|
||||
private static final long ANIMATION_DURATION = 250;
|
||||
private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
|
||||
|
||||
private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) {
|
||||
final float origin = child.getBehaviorYTranslation();
|
||||
if (Math.abs(destination - origin) < fullSpan / 2) {
|
||||
child.setBehaviorYTranslation(destination);
|
||||
return;
|
||||
}
|
||||
final ValueAnimator animator = new ValueAnimator();
|
||||
animator.setFloatValues(origin, destination);
|
||||
animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
|
||||
animator.setDuration((long)(ANIMATION_DURATION * (Math.abs(destination - origin) / fullSpan)));
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator a) {
|
||||
child.setBehaviorYTranslation(destination);
|
||||
}
|
||||
});
|
||||
animator.addUpdateListener(a -> child.setBehaviorYTranslation((float)a.getAnimatedValue()));
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
||||
final View dependency) {
|
||||
animateChange(child, Math.min(0, dependency.getTranslationY() - dependency.getMeasuredHeight()), dependency.getMeasuredHeight());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependentViewRemoved(final CoordinatorLayout parent, final FloatingActionsMenu child,
|
||||
final View dependency) {
|
||||
animateChange(child, 0, dependency.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -67,6 +68,8 @@ public class FloatingActionsMenu extends ViewGroup {
|
||||
@Nullable private TouchDelegateGroup mTouchDelegateGroup;
|
||||
@Nullable private OnFloatingActionsMenuUpdateListener mListener;
|
||||
private final Rect touchArea = new Rect(0, 0, 0, 0);
|
||||
private float scrollYTranslation;
|
||||
private float behaviorYTranslation;
|
||||
|
||||
public FloatingActionsMenu(final Context context) {
|
||||
this(context, null);
|
||||
@@ -103,6 +106,24 @@ public class FloatingActionsMenu extends ViewGroup {
|
||||
createAddButton(context);
|
||||
}
|
||||
|
||||
public float getScrollYTranslation() {
|
||||
return scrollYTranslation;
|
||||
}
|
||||
|
||||
public void setScrollYTranslation(final float scrollYTranslation) {
|
||||
this.scrollYTranslation = scrollYTranslation;
|
||||
setTranslationY(behaviorYTranslation + scrollYTranslation);
|
||||
}
|
||||
|
||||
public float getBehaviorYTranslation() {
|
||||
return behaviorYTranslation;
|
||||
}
|
||||
|
||||
public void setBehaviorYTranslation(final float behaviorYTranslation) {
|
||||
this.behaviorYTranslation = behaviorYTranslation;
|
||||
setTranslationY(behaviorYTranslation + scrollYTranslation);
|
||||
}
|
||||
|
||||
public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
@@ -112,7 +133,7 @@ public class FloatingActionsMenu extends ViewGroup {
|
||||
}
|
||||
|
||||
private void createAddButton(final Context context) {
|
||||
final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_inverse, context.getTheme()));
|
||||
final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_white, context.getTheme()));
|
||||
mRotatingDrawable = rotatingDrawable;
|
||||
|
||||
final TimeInterpolator interpolator = new OvershootInterpolator();
|
||||
@@ -372,8 +393,10 @@ public class FloatingActionsMenu extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS");
|
||||
|
||||
private void createLabels() {
|
||||
final Context context = new ContextThemeWrapper(getContext(), mLabelsStyle);
|
||||
final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
|
||||
|
||||
for (int i = 0; i < mButtonsCount; i++) {
|
||||
final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
|
||||
@@ -382,7 +405,8 @@ public class FloatingActionsMenu extends ViewGroup {
|
||||
final String title = ((LabeledFloatingActionButton) button).getTitle();
|
||||
|
||||
final AppCompatTextView label = new AppCompatTextView(context);
|
||||
label.setTextAppearance(context, mLabelsStyle);
|
||||
if (!BROKEN_LABEL_STYLE)
|
||||
label.setTextAppearance(context, mLabelsStyle);
|
||||
label.setText(title);
|
||||
addView(label);
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.support.v7.widget.RecyclerView;
|
||||
|
||||
public class FloatingActionsMenuRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
|
||||
private static final float SCALE_FACTOR = 1.5f;
|
||||
private final FloatingActionsMenu menu;
|
||||
|
||||
public FloatingActionsMenuRecyclerViewScrollListener(final FloatingActionsMenu menu) {
|
||||
this.menu = menu;
|
||||
}
|
||||
|
||||
private static float bound(final float min, final float proposal, final float max) {
|
||||
return Math.min(max, Math.max(min, proposal));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
menu.setScrollYTranslation(bound(0, menu.getScrollYTranslation() + dy * SCALE_FACTOR, menu.getMeasuredHeight() - menu.getTranslationY()));
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -56,6 +56,7 @@ public enum Attribute {
|
||||
return TextUtils.join(", ", iterable);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Attribute match(final CharSequence line) {
|
||||
return KEY_MAP.get(SEPARATOR_PATTERN.split(line)[0].toLowerCase());
|
||||
}
|
||||
@@ -66,12 +67,14 @@ public enum Attribute {
|
||||
return LIST_SEPARATOR_PATTERN.split(string.trim());
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String composeWith(@Nullable final Object value) {
|
||||
return String.format("%s = %s%n", token, value);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String composeWith(final int value) {
|
||||
return String.format(Locale.getDefault(), "%s = %d%n", token, value);
|
||||
return String.format("%s = %d%n", token, value);
|
||||
}
|
||||
|
||||
public <T> String composeWith(final Iterable<T> value) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.content.Context;
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.databinding.ObservableArrayList;
|
||||
@@ -15,11 +16,14 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.android.databinding.library.baseAdapters.BR;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -32,12 +36,17 @@ public class Config {
|
||||
private final Interface interfaceSection = new Interface();
|
||||
private List<Peer> peers = new ArrayList<>();
|
||||
|
||||
public static Config from(final String string) throws IOException {
|
||||
return from(new BufferedReader(new StringReader(string)));
|
||||
}
|
||||
|
||||
public static Config from(final InputStream stream) throws IOException {
|
||||
return from(new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
public static Config from(final BufferedReader reader) throws IOException {
|
||||
final Config config = new Config();
|
||||
final Context context = Application.get();
|
||||
Peer currentPeer = null;
|
||||
String line;
|
||||
boolean inInterfaceSection = false;
|
||||
@@ -60,11 +69,11 @@ public class Config {
|
||||
} else if (currentPeer != null) {
|
||||
currentPeer.parse(line);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid configuration line: " + line);
|
||||
throw new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_config_line, line));
|
||||
}
|
||||
}
|
||||
if (!inInterfaceSection && currentPeer == null) {
|
||||
throw new IllegalArgumentException("Could not find any config information");
|
||||
throw new IllegalArgumentException(context.getString(R.string.tunnel_error_no_config_information));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ package com.wireguard.config;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
@@ -30,7 +33,7 @@ public final class InetAddresses {
|
||||
|
||||
public static InetAddress parse(@Nullable final String address) {
|
||||
if (address == null || address.isEmpty())
|
||||
throw new IllegalArgumentException("Empty address");
|
||||
throw new IllegalArgumentException(Application.get().getString(R.string.tunnel_error_empty_inetaddress));
|
||||
try {
|
||||
return (InetAddress) PARSER_METHOD.invoke(null, address);
|
||||
} catch (final IllegalAccessException | InvocationTargetException e) {
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.content.Context;
|
||||
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.Application;
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.crypto.Keypair;
|
||||
|
||||
import java.net.InetAddress;
|
||||
@@ -31,6 +34,7 @@ public class Interface {
|
||||
@Nullable private Keypair keypair;
|
||||
private int listenPort;
|
||||
private int mtu;
|
||||
private final Context context = Application.get();
|
||||
|
||||
public Interface() {
|
||||
addressList = new ArrayList<>();
|
||||
@@ -42,7 +46,7 @@ public class Interface {
|
||||
if (addresses != null && addresses.length > 0) {
|
||||
for (final String addr : addresses) {
|
||||
if (addr.isEmpty())
|
||||
throw new IllegalArgumentException("Address is empty");
|
||||
throw new IllegalArgumentException(context.getString(R.string.tunnel_error_empty_interface_address));
|
||||
addressList.add(new InetNetwork(addr));
|
||||
}
|
||||
}
|
||||
@@ -140,6 +144,8 @@ public class Interface {
|
||||
|
||||
public void parse(final String line) {
|
||||
final Attribute key = Attribute.match(line);
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException(String.format(context.getString(R.string.tunnel_error_interface_parse_failed), line));
|
||||
switch (key) {
|
||||
case ADDRESS:
|
||||
addAddresses(key.parseList(line));
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.databinding.BaseObservable;
|
||||
import android.databinding.Bindable;
|
||||
import android.os.Parcel;
|
||||
@@ -13,6 +15,8 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.android.databinding.library.baseAdapters.BR;
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.crypto.KeyEncoding;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
@@ -25,7 +29,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import java9.lang.Iterables;
|
||||
|
||||
@@ -39,6 +42,7 @@ public class Peer {
|
||||
private int persistentKeepalive;
|
||||
@Nullable private String preSharedKey;
|
||||
@Nullable private String publicKey;
|
||||
private final Context context = Application.get();
|
||||
|
||||
public Peer() {
|
||||
allowedIPsList = new ArrayList<>();
|
||||
@@ -68,11 +72,15 @@ public class Peer {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Nullable
|
||||
private String getEndpointString() {
|
||||
if (endpoint == null)
|
||||
return null;
|
||||
return String.format(Locale.getDefault(), "%s:%d", endpoint.getHostString(), endpoint.getPort());
|
||||
if (endpoint.getHostString().contains(":") && !endpoint.getHostString().contains("["))
|
||||
return String.format("[%s]:%d", endpoint.getHostString(), endpoint.getPort());
|
||||
else
|
||||
return String.format("%s:%d", endpoint.getHostString(), endpoint.getPort());
|
||||
}
|
||||
|
||||
public int getPersistentKeepalive() {
|
||||
@@ -96,6 +104,7 @@ public class Peer {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String getResolvedEndpointString() throws UnknownHostException {
|
||||
if (endpoint == null)
|
||||
throw new UnknownHostException("{empty}");
|
||||
@@ -104,18 +113,18 @@ public class Peer {
|
||||
if (endpoint.isUnresolved())
|
||||
throw new UnknownHostException(endpoint.getHostString());
|
||||
if (endpoint.getAddress() instanceof Inet6Address)
|
||||
return String.format(Locale.getDefault(),
|
||||
"[%s]:%d",
|
||||
return String.format("[%s]:%d",
|
||||
endpoint.getAddress().getHostAddress(),
|
||||
endpoint.getPort());
|
||||
return String.format(Locale.getDefault(),
|
||||
"%s:%d",
|
||||
return String.format("%s:%d",
|
||||
endpoint.getAddress().getHostAddress(),
|
||||
endpoint.getPort());
|
||||
}
|
||||
|
||||
public void parse(final String line) {
|
||||
final Attribute key = Attribute.match(line);
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException(context.getString(R.string.tunnel_error_interface_parse_failed, line));
|
||||
switch (key) {
|
||||
case ALLOWED_IPS:
|
||||
addAllowedIPs(key.parseList(line));
|
||||
@@ -150,7 +159,7 @@ public class Peer {
|
||||
if (endpoint != null && !endpoint.isEmpty()) {
|
||||
final InetSocketAddress constructedEndpoint;
|
||||
if (endpoint.indexOf('/') != -1 || endpoint.indexOf('?') != -1 || endpoint.indexOf('#') != -1)
|
||||
throw new IllegalArgumentException("Forbidden characters in endpoint");
|
||||
throw new IllegalArgumentException(context.getString(R.string.tunnel_error_forbidden_endpoint_chars));
|
||||
final URI uri;
|
||||
try {
|
||||
uri = new URI("wg://" + endpoint);
|
||||
@@ -251,7 +260,7 @@ public class Peer {
|
||||
parent.setPreSharedKey(preSharedKey);
|
||||
parent.setPublicKey(publicKey);
|
||||
if (parent.getPublicKey() == null)
|
||||
throw new IllegalArgumentException("Peer public key may not be empty");
|
||||
throw new IllegalArgumentException(Application.get().getString(R.string.tunnel_error_empty_peer_public_key));
|
||||
loadData(parent);
|
||||
notifyChange();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
package com.wireguard.crypto;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
|
||||
/**
|
||||
* This is a specialized constant-time base64 and hex implementation that resists side-channel attacks.
|
||||
*/
|
||||
@@ -15,11 +18,11 @@ public final class KeyEncoding {
|
||||
public static final int KEY_LENGTH_BASE64 = 44;
|
||||
public static final int KEY_LENGTH_HEX = 64;
|
||||
private static final String KEY_LENGTH_BASE64_EXCEPTION_MESSAGE =
|
||||
"WireGuard base64 keys must be 44 characters encoding 32 bytes";
|
||||
Application.get().getString(R.string.key_length_base64_exception_message);
|
||||
private static final String KEY_LENGTH_EXCEPTION_MESSAGE =
|
||||
"WireGuard keys must be 32 bytes";
|
||||
Application.get().getString(R.string.key_length_exception_message);
|
||||
private static final String KEY_LENGTH_HEX_EXCEPTION_MESSAGE =
|
||||
"WireGuard hex keys must be 64 characters encoding 32 bytes";
|
||||
Application.get().getString(R.string.key_length_hex_exception_message);
|
||||
|
||||
private KeyEncoding() {
|
||||
// Prevent instantiation.
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package com.wireguard.util;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -18,8 +20,10 @@ public interface KeyedList<K, E extends Keyed<? extends K>> extends List<E> {
|
||||
|
||||
boolean containsKey(K key);
|
||||
|
||||
@Nullable
|
||||
E get(K key);
|
||||
|
||||
@Nullable
|
||||
E getLast(K key);
|
||||
|
||||
int indexOfKey(K key);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package com.wireguard.util;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
@@ -17,10 +19,12 @@ import java.util.Set;
|
||||
public interface SortedKeyedList<K, E extends Keyed<? extends K>> extends KeyedList<K, E> {
|
||||
Comparator<? super K> comparator();
|
||||
|
||||
@Nullable
|
||||
K firstKey();
|
||||
|
||||
Set<K> keySet();
|
||||
|
||||
@Nullable
|
||||
K lastKey();
|
||||
|
||||
Collection<E> values();
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorBackground"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorBackground"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
|
||||
</vector>
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorBackground"
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" />
|
||||
</vector>
|
||||
@@ -1,5 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/list_item_activated" android:state_activated="true" /> <!-- TODO(msf): themeify this -->
|
||||
<item android:drawable="@android:color/transparent" /> <!-- TODO(msf): themeify this -->
|
||||
</selector>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item>
|
||||
<selector>
|
||||
<item app:state_multiselected="true" android:state_activated="true">
|
||||
<color android:color="?attr/colorControlActivated" />
|
||||
</item>
|
||||
<item app:state_multiselected="false" android:state_activated="true">
|
||||
<color android:color="?attr/colorControlHighlight" />
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
<item android:drawable="?attr/selectableItemBackground" />
|
||||
</layer-list>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/list_item_ripple"> <!-- TODO(msf): themeify this -->
|
||||
<item android:drawable="@drawable/list_item_background" />
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/white" />
|
||||
</ripple>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/master_detail_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:divider="?attr/dividerHorizontal"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
tools:context=".activity.MainActivity">
|
||||
|
||||
<fragment
|
||||
android:name="com.wireguard.android.fragment.TunnelListFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="2"
|
||||
android:tag="LIST" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/detail_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3" />
|
||||
</LinearLayout>
|
||||
@@ -22,7 +22,7 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_item_background_anim"
|
||||
android:background="@drawable/list_item_background"
|
||||
android:padding="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="com.wireguard.android.widget.NameInputFilter" />
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/tunnel_name_text_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tunnel_name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/tunnel_name"
|
||||
android:inputType="textNoSuggestions|textVisiblePassword"
|
||||
app:filter="@{NameInputFilter.newInstance()}" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</layout>
|
||||
@@ -1,7 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/master_fragment"
|
||||
android:id="@+id/master_detail_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
tools:context=".activity.MainActivity">
|
||||
|
||||
<fragment
|
||||
android:name="com.wireguard.android.fragment.TunnelListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:tag="LIST" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toStartOf="@+id/tunnel_switch"
|
||||
android:text="@string/interface_title" />
|
||||
|
||||
<com.wireguard.android.widget.ToggleSwitch
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/public_key_text"
|
||||
style="?android:attr/editTextStyle"
|
||||
style="?attr/editTextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/public_key_label"
|
||||
|
||||
@@ -30,20 +30,65 @@
|
||||
android:id="@+id/tunnel_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@{@dimen/design_fab_size_normal * 1.1f}"
|
||||
android:clipToPadding="false"
|
||||
android:choiceMode="multipleChoiceModal"
|
||||
android:visibility="@{tunnels.size() > 0 ? android.view.View.VISIBLE : android.view.View.GONE}"
|
||||
app:items="@{tunnels}"
|
||||
app:layout="@{@layout/tunnel_list_item}"
|
||||
app:configurationHandler="@{rowConfigurationHandler}" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="@{tunnels.size() == 0 ? android.view.View.VISIBLE : android.view.View.GONE}"
|
||||
android:layout_gravity="center">
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/logo_placeholder"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0.3333333"
|
||||
android:layout_marginTop="-70dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/tunnel_list_placeholder" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.wireguard.android.widget.fab.FloatingActionsMenu
|
||||
android:id="@+id/create_menu"
|
||||
android:clipChildren="false"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
app:fab_labelStyle="@style/fab_label"
|
||||
android:clipChildren="false"
|
||||
app:fab_labelsPosition="left" >
|
||||
app:fab_labelsPosition="@integer/label_position"
|
||||
app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior" >
|
||||
|
||||
<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:srcCompat="@drawable/ic_action_open_white"
|
||||
app:fabSize="mini"
|
||||
app:fab_title="@string/create_from_file" />
|
||||
|
||||
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
|
||||
android:id="@+id/scan_qr_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{fragment::onRequestScanQRCode}"
|
||||
app:srcCompat="@drawable/ic_action_scan_qr_code_white"
|
||||
app:fabSize="mini"
|
||||
app:fab_title="@string/create_from_qrcode" />
|
||||
|
||||
<com.wireguard.android.widget.fab.LabeledFloatingActionButton
|
||||
android:id="@+id/create_empty"
|
||||
@@ -51,17 +96,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{fragment::onRequestCreateConfig}"
|
||||
app:fabSize="mini"
|
||||
app:srcCompat="@drawable/ic_action_edit_inverse"
|
||||
app:srcCompat="@drawable/ic_action_edit_white"
|
||||
app:fab_title="@string/create_empty" />
|
||||
|
||||
<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: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>
|
||||
</layout>
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
type="com.wireguard.android.fragment.TunnelListFragment" />
|
||||
</data>
|
||||
|
||||
<RelativeLayout
|
||||
<com.wireguard.android.widget.MultiselectableRelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_item_background_anim"
|
||||
android:background="@drawable/list_item_background"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:padding="16dp">
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toStartOf="@+id/tunnel_switch"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{key}" />
|
||||
@@ -52,5 +51,5 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
app:checked="@{item.state == State.UP}"
|
||||
app:onBeforeCheckedChanged="@{fragment::setTunnelState}" />
|
||||
</RelativeLayout>
|
||||
</com.wireguard.android.widget.MultiselectableRelativeLayout>
|
||||
</layout>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="label_position">1</integer>
|
||||
</resources>
|
||||
@@ -1,10 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- TODO(msf): remove these 4 hard-coded colors and replace with theme colors -->
|
||||
<color name="list_item_activated">#64cfd8dc</color>
|
||||
<color name="list_item_ripple">#a0808e95</color>
|
||||
<!-- TODO(msf): remove these 2 hard-coded colors and replace with theme colors -->
|
||||
<color name="fab_label_text_color">#000000</color>
|
||||
<color name="fab_label_background_color">#bbbbbb</color>
|
||||
|
||||
<color name="accent">#BBDEFB</color>
|
||||
<color name="accent">#5e97f6</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="Multiselected">
|
||||
<attr name="state_multiselected" format="boolean"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- TODO(msf): remove these 4 hard-coded colors and replace with theme colors -->
|
||||
<color name="list_item_activated">#cfd8dc</color> <!-- Blue Grey 200 -->
|
||||
<color name="list_item_ripple">#808e95</color> <!-- Blue Grey 200 dark -->
|
||||
<!-- TODO(msf): remove these 2 hard-coded colors and replace with theme colors -->
|
||||
<color name="fab_label_text_color">#ffffff</color>
|
||||
<color name="fab_label_background_color">#444444</color>
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
||||
@@ -27,4 +27,5 @@
|
||||
<enum name="right" value="3"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
<integer name="label_position">0</integer>
|
||||
</resources>
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
||||
<string name="create_empty">Create from scratch</string>
|
||||
<string name="create_from_file">Create from file or archive</string>
|
||||
<string name="create_from_qrcode">Create from QR code</string>
|
||||
<string name="create_tunnel">Create Tunnel</string>
|
||||
<string name="dark_theme_title">Use dark theme</string>
|
||||
<string name="dark_theme_summary_on">Currently using dark night theme</string>
|
||||
<string name="dark_theme_summary_off">Currently using light day theme</string>
|
||||
@@ -50,6 +52,7 @@
|
||||
<string name="hint_generated">(generated)</string>
|
||||
<string name="hint_optional">(optional)</string>
|
||||
<string name="hint_random">(random)</string>
|
||||
<string name="import_from_qrcode">Import Tunnel from QR Code</string>
|
||||
<string name="import_error">Unable to import tunnel: %s</string>
|
||||
<string name="import_success">Imported “%s”</string>
|
||||
<string name="interface_title">Interface</string>
|
||||
@@ -68,6 +71,7 @@
|
||||
<string name="private_key">Private key</string>
|
||||
<string name="public_key">Public key</string>
|
||||
<string name="public_key_description">Public key</string>
|
||||
<string name="qrcode_hint">Tip: generate with `qrencode -t ansiutf8 < tunnel.conf`.</string>
|
||||
<string name="restore_on_boot_summary">Bring up previously-enabled tunnels on boot</string>
|
||||
<string name="restore_on_boot_title">Restore on boot</string>
|
||||
<string name="save">Save</string>
|
||||
@@ -89,8 +93,22 @@
|
||||
<string name="tools_installer_title">Install command line tools</string>
|
||||
<string name="tunnel_create_error">Unable to create tunnel: %s</string>
|
||||
<string name="tunnel_create_success">Successfully created tunnel “%s”</string>
|
||||
<string name="tunnel_list_placeholder">Add a tunnel using the blue button</string>
|
||||
<string name="tunnel_name">Tunnel Name</string>
|
||||
<string name="tunnel_rename_error">Unable to rename tunnel: %s</string>
|
||||
<string name="tunnel_rename_success">Successfully renamed tunnel to “%s”</string>
|
||||
<string name="tunnel_error_invalid_name">Invalid name</string>
|
||||
<string name="tunnel_error_already_exists">Tunnel %s already exists</string>
|
||||
<string name="tunnel_error_empty_inetaddress">Empty address</string>
|
||||
<string name="tunnel_error_empty_interface_address">Address is empty</string>
|
||||
<string name="tunnel_error_interface_parse_failed">Unable to parse line: “%s”</string>
|
||||
<string name="tunnel_error_forbidden_endpoint_chars">Forbidden characters in endpoint</string>
|
||||
<string name="tunnel_error_empty_peer_public_key">Peer public key may not be empty</string>
|
||||
<string name="tunnel_error_invalid_config_line">Invalid configuration line: %s</string>
|
||||
<string name="tunnel_error_no_config_information">Could not find any config information</string>
|
||||
<string name="key_length_base64_exception_message">WireGuard base64 keys must be 44 characters encoding 32 bytes</string>
|
||||
<string name="key_length_exception_message">WireGuard keys must be 32 bytes</string>
|
||||
<string name="key_length_hex_exception_message">WireGuard hex keys must be 64 characters encoding 32 bytes</string>
|
||||
<string name="version_title">WireGuard for Android v%s"</string>
|
||||
<string name="version_summary">%s backend v%s</string>
|
||||
<string name="version_summary_checking">Checking %s backend version</string>
|
||||
|
||||
@@ -8,12 +8,12 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||
# Work around https://github.com/android-ndk/ndk/issues/602
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
|
||||
|
||||
add_executable(libwg-quick.so wireguard/src/tools/wg-quick/android.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -pedantic -Wno-missing-field-initializers)
|
||||
add_executable(libwg-quick.so wireguard/src/tools/wg-quick/android.c ndk-compat/compat.c)
|
||||
target_compile_options(libwg-quick.so PUBLIC -O3 -std=gnu11 -Wall -pedantic -Wno-missing-field-initializers -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DWG_PACKAGE_NAME=\"${ANDROID_PACKAGE_NAME}\")
|
||||
|
||||
file(GLOB WG_SOURCES wireguard/src/tools/*.c libmnl/src/*.c)
|
||||
file(GLOB WG_SOURCES wireguard/src/tools/*.c libmnl/src/*.c ndk-compat/compat.c)
|
||||
add_executable(libwg.so ${WG_SOURCES})
|
||||
target_compile_options(libwg.so PUBLIC -idirafter "${CMAKE_CURRENT_SOURCE_DIR}/libmnl/include/" -I "${CMAKE_CURRENT_SOURCE_DIR}/wireguard/src/tools/" -O3 -std=gnu11 -D_GNU_SOURCE -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"\\\"/data/data/com.wireguard.android/cache\\\"\" -Wno-pointer-arith -Wno-unused-parameter)
|
||||
target_compile_options(libwg.so PUBLIC -idirafter "${CMAKE_CURRENT_SOURCE_DIR}/libmnl/include/" -I "${CMAKE_CURRENT_SOURCE_DIR}/wireguard/src/tools/" -O3 -std=gnu11 -D_GNU_SOURCE -include ${CMAKE_CURRENT_SOURCE_DIR}/ndk-compat/compat.h -DHAVE_VISIBILITY_HIDDEN -DRUNSTATEDIR=\"/data/data/${ANDROID_PACKAGE_NAME}/cache\" -Wno-pointer-arith -Wno-unused-parameter)
|
||||
|
||||
add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libwg-go" COMMENT "Building wireguard-go" VERBATIM COMMAND make
|
||||
ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME}
|
||||
@@ -21,6 +21,7 @@ add_custom_target(libwg-go.so WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib
|
||||
ANDROID_TOOLCHAIN_ROOT=${ANDROID_TOOLCHAIN_ROOT}
|
||||
ANDROID_LLVM_TRIPLE=${ANDROID_LLVM_TRIPLE}
|
||||
ANDROID_SYSROOT=${ANDROID_SYSROOT}
|
||||
ANDROID_PACKAGE_NAME=${ANDROID_PACKAGE_NAME}
|
||||
CFLAGS=${CMAKE_C_FLAGS}\ -Wno-unused-command-line-argument
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ -fuse-ld=gold
|
||||
DESTDIR=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
|
||||
|
||||
@@ -23,7 +23,7 @@ default: $(DESTDIR)/libwg-go.so
|
||||
|
||||
GOBUILDARCH := $(NDK_GO_ARCH_MAP_$(shell uname -m))
|
||||
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
GOBUILDVERSION := 1.10.2
|
||||
GOBUILDVERSION := 1.11
|
||||
GOBUILDTARBALL := https://dl.google.com/go/go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
|
||||
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
|
||||
|
||||
@@ -54,5 +54,5 @@ $(DESTDIR)/libwg-go.so: $(FILES) src/git.zx2c4.com/wireguard-go/api-android.go s
|
||||
mkdir -p $(subst ../wireguard-go/,./src/git.zx2c4.com/wireguard-go/,$(dir $(FILES)))
|
||||
$(foreach FILE,$(FILES),ln -sfrt $(subst ../wireguard-go/,./src/git.zx2c4.com/wireguard-go/,$(dir $(FILE))) $(FILE);)
|
||||
GOPATH=$(PWD) go get -v -d git.zx2c4.com/wireguard-go
|
||||
GOPATH=$(PWD) go build -v -o $(DESTDIR)/libwg-go.so -buildmode c-shared git.zx2c4.com/wireguard-go
|
||||
GOPATH=$(PWD) go build -ldflags="-X main.socketDirectory=/data/data/$(ANDROID_PACKAGE_NAME)/cache/wireguard" -v -o $(DESTDIR)/libwg-go.so -buildmode c-shared git.zx2c4.com/wireguard-go
|
||||
go version > .gobuildversion
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
diff -ru b/src/runtime/sys_linux_386.s a/src/runtime/sys_linux_386.s
|
||||
--- b/src/runtime/sys_linux_386.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_386.s 2018-05-09 17:09:42.319531743 +0200
|
||||
@@ -287,13 +287,13 @@
|
||||
From 6f345a38d9533db388009d91d29981673dcf95b4 Mon Sep 17 00:00:00 2001
|
||||
From: Jason A. Donenfeld <Jason@zx2c4.com>
|
||||
Date: Fri, 04 May 2018 03:48:23 +0200
|
||||
Subject: [PATCH] runtime: use CLOCK_BOOTTIME in nanotime on Linux
|
||||
|
||||
This makes timers account for having expired while a computer was
|
||||
asleep, which is quite common on mobile devices. Note that BOOTTIME is
|
||||
identical to MONOTONIC, except that it takes into account time spent
|
||||
in suspend. In Linux 4.17, the kernel will actually make MONOTONIC act
|
||||
like BOOTTIME anyway, so this switch will additionally unify the
|
||||
timer behavior across kernels.
|
||||
|
||||
BOOTTIME was introduced into Linux 2.6.39-rc1 with 70a08cca1227d in
|
||||
2011.
|
||||
|
||||
Fixes #24595
|
||||
|
||||
Change-Id: I7b2a6ca0c5bc5fce57ec0eeafe7b68270b429321
|
||||
---
|
||||
|
||||
diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s
|
||||
index 8d5a4ff..bfad064 100644
|
||||
--- a/src/runtime/sys_linux_386.s
|
||||
+++ b/src/runtime/sys_linux_386.s
|
||||
@@ -285,13 +285,13 @@
|
||||
|
||||
LEAL 8(SP), BX // &ts (struct timespec)
|
||||
MOVL BX, 4(SP)
|
||||
@@ -17,11 +38,12 @@ diff -ru b/src/runtime/sys_linux_386.s a/src/runtime/sys_linux_386.s
|
||||
LEAL 8(SP), CX
|
||||
INVOKE_SYSCALL
|
||||
|
||||
diff -ru b/src/runtime/sys_linux_amd64.s a/src/runtime/sys_linux_amd64.s
|
||||
--- b/src/runtime/sys_linux_amd64.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_amd64.s 2018-05-09 17:09:42.319531743 +0200
|
||||
@@ -258,7 +258,7 @@
|
||||
MOVQ runtime·__vdso_clock_gettime_sym(SB), AX
|
||||
diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s
|
||||
index 62d8024..8510be4 100644
|
||||
--- a/src/runtime/sys_linux_amd64.s
|
||||
+++ b/src/runtime/sys_linux_amd64.s
|
||||
@@ -257,7 +257,7 @@
|
||||
MOVQ runtime·vdsoClockgettimeSym(SB), AX
|
||||
CMPQ AX, $0
|
||||
JEQ fallback
|
||||
- MOVL $1, DI // CLOCK_MONOTONIC
|
||||
@@ -29,34 +51,55 @@ diff -ru b/src/runtime/sys_linux_amd64.s a/src/runtime/sys_linux_amd64.s
|
||||
LEAQ 0(SP), SI
|
||||
CALL AX
|
||||
MOVQ 0(SP), AX // sec
|
||||
diff -ru b/src/runtime/sys_linux_arm64.s a/src/runtime/sys_linux_arm64.s
|
||||
--- b/src/runtime/sys_linux_arm64.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_arm64.s 2018-05-09 17:12:47.844591628 +0200
|
||||
@@ -201,7 +201,7 @@
|
||||
RET
|
||||
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
|
||||
index aa39732..d29cb88 100644
|
||||
--- a/src/runtime/sys_linux_arm.s
|
||||
+++ b/src/runtime/sys_linux_arm.s
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$24-8
|
||||
- MOVW $1, R0 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R0 // CLOCK_BOOTTIME
|
||||
MOVD RSP, R1
|
||||
MOVD $SYS_clock_gettime, R8
|
||||
SVC
|
||||
diff -ru b/src/runtime/sys_linux_arm.s a/src/runtime/sys_linux_arm.s
|
||||
--- b/src/runtime/sys_linux_arm.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_arm.s 2018-05-09 17:13:18.983281049 +0200
|
||||
@@ -235,7 +235,7 @@
|
||||
#define CLOCK_REALTIME 0
|
||||
-#define CLOCK_MONOTONIC 1
|
||||
+#define CLOCK_BOOTTIME 7
|
||||
|
||||
// int64 nanotime(void)
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$32
|
||||
- MOVW $1, R0 // CLOCK_MONOTONIC
|
||||
+ MOVW $7, R0 // CLOCK_BOOTTIME
|
||||
MOVW $8(R13), R1 // timespec
|
||||
MOVW $SYS_clock_gettime, R7
|
||||
SWI $0
|
||||
diff -ru b/src/runtime/sys_linux_mips64x.s a/src/runtime/sys_linux_mips64x.s
|
||||
--- b/src/runtime/sys_linux_mips64x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_mips64x.s 2018-05-09 17:09:42.321531721 +0200
|
||||
@@ -197,7 +197,7 @@
|
||||
// for EABI, as we don't support OABI
|
||||
#define SYS_BASE 0x0
|
||||
@@ -287,7 +287,7 @@
|
||||
SUB $24, R13 // Space for results
|
||||
BIC $0x7, R13 // Align for C code
|
||||
|
||||
- MOVW $CLOCK_MONOTONIC, R0
|
||||
+ MOVW $CLOCK_BOOTTIME, R0
|
||||
MOVW $8(R13), R1 // timespec
|
||||
MOVW runtime·vdsoClockgettimeSym(SB), R11
|
||||
CMP $0, R11
|
||||
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
|
||||
index c6afd76..24294e8 100644
|
||||
--- a/src/runtime/sys_linux_arm64.s
|
||||
+++ b/src/runtime/sys_linux_arm64.s
|
||||
@@ -13,7 +13,7 @@
|
||||
#define AT_FDCWD -100
|
||||
|
||||
#define CLOCK_REALTIME 0
|
||||
-#define CLOCK_MONOTONIC 1
|
||||
+#define CLOCK_BOOTTIME 7
|
||||
|
||||
#define SYS_exit 93
|
||||
#define SYS_read 63
|
||||
@@ -243,7 +243,7 @@
|
||||
BIC $15, R1
|
||||
MOVD R1, RSP
|
||||
|
||||
- MOVW $CLOCK_MONOTONIC, R0
|
||||
+ MOVW $CLOCK_BOOTTIME, R0
|
||||
MOVD runtime·vdsoClockgettimeSym(SB), R2
|
||||
CBZ R2, fallback
|
||||
BL (R2)
|
||||
diff --git a/src/runtime/sys_linux_mips64x.s b/src/runtime/sys_linux_mips64x.s
|
||||
index 8e64f1c..521062f 100644
|
||||
--- a/src/runtime/sys_linux_mips64x.s
|
||||
+++ b/src/runtime/sys_linux_mips64x.s
|
||||
@@ -185,7 +185,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
@@ -65,10 +108,11 @@ diff -ru b/src/runtime/sys_linux_mips64x.s a/src/runtime/sys_linux_mips64x.s
|
||||
MOVV $0(R29), R5
|
||||
MOVV $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff -ru b/src/runtime/sys_linux_mipsx.s a/src/runtime/sys_linux_mipsx.s
|
||||
--- b/src/runtime/sys_linux_mipsx.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_mipsx.s 2018-05-09 17:09:42.321531721 +0200
|
||||
@@ -202,7 +202,7 @@
|
||||
diff --git a/src/runtime/sys_linux_mipsx.s b/src/runtime/sys_linux_mipsx.s
|
||||
index a6bca3b..920ed9a 100644
|
||||
--- a/src/runtime/sys_linux_mipsx.s
|
||||
+++ b/src/runtime/sys_linux_mipsx.s
|
||||
@@ -189,7 +189,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$8-8
|
||||
@@ -77,10 +121,11 @@ diff -ru b/src/runtime/sys_linux_mipsx.s a/src/runtime/sys_linux_mipsx.s
|
||||
MOVW $4(R29), R5
|
||||
MOVW $SYS_clock_gettime, R2
|
||||
SYSCALL
|
||||
diff -ru b/src/runtime/sys_linux_ppc64x.s a/src/runtime/sys_linux_ppc64x.s
|
||||
--- b/src/runtime/sys_linux_ppc64x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_ppc64x.s 2018-05-09 17:09:42.322531710 +0200
|
||||
@@ -176,7 +176,7 @@
|
||||
diff --git a/src/runtime/sys_linux_ppc64x.s b/src/runtime/sys_linux_ppc64x.s
|
||||
index 483cb8e..11f3c49 100644
|
||||
--- a/src/runtime/sys_linux_ppc64x.s
|
||||
+++ b/src/runtime/sys_linux_ppc64x.s
|
||||
@@ -164,7 +164,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
@@ -89,10 +134,11 @@ diff -ru b/src/runtime/sys_linux_ppc64x.s a/src/runtime/sys_linux_ppc64x.s
|
||||
MOVD $0(R1), R4
|
||||
SYSCALL $SYS_clock_gettime
|
||||
MOVD 0(R1), R3 // sec
|
||||
diff -ru b/src/runtime/sys_linux_s390x.s a/src/runtime/sys_linux_s390x.s
|
||||
--- b/src/runtime/sys_linux_s390x.s 2018-04-30 22:26:39.000000000 +0200
|
||||
+++ a/src/runtime/sys_linux_s390x.s 2018-05-09 17:09:42.322531710 +0200
|
||||
@@ -188,7 +188,7 @@
|
||||
diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s
|
||||
index 1ff110c..e9049e0 100644
|
||||
--- a/src/runtime/sys_linux_s390x.s
|
||||
+++ b/src/runtime/sys_linux_s390x.s
|
||||
@@ -176,7 +176,7 @@
|
||||
RET
|
||||
|
||||
TEXT runtime·nanotime(SB),NOSPLIT,$16
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
@@ -33,11 +34,16 @@ func (l AndroidLogger) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var tunnelHandles map[int32]*Device
|
||||
type TunnelHandle struct {
|
||||
device *Device
|
||||
uapi net.Listener
|
||||
}
|
||||
|
||||
var tunnelHandles map[int32]TunnelHandle
|
||||
|
||||
func init() {
|
||||
roamingDisabled = true
|
||||
tunnelHandles = make(map[int32]*Device)
|
||||
tunnelHandles = make(map[int32]TunnelHandle)
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
go func() {
|
||||
@@ -85,6 +91,29 @@ func wgTurnOn(ifnameRef string, tun_fd int32, settings string) int32 {
|
||||
return -1
|
||||
}
|
||||
|
||||
var uapi net.Listener;
|
||||
|
||||
uapiFile, err := UAPIOpen(name)
|
||||
if err != nil {
|
||||
logger.Error.Println(err)
|
||||
} else {
|
||||
uapi, err = UAPIListen(name, uapiFile)
|
||||
if err != nil {
|
||||
uapiFile.Close()
|
||||
logger.Error.Println(err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
conn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go ipcHandle(device, conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
device.Up()
|
||||
logger.Info.Println("Device started")
|
||||
|
||||
@@ -98,27 +127,30 @@ func wgTurnOn(ifnameRef string, tun_fd int32, settings string) int32 {
|
||||
unix.Close(int(tun_fd))
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = device
|
||||
tunnelHandles[i] = TunnelHandle{device: device, uapi: uapi}
|
||||
return i
|
||||
}
|
||||
|
||||
//export wgTurnOff
|
||||
func wgTurnOff(tunnelHandle int32) {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(tunnelHandles, tunnelHandle)
|
||||
device.Close()
|
||||
if handle.uapi != nil {
|
||||
handle.uapi.Close()
|
||||
}
|
||||
handle.device.Close()
|
||||
}
|
||||
|
||||
//export wgGetSocketV4
|
||||
func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
native, ok := device.net.bind.(*NativeBind)
|
||||
native, ok := handle.device.net.bind.(*NativeBind)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
@@ -138,11 +170,11 @@ func wgGetSocketV4(tunnelHandle int32) int32 {
|
||||
|
||||
//export wgGetSocketV6
|
||||
func wgGetSocketV6(tunnelHandle int32) int32 {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
handle, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
native, ok := device.net.bind.(*NativeBind)
|
||||
native, ok := handle.device.net.bind.(*NativeBind)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FILE_IS_EMPTY
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
|
||||
#undef FILE_IS_EMPTY
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
|
||||
{
|
||||
char *ptr, *eptr;
|
||||
|
||||
if (*buf == NULL || *bufsiz == 0) {
|
||||
*bufsiz = BUFSIZ;
|
||||
if ((*buf = malloc(*bufsiz)) == NULL)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ptr = *buf, eptr = *buf + *bufsiz;;) {
|
||||
int c = fgetc(fp);
|
||||
if (c == -1) {
|
||||
if (feof(fp)) {
|
||||
ssize_t diff = (ssize_t)(ptr - *buf);
|
||||
if (diff != 0) {
|
||||
*ptr = '\0';
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
*ptr++ = c;
|
||||
if (c == delimiter) {
|
||||
*ptr = '\0';
|
||||
return ptr - *buf;
|
||||
}
|
||||
if (ptr + 2 >= eptr) {
|
||||
char *nbuf;
|
||||
size_t nbufsiz = *bufsiz * 2;
|
||||
ssize_t d = ptr - *buf;
|
||||
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
|
||||
return -1;
|
||||
*buf = nbuf;
|
||||
*bufsiz = nbufsiz;
|
||||
eptr = nbuf + nbufsiz;
|
||||
ptr = nbuf + d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp)
|
||||
{
|
||||
return getdelim(buf, bufsiz, '\n', fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
|
||||
#undef FILE_IS_EMPTY
|
||||
#include <string.h>
|
||||
|
||||
char *strchrnul(const char *s, int c)
|
||||
{
|
||||
char *x = strchr(s, c);
|
||||
if (!x)
|
||||
return (char *)s + strlen(s);
|
||||
return x;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FILE_IS_EMPTY
|
||||
#undef FILE_IS_EMPTY
|
||||
static char ____x __attribute__((unused));
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: BSD
|
||||
*
|
||||
* Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 18
|
||||
#include <stdio.h>
|
||||
ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);
|
||||
ssize_t getline(char **buf, size_t *bufsiz, FILE *fp);
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID_API__) && __ANDROID_API__ < 24
|
||||
char *strchrnul(const char *s, int c);
|
||||
#endif
|
||||
|
||||
+1
-1
Submodule app/tools/wireguard updated: e80799ba76...393d8f2fcb
+1
-1
Submodule app/tools/wireguard-go updated: 6b3b1c3b91...15da869b31
+1
-1
@@ -7,7 +7,7 @@ allprojects {
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
|
||||
Reference in New Issue
Block a user