aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/wireguard/android')
-rw-r--r--app/src/main/java/com/wireguard/android/Application.java147
-rw-r--r--app/src/main/java/com/wireguard/android/BootShutdownReceiver.java38
-rw-r--r--app/src/main/java/com/wireguard/android/QuickTileService.java175
-rw-r--r--app/src/main/java/com/wireguard/android/activity/BaseActivity.java99
-rw-r--r--app/src/main/java/com/wireguard/android/activity/MainActivity.java139
-rw-r--r--app/src/main/java/com/wireguard/android/activity/SettingsActivity.java128
-rw-r--r--app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java82
-rw-r--r--app/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java34
-rw-r--r--app/src/main/java/com/wireguard/android/backend/Backend.java79
-rw-r--r--app/src/main/java/com/wireguard/android/backend/GoBackend.java241
-rw-r--r--app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java133
-rw-r--r--app/src/main/java/com/wireguard/android/configStore/ConfigStore.java67
-rw-r--r--app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java103
-rw-r--r--app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java148
-rw-r--r--app/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java140
-rw-r--r--app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java159
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java133
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/BaseFragment.java126
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java118
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java75
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java262
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java479
-rw-r--r--app/src/main/java/com/wireguard/android/model/ApplicationData.java54
-rw-r--r--app/src/main/java/com/wireguard/android/model/Tunnel.java158
-rw-r--r--app/src/main/java/com/wireguard/android/model/TunnelManager.java300
-rw-r--r--app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java112
-rw-r--r--app/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java91
-rw-r--r--app/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java102
-rw-r--r--app/src/main/java/com/wireguard/android/preference/VersionPreference.java60
-rw-r--r--app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java119
-rw-r--r--app/src/main/java/com/wireguard/android/util/AsyncWorker.java63
-rw-r--r--app/src/main/java/com/wireguard/android/util/ClipboardUtils.java37
-rw-r--r--app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java96
-rw-r--r--app/src/main/java/com/wireguard/android/util/ErrorMessages.java130
-rw-r--r--app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java36
-rw-r--r--app/src/main/java/com/wireguard/android/util/FragmentUtils.java27
-rw-r--r--app/src/main/java/com/wireguard/android/util/ModuleLoader.java186
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java109
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java19
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java198
-rw-r--r--app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java17
-rw-r--r--app/src/main/java/com/wireguard/android/util/RootShell.java199
-rw-r--r--app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java92
-rw-r--r--app/src/main/java/com/wireguard/android/util/ToolsInstaller.java180
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java93
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java190
-rw-r--r--app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java380
-rw-r--r--app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java54
-rw-r--r--app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java59
-rw-r--r--app/src/main/java/com/wireguard/android/widget/NameInputFilter.java53
-rw-r--r--app/src/main/java/com/wireguard/android/widget/SlashDrawable.java216
-rw-r--r--app/src/main/java/com/wireguard/android/widget/ToggleSwitch.java59
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java66
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java629
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java27
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java58
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java78
57 files changed, 0 insertions, 7452 deletions
diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java
deleted file mode 100644
index 744986e8..00000000
--- a/app/src/main/java/com/wireguard/android/Application.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import androidx.preference.PreferenceManager;
-import androidx.annotation.Nullable;
-import androidx.appcompat.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.FileConfigStore;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.util.AsyncWorker;
-import com.wireguard.android.util.ModuleLoader;
-import com.wireguard.android.util.RootShell;
-import com.wireguard.android.util.ToolsInstaller;
-
-import java.io.File;
-import java.lang.ref.WeakReference;
-
-import java9.util.concurrent.CompletableFuture;
-
-public class Application extends android.app.Application {
- @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
- private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
- @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
- @Nullable private Backend backend;
- @SuppressWarnings("NullableProblems") private RootShell rootShell;
- @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
- @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
- @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader;
- @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
-
- public Application() {
- weakSelf = new WeakReference<>(this);
- }
-
- public static Application get() {
- return weakSelf.get();
- }
-
- public static AsyncWorker getAsyncWorker() {
- return get().asyncWorker;
- }
-
- public static Backend getBackend() {
- final Application app = get();
- synchronized (app.futureBackend) {
- if (app.backend == null) {
- Backend backend = null;
- boolean didStartRootShell = false;
- if (!app.moduleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) {
- try {
- app.rootShell.start();
- didStartRootShell = true;
- app.moduleLoader.loadModule();
- } catch (final Exception ignored) {
- }
- }
- if (app.moduleLoader.isModuleLoaded()) {
- try {
- if (!didStartRootShell)
- app.rootShell.start();
- backend = new WgQuickBackend(app.getApplicationContext());
- } catch (final Exception ignored) {
- }
- }
- if (backend == null)
- backend = new GoBackend(app.getApplicationContext());
- app.backend = backend;
- }
- return app.backend;
- }
- }
-
- public static CompletableFuture<Backend> getBackendAsync() {
- return get().futureBackend;
- }
-
- public static RootShell getRootShell() {
- return get().rootShell;
- }
-
- public static SharedPreferences getSharedPreferences() {
- return get().sharedPreferences;
- }
-
- public static ToolsInstaller getToolsInstaller() {
- return get().toolsInstaller;
- }
- public static ModuleLoader getModuleLoader() {
- return get().moduleLoader;
- }
-
- public static TunnelManager getTunnelManager() {
- return get().tunnelManager;
- }
-
- @Override
- protected void attachBaseContext(final Context context) {
- super.attachBaseContext(context);
-
- if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_HOME);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- System.exit(0);
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- asyncWorker = new AsyncWorker(AsyncTask.SERIAL_EXECUTOR, new Handler(Looper.getMainLooper()));
- rootShell = new RootShell(getApplicationContext());
- toolsInstaller = new ToolsInstaller(getApplicationContext());
- moduleLoader = new ModuleLoader(getApplicationContext());
-
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- AppCompatDelegate.setDefaultNightMode(
- sharedPreferences.getBoolean("dark_theme", false) ?
- AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
- } else {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
- }
-
- tunnelManager = new TunnelManager(new FileConfigStore(getApplicationContext()));
- tunnelManager.onCreate();
-
- asyncWorker.supplyAsync(Application::getBackend).thenAccept(futureBackend::complete);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/BootShutdownReceiver.java b/app/src/main/java/com/wireguard/android/BootShutdownReceiver.java
deleted file mode 100644
index e3ffce7a..00000000
--- a/app/src/main/java/com/wireguard/android/BootShutdownReceiver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import com.wireguard.android.backend.WgQuickBackend;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.util.ExceptionLoggers;
-
-public class BootShutdownReceiver extends BroadcastReceiver {
- private static final String TAG = "WireGuard/" + BootShutdownReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- Application.getBackendAsync().thenAccept(backend -> {
- if (!(backend instanceof WgQuickBackend))
- return;
- final String action = intent.getAction();
- if (action == null)
- return;
- final TunnelManager tunnelManager = Application.getTunnelManager();
- if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- Log.i(TAG, "Broadcast receiver restoring state (boot)");
- tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D);
- } else if (Intent.ACTION_SHUTDOWN.equals(action)) {
- Log.i(TAG, "Broadcast receiver saving state (shutdown)");
- tunnelManager.saveState();
- }
- });
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java
deleted file mode 100644
index 425738ef..00000000
--- a/app/src/main/java/com/wireguard/android/QuickTileService.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import androidx.databinding.Observable;
-import androidx.databinding.Observable.OnPropertyChangedCallback;
-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 androidx.annotation.Nullable;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.wireguard.android.activity.MainActivity;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.model.Tunnel.State;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.widget.SlashDrawable;
-
-import java.util.Objects;
-
-/**
- * Service that maintains the application's custom Quick Settings tile. This service is bound by the
- * system framework as necessary to update the appearance of the tile in the system UI, and to
- * forward click events to the application.
- */
-
-@TargetApi(Build.VERSION_CODES.N)
-public class QuickTileService extends TileService {
- private static final String TAG = "WireGuard/" + QuickTileService.class.getSimpleName();
-
- private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
- private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
- @Nullable private Icon iconOff;
- @Nullable private Icon iconOn;
- @Nullable private Tunnel tunnel;
-
- /* 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 onClick() {
- if (tunnel != null) {
- final Tile tile = getQsTile();
- if (tile != null) {
- tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
- tile.updateTile();
- }
- tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
- } else {
- final Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityAndCollapse(intent);
- }
- }
-
- @Override
- public void onCreate() {
- 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);
- Canvas c = new Canvas(b);
- icon.setBounds(0, 0, c.getWidth(), c.getHeight());
- icon.draw(c);
- iconOn = Icon.createWithBitmap(b);
- icon.setSlashed(true);
- b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- c = new Canvas(b);
- icon.setBounds(0, 0, c.getWidth(), c.getHeight());
- icon.draw(c);
- iconOff = Icon.createWithBitmap(b);
- }
-
- @Override
- public void onStartListening() {
- Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback);
- if (tunnel != null)
- tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
- updateTile();
- }
-
- @Override
- public void onStopListening() {
- if (tunnel != null)
- tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
- Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback);
- }
-
- private void onToggleFinished(@SuppressWarnings("unused") final State state,
- @Nullable final Throwable throwable) {
- if (throwable == null)
- return;
- final String error = ErrorMessages.get(throwable);
- final String message = getString(R.string.toggle_error, error);
- Log.e(TAG, message, throwable);
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
-
- private void updateTile() {
- // Update the tunnel.
- final Tunnel newTunnel = Application.getTunnelManager().getLastUsedTunnel();
- if (newTunnel != tunnel) {
- if (tunnel != null)
- tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
- tunnel = newTunnel;
- if (tunnel != null)
- tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
- }
- // Update the tile contents.
- final String label;
- final int state;
- final Tile tile = getQsTile();
- if (tunnel != null) {
- label = tunnel.getName();
- state = tunnel.getState() == Tunnel.State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- } else {
- label = getString(R.string.app_name);
- state = Tile.STATE_INACTIVE;
- }
- if (tile == null)
- return;
- tile.setLabel(label);
- if (tile.getState() != state) {
- tile.setIcon(state == Tile.STATE_ACTIVE ? iconOn : iconOff);
- tile.setState(state);
- }
- tile.updateTile();
- }
-
- private final class OnStateChangedCallback extends OnPropertyChangedCallback {
- @Override
- public void onPropertyChanged(final Observable sender, final int propertyId) {
- if (!Objects.equals(sender, tunnel)) {
- sender.removeOnPropertyChangedCallback(this);
- return;
- }
- if (propertyId != 0 && propertyId != BR.state)
- return;
- updateTile();
- }
- }
-
- private final class OnTunnelChangedCallback extends OnPropertyChangedCallback {
- @Override
- public void onPropertyChanged(final Observable sender, final int propertyId) {
- if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
- return;
- updateTile();
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java b/app/src/main/java/com/wireguard/android/activity/BaseActivity.java
deleted file mode 100644
index b27ca6dd..00000000
--- a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import androidx.databinding.CallbackRegistry;
-import androidx.databinding.CallbackRegistry.NotifierCallback;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.model.Tunnel;
-
-import java.util.Objects;
-
-/**
- * Base class for activities that need to remember the currently-selected tunnel.
- */
-
-public abstract class BaseActivity extends ThemeChangeAwareActivity {
- private static final String KEY_SELECTED_TUNNEL = "selected_tunnel";
-
- private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry();
- @Nullable private Tunnel selectedTunnel;
-
- public void addOnSelectedTunnelChangedListener(final OnSelectedTunnelChangedListener listener) {
- selectionChangeRegistry.add(listener);
- }
-
- @Nullable
- public Tunnel getSelectedTunnel() {
- return selectedTunnel;
- }
-
- @Override
- protected void onCreate(@Nullable final Bundle savedInstanceState) {
- // Restore the saved tunnel if there is one; otherwise grab it from the arguments.
- final String savedTunnelName;
- if (savedInstanceState != null)
- savedTunnelName = savedInstanceState.getString(KEY_SELECTED_TUNNEL);
- else if (getIntent() != null)
- savedTunnelName = getIntent().getStringExtra(KEY_SELECTED_TUNNEL);
- 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);
- }
-
- @Override
- protected void onSaveInstanceState(final Bundle outState) {
- if (selectedTunnel != null)
- outState.putString(KEY_SELECTED_TUNNEL, selectedTunnel.getName());
- super.onSaveInstanceState(outState);
- }
-
- protected abstract void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel);
-
- public void removeOnSelectedTunnelChangedListener(
- final OnSelectedTunnelChangedListener listener) {
- selectionChangeRegistry.remove(listener);
- }
-
- public void setSelectedTunnel(@Nullable final Tunnel tunnel) {
- final Tunnel oldTunnel = selectedTunnel;
- if (Objects.equals(oldTunnel, tunnel))
- return;
- selectedTunnel = tunnel;
- onSelectedTunnelChanged(oldTunnel, tunnel);
- selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, tunnel);
- }
-
- public interface OnSelectedTunnelChangedListener {
- void onSelectedTunnelChanged(@Nullable Tunnel oldTunnel, @Nullable Tunnel newTunnel);
- }
-
- private static final class SelectionChangeNotifier
- extends NotifierCallback<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
- @Override
- public void onNotifyCallback(final OnSelectedTunnelChangedListener listener,
- final Tunnel oldTunnel, final int ignored,
- final Tunnel newTunnel) {
- listener.onSelectedTunnelChanged(oldTunnel, newTunnel);
- }
- }
-
- private static final class SelectionChangeRegistry
- extends CallbackRegistry<OnSelectedTunnelChangedListener, Tunnel, Tunnel> {
- private SelectionChangeRegistry() {
- super(new SelectionChangeNotifier());
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/activity/MainActivity.java b/app/src/main/java/com/wireguard/android/activity/MainActivity.java
deleted file mode 100644
index ab9a4f8a..00000000
--- a/app/src/main/java/com/wireguard/android/activity/MainActivity.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.appcompat.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;
-import com.wireguard.android.fragment.TunnelEditorFragment;
-import com.wireguard.android.fragment.TunnelListFragment;
-import com.wireguard.android.model.Tunnel;
-
-/**
- * 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
- implements FragmentManager.OnBackStackChangedListener {
- @Nullable private ActionBar actionBar;
- private boolean isTwoPaneLayout;
- @Nullable private TunnelListFragment listFragment;
-
- @Override
- public void onBackPressed() {
- 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 the two-pane layout does not have an editor open, going back should exit the app.
- if (isTwoPaneLayout && backStackEntries <= 1) {
- finish();
- return;
- }
- // 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
- // calling View#performClick defeats the purpose of it.
- @SuppressLint("ClickableViewAccessibility")
- @Override
- protected void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main_activity);
- 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
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.main_activity, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // The back arrow in the action bar should act the same as the back button.
- onBackPressed();
- return true;
- case R.id.menu_action_edit:
- 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.
- return false;
- case R.id.menu_settings:
- startActivity(new Intent(this, SettingsActivity.class));
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- 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;
- }
- 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();
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
deleted file mode 100644
index 442c93e6..00000000
--- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceScreen;
-import android.util.SparseArray;
-import android.view.MenuItem;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.backend.WgQuickBackend;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Interface for changing application-global persistent settings.
- */
-
-public class SettingsActivity extends ThemeChangeAwareActivity {
- private final SparseArray<PermissionRequestCallback> permissionRequestCallbacks = new SparseArray<>();
- private int permissionRequestCounter;
-
- public void ensurePermissions(final String[] permissions, final PermissionRequestCallback cb) {
- final List<String> needPermissions = new ArrayList<>(permissions.length);
- for (final String permission : permissions) {
- if (ContextCompat.checkSelfPermission(this, permission)
- != PackageManager.PERMISSION_GRANTED)
- needPermissions.add(permission);
- }
- if (needPermissions.isEmpty()) {
- final int[] granted = new int[permissions.length];
- Arrays.fill(granted, PackageManager.PERMISSION_GRANTED);
- cb.done(permissions, granted);
- return;
- }
- final int idx = permissionRequestCounter++;
- permissionRequestCallbacks.put(idx, cb);
- ActivityCompat.requestPermissions(this,
- needPermissions.toArray(new String[needPermissions.size()]), idx);
- }
-
- @Override
- protected void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
- getSupportFragmentManager().beginTransaction()
- .add(android.R.id.content, new SettingsFragment())
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public void onRequestPermissionsResult(final int requestCode,
- final String[] permissions,
- final int[] grantResults) {
- final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode);
- if (f != null) {
- permissionRequestCallbacks.remove(requestCode);
- f.done(permissions, grantResults);
- }
- }
-
- public interface PermissionRequestCallback {
- void done(String[] permissions, int[] grantResults);
- }
-
- public static class SettingsFragment extends PreferenceFragmentCompat {
- @Override
- public void onCreatePreferences(final Bundle savedInstanceState, final String key) {
- addPreferencesFromResource(R.xml.preferences);
- final PreferenceScreen screen = getPreferenceScreen();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- screen.removePreference(getPreferenceManager().findPreference("dark_theme"));
-
- final Preference wgQuickOnlyPrefs[] = {
- getPreferenceManager().findPreference("tools_installer"),
- getPreferenceManager().findPreference("restore_on_boot")
- };
- for (final Preference pref : wgQuickOnlyPrefs)
- pref.setVisible(false);
- Application.getBackendAsync().thenAccept(backend -> {
- for (final Preference pref : wgQuickOnlyPrefs) {
- if (backend instanceof WgQuickBackend)
- pref.setVisible(true);
- else
- screen.removePreference(pref);
- }
- });
-
- final Preference moduleInstaller = getPreferenceManager().findPreference("module_downloader");
- moduleInstaller.setVisible(false);
- if (Application.getModuleLoader().isModuleLoaded()) {
- screen.removePreference(moduleInstaller);
- } else {
- Application.getAsyncWorker().runAsync(Application.getRootShell()::start).whenComplete((v, e) -> {
- if (e == null)
- moduleInstaller.setVisible(true);
- else
- screen.removePreference(moduleInstaller);
- });
- }
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java b/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java
deleted file mode 100644
index 977f64ea..00000000
--- a/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.app.AppCompatDelegate;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-
-import java.lang.reflect.Field;
-
-public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName();
- private static boolean lastDarkMode;
- @Nullable private static Resources lastResources;
-
- private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) {
- if (resources == lastResources && darkMode == lastDarkMode)
- return;
-
- try {
- Field f;
- Object o = resources;
- try {
- f = o.getClass().getDeclaredField("mResourcesImpl");
- f.setAccessible(true);
- o = f.get(o);
- } catch (final Exception ignored) {
- }
- f = o.getClass().getDeclaredField("mDrawableCache");
- f.setAccessible(true);
- o = f.get(o);
- try {
- o.getClass().getMethod("onConfigurationChange", int.class).invoke(o, -1);
- } catch (final Exception ignored) {
- o.getClass().getMethod("clear").invoke(o);
- }
- } catch (final Exception e) {
- Log.e(TAG, "Failed to flush drawable cache", e);
- }
-
- lastResources = resources;
- lastDarkMode = darkMode;
- }
-
-
- @Override
- protected void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
- Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- protected void onDestroy() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
- Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
- super.onDestroy();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
- if ("dark_theme".equals(key)) {
- final boolean darkMode = sharedPreferences.getBoolean(key, false);
- AppCompatDelegate.setDefaultNightMode(
- sharedPreferences.getBoolean(key, false) ?
- AppCompatDelegate.MODE_NIGHT_YES :
- AppCompatDelegate.MODE_NIGHT_NO);
- invalidateDrawableCache(getResources(), darkMode);
- recreate();
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java b/app/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java
deleted file mode 100644
index 08ea8e7e..00000000
--- a/app/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-import com.wireguard.android.fragment.TunnelEditorFragment;
-import com.wireguard.android.model.Tunnel;
-
-/**
- * Standalone activity for creating tunnels.
- */
-
-public class TunnelCreatorActivity extends BaseActivity {
- @Override
- @SuppressWarnings("UnnecessaryFullyQualifiedName")
- protected void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
- getSupportFragmentManager().beginTransaction()
- .add(android.R.id.content, new TunnelEditorFragment())
- .commit();
- }
- }
-
- @Override
- protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
- finish();
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/Backend.java b/app/src/main/java/com/wireguard/android/backend/Backend.java
deleted file mode 100644
index a25bfd04..00000000
--- a/app/src/main/java/com/wireguard/android/backend/Backend.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.model.Tunnel.State;
-import com.wireguard.android.model.Tunnel.Statistics;
-import com.wireguard.config.Config;
-
-import java.util.Set;
-
-/**
- * Interface for implementations of the WireGuard secure network tunnel.
- */
-
-public interface Backend {
- /**
- * Update the volatile configuration of a running tunnel and return the resulting configuration.
- * If the tunnel is not up, return the configuration that would result (if known), or else
- * simply return the given configuration.
- *
- * @param tunnel The tunnel to apply the configuration to.
- * @param config The new configuration for this tunnel.
- * @return The updated configuration of the tunnel.
- */
- Config applyConfig(Tunnel tunnel, Config config) throws Exception;
-
- /**
- * Enumerate the names of currently-running tunnels.
- *
- * @return The set of running tunnel names.
- */
- Set<String> enumerate();
-
- /**
- * Get the actual state of a tunnel.
- *
- * @param tunnel The tunnel to examine the state of.
- * @return The state of the tunnel.
- */
- State getState(Tunnel tunnel) throws Exception;
-
- /**
- * Get statistics about traffic and errors on this tunnel. If the tunnel is not running, the
- * statistics object will be filled with zero values.
- *
- * @param tunnel The tunnel to retrieve statistics for.
- * @return The statistics for the tunnel.
- */
- Statistics getStatistics(Tunnel tunnel) throws Exception;
-
- /**
- * Determine type name of underlying backend.
- *
- * @return Type name
- */
- String getTypePrettyName();
-
- /**
- * Determine version of underlying backend.
- *
- * @return The version of the backend.
- * @throws Exception
- */
- String getVersion() throws Exception;
-
- /**
- * Set the state of a tunnel.
- *
- * @param tunnel The tunnel to control the state of.
- * @param state The new state for this tunnel. Must be {@code UP}, {@code DOWN}, or
- * {@code TOGGLE}.
- * @return The updated state of the tunnel.
- */
- State setState(Tunnel tunnel, State state) throws Exception;
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/GoBackend.java b/app/src/main/java/com/wireguard/android/backend/GoBackend.java
deleted file mode 100644
index e85f2b0d..00000000
--- a/app/src/main/java/com/wireguard/android/backend/GoBackend.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.ParcelFileDescriptor;
-import androidx.annotation.Nullable;
-import androidx.collection.ArraySet;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.activity.MainActivity;
-import com.wireguard.android.model.Tunnel;
-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.SharedLibraryLoader;
-import com.wireguard.config.Config;
-import com.wireguard.config.InetNetwork;
-import com.wireguard.config.Peer;
-
-import java.net.InetAddress;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import java9.util.concurrent.CompletableFuture;
-
-public final class GoBackend implements Backend {
- private static final String TAG = "WireGuard/" + GoBackend.class.getSimpleName();
- private static CompletableFuture<VpnService> vpnService = new CompletableFuture<>();
-
- private final Context context;
- @Nullable private Tunnel currentTunnel;
- private int currentTunnelHandle = -1;
-
- public GoBackend(final Context context) {
- SharedLibraryLoader.loadSharedLibrary(context, "wg-go");
- this.context = context;
- }
-
- private static native int wgGetSocketV4(int handle);
-
- private static native int wgGetSocketV6(int handle);
-
- private static native void wgTurnOff(int handle);
-
- private static native int wgTurnOn(String ifName, int tunFd, String settings);
-
- private static native String wgVersion();
-
- @Override
- public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
- if (tunnel.getState() == State.UP) {
- // Restart the tunnel to apply the new config.
- setStateInternal(tunnel, tunnel.getConfig(), State.DOWN);
- try {
- setStateInternal(tunnel, config, State.UP);
- } catch (final Exception e) {
- // The new configuration didn't work, so try to go back to the old one.
- setStateInternal(tunnel, tunnel.getConfig(), State.UP);
- throw e;
- }
- }
- return config;
- }
-
- @Override
- public Set<String> enumerate() {
- if (currentTunnel != null) {
- final Set<String> runningTunnels = new ArraySet<>();
- runningTunnels.add(currentTunnel.getName());
- return runningTunnels;
- }
- return Collections.emptySet();
- }
-
- @Override
- public State getState(final Tunnel tunnel) {
- return currentTunnel == tunnel ? State.UP : State.DOWN;
- }
-
- @Override
- public Statistics getStatistics(final Tunnel tunnel) {
- return new Statistics();
- }
-
- @Override
- public String getTypePrettyName() {
- return context.getString(R.string.type_name_go_userspace);
- }
-
- @Override
- public String getVersion() {
- return wgVersion();
- }
-
- @Override
- public State setState(final Tunnel tunnel, State state) throws Exception {
- final State originalState = getState(tunnel);
- if (state == State.TOGGLE)
- state = originalState == State.UP ? State.DOWN : State.UP;
- if (state == originalState)
- return originalState;
- if (state == State.UP && currentTunnel != null)
- throw new IllegalStateException(context.getString(R.string.multiple_tunnels_error));
- Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state);
- setStateInternal(tunnel, tunnel.getConfig(), state);
- return getState(tunnel);
- }
-
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state)
- throws Exception {
-
- if (state == State.UP) {
- Log.i(TAG, "Bringing tunnel up");
-
- Objects.requireNonNull(config, context.getString(R.string.no_config_error));
-
- if (VpnService.prepare(context) != null)
- throw new Exception(context.getString(R.string.vpn_not_authorized_error));
-
- final VpnService service;
- if (!vpnService.isDone())
- startVpnService();
-
- try {
- service = vpnService.get(2, TimeUnit.SECONDS);
- } catch (final TimeoutException e) {
- throw new Exception(context.getString(R.string.vpn_start_error), e);
- }
-
- if (currentTunnelHandle != -1) {
- Log.w(TAG, "Tunnel already up");
- return;
- }
-
- // Build config
- final String goConfig = config.toWgUserspaceString();
-
- // Create the vpn tunnel with android API
- final VpnService.Builder builder = service.getBuilder();
- builder.setSession(tunnel.getName());
-
- final Intent configureIntent = new Intent(context, MainActivity.class);
- configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- builder.setConfigureIntent(PendingIntent.getActivity(context, 0, configureIntent, 0));
-
- for (final String excludedApplication : config.getInterface().getExcludedApplications())
- builder.addDisallowedApplication(excludedApplication);
-
- for (final InetNetwork addr : config.getInterface().getAddresses())
- builder.addAddress(addr.getAddress(), addr.getMask());
-
- for (final InetAddress addr : config.getInterface().getDnsServers())
- builder.addDnsServer(addr.getHostAddress());
-
- for (final Peer peer : config.getPeers()) {
- for (final InetNetwork addr : peer.getAllowedIps())
- builder.addRoute(addr.getAddress(), addr.getMask());
- }
-
- builder.setMtu(config.getInterface().getMtu().orElse(1280));
-
- builder.setBlocking(true);
- try (final ParcelFileDescriptor tun = builder.establish()) {
- if (tun == null)
- throw new Exception(context.getString(R.string.tun_create_error));
- Log.d(TAG, "Go backend v" + wgVersion());
- currentTunnelHandle = wgTurnOn(tunnel.getName(), tun.detachFd(), goConfig);
- }
- if (currentTunnelHandle < 0)
- throw new Exception(context.getString(R.string.tunnel_on_error, currentTunnelHandle));
-
- currentTunnel = tunnel;
-
- service.protect(wgGetSocketV4(currentTunnelHandle));
- service.protect(wgGetSocketV6(currentTunnelHandle));
- } else {
- Log.i(TAG, "Bringing tunnel down");
-
- if (currentTunnelHandle == -1) {
- Log.w(TAG, "Tunnel already down");
- return;
- }
-
- wgTurnOff(currentTunnelHandle);
- currentTunnel = null;
- currentTunnelHandle = -1;
- }
- }
-
- private void startVpnService() {
- Log.d(TAG, "Requesting to start VpnService");
- context.startService(new Intent(context, VpnService.class));
- }
-
- public static class VpnService extends android.net.VpnService {
- public Builder getBuilder() {
- return new Builder();
- }
-
- @Override
- public void onCreate() {
- vpnService.complete(this);
- super.onCreate();
- }
-
- @Override
- public void onDestroy() {
- 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();
- }
-
- @Override
- public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
- vpnService.complete(this);
- if (intent == null || intent.getComponent() == null || !intent.getComponent().getPackageName().equals(getPackageName())) {
- Log.d(TAG, "Service started by Always-on VPN feature");
- Application.getTunnelManager().restoreState(true).whenComplete(ExceptionLoggers.D);
- }
- return super.onStartCommand(intent, flags, startId);
- }
-
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java b/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
deleted file mode 100644
index 71427d8a..00000000
--- a/app/src/main/java/com/wireguard/android/backend/WgQuickBackend.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.backend;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.model.Tunnel.State;
-import com.wireguard.android.model.Tunnel.Statistics;
-import com.wireguard.config.Config;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Set;
-
-import java9.util.stream.Collectors;
-import java9.util.stream.Stream;
-
-/**
- * WireGuard backend that uses {@code wg-quick} to implement tunnel configuration.
- */
-
-public final class WgQuickBackend implements Backend {
- private static final String TAG = "WireGuard/" + WgQuickBackend.class.getSimpleName();
-
- private final File localTemporaryDir;
- private final Context context;
-
- public WgQuickBackend(final Context context) {
- localTemporaryDir = new File(context.getCacheDir(), "tmp");
- this.context = context;
- }
-
- @Override
- public Config applyConfig(final Tunnel tunnel, final Config config) throws Exception {
- if (tunnel.getState() == State.UP) {
- // Restart the tunnel to apply the new config.
- setStateInternal(tunnel, tunnel.getConfig(), State.DOWN);
- try {
- setStateInternal(tunnel, config, State.UP);
- } catch (final Exception e) {
- // The new configuration didn't work, so try to go back to the old one.
- setStateInternal(tunnel, tunnel.getConfig(), State.UP);
- throw e;
- }
- }
- return config;
- }
-
- @Override
- public Set<String> enumerate() {
- final List<String> output = new ArrayList<>();
- // Don't throw an exception here or nothing will show up in the UI.
- try {
- Application.getToolsInstaller().ensureToolsAvailable();
- if (Application.getRootShell().run(output, "wg show interfaces") != 0 || output.isEmpty())
- return Collections.emptySet();
- } catch (final Exception e) {
- Log.w(TAG, "Unable to enumerate running tunnels", e);
- return Collections.emptySet();
- }
- // wg puts all interface names on the same line. Split them into separate elements.
- return Stream.of(output.get(0).split(" ")).collect(Collectors.toUnmodifiableSet());
- }
-
- @Override
- public State getState(final Tunnel tunnel) {
- return enumerate().contains(tunnel.getName()) ? State.UP : State.DOWN;
- }
-
- @Override
- public Statistics getStatistics(final Tunnel tunnel) {
- return new Statistics();
- }
-
- @Override
- public String getTypePrettyName() {
- return context.getString(R.string.type_name_kernel_module);
- }
-
- @Override
- public String getVersion() throws Exception {
- final List<String> output = new ArrayList<>();
- if (Application.getRootShell()
- .run(output, "cat /sys/module/wireguard/version") != 0 || output.isEmpty())
- throw new Exception(context.getString(R.string.module_version_error));
- return output.get(0);
- }
-
- @Override
- public State setState(final Tunnel tunnel, State state) throws Exception {
- final State originalState = getState(tunnel);
- if (state == State.TOGGLE)
- state = originalState == State.UP ? State.DOWN : State.UP;
- if (state == originalState)
- return originalState;
- Log.d(TAG, "Changing tunnel " + tunnel.getName() + " to state " + state);
- Application.getToolsInstaller().ensureToolsAvailable();
- setStateInternal(tunnel, tunnel.getConfig(), state);
- return getState(tunnel);
- }
-
- private void setStateInternal(final Tunnel tunnel, @Nullable final Config config, final State state) throws Exception {
- Objects.requireNonNull(config, "Trying to set state with a null config");
-
- final File tempFile = new File(localTemporaryDir, tunnel.getName() + ".conf");
- try (final FileOutputStream stream = new FileOutputStream(tempFile, false)) {
- stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
- }
- String command = String.format("wg-quick %s '%s'",
- state.toString().toLowerCase(Locale.ENGLISH), tempFile.getAbsolutePath());
- if (state == State.UP)
- command = "cat /sys/module/wireguard/version && " + command;
- final int result = Application.getRootShell().run(null, command);
- // noinspection ResultOfMethodCallIgnored
- tempFile.delete();
- if (result != 0)
- throw new Exception(context.getString(R.string.tunnel_config_error, result));
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java
deleted file mode 100644
index d4761464..00000000
--- a/app/src/main/java/com/wireguard/android/configStore/ConfigStore.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.configStore;
-
-import com.wireguard.config.Config;
-
-import java.util.Set;
-
-/**
- * Interface for persistent storage providers for WireGuard configurations.
- */
-
-public interface ConfigStore {
- /**
- * Create a persistent tunnel, which must have a unique name within the persistent storage
- * medium.
- *
- * @param name The name of the tunnel to create.
- * @param config Configuration for the new tunnel.
- * @return The configuration that was actually saved to persistent storage.
- */
- Config create(final String name, final Config config) throws Exception;
-
- /**
- * Delete a persistent tunnel.
- *
- * @param name The name of the tunnel to delete.
- */
- void delete(final String name) throws Exception;
-
- /**
- * Enumerate the names of tunnels present in persistent storage.
- *
- * @return The set of present tunnel names.
- */
- Set<String> enumerate();
-
- /**
- * Load the configuration for the tunnel given by {@code name}.
- *
- * @param name The identifier for the configuration in persistent storage (i.e. the name of the
- * tunnel).
- * @return An in-memory representation of the configuration loaded from persistent storage.
- */
- Config load(final String name) throws Exception;
-
- /**
- * Rename the configuration for the tunnel given by {@code name}.
- *
- * @param name The identifier for the existing configuration in persistent storage.
- * @param replacement The new identifier for the configuration in persistent storage.
- */
- void rename(String name, String replacement) throws Exception;
-
- /**
- * Save the configuration for an existing tunnel given by {@code name}.
- *
- * @param name The identifier for the configuration in persistent storage (i.e. the name of
- * the tunnel).
- * @param config An updated configuration object for the tunnel.
- * @return The configuration that was actually saved to persistent storage.
- */
- Config save(final String name, final Config config) throws Exception;
-}
diff --git a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java b/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
deleted file mode 100644
index 45f2f759..00000000
--- a/app/src/main/java/com/wireguard/android/configStore/FileConfigStore.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.configStore;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.wireguard.android.R;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.Config;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Set;
-
-import java9.util.stream.Collectors;
-import java9.util.stream.Stream;
-
-/**
- * Configuration store that uses a {@code wg-quick}-style file for each configured tunnel.
- */
-
-public final class FileConfigStore implements ConfigStore {
- private static final String TAG = "WireGuard/" + FileConfigStore.class.getSimpleName();
-
- private final Context context;
-
- public FileConfigStore(final Context context) {
- this.context = context;
- }
-
- @Override
- public Config create(final String name, final Config config) throws IOException {
- Log.d(TAG, "Creating configuration for tunnel " + name);
- final File file = fileFor(name);
- if (!file.createNewFile())
- throw new IOException(context.getString(R.string.config_file_exists_error, file.getName()));
- try (final FileOutputStream stream = new FileOutputStream(file, false)) {
- stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
- }
- return config;
- }
-
- @Override
- public void delete(final String name) throws IOException {
- Log.d(TAG, "Deleting configuration for tunnel " + name);
- final File file = fileFor(name);
- if (!file.delete())
- throw new IOException(context.getString(R.string.config_delete_error, file.getName()));
- }
-
- @Override
- public Set<String> enumerate() {
- return Stream.of(context.fileList())
- .filter(name -> name.endsWith(".conf"))
- .map(name -> name.substring(0, name.length() - ".conf".length()))
- .collect(Collectors.toUnmodifiableSet());
- }
-
- private File fileFor(final String name) {
- return new File(context.getFilesDir(), name + ".conf");
- }
-
- @Override
- public Config load(final String name) throws BadConfigException, IOException {
- try (final FileInputStream stream = new FileInputStream(fileFor(name))) {
- return Config.parse(stream);
- }
- }
-
- @Override
- public void rename(final String name, final String replacement) throws IOException {
- Log.d(TAG, "Renaming configuration for tunnel " + name + " to " + replacement);
- final File file = fileFor(name);
- final File replacementFile = fileFor(replacement);
- if (!replacementFile.createNewFile())
- throw new IOException(context.getString(R.string.config_exists_error, replacement));
- if (!file.renameTo(replacementFile)) {
- if (!replacementFile.delete())
- Log.w(TAG, "Couldn't delete marker file for new name " + replacement);
- throw new IOException(context.getString(R.string.config_rename_error, file.getName()));
- }
- }
-
- @Override
- public Config save(final String name, final Config config) throws IOException {
- Log.d(TAG, "Saving configuration for tunnel " + name);
- final File file = fileFor(name);
- if (!file.isFile())
- throw new FileNotFoundException(context.getString(R.string.config_not_found_error, file.getName()));
- try (final FileOutputStream stream = new FileOutputStream(file, false)) {
- stream.write(config.toWgQuickString().getBytes(StandardCharsets.UTF_8));
- }
- return config;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java b/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
deleted file mode 100644
index ee216d4c..00000000
--- a/app/src/main/java/com/wireguard/android/databinding/BindingAdapters.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import androidx.databinding.BindingAdapter;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-import androidx.databinding.adapters.ListenerUtil;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import android.text.InputFilter;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.wireguard.android.BR;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler;
-import com.wireguard.android.util.ObservableKeyedList;
-import com.wireguard.android.widget.ToggleSwitch;
-import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
-import com.wireguard.config.Attribute;
-import com.wireguard.config.InetNetwork;
-import com.wireguard.util.Keyed;
-
-import java9.util.Optional;
-
-/**
- * Static methods for use by generated code in the Android data binding library.
- */
-
-@SuppressWarnings("unused")
-public final class BindingAdapters {
- private BindingAdapters() {
- // Prevent instantiation.
- }
-
- @BindingAdapter("checked")
- public static void setChecked(final ToggleSwitch view, final boolean checked) {
- view.setCheckedInternal(checked);
- }
-
- @BindingAdapter("filter")
- public static void setFilter(final TextView view, final InputFilter filter) {
- view.setFilters(new InputFilter[]{filter});
- }
-
- @BindingAdapter({"items", "layout"})
- public static <E>
- void setItems(final LinearLayout view,
- @Nullable final ObservableList<E> oldList, final int oldLayoutId,
- @Nullable final ObservableList<E> newList, final int newLayoutId) {
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- ItemChangeListener<E> listener = ListenerUtil.getListener(view, R.id.item_change_listener);
- // If the layout changes, any existing listener must be replaced.
- if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
- listener.setList(null);
- listener = null;
- // Stop tracking the old listener.
- ListenerUtil.trackListener(view, null, R.id.item_change_listener);
- }
- // Avoid adding a listener when there is no new list or layout.
- if (newList == null || newLayoutId == 0)
- return;
- if (listener == null) {
- listener = new ItemChangeListener<>(view, newLayoutId);
- ListenerUtil.trackListener(view, listener, R.id.item_change_listener);
- }
- // Either the list changed, or this is an entirely new listener because the layout changed.
- listener.setList(newList);
- }
-
- @BindingAdapter({"items", "layout"})
- public static <E>
- void setItems(final LinearLayout view,
- @Nullable final Iterable<E> oldList, final int oldLayoutId,
- @Nullable final Iterable<E> newList, final int newLayoutId) {
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- view.removeAllViews();
- if (newList == null)
- return;
- final LayoutInflater layoutInflater = LayoutInflater.from(view.getContext());
- for (final E item : newList) {
- final ViewDataBinding binding =
- DataBindingUtil.inflate(layoutInflater, newLayoutId, view, false);
- binding.setVariable(BR.collection, newList);
- binding.setVariable(BR.item, item);
- binding.executePendingBindings();
- view.addView(binding.getRoot());
- }
- }
-
- @BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
- public static <K, E extends Keyed<? extends K>>
- void setItems(final RecyclerView view,
- @Nullable final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
- final RowConfigurationHandler oldRowConfigurationHandler,
- @Nullable final ObservableKeyedList<K, E> newList, final int newLayoutId,
- final RowConfigurationHandler newRowConfigurationHandler) {
- if (view.getLayoutManager() == null)
- view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
-
- if (oldList == newList && oldLayoutId == newLayoutId)
- return;
- // The ListAdapter interface is not generic, so this cannot be checked.
- @SuppressWarnings("unchecked") ObservableKeyedRecyclerViewAdapter<K, E> adapter =
- (ObservableKeyedRecyclerViewAdapter<K, E>) view.getAdapter();
- // If the layout changes, any existing adapter must be replaced.
- if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
- adapter.setList(null);
- adapter = null;
- }
- // Avoid setting an adapter when there is no new list or layout.
- if (newList == null || newLayoutId == 0)
- return;
- if (adapter == null) {
- adapter = new ObservableKeyedRecyclerViewAdapter<>(view.getContext(), newLayoutId, newList);
- view.setAdapter(adapter);
- }
-
- adapter.setRowConfigurationHandler(newRowConfigurationHandler);
- // Either the list changed, or this is an entirely new listener because the layout changed.
- adapter.setList(newList);
- }
-
- @BindingAdapter("onBeforeCheckedChanged")
- public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
- final OnBeforeCheckedChangeListener listener) {
- view.setOnBeforeCheckedChangeListener(listener);
- }
-
- @BindingAdapter("android:text")
- public static void setText(final TextView view, final Optional<?> text) {
- view.setText(text.map(Object::toString).orElse(""));
- }
-
- @BindingAdapter("android:text")
- public static void setText(final TextView view, @Nullable final Iterable<InetNetwork> networks) {
- view.setText(networks != null ? Attribute.join(networks) : "");
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java b/app/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java
deleted file mode 100644
index e7303eae..00000000
--- a/app/src/main/java/com/wireguard/android/databinding/ItemChangeListener.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.wireguard.android.BR;
-
-import java.lang.ref.WeakReference;
-import java.util.Objects;
-
-/**
- * Helper class for binding an ObservableList to the children of a ViewGroup.
- */
-
-class ItemChangeListener<T> {
- private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this);
- private final ViewGroup container;
- private final int layoutId;
- private final LayoutInflater layoutInflater;
- @Nullable private ObservableList<T> list;
-
- ItemChangeListener(final ViewGroup container, final int layoutId) {
- this.container = container;
- this.layoutId = layoutId;
- layoutInflater = LayoutInflater.from(container.getContext());
- }
-
- private View getView(final int position, @Nullable final View convertView) {
- ViewDataBinding binding = convertView != null ? DataBindingUtil.getBinding(convertView) : null;
- if (binding == null) {
- binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false);
- }
-
- Objects.requireNonNull(list, "Trying to get a view while list is still null");
-
- binding.setVariable(BR.collection, list);
- binding.setVariable(BR.item, list.get(position));
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- void setList(@Nullable final ObservableList<T> newList) {
- if (list != null)
- list.removeOnListChangedCallback(callback);
- list = newList;
- if (list != null) {
- list.addOnListChangedCallback(callback);
- callback.onChanged(list);
- } else {
- container.removeAllViews();
- }
- }
-
- private static final class OnListChangedCallback<T>
- extends ObservableList.OnListChangedCallback<ObservableList<T>> {
-
- private final WeakReference<ItemChangeListener<T>> weakListener;
-
- private OnListChangedCallback(final ItemChangeListener<T> listener) {
- weakListener = new WeakReference<>(listener);
- }
-
- @Override
- public void onChanged(final ObservableList<T> sender) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- // TODO: recycle views
- listener.container.removeAllViews();
- for (int i = 0; i < sender.size(); ++i)
- listener.container.addView(listener.getView(i, null));
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- for (int i = positionStart; i < positionStart + itemCount; ++i) {
- final View child = listener.container.getChildAt(i);
- listener.container.removeViewAt(i);
- listener.container.addView(listener.getView(i, child));
- }
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- for (int i = positionStart; i < positionStart + itemCount; ++i)
- listener.container.addView(listener.getView(i, null));
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeMoved(final ObservableList<T> sender, final int fromPosition,
- final int toPosition, final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- final View[] views = new View[itemCount];
- for (int i = 0; i < itemCount; ++i)
- views[i] = listener.container.getChildAt(fromPosition + i);
- listener.container.removeViews(fromPosition, itemCount);
- for (int i = 0; i < itemCount; ++i)
- listener.container.addView(views[i], toPosition + i);
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<T> sender, final int positionStart,
- final int itemCount) {
- final ItemChangeListener<T> listener = weakListener.get();
- if (listener != null) {
- listener.container.removeViews(positionStart, itemCount);
- } else {
- sender.removeOnListChangedCallback(this);
- }
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java b/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
deleted file mode 100644
index 8b40dd91..00000000
--- a/app/src/main/java/com/wireguard/android/databinding/ObservableKeyedRecyclerViewAdapter.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.databinding;
-
-import android.content.Context;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ObservableList;
-import androidx.databinding.ViewDataBinding;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.wireguard.android.BR;
-import com.wireguard.android.util.ObservableKeyedList;
-import com.wireguard.util.Keyed;
-
-import java.lang.ref.WeakReference;
-
-/**
- * A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
- */
-
-public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extends Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder> {
-
- private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
- private final int layoutId;
- private final LayoutInflater layoutInflater;
- @Nullable private ObservableKeyedList<K, E> list;
- @Nullable private RowConfigurationHandler rowConfigurationHandler;
-
- ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
- final ObservableKeyedList<K, E> list) {
- this.layoutId = layoutId;
- layoutInflater = LayoutInflater.from(context);
- setList(list);
- }
-
- @Nullable
- private E getItem(final int position) {
- if (list == null || position < 0 || position >= list.size())
- return null;
- return list.get(position);
- }
-
- @Override
- public int getItemCount() {
- return list != null ? list.size() : 0;
- }
-
- @Override
- public long getItemId(final int position) {
- final K key = getKey(position);
- return key != null ? key.hashCode() : -1;
- }
-
- @Nullable
- private K getKey(final int position) {
- final E item = getItem(position);
- return item != null ? item.getKey() : null;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void onBindViewHolder(final ViewHolder holder, final int position) {
- holder.binding.setVariable(BR.collection, list);
- holder.binding.setVariable(BR.key, getKey(position));
- holder.binding.setVariable(BR.item, getItem(position));
- holder.binding.executePendingBindings();
-
- if (rowConfigurationHandler != null) {
- final E item = getItem(position);
- if (item != null) {
- rowConfigurationHandler.onConfigureRow(holder.binding, item, position);
- }
- }
- }
-
- @Override
- public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
- return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
- }
-
- void setList(@Nullable final ObservableKeyedList<K, E> newList) {
- if (list != null)
- list.removeOnListChangedCallback(callback);
- list = newList;
- if (list != null) {
- list.addOnListChangedCallback(callback);
- }
- notifyDataSetChanged();
- }
-
- void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
- this.rowConfigurationHandler = rowConfigurationHandler;
- }
-
- public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
- void onConfigureRow(B binding, T item, int position);
- }
-
- private static final class OnListChangedCallback<E extends Keyed<?>>
- extends ObservableList.OnListChangedCallback<ObservableList<E>> {
-
- private final WeakReference<ObservableKeyedRecyclerViewAdapter<?, E>> weakAdapter;
-
- private OnListChangedCallback(final ObservableKeyedRecyclerViewAdapter<?, E> adapter) {
- weakAdapter = new WeakReference<>(adapter);
- }
-
- @Override
- public void onChanged(final ObservableList<E> sender) {
- final ObservableKeyedRecyclerViewAdapter adapter = weakAdapter.get();
- if (adapter != null)
- adapter.notifyDataSetChanged();
- else
- sender.removeOnListChangedCallback(this);
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeMoved(final ObservableList<E> sender, final int fromPosition,
- final int toPosition, final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<E> sender, final int positionStart,
- final int itemCount) {
- onChanged(sender);
- }
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- final ViewDataBinding binding;
-
- public ViewHolder(final ViewDataBinding binding) {
- super(binding.getRoot());
-
- this.binding = binding;
- }
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
deleted file mode 100644
index 67059c73..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.appcompat.app.AlertDialog;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.AppListDialogFragmentBinding;
-import com.wireguard.android.model.ApplicationData;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.util.ObservableKeyedArrayList;
-import com.wireguard.android.util.ObservableKeyedList;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import java9.util.Comparators;
-
-public class AppListDialogFragment extends DialogFragment {
-
- private static final String KEY_EXCLUDED_APPS = "excludedApps";
- private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>();
- @Nullable private List<String> currentlyExcludedApps;
-
- public static <T extends Fragment & AppExclusionListener>
- AppListDialogFragment newInstance(final ArrayList<String> excludedApps, final T target) {
- final Bundle extras = new Bundle();
- extras.putStringArrayList(KEY_EXCLUDED_APPS, excludedApps);
- final AppListDialogFragment fragment = new AppListDialogFragment();
- fragment.setTargetFragment(target, 0);
- fragment.setArguments(extras);
- return fragment;
- }
-
- private void loadData() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- final PackageManager pm = activity.getPackageManager();
- Application.getAsyncWorker().supplyAsync(() -> {
- final Intent launcherIntent = new Intent(Intent.ACTION_MAIN, null);
- launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(launcherIntent, 0);
-
- final List<ApplicationData> appData = new ArrayList<>();
- for (ResolveInfo resolveInfo : resolveInfos) {
- String packageName = resolveInfo.activityInfo.packageName;
- appData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName)));
- }
-
- Collections.sort(appData, Comparators.comparing(ApplicationData::getName, String.CASE_INSENSITIVE_ORDER));
- return appData;
- }).whenComplete(((data, throwable) -> {
- if (data != null) {
- appData.clear();
- appData.addAll(data);
- } else {
- final String error = ErrorMessages.get(throwable);
- final String message = activity.getString(R.string.error_fetching_apps, error);
- Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
- dismissAllowingStateLoss();
- }
- }));
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- currentlyExcludedApps = getArguments().getStringArrayList(KEY_EXCLUDED_APPS);
- }
-
- @Override
- public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
- alertDialogBuilder.setTitle(R.string.excluded_applications);
-
- final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(getActivity().getLayoutInflater(), null, false);
- binding.executePendingBindings();
- alertDialogBuilder.setView(binding.getRoot());
-
- alertDialogBuilder.setPositiveButton(R.string.set_exclusions, (dialog, which) -> setExclusionsAndDismiss());
- alertDialogBuilder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
- alertDialogBuilder.setNeutralButton(R.string.deselect_all, (dialog, which) -> {
- });
-
- binding.setFragment(this);
- binding.setAppData(appData);
-
- loadData();
-
- final AlertDialog dialog = alertDialogBuilder.create();
- dialog.setOnShowListener(d -> dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(view -> {
- for (final ApplicationData app : appData)
- app.setExcludedFromTunnel(false);
- }));
- return dialog;
- }
-
- void setExclusionsAndDismiss() {
- final List<String> excludedApps = new ArrayList<>();
- for (final ApplicationData data : appData) {
- if (data.isExcludedFromTunnel()) {
- excludedApps.add(data.getPackageName());
- }
- }
-
- ((AppExclusionListener) getTargetFragment()).onExcludedAppsSelected(excludedApps);
- dismiss();
- }
-
- public interface AppExclusionListener {
- void onExcludedAppsSelected(List<String> excludedApps);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java b/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java
deleted file mode 100644
index 00e26348..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/BaseFragment.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.content.Context;
-import android.content.Intent;
-import androidx.databinding.DataBindingUtil;
-import androidx.databinding.ViewDataBinding;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.fragment.app.Fragment;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.activity.BaseActivity;
-import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener;
-import com.wireguard.android.backend.GoBackend;
-import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
-import com.wireguard.android.databinding.TunnelListItemBinding;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.model.Tunnel.State;
-import com.wireguard.android.util.ErrorMessages;
-
-/**
- * Base class for fragments that need to know the currently-selected tunnel. Only does anything when
- * attached to a {@code BaseActivity}.
- */
-
-public abstract class BaseFragment extends Fragment implements OnSelectedTunnelChangedListener {
- private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
- private static final String TAG = "WireGuard/" + BaseFragment.class.getSimpleName();
- @Nullable private BaseActivity activity;
- @Nullable private Tunnel pendingTunnel;
- @Nullable private Boolean pendingTunnelUp;
-
- @Nullable
- protected Tunnel getSelectedTunnel() {
- return activity != null ? activity.getSelectedTunnel() : null;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (requestCode == REQUEST_CODE_VPN_PERMISSION) {
- if (pendingTunnel != null && pendingTunnelUp != null)
- setTunnelStateWithPermissionsResult(pendingTunnel, pendingTunnelUp);
- pendingTunnel = null;
- pendingTunnelUp = null;
- }
- }
-
- @Override
- public void onAttach(final Context context) {
- super.onAttach(context);
- if (context instanceof BaseActivity) {
- activity = (BaseActivity) context;
- activity.addOnSelectedTunnelChangedListener(this);
- } else {
- activity = null;
- }
- }
-
- @Override
- public void onDetach() {
- if (activity != null)
- activity.removeOnSelectedTunnelChangedListener(this);
- activity = null;
- super.onDetach();
- }
-
- protected void setSelectedTunnel(@Nullable final Tunnel tunnel) {
- if (activity != null)
- activity.setSelectedTunnel(tunnel);
- }
-
- public void setTunnelState(final View view, final boolean checked) {
- final ViewDataBinding binding = DataBindingUtil.findBinding(view);
- final Tunnel tunnel;
- if (binding instanceof TunnelDetailFragmentBinding)
- tunnel = ((TunnelDetailFragmentBinding) binding).getTunnel();
- else if (binding instanceof TunnelListItemBinding)
- tunnel = ((TunnelListItemBinding) binding).getItem();
- else
- return;
- if (tunnel == null)
- return;
-
- Application.getBackendAsync().thenAccept(backend -> {
- if (backend instanceof GoBackend) {
- final Intent intent = GoBackend.VpnService.prepare(view.getContext());
- if (intent != null) {
- pendingTunnel = tunnel;
- pendingTunnelUp = checked;
- startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION);
- return;
- }
- }
-
- setTunnelStateWithPermissionsResult(tunnel, checked);
- });
- }
-
- private void setTunnelStateWithPermissionsResult(final Tunnel tunnel, final boolean checked) {
- tunnel.setState(State.of(checked)).whenComplete((state, throwable) -> {
- if (throwable == null)
- return;
- final String error = ErrorMessages.get(throwable);
- final int messageResId = checked ? R.string.error_up : R.string.error_down;
- final String message = getContext().getString(messageResId, error);
- final View view = getView();
- if (view != null)
- Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
- else
- Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
- Log.e(TAG, message, throwable);
- });
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java b/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
deleted file mode 100644
index e8f0419c..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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 androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.appcompat.app.AlertDialog;
-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.BadConfigException;
-import com.wireguard.config.Config;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Objects;
-
-public class ConfigNamingDialogFragment extends DialogFragment {
- private static final String KEY_CONFIG_TEXT = "config_text";
-
- @Nullable private ConfigNamingDialogFragmentBinding binding;
- @Nullable private Config config;
- @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;
- }
-
- private void createTunnelAndDismiss() {
- if (binding != null) {
- final String name = binding.tunnelNameText.getText().toString();
-
- Application.getTunnelManager().create(name, config).whenComplete((tunnel, throwable) -> {
- if (tunnel != null) {
- dismiss();
- } else {
- binding.tunnelNameTextLayout.setError(throwable.getMessage());
- }
- });
- }
- }
-
- @Override
- public void dismiss() {
- setKeyboardVisible(false);
- super.dismiss();
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle arguments = getArguments();
- final String configText = arguments.getString(KEY_CONFIG_TEXT);
- final byte[] configBytes = configText.getBytes(StandardCharsets.UTF_8);
- try {
- config = Config.parse(new ByteArrayInputStream(configBytes));
- } catch (final BadConfigException | IOException e) {
- throw new IllegalArgumentException("Invalid config passed to " + getClass().getSimpleName(), e);
- }
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Activity activity = Objects.requireNonNull(getActivity());
-
- imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
-
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
- alertDialogBuilder.setTitle(R.string.import_from_qr_code);
-
- binding = ConfigNamingDialogFragmentBinding.inflate(activity.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 onResume() {
- super.onResume();
-
- final AlertDialog dialog = (AlertDialog) getDialog();
- if (dialog != null) {
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> createTunnelAndDismiss());
-
- setKeyboardVisible(true);
- }
- }
-
- 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);
- }
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
deleted file mode 100644
index 8d2be476..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.TunnelDetailFragmentBinding;
-import com.wireguard.android.model.Tunnel;
-
-/**
- * Fragment that shows details about a specific tunnel.
- */
-
-public class TunnelDetailFragment extends BaseFragment {
- @Nullable private TunnelDetailFragmentBinding binding;
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.tunnel_detail, menu);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelDetailFragmentBinding.inflate(inflater, container, false);
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
- if (binding == null)
- return;
- binding.setTunnel(newTunnel);
- if (newTunnel == null)
- binding.setConfig(null);
- else
- newTunnel.getConfigAsync().thenAccept(binding::setConfig);
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
- onSelectedTunnelChanged(null, getSelectedTunnel());
- super.onViewStateRestored(savedInstanceState);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
deleted file mode 100644
index 4990e69f..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.app.Activity;
-import android.content.Context;
-import androidx.databinding.ObservableList;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.fragment.app.FragmentManager;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.databinding.TunnelEditorFragmentBinding;
-import com.wireguard.android.fragment.AppListDialogFragment.AppExclusionListener;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.viewmodel.ConfigProxy;
-import com.wireguard.config.Config;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Fragment for editing a WireGuard configuration.
- */
-
-public class TunnelEditorFragment extends BaseFragment implements AppExclusionListener {
- private static final String KEY_LOCAL_CONFIG = "local_config";
- private static final String KEY_ORIGINAL_NAME = "original_name";
- private static final String TAG = "WireGuard/" + TunnelEditorFragment.class.getSimpleName();
-
- @Nullable private TunnelEditorFragmentBinding binding;
- @Nullable private Tunnel tunnel;
-
- private void onConfigLoaded(final Config config) {
- if (binding != null) {
- binding.setConfig(new ConfigProxy(config));
- }
- }
-
- private void onConfigSaved(final Tunnel savedTunnel,
- @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- message = getString(R.string.config_save_success, savedTunnel.getName());
- Log.d(TAG, message);
- Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
- onFinished();
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.config_save_error, savedTunnel.getName(), error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- @Override
- public void onCreate(@Nullable final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.config_editor, menu);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelEditorFragmentBinding.inflate(inflater, container, false);
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onExcludedAppsSelected(final List<String> excludedApps) {
- Objects.requireNonNull(binding, "Tried to set excluded apps while no view was loaded");
- final ObservableList<String> excludedApplications =
- binding.getConfig().getInterface().getExcludedApplications();
- excludedApplications.clear();
- excludedApplications.addAll(excludedApps);
- }
-
- private void onFinished() {
- // Hide the keyboard; it rarely goes away on its own.
- final Activity activity = getActivity();
- if (activity == null) return;
- final View focusedView = activity.getCurrentFocus();
- if (focusedView != null) {
- final Object service = activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- final InputMethodManager inputManager = (InputMethodManager) service;
- if (inputManager != null)
- inputManager.hideSoftInputFromWindow(focusedView.getWindowToken(),
- InputMethodManager.HIDE_NOT_ALWAYS);
- }
- // 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())
- setSelectedTunnel(null);
- setSelectedTunnel(savedTunnel);
- });
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_action_save:
- if (binding == null)
- return false;
- final Config newConfig;
- try {
- newConfig = binding.getConfig().resolve();
- } catch (final Exception e) {
- final String error = ErrorMessages.get(e);
- final String tunnelName = tunnel == null ? binding.getName() : tunnel.getName();
- final String message = getString(R.string.config_save_error, tunnelName, error);
- Log.e(TAG, message, e);
- Snackbar.make(binding.mainContainer, error, Snackbar.LENGTH_LONG).show();
- return false;
- }
- if (tunnel == null) {
- Log.d(TAG, "Attempting to create new tunnel " + binding.getName());
- final TunnelManager manager = Application.getTunnelManager();
- manager.create(binding.getName(), newConfig)
- .whenComplete(this::onTunnelCreated);
- } else if (!tunnel.getName().equals(binding.getName())) {
- Log.d(TAG, "Attempting to rename tunnel to " + binding.getName());
- tunnel.setName(binding.getName())
- .whenComplete((a, b) -> onTunnelRenamed(tunnel, newConfig, b));
- } else {
- Log.d(TAG, "Attempting to save config of " + tunnel.getName());
- tunnel.setConfig(newConfig)
- .whenComplete((a, b) -> onConfigSaved(tunnel, b));
- }
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- public void onRequestSetExcludedApplications(@SuppressWarnings("unused") final View view) {
- final FragmentManager fragmentManager = getFragmentManager();
- if (fragmentManager != null && binding != null) {
- final ArrayList<String> excludedApps = new ArrayList<>(binding.getConfig().getInterface().getExcludedApplications());
- final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this);
- fragment.show(fragmentManager, null);
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- if (binding != null)
- outState.putParcelable(KEY_LOCAL_CONFIG, binding.getConfig());
- outState.putString(KEY_ORIGINAL_NAME, tunnel == null ? null : tunnel.getName());
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel,
- @Nullable final Tunnel newTunnel) {
- tunnel = newTunnel;
- if (binding == null)
- return;
- binding.setConfig(new ConfigProxy());
- if (tunnel != null) {
- binding.setName(tunnel.getName());
- tunnel.getConfigAsync().thenAccept(this::onConfigLoaded);
- } else {
- binding.setName("");
- }
- }
-
- private void onTunnelCreated(final Tunnel newTunnel, @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- tunnel = newTunnel;
- message = getString(R.string.tunnel_create_success, tunnel.getName());
- Log.d(TAG, message);
- Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
- onFinished();
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.tunnel_create_error, error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- private void onTunnelRenamed(final Tunnel renamedTunnel, final Config newConfig,
- @Nullable final Throwable throwable) {
- final String message;
- if (throwable == null) {
- message = getString(R.string.tunnel_rename_success, renamedTunnel.getName());
- Log.d(TAG, message);
- // Now save the rest of configuration changes.
- Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel.getName());
- renamedTunnel.setConfig(newConfig).whenComplete((a, b) -> onConfigSaved(renamedTunnel, b));
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.tunnel_rename_error, error);
- Log.e(TAG, message, throwable);
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
-
- if (savedInstanceState == null) {
- onSelectedTunnelChanged(null, getSelectedTunnel());
- } else {
- tunnel = getSelectedTunnel();
- final ConfigProxy config = savedInstanceState.getParcelable(KEY_LOCAL_CONFIG);
- final String originalName = savedInstanceState.getString(KEY_ORIGINAL_NAME);
- if (tunnel != null && !tunnel.getName().equals(originalName))
- onSelectedTunnelChanged(null, tunnel);
- else
- binding.setConfig(config);
- }
-
- super.onViewStateRestored(savedInstanceState);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
deleted file mode 100644
index 59260500..00000000
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.fragment;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.OpenableColumns;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.fragment.app.FragmentManager;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.view.ActionMode;
-import androidx.recyclerview.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-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;
-import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter;
-import com.wireguard.android.databinding.TunnelListFragmentBinding;
-import com.wireguard.android.databinding.TunnelListItemBinding;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.widget.MultiselectableRelativeLayout;
-import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.Config;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import java9.util.concurrent.CompletableFuture;
-import java9.util.stream.StreamSupport;
-
-/**
- * Fragment containing a list of known WireGuard tunnels. It allows creating and deleting tunnels.
- */
-
-public class TunnelListFragment extends BaseFragment {
- private static final int REQUEST_IMPORT = 1;
- private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
-
- private final ActionModeListener actionModeListener = new ActionModeListener();
- @Nullable private ActionMode actionMode;
- @Nullable private TunnelListFragmentBinding binding;
-
- public boolean collapseActionMenu() {
- if (binding != null && binding.createMenu.isExpanded()) {
- binding.createMenu.collapse();
- return true;
- }
- return false;
- }
-
- private void importTunnel(@NonNull final String configText) {
- try {
- // Ensure the config text is parseable before proceeding…
- Config.parse(new ByteArrayInputStream(configText.getBytes(StandardCharsets.UTF_8)));
-
- // Config text is valid, now create the tunnel…
- final FragmentManager fragmentManager = getFragmentManager();
- if (fragmentManager != null)
- ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null);
- } catch (final BadConfigException | IOException e) {
- onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(e));
- }
- }
-
- private void importTunnel(@Nullable final Uri uri) {
- final Activity activity = getActivity();
- if (activity == null || uri == null)
- return;
- final ContentResolver contentResolver = activity.getContentResolver();
-
- final Collection<CompletableFuture<Tunnel>> futureTunnels = new ArrayList<>();
- final List<Throwable> throwables = new ArrayList<>();
- Application.getAsyncWorker().supplyAsync(() -> {
- final String[] columns = {OpenableColumns.DISPLAY_NAME};
- String name = null;
- try (Cursor cursor = contentResolver.query(uri, columns,
- null, null, null)) {
- if (cursor != null && cursor.moveToFirst() && !cursor.isNull(0))
- name = cursor.getString(0);
- }
- if (name == null)
- name = Uri.decode(uri.getLastPathSegment());
- int idx = name.lastIndexOf('/');
- if (idx >= 0) {
- if (idx >= name.length() - 1)
- throw new IllegalArgumentException(getResources().getString(R.string.illegal_filename_error, name));
- name = name.substring(idx + 1);
- }
- boolean isZip = name.toLowerCase(Locale.ENGLISH).endsWith(".zip");
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf"))
- name = name.substring(0, name.length() - ".conf".length());
- else if (!isZip)
- throw new IllegalArgumentException(getResources().getString(R.string.bad_extension_error));
-
- if (isZip) {
- try (ZipInputStream zip = new ZipInputStream(contentResolver.openInputStream(uri));
- BufferedReader reader = new BufferedReader(new InputStreamReader(zip))) {
- ZipEntry entry;
- while ((entry = zip.getNextEntry()) != null) {
- if (entry.isDirectory())
- continue;
- name = entry.getName();
- idx = name.lastIndexOf('/');
- if (idx >= 0) {
- if (idx >= name.length() - 1)
- continue;
- name = name.substring(name.lastIndexOf('/') + 1);
- }
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".conf"))
- name = name.substring(0, name.length() - ".conf".length());
- else
- continue;
- Config config = null;
- try {
- config = Config.parse(reader);
- } catch (Exception e) {
- throwables.add(e);
- }
- if (config != null)
- futureTunnels.add(Application.getTunnelManager().create(name, config).toCompletableFuture());
- }
- }
- } else {
- futureTunnels.add(Application.getTunnelManager().create(name,
- Config.parse(contentResolver.openInputStream(uri))).toCompletableFuture());
- }
-
- if (futureTunnels.isEmpty()) {
- if (throwables.size() == 1)
- throw throwables.get(0);
- else if (throwables.isEmpty())
- throw new IllegalArgumentException(getResources().getString(R.string.no_configs_error));
- }
-
- return CompletableFuture.allOf(futureTunnels.toArray(new CompletableFuture[futureTunnels.size()]));
- }).whenComplete((future, exception) -> {
- if (exception != null) {
- onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception));
- } else {
- future.whenComplete((ignored1, ignored2) -> {
- final List<Tunnel> tunnels = new ArrayList<>(futureTunnels.size());
- for (final CompletableFuture<Tunnel> futureTunnel : futureTunnels) {
- Tunnel tunnel = null;
- try {
- tunnel = futureTunnel.getNow(null);
- } catch (final Exception e) {
- throwables.add(e);
- }
- if (tunnel != null)
- tunnels.add(tunnel);
- }
- onTunnelImportFinished(tunnels, throwables);
- });
- }
- });
- }
-
- @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 onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
- switch (requestCode) {
- case REQUEST_IMPORT:
- 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,
- @Nullable final Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- binding = TunnelListFragmentBinding.inflate(inflater, container, false);
-
- binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
- if (binding != null) {
- binding.createMenu.collapse();
- }
- return false;
- });
- binding.tunnelList.setOnScrollListener(new FloatingActionsMenuRecyclerViewScrollListener(binding.createMenu));
- binding.executePendingBindings();
- return binding.getRoot();
- }
-
- @Override
- public void onDestroyView() {
- binding = null;
- super.onDestroyView();
- }
-
- @Override
- public void onPause() {
- if (binding != null) {
- binding.createMenu.collapse();
- }
- super.onPause();
- }
-
- public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
- startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- public void onRequestImportConfig(@SuppressWarnings("unused") final View view) {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, REQUEST_IMPORT);
- if (binding != null)
- 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.qr_code_hint));
- intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE));
-
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems());
- }
-
- @Override
- public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
- 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) {
- final String message;
- if (throwable == null) {
- message = getResources().getQuantityString(R.plurals.delete_success, count, count);
- } else {
- final String error = ErrorMessages.get(throwable);
- message = getResources().getQuantityString(R.plurals.delete_error, count, count, error);
- Log.e(TAG, message, throwable);
- }
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
- }
-
- private void onTunnelImportFinished(final List<Tunnel> tunnels, final Collection<Throwable> throwables) {
- String message = null;
-
- for (final Throwable throwable : throwables) {
- final String error = ErrorMessages.get(throwable);
- message = getString(R.string.import_error, error);
- Log.e(TAG, message, throwable);
- }
-
- if (tunnels.size() == 1 && throwables.isEmpty())
- message = getString(R.string.import_success, tunnels.get(0).getName());
- else if (tunnels.isEmpty() && throwables.size() == 1)
- /* Use the exception message from above. */ ;
- else if (throwables.isEmpty())
- message = getResources().getQuantityString(R.plurals.import_total_success,
- tunnels.size(), tunnels.size());
- else if (!throwables.isEmpty())
- message = getResources().getQuantityString(R.plurals.import_partial_success,
- tunnels.size() + throwables.size(),
- tunnels.size(), tunnels.size() + throwables.size());
-
- if (binding != null)
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
-
- @Override
- public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
-
- if (binding == null) {
- return;
- }
-
- binding.setFragment(this);
- Application.getTunnelManager().getTunnels().thenAccept(binding::setTunnels);
- binding.setRowConfigurationHandler((ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<TunnelListItemBinding, Tunnel>) (binding, tunnel, position) -> {
- binding.setFragment(this);
- binding.getRoot().setOnClickListener(clicked -> {
- if (actionMode == null) {
- setSelectedTunnel(tunnel);
- } else {
- actionModeListener.toggleItemChecked(position);
- }
- });
- binding.getRoot().setOnLongClickListener(clicked -> {
- actionModeListener.toggleItemChecked(position);
- return true;
- });
-
- if (actionMode != null)
- ((MultiselectableRelativeLayout) binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position));
- else
- ((MultiselectableRelativeLayout) binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel);
- });
- }
-
- private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) {
- return (MultiselectableRelativeLayout) binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView;
- }
-
- private final class ActionModeListener implements ActionMode.Callback {
- private final Collection<Integer> checkedItems = new HashSet<>();
-
- @Nullable private Resources resources;
-
- public ArrayList<Integer> getCheckedItems() {
- return new ArrayList<>(checkedItems);
- }
-
- @Override
- public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_action_delete:
- 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);
-
- });
- checkedItems.clear();
- mode.finish();
- return true;
- case R.id.menu_action_select_all:
- Application.getTunnelManager().getTunnels().thenAccept(tunnels -> {
- for (int i = 0; i < tunnels.size(); ++i) {
- setItemChecked(i, true);
- }
- });
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
- actionMode = mode;
- if (getActivity() != null) {
- resources = getActivity().getResources();
- }
- mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
- binding.tunnelList.getAdapter().notifyDataSetChanged();
- return true;
- }
-
- @Override
- public void onDestroyActionMode(final ActionMode mode) {
- actionMode = null;
- resources = null;
- checkedItems.clear();
- binding.tunnelList.getAdapter().notifyDataSetChanged();
- }
-
- @Override
- public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
- updateTitle(mode);
- return false;
- }
-
- void setItemChecked(final int position, final boolean checked) {
- if (checked) {
- checkedItems.add(position);
- } else {
- 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 (adapter != null)
- adapter.notifyItemChanged(position);
-
- updateTitle(actionMode);
- }
-
- void toggleItemChecked(final int position) {
- setItemChecked(position, !checkedItems.contains(position));
- }
-
- private void updateTitle(@Nullable final ActionMode mode) {
- if (mode == null) {
- return;
- }
-
- final int count = checkedItems.size();
- if (count == 0) {
- mode.setTitle("");
- } else {
- mode.setTitle(resources.getQuantityString(R.plurals.delete_title, count, count));
- }
- }
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/model/ApplicationData.java b/app/src/main/java/com/wireguard/android/model/ApplicationData.java
deleted file mode 100644
index 65edff90..00000000
--- a/app/src/main/java/com/wireguard/android/model/ApplicationData.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.model;
-
-import androidx.databinding.BaseObservable;
-import androidx.databinding.Bindable;
-import android.graphics.drawable.Drawable;
-
-import com.wireguard.android.BR;
-import com.wireguard.util.Keyed;
-
-public class ApplicationData extends BaseObservable implements Keyed<String> {
- private final Drawable icon;
- private final String name;
- private final String packageName;
- private boolean excludedFromTunnel;
-
- public ApplicationData(final Drawable icon, final String name, final String packageName, final boolean excludedFromTunnel) {
- this.icon = icon;
- this.name = name;
- this.packageName = packageName;
- this.excludedFromTunnel = excludedFromTunnel;
- }
-
- public Drawable getIcon() {
- return icon;
- }
-
- @Override
- public String getKey() {
- return name;
- }
-
- public String getName() {
- return name;
- }
-
- public String getPackageName() {
- return packageName;
- }
-
- @Bindable
- public boolean isExcludedFromTunnel() {
- return excludedFromTunnel;
- }
-
- public void setExcludedFromTunnel(final boolean excludedFromTunnel) {
- this.excludedFromTunnel = excludedFromTunnel;
- notifyPropertyChanged(BR.excludedFromTunnel);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/model/Tunnel.java b/app/src/main/java/com/wireguard/android/model/Tunnel.java
deleted file mode 100644
index 49e78a22..00000000
--- a/app/src/main/java/com/wireguard/android/model/Tunnel.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.model;
-
-import androidx.databinding.BaseObservable;
-import androidx.databinding.Bindable;
-import androidx.annotation.Nullable;
-
-import com.wireguard.android.BR;
-import com.wireguard.android.util.ExceptionLoggers;
-import com.wireguard.config.Config;
-import com.wireguard.util.Keyed;
-
-import java.util.regex.Pattern;
-
-import java9.util.concurrent.CompletableFuture;
-import java9.util.concurrent.CompletionStage;
-
-/**
- * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
- */
-
-public class Tunnel extends BaseObservable implements Keyed<String> {
- public static final int NAME_MAX_LENGTH = 15;
- private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,15}");
-
- private final TunnelManager manager;
- @Nullable private Config config;
- private String name;
- private State state;
- @Nullable private Statistics statistics;
-
- Tunnel(final TunnelManager manager, final String name,
- @Nullable final Config config, final State state) {
- this.manager = manager;
- this.name = name;
- this.config = config;
- this.state = state;
- }
-
- public static boolean isNameInvalid(final CharSequence name) {
- return !NAME_PATTERN.matcher(name).matches();
- }
-
- public CompletionStage<Void> delete() {
- return manager.delete(this);
- }
-
- @Bindable
- @Nullable
- public Config getConfig() {
- if (config == null)
- manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E);
- return config;
- }
-
- public CompletionStage<Config> getConfigAsync() {
- if (config == null)
- return manager.getTunnelConfig(this);
- return CompletableFuture.completedFuture(config);
- }
-
- @Override
- public String getKey() {
- return name;
- }
-
- @Bindable
- public String getName() {
- return name;
- }
-
- @Bindable
- public State getState() {
- return state;
- }
-
- public CompletionStage<State> getStateAsync() {
- return TunnelManager.getTunnelState(this);
- }
-
- @Bindable
- @Nullable
- public Statistics getStatistics() {
- // FIXME: Check age of statistics.
- if (statistics == null)
- TunnelManager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E);
- return statistics;
- }
-
- public CompletionStage<Statistics> getStatisticsAsync() {
- // FIXME: Check age of statistics.
- if (statistics == null)
- return TunnelManager.getTunnelStatistics(this);
- return CompletableFuture.completedFuture(statistics);
- }
-
- Config onConfigChanged(final Config config) {
- this.config = config;
- notifyPropertyChanged(BR.config);
- return config;
- }
-
- public String onNameChanged(final String name) {
- this.name = name;
- notifyPropertyChanged(BR.name);
- return name;
- }
-
- State onStateChanged(final State state) {
- if (state != State.UP)
- onStatisticsChanged(null);
- this.state = state;
- notifyPropertyChanged(BR.state);
- return state;
- }
-
- @Nullable
- Statistics onStatisticsChanged(@Nullable final Statistics statistics) {
- this.statistics = statistics;
- notifyPropertyChanged(BR.statistics);
- return statistics;
- }
-
- public CompletionStage<Config> setConfig(final Config config) {
- if (!config.equals(this.config))
- return manager.setTunnelConfig(this, config);
- return CompletableFuture.completedFuture(this.config);
- }
-
- public CompletionStage<String> setName(final String name) {
- if (!name.equals(this.name))
- return manager.setTunnelName(this, name);
- return CompletableFuture.completedFuture(this.name);
- }
-
- public CompletionStage<State> setState(final State state) {
- if (state != this.state)
- return manager.setTunnelState(this, state);
- return CompletableFuture.completedFuture(this.state);
- }
-
- public enum State {
- DOWN,
- TOGGLE,
- UP;
-
- public static State of(final boolean running) {
- return running ? UP : DOWN;
- }
- }
-
- public static class Statistics extends BaseObservable {
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/model/TunnelManager.java b/app/src/main/java/com/wireguard/android/model/TunnelManager.java
deleted file mode 100644
index 2deea6df..00000000
--- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.model;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import androidx.databinding.BaseObservable;
-import androidx.databinding.Bindable;
-import androidx.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.ObservableSortedKeyedArrayList;
-import com.wireguard.android.util.ObservableSortedKeyedList;
-import com.wireguard.config.Config;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Set;
-
-import java9.util.Comparators;
-import java9.util.concurrent.CompletableFuture;
-import java9.util.concurrent.CompletionStage;
-import java9.util.stream.Collectors;
-import java9.util.stream.StreamSupport;
-
-/**
- * Maintains and mediates changes to the set of available WireGuard tunnels,
- */
-
-public final class TunnelManager extends BaseObservable {
- private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing(
- String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder());
- private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel";
- private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
- private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
-
- private final CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> completableTunnels = new CompletableFuture<>();
- private final ConfigStore configStore;
- private final Context context = Application.get();
- private final ArrayList<CompletableFuture<Void>> delayedLoadRestoreTunnels = new ArrayList<>();
- private final ObservableSortedKeyedList<String, Tunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR);
- private boolean haveLoaded;
- @Nullable private Tunnel lastUsedTunnel;
-
- public TunnelManager(final ConfigStore configStore) {
- this.configStore = configStore;
- }
-
- static CompletionStage<State> getTunnelState(final Tunnel tunnel) {
- return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel))
- .thenApply(tunnel::onStateChanged);
- }
-
- static CompletionStage<Statistics> getTunnelStatistics(final Tunnel tunnel) {
- return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel))
- .thenApply(tunnel::onStatisticsChanged);
- }
-
- private Tunnel addToList(final String name, @Nullable final Config config, final State state) {
- final Tunnel tunnel = new Tunnel(this, name, config, state);
- tunnels.add(tunnel);
- return tunnel;
- }
-
- public CompletionStage<Tunnel> create(final String name, @Nullable final Config config) {
- if (Tunnel.isNameInvalid(name))
- return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)));
- if (tunnels.containsKey(name)) {
- 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))
- .thenApply(savedConfig -> addToList(name, savedConfig, State.DOWN));
- }
-
- CompletionStage<Void> delete(final Tunnel tunnel) {
- final State originalState = tunnel.getState();
- final boolean wasLastUsed = tunnel == lastUsedTunnel;
- // Make sure nothing touches the tunnel.
- if (wasLastUsed)
- setLastUsedTunnel(null);
- tunnels.remove(tunnel);
- return Application.getAsyncWorker().runAsync(() -> {
- if (originalState == State.UP)
- Application.getBackend().setState(tunnel, State.DOWN);
- try {
- configStore.delete(tunnel.getName());
- } catch (final Exception e) {
- if (originalState == State.UP)
- Application.getBackend().setState(tunnel, State.UP);
- // Re-throw the exception to fail the completion.
- throw e;
- }
- }).whenComplete((x, e) -> {
- if (e == null)
- return;
- // Failure, put the tunnel back.
- tunnels.add(tunnel);
- if (wasLastUsed)
- setLastUsedTunnel(tunnel);
- });
- }
-
- @Bindable
- @Nullable
- public Tunnel getLastUsedTunnel() {
- return lastUsedTunnel;
- }
-
- CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
- return Application.getAsyncWorker().supplyAsync(() -> configStore.load(tunnel.getName()))
- .thenApply(tunnel::onConfigChanged);
- }
-
- public CompletableFuture<ObservableSortedKeyedList<String, Tunnel>> getTunnels() {
- return completableTunnels;
- }
-
- public void onCreate() {
- Application.getAsyncWorker().supplyAsync(configStore::enumerate)
- .thenAcceptBoth(Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate()), this::onTunnelsLoaded)
- .whenComplete(ExceptionLoggers.E);
- }
-
- @SuppressWarnings("unchecked")
- private void onTunnelsLoaded(final Iterable<String> present, final Collection<String> running) {
- for (final String name : present)
- addToList(name, null, running.contains(name) ? State.UP : State.DOWN);
- final String lastUsedName = Application.getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null);
- if (lastUsedName != null)
- setLastUsedTunnel(tunnels.get(lastUsedName));
- final CompletableFuture<Void>[] toComplete;
- synchronized (delayedLoadRestoreTunnels) {
- haveLoaded = true;
- toComplete = delayedLoadRestoreTunnels.toArray(new CompletableFuture[delayedLoadRestoreTunnels.size()]);
- delayedLoadRestoreTunnels.clear();
- }
- restoreState(true).whenComplete((v, t) -> {
- for (final CompletableFuture<Void> f : toComplete) {
- if (t == null)
- f.complete(v);
- else
- f.completeExceptionally(t);
- }
- });
-
- completableTunnels.complete(tunnels);
- }
-
- public void refreshTunnelStates() {
- Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().enumerate())
- .thenAccept(running -> {
- for (final Tunnel tunnel : tunnels)
- tunnel.onStateChanged(running.contains(tunnel.getName()) ? State.UP : State.DOWN);
- })
- .whenComplete(ExceptionLoggers.E);
- }
-
- public CompletionStage<Void> restoreState(final boolean force) {
- if (!force && !Application.getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false))
- return CompletableFuture.completedFuture(null);
- synchronized (delayedLoadRestoreTunnels) {
- if (!haveLoaded) {
- final CompletableFuture<Void> f = new CompletableFuture<>();
- delayedLoadRestoreTunnels.add(f);
- return f;
- }
- }
- final Set<String> previouslyRunning = Application.getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null);
- if (previouslyRunning == null)
- return CompletableFuture.completedFuture(null);
- return CompletableFuture.allOf(StreamSupport.stream(tunnels)
- .filter(tunnel -> previouslyRunning.contains(tunnel.getName()))
- .map(tunnel -> setTunnelState(tunnel, State.UP))
- .toArray(CompletableFuture[]::new));
- }
-
- public void saveState() {
- final Set<String> runningTunnels = StreamSupport.stream(tunnels)
- .filter(tunnel -> tunnel.getState() == State.UP)
- .map(Tunnel::getName)
- .collect(Collectors.toUnmodifiableSet());
- Application.getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply();
- }
-
- private void setLastUsedTunnel(@Nullable final Tunnel tunnel) {
- if (tunnel == lastUsedTunnel)
- return;
- lastUsedTunnel = tunnel;
- notifyPropertyChanged(BR.lastUsedTunnel);
- if (tunnel != null)
- Application.getSharedPreferences().edit().putString(KEY_LAST_USED_TUNNEL, tunnel.getName()).apply();
- else
- Application.getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).apply();
- }
-
- CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
- return Application.getAsyncWorker().supplyAsync(() -> {
- final Config appliedConfig = Application.getBackend().applyConfig(tunnel, config);
- return configStore.save(tunnel.getName(), appliedConfig);
- }).thenApply(tunnel::onConfigChanged);
- }
-
- CompletionStage<String> setTunnelName(final Tunnel tunnel, final String name) {
- if (Tunnel.isNameInvalid(name))
- return CompletableFuture.failedFuture(new IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)));
- if (tunnels.containsKey(name)) {
- final String message = context.getString(R.string.tunnel_error_already_exists, name);
- return CompletableFuture.failedFuture(new IllegalArgumentException(message));
- }
- final State originalState = tunnel.getState();
- final boolean wasLastUsed = tunnel == lastUsedTunnel;
- // Make sure nothing touches the tunnel.
- if (wasLastUsed)
- setLastUsedTunnel(null);
- tunnels.remove(tunnel);
- return Application.getAsyncWorker().supplyAsync(() -> {
- if (originalState == State.UP)
- Application.getBackend().setState(tunnel, State.DOWN);
- configStore.rename(tunnel.getName(), name);
- final String newName = tunnel.onNameChanged(name);
- if (originalState == State.UP)
- Application.getBackend().setState(tunnel, State.UP);
- return newName;
- }).whenComplete((newName, e) -> {
- // On failure, we don't know what state the tunnel might be in. Fix that.
- if (e != null)
- getTunnelState(tunnel);
- // Add the tunnel back to the manager, under whatever name it thinks it has.
- tunnels.add(tunnel);
- if (wasLastUsed)
- setLastUsedTunnel(tunnel);
- });
- }
-
- CompletionStage<State> setTunnelState(final Tunnel tunnel, final State state) {
- // Ensure the configuration is loaded before trying to use it.
- return tunnel.getConfigAsync().thenCompose(x ->
- Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().setState(tunnel, state))
- ).whenComplete((newState, e) -> {
- // Ensure onStateChanged is always called (failure or not), and with the correct state.
- tunnel.onStateChanged(e == null ? newState : tunnel.getState());
- if (e == null && newState == State.UP)
- setLastUsedTunnel(tunnel);
- saveState();
- });
- }
-
- public static final class IntentReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, @Nullable final Intent intent) {
- final TunnelManager manager = Application.getTunnelManager();
- if (intent == null)
- return;
- final String action = intent.getAction();
- if (action == null)
- return;
-
- if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES".equals(action)) {
- manager.refreshTunnelStates();
- return;
- }
-
- /* We disable the below, for now, as the security model of allowing this
- * might take a bit more consideration.
- */
- if (true)
- return;
-
- final State state;
- if ("com.wireguard.android.action.SET_TUNNEL_UP".equals(action))
- state = State.UP;
- else if ("com.wireguard.android.action.SET_TUNNEL_DOWN".equals(action))
- state = State.DOWN;
- else
- return;
-
- final String tunnelName = intent.getStringExtra("tunnel");
- if (tunnelName == null)
- return;
- manager.getTunnels().thenAccept(tunnels -> {
- final Tunnel tunnel = tunnels.get(tunnelName);
- if (tunnel == null)
- return;
- manager.setTunnelState(tunnel, state);
- });
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
deleted file mode 100644
index 565854b4..00000000
--- a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.preference;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.preference.Preference;
-
-import android.util.AttributeSet;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.util.DownloadsFileSaver;
-import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.util.FragmentUtils;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-
-/**
- * Preference implementing a button that asynchronously exports logs.
- */
-
-public class LogExporterPreference extends Preference {
- private static final String TAG = "WireGuard/" + LogExporterPreference.class.getSimpleName();
-
- @Nullable private String exportedFilePath;
-
- public LogExporterPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- private void exportLog() {
- Application.getAsyncWorker().supplyAsync(() -> {
- DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-log.txt", "text/plain", true);
- try {
- final Process process = Runtime.getRuntime().exec(new String[]{
- "logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
- try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
- final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())))
- {
- String line;
- while ((line = stdout.readLine()) != null) {
- outputFile.getOutputStream().write(line.getBytes());
- outputFile.getOutputStream().write('\n');
- }
- outputFile.getOutputStream().close();
- stdout.close();
- if (process.waitFor() != 0) {
- final StringBuilder errors = new StringBuilder();
- errors.append(R.string.logcat_error);
- while ((line = stderr.readLine()) != null)
- errors.append(line);
- throw new Exception(errors.toString());
- }
- }
- } catch (final Exception e) {
- outputFile.delete();
- throw e;
- }
- return outputFile.getFileName();
- }).whenComplete(this::exportLogComplete);
- }
-
- private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) {
- if (throwable != null) {
- final String error = ErrorMessages.get(throwable);
- final String message = getContext().getString(R.string.log_export_error, error);
- Log.e(TAG, message, throwable);
- Snackbar.make(
- FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
- message, Snackbar.LENGTH_LONG).show();
- setEnabled(true);
- } else {
- exportedFilePath = filePath;
- notifyChanged();
- }
- }
-
- @Override
- public CharSequence getSummary() {
- return exportedFilePath == null ?
- getContext().getString(R.string.log_export_summary) :
- getContext().getString(R.string.log_export_success, exportedFilePath);
- }
-
- @Override
- public CharSequence getTitle() {
- return getContext().getString(R.string.log_export_title);
- }
-
- @Override
- protected void onClick() {
- FragmentUtils.getPrefActivity(this).ensurePermissions(
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- (permissions, granted) -> {
- if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) {
- setEnabled(false);
- exportLog();
- }
- });
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java b/app/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java
deleted file mode 100644
index a04bed76..00000000
--- a/app/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright © 2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.preference;
-
-import android.content.Context;
-import android.content.Intent;
-import android.system.OsConstants;
-import android.util.AttributeSet;
-import android.widget.Toast;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.util.ModuleLoader;
-import com.wireguard.android.util.ToolsInstaller;
-
-import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-
-public class ModuleDownloaderPreference extends Preference {
- private State state = State.INITIAL;
-
- public ModuleDownloaderPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public CharSequence getSummary() {
- return getContext().getString(state.messageResourceId);
- }
-
- @Override
- public CharSequence getTitle() {
- return getContext().getString(R.string.module_installer_title);
- }
-
- @Override
- protected void onClick() {
- setState(State.WORKING);
- Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult);
- }
-
- private void onDownloadResult(final Integer result, @Nullable final Throwable throwable) {
- if (throwable != null) {
- setState(State.FAILURE);
- Toast.makeText(getContext(), throwable.getMessage(), Toast.LENGTH_LONG).show();
- } else if (result == OsConstants.ENOENT)
- setState(State.NOTFOUND);
- else if (result == OsConstants.EXIT_SUCCESS) {
- setState(State.SUCCESS);
- Application.getAsyncWorker().runAsync(() -> {
- Thread.sleep(1000 * 5);
- Intent i = getContext().getPackageManager().getLaunchIntentForPackage(getContext().getPackageName());
- if (i == null)
- return;
- i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Application.get().startActivity(i);
- System.exit(0);
- });
- } else
- setState(State.FAILURE);
- }
-
- private void setState(final State state) {
- if (this.state == state)
- return;
- this.state = state;
- if (isEnabled() != state.shouldEnableView)
- setEnabled(state.shouldEnableView);
- notifyChanged();
- }
-
- private enum State {
- INITIAL(R.string.module_installer_initial, true),
- FAILURE(R.string.module_installer_error, true),
- WORKING(R.string.module_installer_working, false),
- SUCCESS(R.string.module_installer_success, false),
- NOTFOUND(R.string.module_installer_not_found, false);
-
- private final int messageResourceId;
- private final boolean shouldEnableView;
-
- State(final int messageResourceId, final boolean shouldEnableView) {
- this.messageResourceId = messageResourceId;
- this.shouldEnableView = shouldEnableView;
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java b/app/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java
deleted file mode 100644
index 78a7497b..00000000
--- a/app/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.preference;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-import android.util.AttributeSet;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.util.ToolsInstaller;
-
-/**
- * Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
- * result as the preference summary.
- */
-
-public class ToolsInstallerPreference extends Preference {
- private State state = State.INITIAL;
-
- public ToolsInstallerPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public CharSequence getSummary() {
- return getContext().getString(state.messageResourceId);
- }
-
- @Override
- public CharSequence getTitle() {
- return getContext().getString(R.string.tools_installer_title);
- }
-
- @Override
- public void onAttached() {
- super.onAttached();
- Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult);
- }
-
- private void onCheckResult(final int state, @Nullable final Throwable throwable) {
- if (throwable != null || state == ToolsInstaller.ERROR)
- setState(State.INITIAL);
- else if ((state & ToolsInstaller.YES) == ToolsInstaller.YES)
- setState(State.ALREADY);
- else if ((state & (ToolsInstaller.MAGISK | ToolsInstaller.NO)) == (ToolsInstaller.MAGISK | ToolsInstaller.NO))
- setState(State.INITIAL_MAGISK);
- else if ((state & (ToolsInstaller.SYSTEM | ToolsInstaller.NO)) == (ToolsInstaller.SYSTEM | ToolsInstaller.NO))
- setState(State.INITIAL_SYSTEM);
- else
- setState(State.INITIAL);
- }
-
- @Override
- protected void onClick() {
- setState(State.WORKING);
- Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult);
- }
-
- private void onInstallResult(final Integer result, @Nullable final Throwable throwable) {
- if (throwable != null)
- setState(State.FAILURE);
- else if ((result & (ToolsInstaller.YES | ToolsInstaller.MAGISK)) == (ToolsInstaller.YES | ToolsInstaller.MAGISK))
- setState(State.SUCCESS_MAGISK);
- else if ((result & (ToolsInstaller.YES | ToolsInstaller.SYSTEM)) == (ToolsInstaller.YES | ToolsInstaller.SYSTEM))
- setState(State.SUCCESS_SYSTEM);
- else
- setState(State.FAILURE);
- }
-
- private void setState(final State state) {
- if (this.state == state)
- return;
- this.state = state;
- if (isEnabled() != state.shouldEnableView)
- setEnabled(state.shouldEnableView);
- notifyChanged();
- }
-
- private enum State {
- INITIAL(R.string.tools_installer_initial, true),
- ALREADY(R.string.tools_installer_already, false),
- FAILURE(R.string.tools_installer_failure, true),
- WORKING(R.string.tools_installer_working, false),
- INITIAL_SYSTEM(R.string.tools_installer_initial_system, true),
- SUCCESS_SYSTEM(R.string.tools_installer_success_system, false),
- INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true),
- SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false);
-
- private final int messageResourceId;
- private final boolean shouldEnableView;
-
- State(final int messageResourceId, final boolean shouldEnableView) {
- this.messageResourceId = messageResourceId;
- this.shouldEnableView = shouldEnableView;
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java b/app/src/main/java/com/wireguard/android/preference/VersionPreference.java
deleted file mode 100644
index a0a5d1ff..00000000
--- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.preference;
-
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-import android.util.AttributeSet;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.BuildConfig;
-import com.wireguard.android.R;
-
-import java.util.Locale;
-
-public class VersionPreference extends Preference {
- @Nullable private String versionSummary;
-
- public VersionPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- Application.getBackendAsync().thenAccept(backend -> {
- versionSummary = getContext().getString(R.string.version_summary_checking, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH));
- Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> {
- versionSummary = exception == null
- ? getContext().getString(R.string.version_summary, backend.getTypePrettyName(), version)
- : getContext().getString(R.string.version_summary_unknown, backend.getTypePrettyName().toLowerCase(Locale.ENGLISH));
- notifyChanged();
- });
- });
- }
-
- @Nullable
- @Override
- public CharSequence getSummary() {
- return versionSummary;
- }
-
- @Override
- public CharSequence getTitle() {
- return getContext().getString(R.string.version_title, BuildConfig.VERSION_NAME);
- }
-
- @Override
- protected void onClick() {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse("https://www.wireguard.com/"));
- try {
- getContext().startActivity(intent);
- } catch (final ActivityNotFoundException ignored) {
- }
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
deleted file mode 100644
index efda91bb..00000000
--- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.preference;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import androidx.annotation.Nullable;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.preference.Preference;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.android.model.Tunnel;
-import com.wireguard.android.util.DownloadsFileSaver;
-import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.android.util.FragmentUtils;
-import com.wireguard.config.Config;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import java9.util.concurrent.CompletableFuture;
-
-/**
- * Preference implementing a button that asynchronously exports config zips.
- */
-
-public class ZipExporterPreference extends Preference {
- private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
-
- @Nullable private String exportedFilePath;
-
- public ZipExporterPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- private void exportZip() {
- 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());
- if (futureConfigs.isEmpty()) {
- exportZipComplete(null, new IllegalArgumentException(
- getContext().getString(R.string.no_tunnels_error)));
- return;
- }
- CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
- .whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
- if (exception != null)
- throw exception;
- DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-export.zip", "application/zip", true);
- try (ZipOutputStream zip = new ZipOutputStream(outputFile.getOutputStream())) {
- for (int i = 0; i < futureConfigs.size(); ++i) {
- zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
- zip.write(futureConfigs.get(i).getNow(null).
- toWgQuickString().getBytes(StandardCharsets.UTF_8));
- }
- zip.closeEntry();
- } catch (final Exception e) {
- outputFile.delete();
- throw e;
- }
- return outputFile.getFileName();
- }).whenComplete(this::exportZipComplete));
- }
-
- private void exportZipComplete(@Nullable final String filePath, @Nullable final Throwable throwable) {
- if (throwable != null) {
- final String error = ErrorMessages.get(throwable);
- final String message = getContext().getString(R.string.zip_export_error, error);
- Log.e(TAG, message, throwable);
- Snackbar.make(
- FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
- message, Snackbar.LENGTH_LONG).show();
- setEnabled(true);
- } else {
- exportedFilePath = filePath;
- notifyChanged();
- }
- }
-
- @Override
- public CharSequence getSummary() {
- return exportedFilePath == null ?
- getContext().getString(R.string.zip_export_summary) :
- getContext().getString(R.string.zip_export_success, exportedFilePath);
- }
-
- @Override
- public CharSequence getTitle() {
- return getContext().getString(R.string.zip_export_title);
- }
-
- @Override
- protected void onClick() {
- FragmentUtils.getPrefActivity(this).ensurePermissions(
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- (permissions, granted) -> {
- if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) {
- setEnabled(false);
- exportZip();
- }
- });
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java b/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
deleted file mode 100644
index 1d041851..00000000
--- a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.os.Handler;
-
-import java.util.concurrent.Executor;
-
-import java9.util.concurrent.CompletableFuture;
-import java9.util.concurrent.CompletionStage;
-
-/**
- * Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
- */
-
-public class AsyncWorker {
- private final Executor executor;
- private final Handler handler;
-
- public AsyncWorker(final Executor executor, final Handler handler) {
- this.executor = executor;
- this.handler = handler;
- }
-
- public CompletionStage<Void> runAsync(final AsyncRunnable<?> runnable) {
- final CompletableFuture<Void> future = new CompletableFuture<>();
- executor.execute(() -> {
- try {
- runnable.run();
- handler.post(() -> future.complete(null));
- } catch (final Throwable t) {
- handler.post(() -> future.completeExceptionally(t));
- }
- });
- return future;
- }
-
- public <T> CompletionStage<T> supplyAsync(final AsyncSupplier<T, ?> supplier) {
- final CompletableFuture<T> future = new CompletableFuture<>();
- executor.execute(() -> {
- try {
- final T result = supplier.get();
- handler.post(() -> future.complete(result));
- } catch (final Throwable t) {
- handler.post(() -> future.completeExceptionally(t));
- }
- });
- return future;
- }
-
- @FunctionalInterface
- public interface AsyncRunnable<E extends Throwable> {
- void run() throws E;
- }
-
- @FunctionalInterface
- public interface AsyncSupplier<T, E extends Throwable> {
- T get() throws E;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java b/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java
deleted file mode 100644
index 0df5e96a..00000000
--- a/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import com.google.android.material.snackbar.Snackbar;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * Standalone utilities for interacting with the system clipboard.
- */
-
-public final class ClipboardUtils {
- private ClipboardUtils() {
- // Prevent instantiation
- }
-
- public static void copyTextView(final View view) {
- if (!(view instanceof TextView))
- return;
- final CharSequence text = ((TextView) view).getText();
- if (text == null || text.length() == 0)
- return;
- final Object service = view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- if (!(service instanceof ClipboardManager))
- return;
- final CharSequence description = view.getContentDescription();
- ((ClipboardManager) service).setPrimaryClip(ClipData.newPlainText(description, text));
- Snackbar.make(view, description + " copied to clipboard", Snackbar.LENGTH_LONG).show();
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java b/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java
deleted file mode 100644
index efe09f3f..00000000
--- a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright © 2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-
-import com.wireguard.android.R;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class DownloadsFileSaver {
-
- public static class DownloadsFile {
- private Context context;
- private OutputStream outputStream;
- private String fileName;
- private Uri uri;
-
- private DownloadsFile(final Context context, final OutputStream outputStream, final String fileName, final Uri uri) {
- this.context = context;
- this.outputStream = outputStream;
- this.fileName = fileName;
- this.uri = uri;
- }
-
- public OutputStream getOutputStream() { return outputStream; }
- public String getFileName() { return fileName; }
-
- public void delete() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- context.getContentResolver().delete(uri, null, null);
- else
- new File(fileName).delete();
- }
- }
-
- public static DownloadsFile save(final Context context, final String name, final String mimeType, final boolean overwriteExisting) throws Exception {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- final ContentResolver contentResolver = context.getContentResolver();
- if (overwriteExisting)
- contentResolver.delete(MediaStore.Downloads.EXTERNAL_CONTENT_URI, String.format("%s = ?", MediaColumns.DISPLAY_NAME), new String[]{name});
- final ContentValues contentValues = new ContentValues();
- contentValues.put(MediaColumns.DISPLAY_NAME, name);
- contentValues.put(MediaColumns.MIME_TYPE, mimeType);
- final Uri contentUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
- if (contentUri == null)
- throw new IOException(context.getString(R.string.create_downloads_file_error));
- final OutputStream contentStream = contentResolver.openOutputStream(contentUri);
- if (contentStream == null)
- throw new IOException(context.getString(R.string.create_downloads_file_error));
- Cursor cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DATA}, null, null, null);
- String path = null;
- if (cursor != null) {
- try {
- if (cursor.moveToFirst())
- path = cursor.getString(0);
- } finally {
- cursor.close();
- }
- }
- if (path == null) {
- path = "Download/";
- cursor = contentResolver.query(contentUri, new String[]{MediaColumns.DISPLAY_NAME}, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst())
- path += cursor.getString(0);
- } finally {
- cursor.close();
- }
- }
- }
- return new DownloadsFile(context, contentStream, path, contentUri);
- } else {
- final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- final File file = new File(path, name);
- if (!path.isDirectory() && !path.mkdirs())
- throw new IOException(context.getString(R.string.create_output_dir_error));
- return new DownloadsFile(context, new FileOutputStream(file), file.getAbsolutePath(), null);
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java b/app/src/main/java/com/wireguard/android/util/ErrorMessages.java
deleted file mode 100644
index ee9cb12a..00000000
--- a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.res.Resources;
-import androidx.annotation.Nullable;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.R;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.BadConfigException.Location;
-import com.wireguard.config.BadConfigException.Reason;
-import com.wireguard.config.InetEndpoint;
-import com.wireguard.config.InetNetwork;
-import com.wireguard.config.ParseException;
-import com.wireguard.crypto.Key.Format;
-import com.wireguard.crypto.KeyFormatException;
-import com.wireguard.crypto.KeyFormatException.Type;
-
-import java.net.InetAddress;
-import java.util.EnumMap;
-import java.util.Map;
-
-import java9.util.Maps;
-
-public final class ErrorMessages {
- private static final Map<Reason, Integer> BCE_REASON_MAP = new EnumMap<>(Maps.of(
- Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key,
- Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number,
- Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value,
- Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute,
- Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section,
- Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value,
- Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error,
- Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute,
- Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section
- ));
- private static final Map<Format, Integer> KFE_FORMAT_MAP = new EnumMap<>(Maps.of(
- Format.BASE64, R.string.key_length_explanation_base64,
- Format.BINARY, R.string.key_length_explanation_binary,
- Format.HEX, R.string.key_length_explanation_hex
- ));
- private static final Map<Type, Integer> KFE_TYPE_MAP = new EnumMap<>(Maps.of(
- Type.CONTENTS, R.string.key_contents_error,
- Type.LENGTH, R.string.key_length_error
- ));
- private static final Map<Class, Integer> PE_CLASS_MAP = Maps.of(
- InetAddress.class, R.string.parse_error_inet_address,
- InetEndpoint.class, R.string.parse_error_inet_endpoint,
- InetNetwork.class, R.string.parse_error_inet_network,
- Integer.class, R.string.parse_error_integer
- );
-
- private ErrorMessages() {
- // Prevent instantiation
- }
-
- public static String get(@Nullable final Throwable throwable) {
- final Resources resources = Application.get().getResources();
- if (throwable == null)
- return resources.getString(R.string.unknown_error);
- final Throwable rootCause = rootCause(throwable);
- final String message;
- if (rootCause instanceof BadConfigException) {
- final BadConfigException bce = (BadConfigException) rootCause;
- final String reason = getBadConfigExceptionReason(resources, bce);
- final String context = bce.getLocation() == Location.TOP_LEVEL ?
- resources.getString(R.string.bad_config_context_top_level,
- bce.getSection().getName()) :
- resources.getString(R.string.bad_config_context,
- bce.getSection().getName(),
- bce.getLocation().getName());
- final String explanation = getBadConfigExceptionExplanation(resources, bce);
- message = resources.getString(R.string.bad_config_error, reason, context) + explanation;
- } else if (rootCause.getMessage() != null) {
- message = rootCause.getMessage();
- } else {
- final String errorType = rootCause.getClass().getSimpleName();
- message = resources.getString(R.string.generic_error, errorType);
- }
- return message;
- }
-
- private static String getBadConfigExceptionExplanation(final Resources resources,
- final BadConfigException bce) {
- if (bce.getCause() instanceof KeyFormatException) {
- final KeyFormatException kfe = (KeyFormatException) bce.getCause();
- if (kfe.getType() == Type.LENGTH)
- return resources.getString(KFE_FORMAT_MAP.get(kfe.getFormat()));
- } else if (bce.getCause() instanceof ParseException) {
- final ParseException pe = (ParseException) bce.getCause();
- if (pe.getMessage() != null)
- return ": " + pe.getMessage();
- } else if (bce.getLocation() == Location.LISTEN_PORT) {
- return resources.getString(R.string.bad_config_explanation_udp_port);
- } else if (bce.getLocation() == Location.MTU) {
- return resources.getString(R.string.bad_config_explanation_positive_number);
- } else if (bce.getLocation() == Location.PERSISTENT_KEEPALIVE) {
- return resources.getString(R.string.bad_config_explanation_pka);
- }
- return "";
- }
-
- private static String getBadConfigExceptionReason(final Resources resources,
- final BadConfigException bce) {
- if (bce.getCause() instanceof KeyFormatException) {
- final KeyFormatException kfe = (KeyFormatException) bce.getCause();
- return resources.getString(KFE_TYPE_MAP.get(kfe.getType()));
- } else if (bce.getCause() instanceof ParseException) {
- final ParseException pe = (ParseException) bce.getCause();
- final String type = resources.getString(PE_CLASS_MAP.containsKey(pe.getParsingClass()) ?
- PE_CLASS_MAP.get(pe.getParsingClass()) : R.string.parse_error_generic);
- return resources.getString(R.string.parse_error_reason, type, pe.getText());
- }
- return resources.getString(BCE_REASON_MAP.get(bce.getReason()), bce.getText());
- }
-
- private static Throwable rootCause(final Throwable throwable) {
- Throwable cause = throwable;
- while (cause.getCause() != null) {
- if (cause instanceof BadConfigException)
- break;
- cause = cause.getCause();
- }
- return cause;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
deleted file mode 100644
index 5c7a38c0..00000000
--- a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import java9.util.function.BiConsumer;
-
-/**
- * Helpers for logging exceptions from asynchronous tasks. These can be passed to
- * {@code CompletionStage.whenComplete()} at the end of an asynchronous future chain.
- */
-
-public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
- D(Log.DEBUG),
- E(Log.ERROR);
-
- private static final String TAG = "WireGuard/" + ExceptionLoggers.class.getSimpleName();
- private final int priority;
-
- ExceptionLoggers(final int priority) {
- this.priority = priority;
- }
-
- @Override
- public void accept(final Object result, @Nullable final Throwable throwable) {
- if (throwable != null)
- Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable));
- else if (priority <= Log.DEBUG)
- Log.println(priority, TAG, "Future completed successfully");
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java b/app/src/main/java/com/wireguard/android/util/FragmentUtils.java
deleted file mode 100644
index 5fb9a3bc..00000000
--- a/app/src/main/java/com/wireguard/android/util/FragmentUtils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.preference.Preference;
-import android.view.ContextThemeWrapper;
-
-import com.wireguard.android.activity.SettingsActivity;
-
-public final class FragmentUtils {
- private FragmentUtils() {
- // Prevent instantiation
- }
-
- public static SettingsActivity getPrefActivity(final Preference preference) {
- final Context context = preference.getContext();
- if (context instanceof ContextThemeWrapper) {
- if (context instanceof SettingsActivity) {
- return ((SettingsActivity) context);
- }
- }
- return null;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ModuleLoader.java b/app/src/main/java/com/wireguard/android/util/ModuleLoader.java
deleted file mode 100644
index 21ff9c77..00000000
--- a/app/src/main/java/com/wireguard/android/util/ModuleLoader.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright © 2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import android.system.OsConstants;
-import android.util.Base64;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.BuildConfig;
-import com.wireguard.android.util.RootShell.NoRootException;
-
-import net.i2p.crypto.eddsa.EdDSAEngine;
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
-import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
-import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidParameterException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-
-public class ModuleLoader {
- private static final String MODULE_PUBLIC_KEY_BASE64 = "RWRmHuT9PSqtwfsLtEx+QS06BJtLgFYteL9WCNjH7yuyu5Y1DieSN7If";
- private static final String MODULE_LIST_URL = "https://download.wireguard.com/android-module/modules.txt.sig";
- private static final String MODULE_URL = "https://download.wireguard.com/android-module/%s";
- private static final String MODULE_NAME = "wireguard-%s.ko";
-
- private final File moduleDir;
- private final File tmpDir;
-
- public ModuleLoader(final Context context) {
- moduleDir = new File(context.getCacheDir(), "kmod");
- tmpDir = new File(context.getCacheDir(), "tmp");
- }
-
- public boolean moduleMightExist() {
- return moduleDir.exists() && moduleDir.isDirectory();
- }
-
- public void loadModule() throws IOException, NoRootException {
- Application.getRootShell().run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath()));
- }
-
- public boolean isModuleLoaded() {
- return new File("/sys/module/wireguard").exists();
- }
-
- private static final class Sha256Digest {
- private byte[] bytes;
- private Sha256Digest(final String hex) {
- if (hex.length() != 64)
- throw new InvalidParameterException("SHA256 hashes must be 32 bytes long");
- bytes = new byte[32];
- for (int i = 0; i < 32; ++i)
- bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
- }
- }
-
- @Nullable
- private Map<String, Sha256Digest> verifySignedHashes(final String signifyDigest) {
- final byte[] publicKeyBytes = Base64.decode(MODULE_PUBLIC_KEY_BASE64, Base64.DEFAULT);
-
- if (publicKeyBytes == null || publicKeyBytes.length != 32 + 10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd')
- return null;
-
- final String[] lines = signifyDigest.split("\n", 3);
- if (lines.length != 3)
- return null;
- if (!lines[0].startsWith("untrusted comment: "))
- return null;
-
- final byte[] signatureBytes = Base64.decode(lines[1], Base64.DEFAULT);
- if (signatureBytes == null || signatureBytes.length != 64 + 10)
- return null;
- for (int i = 0; i < 10; ++i) {
- if (signatureBytes[i] != publicKeyBytes[i])
- return null;
- }
-
- try {
- EdDSAParameterSpec parameterSpec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
- Signature signature = new EdDSAEngine(MessageDigest.getInstance(parameterSpec.getHashAlgorithm()));
- byte[] rawPublicKeyBytes = new byte[32];
- System.arraycopy(publicKeyBytes, 10, rawPublicKeyBytes, 0, 32);
- signature.initVerify(new EdDSAPublicKey(new EdDSAPublicKeySpec(rawPublicKeyBytes, parameterSpec)));
- signature.update(lines[2].getBytes(StandardCharsets.UTF_8));
- if (!signature.verify(signatureBytes, 10, 64))
- return null;
- } catch (final Exception ignored) {
- return null;
- }
-
- Map<String, Sha256Digest> hashes = new HashMap<>();
- for (final String line : lines[2].split("\n")) {
- final String[] components = line.split(" ", 2);
- if (components.length != 2)
- return null;
- try {
- hashes.put(components[1], new Sha256Digest(components[0]));
- } catch (final Exception ignored) {
- return null;
- }
- }
- return hashes;
- }
-
- public Integer download() throws IOException, NoRootException, NoSuchAlgorithmException {
- final List<String> output = new ArrayList<>();
- Application.getRootShell().run(output, "sha256sum /proc/version|cut -d ' ' -f 1");
- if (output.size() != 1 || output.get(0).length() != 64)
- throw new InvalidParameterException("Invalid sha256 of /proc/version");
- final String moduleName = String.format(MODULE_NAME, output.get(0));
- final String userAgent = String.format("WireGuard/%s (Android)", BuildConfig.VERSION_NAME); //TODO: expand a bit
-
- HttpURLConnection connection = (HttpURLConnection)new URL(MODULE_LIST_URL).openConnection();
- connection.setRequestProperty("User-Agent", userAgent);
- connection.connect();
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
- throw new IOException("Hash list could not be found");
- byte[] input = new byte[1024 * 1024 * 3 /* 3MiB */];
- int len;
- try (final InputStream inputStream = connection.getInputStream()) {
- len = inputStream.read(input);
- }
- if (len <= 0)
- throw new IOException("Hash list was empty");
- final Map<String, Sha256Digest> modules = verifySignedHashes(new String(input, 0, len, StandardCharsets.UTF_8));
- if (modules == null)
- throw new InvalidParameterException("The signature did not verify or invalid hash list format");
- if (!modules.containsKey(moduleName))
- return OsConstants.ENOENT;
- connection = (HttpURLConnection)new URL(String.format(MODULE_URL, moduleName)).openConnection();
- connection.setRequestProperty("User-Agent", userAgent);
- connection.connect();
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
- throw new IOException("Module file could not be found, despite being on hash list");
-
- tmpDir.mkdirs();
- moduleDir.mkdir();
- File tempFile = null;
- try {
- tempFile = File.createTempFile("UNVERIFIED-", null, tmpDir);
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- try (final InputStream inputStream = connection.getInputStream();
- final OutputStream outputStream = new FileOutputStream(tempFile)) {
- int total = 0;
- while ((len = inputStream.read(input)) > 0) {
- total += len;
- if (total > 1024 * 1024 * 15 /* 15 MiB */)
- throw new IOException("File too big");
- outputStream.write(input, 0, len);
- digest.update(input, 0, len);
- }
- }
- if (!Arrays.equals(digest.digest(), modules.get(moduleName).bytes))
- throw new IOException("Incorrect file hash");
-
- if (!tempFile.renameTo(new File(moduleDir, moduleName)))
- throw new IOException("Unable to rename to final destination");
- } finally {
- if (tempFile != null)
- tempFile.delete();
- }
- return OsConstants.EXIT_SUCCESS;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
deleted file mode 100644
index 0ba02184..00000000
--- a/app/src/main/java/com/wireguard/android/util/ObservableKeyedArrayList.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import androidx.databinding.ObservableArrayList;
-import androidx.annotation.Nullable;
-
-import com.wireguard.util.Keyed;
-
-import java.util.Collection;
-import java.util.ListIterator;
-import java.util.Objects;
-
-/**
- * ArrayList that allows looking up elements by some key property. As the key property must always
- * be retrievable, this list cannot hold {@code null} elements. Because this class places no
- * restrictions on the order or duplication of keys, lookup by key, as well as all list modification
- * operations, require O(n) time.
- */
-
-public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
- extends ObservableArrayList<E> implements ObservableKeyedList<K, E> {
- @Override
- public boolean add(@Nullable final E e) {
- if (e == null)
- throw new NullPointerException("Trying to add a null element");
- return super.add(e);
- }
-
- @Override
- public void add(final int index, @Nullable final E e) {
- if (e == null)
- throw new NullPointerException("Trying to add a null element");
- super.add(index, e);
- }
-
- @Override
- public boolean addAll(final Collection<? extends E> c) {
- if (c.contains(null))
- throw new NullPointerException("Trying to add a collection with null element(s)");
- return super.addAll(c);
- }
-
- @Override
- public boolean addAll(final int index, final Collection<? extends E> c) {
- if (c.contains(null))
- throw new NullPointerException("Trying to add a collection with null element(s)");
- return super.addAll(index, c);
- }
-
- @Override
- public boolean containsAllKeys(final Collection<K> keys) {
- for (final K key : keys)
- if (!containsKey(key))
- return false;
- return true;
- }
-
- @Override
- public boolean containsKey(final K key) {
- return indexOfKey(key) >= 0;
- }
-
- @Nullable
- @Override
- public E get(final K key) {
- final int index = indexOfKey(key);
- return index >= 0 ? get(index) : null;
- }
-
- @Nullable
- @Override
- public E getLast(final K key) {
- final int index = lastIndexOfKey(key);
- return index >= 0 ? get(index) : null;
- }
-
- @Override
- public int indexOfKey(final K key) {
- final ListIterator<E> iterator = listIterator();
- while (iterator.hasNext()) {
- final int index = iterator.nextIndex();
- if (Objects.equals(iterator.next().getKey(), key))
- return index;
- }
- return -1;
- }
-
- @Override
- public int lastIndexOfKey(final K key) {
- final ListIterator<E> iterator = listIterator(size());
- while (iterator.hasPrevious()) {
- final int index = iterator.previousIndex();
- if (Objects.equals(iterator.previous().getKey(), key))
- return index;
- }
- return -1;
- }
-
- @Override
- public E set(final int index, @Nullable final E e) {
- if (e == null)
- throw new NullPointerException("Trying to set a null key");
- return super.set(index, e);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java b/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java
deleted file mode 100644
index be8ceb9b..00000000
--- a/app/src/main/java/com/wireguard/android/util/ObservableKeyedList.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import androidx.databinding.ObservableList;
-
-import com.wireguard.util.Keyed;
-import com.wireguard.util.KeyedList;
-
-/**
- * A list that is both keyed and observable.
- */
-
-public interface ObservableKeyedList<K, E extends Keyed<? extends K>>
- extends KeyedList<K, E>, ObservableList<E> {
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
deleted file mode 100644
index 1d585856..00000000
--- a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedArrayList.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import androidx.annotation.Nullable;
-
-import com.wireguard.util.Keyed;
-import com.wireguard.util.SortedKeyedList;
-
-import java.util.AbstractList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.Spliterator;
-
-/**
- * KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
- * binary search to improve lookup and replacement times to O(log(n)). However, due to the
- * array-based nature of this class, insertion and removal of elements with anything but the largest
- * key still require O(n) time.
- */
-
-public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
- extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
- @Nullable private final Comparator<? super K> comparator;
- private final transient KeyList<K, E> keyList = new KeyList<>(this);
-
- @SuppressWarnings("WeakerAccess")
- public ObservableSortedKeyedArrayList() {
- comparator = null;
- }
-
- public ObservableSortedKeyedArrayList(final Comparator<? super K> comparator) {
- this.comparator = comparator;
- }
-
- public ObservableSortedKeyedArrayList(final Collection<? extends E> c) {
- this();
- addAll(c);
- }
-
- public ObservableSortedKeyedArrayList(final SortedKeyedList<K, E> other) {
- this(other.comparator());
- addAll(other);
- }
-
- @Override
- public boolean add(final E e) {
- final int insertionPoint = getInsertionPoint(e);
- if (insertionPoint < 0) {
- // Skipping insertion is non-destructive if the new and existing objects are the same.
- if (e == get(-insertionPoint - 1))
- return false;
- throw new IllegalArgumentException("Element with same key already exists in list");
- }
- super.add(insertionPoint, e);
- return true;
- }
-
- @Override
- public void add(final int index, final E e) {
- final int insertionPoint = getInsertionPoint(e);
- if (insertionPoint < 0)
- throw new IllegalArgumentException("Element with same key already exists in list");
- if (insertionPoint != index)
- throw new IndexOutOfBoundsException("Wrong index given for element");
- super.add(index, e);
- }
-
- @Override
- public boolean addAll(final Collection<? extends E> c) {
- boolean didChange = false;
- for (final E e : c)
- if (add(e))
- didChange = true;
- return didChange;
- }
-
- @Override
- public boolean addAll(int index, final Collection<? extends E> c) {
- for (final E e : c)
- add(index++, e);
- return true;
- }
-
- @Nullable
- @Override
- public Comparator<? super K> comparator() {
- return comparator;
- }
-
- @Override
- public K firstKey() {
- if (isEmpty())
- // The parameter in the exception is only to shut
- // lint up, we never care for the exception message.
- throw new NoSuchElementException("Empty set");
- return get(0).getKey();
- }
-
- private int getInsertionPoint(final E e) {
- if (comparator != null) {
- return -Collections.binarySearch(keyList, e.getKey(), comparator) - 1;
- } else {
- @SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
- (List<Comparable<? super K>>) keyList;
- return -Collections.binarySearch(list, e.getKey()) - 1;
- }
- }
-
- @Override
- public int indexOfKey(final K key) {
- final int index;
- if (comparator != null) {
- index = Collections.binarySearch(keyList, key, comparator);
- } else {
- @SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
- (List<Comparable<? super K>>) keyList;
- index = Collections.binarySearch(list, key);
- }
- return index >= 0 ? index : -1;
- }
-
- @Override
- public Set<K> keySet() {
- return keyList;
- }
-
- @Override
- public int lastIndexOfKey(final K key) {
- // There can never be more than one element with the same key in the list.
- return indexOfKey(key);
- }
-
- @Override
- public K lastKey() {
- if (isEmpty())
- // The parameter in the exception is only to shut
- // lint up, we never care for the exception message.
- throw new NoSuchElementException("Empty set");
- return get(size() - 1).getKey();
- }
-
- @Override
- public E set(final int index, final E e) {
- final int order;
- if (comparator != null) {
- order = comparator.compare(e.getKey(), get(index).getKey());
- } else {
- @SuppressWarnings("unchecked") final Comparable<? super K> key =
- (Comparable<? super K>) e.getKey();
- order = key.compareTo(get(index).getKey());
- }
- if (order != 0) {
- // Allow replacement if the new key would be inserted adjacent to the replaced element.
- final int insertionPoint = getInsertionPoint(e);
- if (insertionPoint < index || insertionPoint > index + 1)
- throw new IndexOutOfBoundsException("Wrong index given for element");
- }
- return super.set(index, e);
- }
-
- @Override
- public Collection<E> values() {
- return this;
- }
-
- private static final class KeyList<K, E extends Keyed<? extends K>>
- extends AbstractList<K> implements Set<K> {
- private final ObservableSortedKeyedArrayList<K, E> list;
-
- private KeyList(final ObservableSortedKeyedArrayList<K, E> list) {
- this.list = list;
- }
-
- @Override
- public K get(final int index) {
- return list.get(index).getKey();
- }
-
- @Override
- public int size() {
- return list.size();
- }
-
- @Override
- @SuppressWarnings("EmptyMethod")
- public Spliterator<K> spliterator() {
- return super.spliterator();
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java b/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java
deleted file mode 100644
index d796704e..00000000
--- a/app/src/main/java/com/wireguard/android/util/ObservableSortedKeyedList.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import com.wireguard.util.Keyed;
-import com.wireguard.util.SortedKeyedList;
-
-/**
- * A list that is both sorted/keyed and observable.
- */
-
-public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>>
- extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> {
-}
diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java
deleted file mode 100644
index 1fe5667a..00000000
--- a/app/src/main/java/com/wireguard/android/util/RootShell.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import com.wireguard.android.BuildConfig;
-import com.wireguard.android.R;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.UUID;
-
-/**
- * Helper class for running commands as root.
- */
-
-public class RootShell {
- private static final String SU = "su";
- private static final String TAG = "WireGuard/" + RootShell.class.getSimpleName();
-
- private final Context context;
- private final String deviceNotRootedMessage;
- private final File localBinaryDir;
- private final File localTemporaryDir;
- private final Object lock = new Object();
- private final String preamble;
- @Nullable private Process process;
- @Nullable private BufferedReader stderr;
- @Nullable private OutputStreamWriter stdin;
- @Nullable private BufferedReader stdout;
-
- public RootShell(final Context context) {
- deviceNotRootedMessage = context.getString(R.string.error_root);
- localBinaryDir = new File(context.getCodeCacheDir(), "bin");
- localTemporaryDir = new File(context.getCacheDir(), "tmp");
- preamble = String.format("export CALLING_PACKAGE=%s PATH=\"%s:$PATH\" TMPDIR='%s'; id -u\n",
- BuildConfig.APPLICATION_ID, localBinaryDir, localTemporaryDir);
- this.context = context;
- }
-
- private static boolean isExecutableInPath(final String name) {
- final String path = System.getenv("PATH");
- if (path == null)
- return false;
- for (final String dir : path.split(":"))
- if (new File(dir, name).canExecute())
- return true;
- return false;
- }
-
- private boolean isRunning() {
- synchronized (lock) {
- try {
- // Throws an exception if the process hasn't finished yet.
- if (process != null)
- process.exitValue();
- return false;
- } catch (final IllegalThreadStateException ignored) {
- // The existing process is still running.
- return true;
- }
- }
- }
-
- /**
- * Run a command in a root shell.
- *
- * @param output Lines read from stdout are appended to this list. Pass null if the
- * output from the shell is not important.
- * @param command Command to run as root.
- * @return The exit value of the command.
- */
- public int run(@Nullable final Collection<String> output, final String command)
- throws IOException, NoRootException {
- synchronized (lock) {
- /* Start inside synchronized block to prevent a concurrent call to stop(). */
- start();
- final String marker = UUID.randomUUID().toString();
- final String script = "echo " + marker + "; echo " + marker + " >&2; (" + command +
- "); ret=$?; echo " + marker + " $ret; echo " + marker + " $ret >&2\n";
- Log.v(TAG, "executing: " + command);
- stdin.write(script);
- stdin.flush();
- String line;
- int errnoStdout = Integer.MIN_VALUE;
- int errnoStderr = Integer.MAX_VALUE;
- int markersSeen = 0;
- while ((line = stdout.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStdout = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 0) {
- if (output != null)
- output.add(line);
- Log.v(TAG, "stdout: " + line);
- }
- }
- while ((line = stderr.readLine()) != null) {
- if (line.startsWith(marker)) {
- ++markersSeen;
- if (line.length() > marker.length() + 1) {
- errnoStderr = Integer.valueOf(line.substring(marker.length() + 1));
- break;
- }
- } else if (markersSeen > 2) {
- Log.v(TAG, "stderr: " + line);
- }
- }
- if (markersSeen != 4)
- throw new IOException(context.getString(R.string.shell_marker_count_error, markersSeen));
- if (errnoStdout != errnoStderr)
- throw new IOException(context.getString(R.string.shell_exit_status_read_error));
- Log.v(TAG, "exit: " + errnoStdout);
- return errnoStdout;
- }
- }
-
- public void start() throws IOException, NoRootException {
- if (!isExecutableInPath(SU))
- throw new NoRootException(deviceNotRootedMessage);
- synchronized (lock) {
- if (isRunning())
- return;
- if (!localBinaryDir.isDirectory() && !localBinaryDir.mkdirs())
- throw new FileNotFoundException(context.getString(R.string.create_bin_dir_error));
- if (!localTemporaryDir.isDirectory() && !localTemporaryDir.mkdirs())
- throw new FileNotFoundException(context.getString(R.string.create_temp_dir_error));
- try {
- final ProcessBuilder builder = new ProcessBuilder().command(SU);
- builder.environment().put("LC_ALL", "C");
- try {
- process = builder.start();
- } catch (final IOException e) {
- // A failure at this stage means the device isn't rooted.
- throw new NoRootException(deviceNotRootedMessage, e);
- }
- stdin = new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8);
- stdout = new BufferedReader(new InputStreamReader(process.getInputStream(),
- StandardCharsets.UTF_8));
- stderr = new BufferedReader(new InputStreamReader(process.getErrorStream(),
- StandardCharsets.UTF_8));
- stdin.write(preamble);
- stdin.flush();
- // Check that the shell started successfully.
- final String uid = stdout.readLine();
- if (!"0".equals(uid)) {
- Log.w(TAG, "Root check did not return correct UID: " + uid);
- throw new NoRootException(deviceNotRootedMessage);
- }
- if (!isRunning()) {
- String line;
- while ((line = stderr.readLine()) != null) {
- Log.w(TAG, "Root check returned an error: " + line);
- if (line.contains("Permission denied"))
- throw new NoRootException(deviceNotRootedMessage);
- }
- throw new IOException(context.getString(R.string.shell_start_error, process.exitValue()));
- }
- } catch (final IOException | NoRootException e) {
- stop();
- throw e;
- }
- }
- }
-
- public void stop() {
- synchronized (lock) {
- if (process != null) {
- process.destroy();
- process = null;
- }
- }
- }
-
- public static class NoRootException extends Exception {
- public NoRootException(final String message, final Throwable cause) {
- super(message, cause);
- }
-
- public NoRootException(final String message) {
- super(message);
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java b/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
deleted file mode 100644
index e3923d19..00000000
--- a/app/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-public final class SharedLibraryLoader {
- private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();
-
- private SharedLibraryLoader() {
- }
-
- public static boolean extractLibrary(final Context context, final String libName, final File destination) throws IOException {
- final Collection<String> apks = new HashSet<>();
- if (context.getApplicationInfo().sourceDir != null)
- apks.add(context.getApplicationInfo().sourceDir);
- if (context.getApplicationInfo().splitSourceDirs != null)
- apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs));
-
- for (final String abi : Build.SUPPORTED_ABIS) {
- for (final String apk : apks) {
- final ZipFile zipFile;
- try {
- zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ);
- } catch (final IOException e) {
- throw new RuntimeException(e);
- }
-
- final String mappedLibName = System.mapLibraryName(libName);
- final byte[] buffer = new byte[1024 * 32];
- final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
- final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
- if (zipEntry == null)
- continue;
- Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath());
- try (final FileOutputStream out = new FileOutputStream(destination);
- final InputStream in = zipFile.getInputStream(zipEntry)) {
- int len;
- while ((len = in.read(buffer)) != -1) {
- out.write(buffer, 0, len);
- }
- }
- return true;
- }
- }
- return false;
- }
-
- public static void loadSharedLibrary(final Context context, final String libName) {
- Throwable noAbiException;
- try {
- System.loadLibrary(libName);
- return;
- } catch (final UnsatisfiedLinkError e) {
- Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e);
- noAbiException = e;
- }
- File f = null;
- try {
- f = File.createTempFile("lib", ".so", context.getCodeCacheDir());
- if (extractLibrary(context, libName, f)) {
- System.load(f.getAbsolutePath());
- return;
- }
- } catch (final Exception e) {
- Log.d(TAG, "Failed to load library apk:/" + libName, e);
- noAbiException = e;
- } finally {
- if (f != null)
- // noinspection ResultOfMethodCallIgnored
- f.delete();
- }
- if (noAbiException instanceof RuntimeException)
- throw (RuntimeException) noAbiException;
- throw new RuntimeException(noAbiException);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java b/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java
deleted file mode 100644
index defdefd8..00000000
--- a/app/src/main/java/com/wireguard/android/util/ToolsInstaller.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.util;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import android.system.OsConstants;
-import android.util.Log;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.BuildConfig;
-import com.wireguard.android.R;
-import com.wireguard.android.util.RootShell.NoRootException;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Helper to install WireGuard tools to the system partition.
- */
-
-public final class ToolsInstaller {
- public static final int ERROR = 0x0;
- public static final int MAGISK = 0x4;
- public static final int NO = 0x2;
- public static final int SYSTEM = 0x8;
- public static final int YES = 0x1;
- private static final String[] EXECUTABLES = {"wg", "wg-quick"};
- private static final File[] INSTALL_DIRS = {
- new File("/system/xbin"),
- new File("/system/bin"),
- };
- @Nullable private static final File INSTALL_DIR = getInstallDir();
- private static final String TAG = "WireGuard/" + ToolsInstaller.class.getSimpleName();
-
- private final Context context;
- private final File localBinaryDir;
- private final Object lock = new Object();
- @Nullable private Boolean areToolsAvailable;
- @Nullable private Boolean installAsMagiskModule;
-
- public ToolsInstaller(final Context context) {
- localBinaryDir = new File(context.getCodeCacheDir(), "bin");
- this.context = context;
- }
-
- @Nullable
- private static File getInstallDir() {
- final String path = System.getenv("PATH");
- if (path == null)
- return INSTALL_DIRS[0];
- final List<String> paths = Arrays.asList(path.split(":"));
- for (final File dir : INSTALL_DIRS) {
- if (paths.contains(dir.getPath()) && dir.isDirectory())
- return dir;
- }
- return null;
- }
-
- public int areInstalled() throws NoRootException {
- if (INSTALL_DIR == null)
- return ERROR;
- final StringBuilder script = new StringBuilder();
- for (final String name : EXECUTABLES) {
- script.append(String.format("cmp -s '%s' '%s' && ",
- new File(localBinaryDir, name).getAbsolutePath(),
- new File(INSTALL_DIR, name).getAbsolutePath()));
- }
- script.append("exit ").append(OsConstants.EALREADY).append(';');
- try {
- final int ret = Application.getRootShell().run(null, script.toString());
- if (ret == OsConstants.EALREADY)
- return willInstallAsMagiskModule() ? YES | MAGISK : YES | SYSTEM;
- else
- return willInstallAsMagiskModule() ? NO | MAGISK : NO | SYSTEM;
- } catch (final IOException ignored) {
- return ERROR;
- }
- }
-
- public void ensureToolsAvailable() throws FileNotFoundException {
- synchronized (lock) {
- if (areToolsAvailable == null) {
- try {
- Log.d(TAG, extract() ? "Tools are now extracted into our private binary dir" :
- "Tools were already extracted into our private binary dir");
- areToolsAvailable = true;
- } catch (final IOException e) {
- Log.e(TAG, "The wg and wg-quick tools are not available", e);
- areToolsAvailable = false;
- }
- }
- if (!areToolsAvailable)
- throw new FileNotFoundException(
- context.getString(R.string.tools_unavailable_error));
- }
- }
-
- public int install() throws NoRootException, IOException {
- return willInstallAsMagiskModule() ? installMagisk() : installSystem();
- }
-
- private int installMagisk() throws NoRootException, IOException {
- extract();
- final StringBuilder script = new StringBuilder("set -ex; ");
-
- script.append("trap 'rm -rf /sbin/.magisk/img/wireguard' INT TERM EXIT; ");
- script.append(String.format("rm -rf /sbin/.magisk/img/wireguard/; mkdir -p /sbin/.magisk/img/wireguard%s; ", INSTALL_DIR));
- script.append(String.format("printf 'name=WireGuard Command Line Tools\nversion=%s\nversionCode=%s\nauthor=zx2c4\ndescription=Command line tools for WireGuard\nminMagisk=1500\n' > /sbin/.magisk/img/wireguard/module.prop; ", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
- script.append("touch /sbin/.magisk/img/wireguard/auto_mount; ");
- for (final String name : EXECUTABLES) {
- final File destination = new File("/sbin/.magisk/img/wireguard" + INSTALL_DIR, name);
- script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; chcon 'u:object_r:system_file:s0' '%s' || true; ",
- new File(localBinaryDir, name), destination, destination, destination));
- }
- script.append("trap - INT TERM EXIT;");
-
- try {
- return Application.getRootShell().run(null, script.toString()) == 0 ? YES | MAGISK : ERROR;
- } catch (final IOException ignored) {
- return ERROR;
- }
- }
-
- private int installSystem() throws NoRootException, IOException {
- if (INSTALL_DIR == null)
- return OsConstants.ENOENT;
- extract();
- final StringBuilder script = new StringBuilder("set -ex; ");
- script.append("trap 'mount -o ro,remount /system' EXIT; mount -o rw,remount /system; ");
- for (final String name : EXECUTABLES) {
- final File destination = new File(INSTALL_DIR, name);
- script.append(String.format("cp '%s' '%s'; chmod 755 '%s'; restorecon '%s' || true; ",
- new File(localBinaryDir, name), destination, destination, destination));
- }
- try {
- return Application.getRootShell().run(null, script.toString()) == 0 ? YES | SYSTEM : ERROR;
- } catch (final IOException ignored) {
- return ERROR;
- }
- }
-
- public boolean extract() throws IOException {
- localBinaryDir.mkdirs();
- final File files[] = new File[EXECUTABLES.length];
- boolean allExist = true;
- for (int i = 0; i < files.length; ++i) {
- files[i] = new File(localBinaryDir, EXECUTABLES[i]);
- allExist &= files[i].exists();
- }
- if (allExist)
- return false;
- for (int i = 0; i < files.length; ++i) {
- if (!SharedLibraryLoader.extractLibrary(context, EXECUTABLES[i], files[i]))
- throw new FileNotFoundException("Unable to find " + EXECUTABLES[i]);
- if (!files[i].setExecutable(true, false))
- throw new IOException("Unable to mark " + files[i].getAbsolutePath() + " as executable");
- }
- return true;
- }
-
- private boolean willInstallAsMagiskModule() {
- synchronized (lock) {
- if (installAsMagiskModule == null) {
- try {
- installAsMagiskModule = Application.getRootShell().run(null, "[ -d /sbin/.magisk/mirror -a -d /sbin/.magisk/img -a ! -f /cache/.disable_magisk ]") == OsConstants.EXIT_SUCCESS;
- } catch (final Exception ignored) {
- installAsMagiskModule = false;
- }
- }
- return installAsMagiskModule;
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java
deleted file mode 100644
index bcfe14e3..00000000
--- a/app/src/main/java/com/wireguard/android/viewmodel/ConfigProxy.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.viewmodel;
-
-import androidx.databinding.ObservableArrayList;
-import androidx.databinding.ObservableList;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.Config;
-import com.wireguard.config.Peer;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-public class ConfigProxy implements Parcelable {
- public static final Parcelable.Creator<ConfigProxy> CREATOR = new ConfigProxyCreator();
-
- private final InterfaceProxy interfaze;
- private final ObservableList<PeerProxy> peers = new ObservableArrayList<>();
-
- private ConfigProxy(final Parcel in) {
- interfaze = in.readParcelable(InterfaceProxy.class.getClassLoader());
- in.readTypedList(peers, PeerProxy.CREATOR);
- for (final PeerProxy proxy : peers)
- proxy.bind(this);
- }
-
- public ConfigProxy(final Config other) {
- interfaze = new InterfaceProxy(other.getInterface());
- for (final Peer peer : other.getPeers()) {
- final PeerProxy proxy = new PeerProxy(peer);
- peers.add(proxy);
- proxy.bind(this);
- }
- }
-
- public ConfigProxy() {
- interfaze = new InterfaceProxy();
- }
-
- public PeerProxy addPeer() {
- final PeerProxy proxy = new PeerProxy();
- peers.add(proxy);
- proxy.bind(this);
- return proxy;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public InterfaceProxy getInterface() {
- return interfaze;
- }
-
- public ObservableList<PeerProxy> getPeers() {
- return peers;
- }
-
- public Config resolve() throws BadConfigException {
- final Collection<Peer> resolvedPeers = new ArrayList<>();
- for (final PeerProxy proxy : peers)
- resolvedPeers.add(proxy.resolve());
- return new Config.Builder()
- .setInterface(interfaze.resolve())
- .addPeers(resolvedPeers)
- .build();
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeParcelable(interfaze, flags);
- dest.writeTypedList(peers);
- }
-
- private static class ConfigProxyCreator implements Parcelable.Creator<ConfigProxy> {
- @Override
- public ConfigProxy createFromParcel(final Parcel in) {
- return new ConfigProxy(in);
- }
-
- @Override
- public ConfigProxy[] newArray(final int size) {
- return new ConfigProxy[size];
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java
deleted file mode 100644
index cc9f2dd8..00000000
--- a/app/src/main/java/com/wireguard/android/viewmodel/InterfaceProxy.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.viewmodel;
-
-import androidx.databinding.BaseObservable;
-import androidx.databinding.Bindable;
-import androidx.databinding.ObservableArrayList;
-import androidx.databinding.ObservableList;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.wireguard.android.BR;
-import com.wireguard.config.Attribute;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.Interface;
-import com.wireguard.crypto.Key;
-import com.wireguard.crypto.KeyFormatException;
-import com.wireguard.crypto.KeyPair;
-
-import java.net.InetAddress;
-import java.util.List;
-
-import java9.util.stream.Collectors;
-import java9.util.stream.StreamSupport;
-
-public class InterfaceProxy extends BaseObservable implements Parcelable {
- public static final Parcelable.Creator<InterfaceProxy> CREATOR = new InterfaceProxyCreator();
-
- private final ObservableList<String> excludedApplications = new ObservableArrayList<>();
- private String addresses;
- private String dnsServers;
- private String listenPort;
- private String mtu;
- private String privateKey;
- private String publicKey;
-
- private InterfaceProxy(final Parcel in) {
- addresses = in.readString();
- dnsServers = in.readString();
- in.readStringList(excludedApplications);
- listenPort = in.readString();
- mtu = in.readString();
- privateKey = in.readString();
- publicKey = in.readString();
- }
-
- public InterfaceProxy(final Interface other) {
- addresses = Attribute.join(other.getAddresses());
- final List<String> dnsServerStrings = StreamSupport.stream(other.getDnsServers())
- .map(InetAddress::getHostAddress)
- .collect(Collectors.toUnmodifiableList());
- dnsServers = Attribute.join(dnsServerStrings);
- excludedApplications.addAll(other.getExcludedApplications());
- listenPort = other.getListenPort().map(String::valueOf).orElse("");
- mtu = other.getMtu().map(String::valueOf).orElse("");
- final KeyPair keyPair = other.getKeyPair();
- privateKey = keyPair.getPrivateKey().toBase64();
- publicKey = keyPair.getPublicKey().toBase64();
- }
-
- public InterfaceProxy() {
- addresses = "";
- dnsServers = "";
- listenPort = "";
- mtu = "";
- privateKey = "";
- publicKey = "";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public void generateKeyPair() {
- final KeyPair keyPair = new KeyPair();
- privateKey = keyPair.getPrivateKey().toBase64();
- publicKey = keyPair.getPublicKey().toBase64();
- notifyPropertyChanged(BR.privateKey);
- notifyPropertyChanged(BR.publicKey);
- }
-
- @Bindable
- public String getAddresses() {
- return addresses;
- }
-
- @Bindable
- public String getDnsServers() {
- return dnsServers;
- }
-
- public ObservableList<String> getExcludedApplications() {
- return excludedApplications;
- }
-
- @Bindable
- public String getListenPort() {
- return listenPort;
- }
-
- @Bindable
- public String getMtu() {
- return mtu;
- }
-
- @Bindable
- public String getPrivateKey() {
- return privateKey;
- }
-
- @Bindable
- public String getPublicKey() {
- return publicKey;
- }
-
- public Interface resolve() throws BadConfigException {
- final Interface.Builder builder = new Interface.Builder();
- if (!addresses.isEmpty())
- builder.parseAddresses(addresses);
- if (!dnsServers.isEmpty())
- builder.parseDnsServers(dnsServers);
- if (!excludedApplications.isEmpty())
- builder.excludeApplications(excludedApplications);
- if (!listenPort.isEmpty())
- builder.parseListenPort(listenPort);
- if (!mtu.isEmpty())
- builder.parseMtu(mtu);
- if (!privateKey.isEmpty())
- builder.parsePrivateKey(privateKey);
- return builder.build();
- }
-
- public void setAddresses(final String addresses) {
- this.addresses = addresses;
- notifyPropertyChanged(BR.addresses);
- }
-
- public void setDnsServers(final String dnsServers) {
- this.dnsServers = dnsServers;
- notifyPropertyChanged(BR.dnsServers);
- }
-
- public void setListenPort(final String listenPort) {
- this.listenPort = listenPort;
- notifyPropertyChanged(BR.listenPort);
- }
-
- public void setMtu(final String mtu) {
- this.mtu = mtu;
- notifyPropertyChanged(BR.mtu);
- }
-
- public void setPrivateKey(final String privateKey) {
- this.privateKey = privateKey;
- try {
- publicKey = new KeyPair(Key.fromBase64(privateKey)).getPublicKey().toBase64();
- } catch (final KeyFormatException ignored) {
- publicKey = "";
- }
- notifyPropertyChanged(BR.privateKey);
- notifyPropertyChanged(BR.publicKey);
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeString(addresses);
- dest.writeString(dnsServers);
- dest.writeStringList(excludedApplications);
- dest.writeString(listenPort);
- dest.writeString(mtu);
- dest.writeString(privateKey);
- dest.writeString(publicKey);
- }
-
- private static class InterfaceProxyCreator implements Parcelable.Creator<InterfaceProxy> {
- @Override
- public InterfaceProxy createFromParcel(final Parcel in) {
- return new InterfaceProxy(in);
- }
-
- @Override
- public InterfaceProxy[] newArray(final int size) {
- return new InterfaceProxy[size];
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java b/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java
deleted file mode 100644
index 7dc50f09..00000000
--- a/app/src/main/java/com/wireguard/android/viewmodel/PeerProxy.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.viewmodel;
-
-import androidx.databinding.BaseObservable;
-import androidx.databinding.Bindable;
-import androidx.databinding.Observable;
-import androidx.databinding.ObservableList;
-import android.os.Parcel;
-import android.os.Parcelable;
-import androidx.annotation.Nullable;
-
-import com.wireguard.android.BR;
-import com.wireguard.config.Attribute;
-import com.wireguard.config.BadConfigException;
-import com.wireguard.config.InetEndpoint;
-import com.wireguard.config.Peer;
-import com.wireguard.crypto.Key;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import java9.util.Lists;
-import java9.util.Sets;
-import java9.util.stream.Collectors;
-import java9.util.stream.Stream;
-
-public class PeerProxy extends BaseObservable implements Parcelable {
- public static final Parcelable.Creator<PeerProxy> CREATOR = new PeerProxyCreator();
- private static final Set<String> IPV4_PUBLIC_NETWORKS = new LinkedHashSet<>(Lists.of(
- "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
- "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
- "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
- "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
- "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
- "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
- ));
- private static final Set<String> IPV4_WILDCARD = Sets.of("0.0.0.0/0");
-
- private final List<String> dnsRoutes = new ArrayList<>();
- private String allowedIps;
- private AllowedIpsState allowedIpsState = AllowedIpsState.INVALID;
- private String endpoint;
- @Nullable private InterfaceDnsListener interfaceDnsListener;
- @Nullable private ConfigProxy owner;
- @Nullable private PeerListListener peerListListener;
- private String persistentKeepalive;
- private String preSharedKey;
- private String publicKey;
- private int totalPeers;
-
- private PeerProxy(final Parcel in) {
- allowedIps = in.readString();
- endpoint = in.readString();
- persistentKeepalive = in.readString();
- preSharedKey = in.readString();
- publicKey = in.readString();
- }
-
- public PeerProxy(final Peer other) {
- allowedIps = Attribute.join(other.getAllowedIps());
- endpoint = other.getEndpoint().map(InetEndpoint::toString).orElse("");
- persistentKeepalive = other.getPersistentKeepalive().map(String::valueOf).orElse("");
- preSharedKey = other.getPreSharedKey().map(Key::toBase64).orElse("");
- publicKey = other.getPublicKey().toBase64();
- }
-
- public PeerProxy() {
- allowedIps = "";
- endpoint = "";
- persistentKeepalive = "";
- preSharedKey = "";
- publicKey = "";
- }
-
- public void bind(final ConfigProxy owner) {
- final InterfaceProxy interfaze = owner.getInterface();
- final ObservableList<PeerProxy> peers = owner.getPeers();
- if (interfaceDnsListener == null)
- interfaceDnsListener = new InterfaceDnsListener(this);
- interfaze.addOnPropertyChangedCallback(interfaceDnsListener);
- setInterfaceDns(interfaze.getDnsServers());
- if (peerListListener == null)
- peerListListener = new PeerListListener(this);
- peers.addOnListChangedCallback(peerListListener);
- setTotalPeers(peers.size());
- this.owner = owner;
- }
-
- private void calculateAllowedIpsState() {
- final AllowedIpsState newState;
- if (totalPeers == 1) {
- // String comparison works because we only care if allowedIps is a superset of one of
- // the above sets of (valid) *networks*. We are not checking for a superset based on
- // the individual addresses in each set.
- final Collection<String> networkStrings = getAllowedIpsSet();
- // If allowedIps contains both the wildcard and the public networks, then private
- // networks aren't excluded!
- if (networkStrings.containsAll(IPV4_WILDCARD))
- newState = AllowedIpsState.CONTAINS_IPV4_WILDCARD;
- else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS))
- newState = AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS;
- else
- newState = AllowedIpsState.OTHER;
- } else {
- newState = AllowedIpsState.INVALID;
- }
- if (newState != allowedIpsState) {
- allowedIpsState = newState;
- notifyPropertyChanged(BR.ableToExcludePrivateIps);
- notifyPropertyChanged(BR.excludingPrivateIps);
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Bindable
- public String getAllowedIps() {
- return allowedIps;
- }
-
- private Set<String> getAllowedIpsSet() {
- return new LinkedHashSet<>(Lists.of(Attribute.split(allowedIps)));
- }
-
- @Bindable
- public String getEndpoint() {
- return endpoint;
- }
-
- @Bindable
- public String getPersistentKeepalive() {
- return persistentKeepalive;
- }
-
- @Bindable
- public String getPreSharedKey() {
- return preSharedKey;
- }
-
- @Bindable
- public String getPublicKey() {
- return publicKey;
- }
-
- @Bindable
- public boolean isAbleToExcludePrivateIps() {
- return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS
- || allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD;
- }
-
- @Bindable
- public boolean isExcludingPrivateIps() {
- return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS;
- }
-
- public Peer resolve() throws BadConfigException {
- final Peer.Builder builder = new Peer.Builder();
- if (!allowedIps.isEmpty())
- builder.parseAllowedIPs(allowedIps);
- if (!endpoint.isEmpty())
- builder.parseEndpoint(endpoint);
- if (!persistentKeepalive.isEmpty())
- builder.parsePersistentKeepalive(persistentKeepalive);
- if (!preSharedKey.isEmpty())
- builder.parsePreSharedKey(preSharedKey);
- if (!publicKey.isEmpty())
- builder.parsePublicKey(publicKey);
- return builder.build();
- }
-
- public void setAllowedIps(final String allowedIps) {
- this.allowedIps = allowedIps;
- notifyPropertyChanged(BR.allowedIps);
- calculateAllowedIpsState();
- }
-
- public void setEndpoint(final String endpoint) {
- this.endpoint = endpoint;
- notifyPropertyChanged(BR.endpoint);
- }
-
- public void setExcludingPrivateIps(final boolean excludingPrivateIps) {
- if (!isAbleToExcludePrivateIps() || isExcludingPrivateIps() == excludingPrivateIps)
- return;
- final Set<String> oldNetworks = excludingPrivateIps ? IPV4_WILDCARD : IPV4_PUBLIC_NETWORKS;
- final Set<String> newNetworks = excludingPrivateIps ? IPV4_PUBLIC_NETWORKS : IPV4_WILDCARD;
- final Collection<String> input = getAllowedIpsSet();
- final int outputSize = input.size() - oldNetworks.size() + newNetworks.size();
- final Collection<String> output = new LinkedHashSet<>(outputSize);
- boolean replaced = false;
- // Replace the first instance of the wildcard with the public network list, or vice versa.
- for (final String network : input) {
- if (oldNetworks.contains(network)) {
- if (!replaced) {
- for (final String replacement : newNetworks)
- if (!output.contains(replacement))
- output.add(replacement);
- replaced = true;
- }
- } else if (!output.contains(network)) {
- output.add(network);
- }
- }
- // DNS servers only need to handled specially when we're excluding private IPs.
- if (excludingPrivateIps)
- output.addAll(dnsRoutes);
- else
- output.removeAll(dnsRoutes);
- allowedIps = Attribute.join(output);
- allowedIpsState = excludingPrivateIps ?
- AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS : AllowedIpsState.CONTAINS_IPV4_WILDCARD;
- notifyPropertyChanged(BR.allowedIps);
- notifyPropertyChanged(BR.excludingPrivateIps);
- }
-
- private void setInterfaceDns(final CharSequence dnsServers) {
- final List<String> newDnsRoutes = Stream.of(Attribute.split(dnsServers))
- .filter(server -> !server.contains(":"))
- .map(server -> server + "/32")
- .collect(Collectors.toUnmodifiableList());
- if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) {
- final Collection<String> input = getAllowedIpsSet();
- final Collection<String> output = new LinkedHashSet<>(input.size() + 1);
- // Yes, this is quadratic in the number of DNS servers, but most users have 1 or 2.
- for (final String network : input)
- if (!dnsRoutes.contains(network) || newDnsRoutes.contains(network))
- output.add(network);
- // Since output is a Set, this does the Right Thing™ (it does not duplicate networks).
- output.addAll(newDnsRoutes);
- // None of the public networks are /32s, so this cannot change the AllowedIPs state.
- allowedIps = Attribute.join(output);
- notifyPropertyChanged(BR.allowedIps);
- }
- dnsRoutes.clear();
- dnsRoutes.addAll(newDnsRoutes);
- }
-
- public void setPersistentKeepalive(final String persistentKeepalive) {
- this.persistentKeepalive = persistentKeepalive;
- notifyPropertyChanged(BR.persistentKeepalive);
- }
-
- public void setPreSharedKey(final String preSharedKey) {
- this.preSharedKey = preSharedKey;
- notifyPropertyChanged(BR.preSharedKey);
- }
-
- public void setPublicKey(final String publicKey) {
- this.publicKey = publicKey;
- notifyPropertyChanged(BR.publicKey);
- }
-
- private void setTotalPeers(final int totalPeers) {
- if (this.totalPeers == totalPeers)
- return;
- this.totalPeers = totalPeers;
- calculateAllowedIpsState();
- }
-
- public void unbind() {
- if (owner == null)
- return;
- final InterfaceProxy interfaze = owner.getInterface();
- final ObservableList<PeerProxy> peers = owner.getPeers();
- if (interfaceDnsListener != null)
- interfaze.removeOnPropertyChangedCallback(interfaceDnsListener);
- if (peerListListener != null)
- peers.removeOnListChangedCallback(peerListListener);
- peers.remove(this);
- setInterfaceDns("");
- setTotalPeers(0);
- owner = null;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeString(allowedIps);
- dest.writeString(endpoint);
- dest.writeString(persistentKeepalive);
- dest.writeString(preSharedKey);
- dest.writeString(publicKey);
- }
-
- private enum AllowedIpsState {
- CONTAINS_IPV4_PUBLIC_NETWORKS,
- CONTAINS_IPV4_WILDCARD,
- INVALID,
- OTHER
- }
-
- private static final class InterfaceDnsListener extends Observable.OnPropertyChangedCallback {
- private final WeakReference<PeerProxy> weakPeerProxy;
-
- private InterfaceDnsListener(final PeerProxy peerProxy) {
- weakPeerProxy = new WeakReference<>(peerProxy);
- }
-
- @Override
- public void onPropertyChanged(final Observable sender, final int propertyId) {
- @Nullable final PeerProxy peerProxy = weakPeerProxy.get();
- if (peerProxy == null) {
- sender.removeOnPropertyChangedCallback(this);
- return;
- }
- // This shouldn't be possible, but try to avoid a ClassCastException anyway.
- if (!(sender instanceof InterfaceProxy))
- return;
- if (!(propertyId == BR._all || propertyId == BR.dnsServers))
- return;
- peerProxy.setInterfaceDns(((InterfaceProxy) sender).getDnsServers());
- }
- }
-
- private static final class PeerListListener
- extends ObservableList.OnListChangedCallback<ObservableList<PeerProxy>> {
- private final WeakReference<PeerProxy> weakPeerProxy;
-
- private PeerListListener(final PeerProxy peerProxy) {
- weakPeerProxy = new WeakReference<>(peerProxy);
- }
-
- @Override
- public void onChanged(final ObservableList<PeerProxy> sender) {
- @Nullable final PeerProxy peerProxy = weakPeerProxy.get();
- if (peerProxy == null) {
- sender.removeOnListChangedCallback(this);
- return;
- }
- peerProxy.setTotalPeers(sender.size());
- }
-
- @Override
- public void onItemRangeChanged(final ObservableList<PeerProxy> sender,
- final int positionStart, final int itemCount) {
- // Do nothing.
- }
-
- @Override
- public void onItemRangeInserted(final ObservableList<PeerProxy> sender,
- final int positionStart, final int itemCount) {
- onChanged(sender);
- }
-
- @Override
- public void onItemRangeMoved(final ObservableList<PeerProxy> sender,
- final int fromPosition, final int toPosition,
- final int itemCount) {
- // Do nothing.
- }
-
- @Override
- public void onItemRangeRemoved(final ObservableList<PeerProxy> sender,
- final int positionStart, final int itemCount) {
- onChanged(sender);
- }
- }
-
- private static class PeerProxyCreator implements Parcelable.Creator<PeerProxy> {
- @Override
- public PeerProxy createFromParcel(final Parcel in) {
- return new PeerProxy(in);
- }
-
- @Override
- public PeerProxy[] newArray(final int size) {
- return new PeerProxy[size];
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java b/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
deleted file mode 100644
index 79572aa3..00000000
--- a/app/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import androidx.annotation.Nullable;
-import android.text.InputFilter;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-
-import com.wireguard.crypto.Key;
-
-/**
- * InputFilter for entering WireGuard private/public keys encoded with base64.
- */
-
-public class KeyInputFilter implements InputFilter {
- private static boolean isAllowed(final char c) {
- return Character.isLetterOrDigit(c) || c == '+' || c == '/';
- }
-
- public static InputFilter newInstance() {
- return new KeyInputFilter();
- }
-
- @Nullable
- @Override
- public CharSequence filter(final CharSequence source,
- final int sStart, final int sEnd,
- final Spanned dest,
- final int dStart, final int dEnd) {
- SpannableStringBuilder replacement = null;
- int rIndex = 0;
- final int dLength = dest.length();
- for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
- final char c = source.charAt(sIndex);
- final int dIndex = dStart + (sIndex - sStart);
- // Restrict characters to the base64 character set.
- // Ensure adding this character does not push the length over the limit.
- if (((dIndex + 1 < Key.Format.BASE64.getLength() && isAllowed(c)) ||
- (dIndex + 1 == Key.Format.BASE64.getLength() && c == '=')) &&
- dLength + (sIndex - sStart) < Key.Format.BASE64.getLength()) {
- ++rIndex;
- } else {
- if (replacement == null)
- replacement = new SpannableStringBuilder(source, sStart, sEnd);
- replacement.delete(rIndex, rIndex + 1);
- }
- }
- return replacement;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java b/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java
deleted file mode 100644
index 2fe9c924..00000000
--- a/app/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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 {
- private static final int[] STATE_MULTISELECTED = {R.attr.state_multiselected};
- private boolean multiselected;
-
- 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);
- }
-
- @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);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java b/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java
deleted file mode 100644
index 07759a18..00000000
--- a/app/src/main/java/com/wireguard/android/widget/NameInputFilter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import androidx.annotation.Nullable;
-import android.text.InputFilter;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-
-import com.wireguard.android.model.Tunnel;
-
-/**
- * InputFilter for entering WireGuard configuration names (Linux interface names).
- */
-
-public class NameInputFilter implements InputFilter {
- private static boolean isAllowed(final char c) {
- return Character.isLetterOrDigit(c) || "_=+.-".indexOf(c) >= 0;
- }
-
- public static InputFilter newInstance() {
- return new NameInputFilter();
- }
-
- @Nullable
- @Override
- public CharSequence filter(final CharSequence source,
- final int sStart, final int sEnd,
- final Spanned dest,
- final int dStart, final int dEnd) {
- SpannableStringBuilder replacement = null;
- int rIndex = 0;
- final int dLength = dest.length();
- for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
- final char c = source.charAt(sIndex);
- final int dIndex = dStart + (sIndex - sStart);
- // Restrict characters to those valid in interfaces.
- // Ensure adding this character does not push the length over the limit.
- if ((dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c)) &&
- dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) {
- ++rIndex;
- } else {
- if (replacement == null)
- replacement = new SpannableStringBuilder(source, sStart, sEnd);
- replacement.delete(rIndex, rIndex + 1);
- }
- }
- return replacement;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java b/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java
deleted file mode 100644
index 61ce1362..00000000
--- a/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright © 2018 The Android Open Source Project
- * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import androidx.annotation.ColorInt;
-import androidx.annotation.IntRange;
-import androidx.annotation.Nullable;
-import android.util.FloatProperty;
-
-@TargetApi(Build.VERSION_CODES.N)
-public class SlashDrawable extends Drawable {
-
- private static final float CENTER_X = 10.65f;
- private static final float CENTER_Y = 11.869239f;
- private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
- // Draw the slash washington-monument style; rotate to no-u-turn style
- private static final float DEFAULT_ROTATION = -45f;
- private static final long QS_ANIM_LENGTH = 350;
- private static final float SCALE = 24f;
- private static final float SLASH_HEIGHT = 28f;
- // These values are derived in un-rotated (vertical) orientation
- private static final float SLASH_WIDTH = 1.8384776f;
- // Bottom is derived during animation
- private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
- private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
- private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
- private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
- @Override
- public Float get(final SlashDrawable object) {
- return object.mCurrentSlashLength;
- }
-
- @Override
- public void setValue(final SlashDrawable object, final float value) {
- object.mCurrentSlashLength = value;
- }
- };
- private final Drawable mDrawable;
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Path mPath = new Path();
- private final RectF mSlashRect = new RectF(0, 0, 0, 0);
- private boolean mAnimationEnabled = true;
- // Animate this value on change
- private float mCurrentSlashLength;
- private float mRotation;
- private boolean mSlashed;
-
- public SlashDrawable(final Drawable d) {
- mDrawable = d;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public void draw(final Canvas canvas) {
- canvas.save();
- final Matrix m = new Matrix();
- final int width = getBounds().width();
- final int height = getBounds().height();
- final float radiusX = scale(CORNER_RADIUS, width);
- final float radiusY = scale(CORNER_RADIUS, height);
- updateRect(
- scale(LEFT, width),
- scale(TOP, height),
- scale(RIGHT, width),
- scale(TOP + mCurrentSlashLength, height)
- );
-
- mPath.reset();
- // Draw the slash vertically
- mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
- // Rotate -45 + desired rotation
- m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.drawPath(mPath, mPaint);
-
- // Rotate back to vertical
- m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
-
- // Draw another rect right next to the first, for clipping
- m.setTranslate(mSlashRect.width(), 0);
- mPath.transform(m);
- mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
- m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
- canvas.clipPath(mPath, Region.Op.DIFFERENCE);
- else
- canvas.clipOutPath(mPath);
-
- mDrawable.draw(canvas);
- canvas.restore();
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mDrawable.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mDrawable.getIntrinsicWidth();
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- protected void onBoundsChange(final Rect bounds) {
- super.onBoundsChange(bounds);
- mDrawable.setBounds(bounds);
- }
-
- private float scale(final float frac, final int width) {
- return frac * width;
- }
-
- @Override
- public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
- mDrawable.setAlpha(alpha);
- mPaint.setAlpha(alpha);
- }
-
- public void setAnimationEnabled(final boolean enabled) {
- mAnimationEnabled = enabled;
- }
-
- @Override
- public void setColorFilter(@Nullable final ColorFilter colorFilter) {
- mDrawable.setColorFilter(colorFilter);
- mPaint.setColorFilter(colorFilter);
- }
-
- private void setDrawableTintList(@Nullable final ColorStateList tint) {
- mDrawable.setTintList(tint);
- }
-
- public void setRotation(final float rotation) {
- if (mRotation == rotation)
- return;
- mRotation = rotation;
- invalidateSelf();
- }
-
- @SuppressWarnings("unchecked")
- public void setSlashed(final boolean slashed) {
- if (mSlashed == slashed) return;
-
- mSlashed = slashed;
-
- final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
- final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
-
- if (mAnimationEnabled) {
- final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
- anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
- anim.setDuration(QS_ANIM_LENGTH);
- anim.start();
- } else {
- mCurrentSlashLength = end;
- invalidateSelf();
- }
- }
-
- @Override
- public void setTint(@ColorInt final int tintColor) {
- super.setTint(tintColor);
- mDrawable.setTint(tintColor);
- mPaint.setColor(tintColor);
- }
-
- @Override
- public void setTintList(@Nullable final ColorStateList tint) {
- super.setTintList(tint);
- setDrawableTintList(tint);
- mPaint.setColor(tint == null ? 0 : tint.getDefaultColor());
- invalidateSelf();
- }
-
- @Override
- public void setTintMode(final Mode tintMode) {
- super.setTintMode(tintMode);
- mDrawable.setTintMode(tintMode);
- }
-
- private void updateRect(final float left, final float top, final float right, final float bottom) {
- mSlashRect.left = left;
- mSlashRect.top = top;
- mSlashRect.right = right;
- mSlashRect.bottom = bottom;
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/ToggleSwitch.java b/app/src/main/java/com/wireguard/android/widget/ToggleSwitch.java
deleted file mode 100644
index dcb9aceb..00000000
--- a/app/src/main/java/com/wireguard/android/widget/ToggleSwitch.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright © 2013 The Android Open Source Project
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.content.Context;
-import android.os.Parcelable;
-import androidx.annotation.Nullable;
-import android.util.AttributeSet;
-import android.widget.Switch;
-
-public class ToggleSwitch extends Switch {
- private boolean isRestoringState;
- @Nullable private OnBeforeCheckedChangeListener listener;
-
- public ToggleSwitch(final Context context) {
- this(context, null);
- }
-
- @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
- public ToggleSwitch(final Context context, @Nullable final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- isRestoringState = true;
- super.onRestoreInstanceState(state);
- isRestoringState = false;
- }
-
- @Override
- public void setChecked(final boolean checked) {
- if (checked == isChecked())
- return;
- if (isRestoringState || listener == null) {
- super.setChecked(checked);
- return;
- }
- setEnabled(false);
- listener.onBeforeCheckedChanged(this, checked);
- }
-
- public void setCheckedInternal(final boolean checked) {
- super.setChecked(checked);
- setEnabled(true);
- }
-
- public void setOnBeforeCheckedChangeListener(final OnBeforeCheckedChangeListener listener) {
- this.listener = listener;
- }
-
- public interface OnBeforeCheckedChangeListener {
- void onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java
deleted file mode 100644
index 616e176e..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. 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 androidx.coordinatorlayout.widget.CoordinatorLayout;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
-import android.util.AttributeSet;
-import android.view.View;
-
-public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
-
- private static final long ANIMATION_DURATION = 250;
- private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
-
- public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- 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 layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
- final View dependency) {
- return dependency instanceof Snackbar.SnackbarLayout;
- }
-
- @Override
- public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
- final View dependency) {
- 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());
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
deleted file mode 100644
index 9704081b..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
+++ /dev/null
@@ -1,629 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. 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.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.TypedArray;
-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 androidx.annotation.Keep;
-import androidx.annotation.Nullable;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import androidx.core.content.res.ResourcesCompat;
-import androidx.appcompat.widget.AppCompatTextView;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.TextView;
-
-import com.wireguard.android.R;
-
-public class FloatingActionsMenu extends ViewGroup {
- public static final int EXPAND_DOWN = 1;
- public static final int EXPAND_LEFT = 2;
- public static final int EXPAND_RIGHT = 3;
- public static final int EXPAND_UP = 0;
- public static final int LABELS_ON_LEFT_SIDE = 0;
- public static final int LABELS_ON_RIGHT_SIDE = 1;
- private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator();
- private static final int ANIMATION_DURATION = 300;
- private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equals("ASUS");
- private static final float COLLAPSED_PLUS_ROTATION = 0f;
- private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f);
- private static final float EXPANDED_PLUS_ROTATION = 90f + 45f;
- private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator();
- private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
- private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
- private final Rect touchArea = new Rect(0, 0, 0, 0);
- private float behaviorYTranslation;
- @Nullable private FloatingActionButton mAddButton;
- private int mButtonSpacing;
- private int mButtonsCount;
- private int mExpandDirection;
- private boolean mExpanded;
- private int mLabelsMargin;
- private int mLabelsPosition;
- private int mLabelsStyle;
- private int mLabelsVerticalOffset;
- @Nullable private OnFloatingActionsMenuUpdateListener mListener;
- private int mMaxButtonHeight;
- private int mMaxButtonWidth;
- @Nullable private RotatingDrawable mRotatingDrawable;
- @Nullable private TouchDelegateGroup mTouchDelegateGroup;
- private float scrollYTranslation;
-
- public FloatingActionsMenu(final Context context) {
- this(context, null);
- }
-
- public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs);
- }
-
- public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- private static int adjustForOvershoot(final int dimension) {
- return dimension * 12 / 10;
- }
-
- public void addButton(final LabeledFloatingActionButton button) {
- addView(button, mButtonsCount - 1);
- mButtonsCount++;
-
- if (mLabelsStyle != 0) {
- createLabels();
- }
- }
-
- public void collapse() {
- collapse(false);
- }
-
- private void collapse(final boolean immediately) {
- if (mExpanded) {
- mExpanded = false;
- mTouchDelegateGroup.setEnabled(false);
- mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
- mCollapseAnimation.start();
- mExpandAnimation.cancel();
-
- if (mListener != null) {
- mListener.onMenuCollapsed();
- }
- }
- }
-
- public void collapseImmediately() {
- collapse(true);
- }
-
- private void createAddButton(final Context context) {
- final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_white, context.getTheme()));
- mRotatingDrawable = rotatingDrawable;
-
- final TimeInterpolator interpolator = new OvershootInterpolator();
-
- final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION);
- final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION);
-
- collapseAnimator.setInterpolator(interpolator);
- expandAnimator.setInterpolator(interpolator);
-
- mExpandAnimation.play(expandAnimator);
- mCollapseAnimation.play(collapseAnimator);
-
- mAddButton = new FloatingActionButton(context);
- mAddButton.setImageDrawable(rotatingDrawable);
- mAddButton.setId(R.id.fab_expand_menu_button);
- mAddButton.setOnClickListener(v -> toggle());
-
- addView(mAddButton, super.generateDefaultLayoutParams());
- mButtonsCount++;
- }
-
- private void createLabels() {
- final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
-
- for (int i = 0; i < mButtonsCount; i++) {
- final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
-
- if (button instanceof LabeledFloatingActionButton) {
- final String title = ((LabeledFloatingActionButton) button).getTitle();
-
- final AppCompatTextView label = new AppCompatTextView(context);
- if (!BROKEN_LABEL_STYLE)
- label.setTextAppearance(context, mLabelsStyle);
- label.setText(title);
- addView(label);
-
- button.setTag(R.id.fab_label, label);
- }
- }
- }
-
- public void expand() {
- if (!mExpanded) {
- mExpanded = true;
- mTouchDelegateGroup.setEnabled(true);
- mCollapseAnimation.cancel();
- mExpandAnimation.start();
-
- if (mListener != null) {
- mListener.onMenuExpanded();
- }
- }
- }
-
- private boolean expandsHorizontally() {
- return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(super.generateDefaultLayoutParams());
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
- return new LayoutParams(super.generateLayoutParams(attrs));
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) {
- return new LayoutParams(super.generateLayoutParams(p));
- }
-
- public float getBehaviorYTranslation() {
- return behaviorYTranslation;
- }
-
- public float getScrollYTranslation() {
- return scrollYTranslation;
- }
-
- private void init(final Context context, @Nullable final AttributeSet attributeSet) {
- mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing));
- mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin);
- mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset);
-
- mTouchDelegateGroup = new TouchDelegateGroup(this);
- setTouchDelegate(mTouchDelegateGroup);
-
- final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
- mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP);
- mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0);
- mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE);
- attr.recycle();
-
- if (mLabelsStyle != 0 && expandsHorizontally()) {
- throw new IllegalStateException("Action labels in horizontal expand orientation are not supported");
- }
-
- createAddButton(context);
- }
-
- public boolean isExpanded() {
- return mExpanded;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- bringChildToFront(mAddButton);
- mButtonsCount = getChildCount();
-
- if (mLabelsStyle != 0) {
- createLabels();
- }
- }
-
- @Override
- protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- final boolean expandUp = mExpandDirection == EXPAND_UP;
-
- if (changed) {
- mTouchDelegateGroup.clearTouchDelegates();
- }
-
- final int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0;
- // Ensure mAddButton is centered on the line where the buttons should be
- final int buttonsHorizontalCenter = (mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? r - l - mMaxButtonWidth / 2
- : mMaxButtonWidth / 2);
- final int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2;
- mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight());
-
- final int labelsOffset = mMaxButtonWidth / 2 + mLabelsMargin;
- final int labelsXNearButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? buttonsHorizontalCenter - labelsOffset
- : buttonsHorizontalCenter + labelsOffset;
-
- int nextY = expandUp ?
- addButtonY - mButtonSpacing :
- addButtonY + mAddButton.getMeasuredHeight() + mButtonSpacing;
-
- for (int i = mButtonsCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if (child == mAddButton || child.getVisibility() == GONE) continue;
-
- final int childX = buttonsHorizontalCenter - child.getMeasuredWidth() / 2;
- final int childY = expandUp ? nextY - child.getMeasuredHeight() : nextY;
- child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
-
- final float collapsedTranslation = addButtonY - childY;
- final float expandedTranslation = 0f;
-
- child.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
- child.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams params = (LayoutParams) child.getLayoutParams();
- params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- params.setAnimationsTarget(child);
-
- final View label = (View) child.getTag(R.id.fab_label);
- if (label != null) {
- final int labelXAwayFromButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelsXNearButton - label.getMeasuredWidth()
- : labelsXNearButton + label.getMeasuredWidth();
-
- final int labelLeft = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelXAwayFromButton
- : labelsXNearButton;
-
- final int labelRight = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelsXNearButton
- : labelXAwayFromButton;
-
- final int labelTop = childY - mLabelsVerticalOffset + (child.getMeasuredHeight() - label.getMeasuredHeight()) / 2;
-
- label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight());
-
- touchArea.set(Math.min(childX, labelLeft),
- childY - mButtonSpacing / 2,
- Math.max(childX + child.getMeasuredWidth(), labelRight),
- childY + child.getMeasuredHeight() + mButtonSpacing / 2);
- mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(new Rect(touchArea), child));
-
- label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
- label.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams labelParams = (LayoutParams) label.getLayoutParams();
- labelParams.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- labelParams.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- labelParams.setAnimationsTarget(label);
- }
-
- nextY = expandUp ?
- childY - mButtonSpacing :
- childY + child.getMeasuredHeight() + mButtonSpacing;
- }
- break;
-
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- final boolean expandLeft = mExpandDirection == EXPAND_LEFT;
-
- final int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0;
- // Ensure mAddButton is centered on the line where the buttons should be
- final int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2;
- mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight());
-
- int nextX = expandLeft ?
- addButtonX - mButtonSpacing :
- addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing;
-
- for (int i = mButtonsCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if (child == mAddButton || child.getVisibility() == GONE) continue;
-
- final int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX;
- final int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2;
- child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
-
- final float collapsedTranslation = addButtonX - childX;
- final float expandedTranslation = 0f;
-
- child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation);
- child.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams params = (LayoutParams) child.getLayoutParams();
- params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- params.setAnimationsTarget(child);
-
- nextX = expandLeft ?
- childX - mButtonSpacing :
- childX + child.getMeasuredWidth() + mButtonSpacing;
- }
-
- break;
- }
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- measureChildren(widthMeasureSpec, heightMeasureSpec);
-
- int width = 0;
- int height = 0;
-
- mMaxButtonWidth = 0;
- mMaxButtonHeight = 0;
- int maxLabelWidth = 0;
-
- for (int i = 0; i < mButtonsCount; i++) {
- final View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
- height += child.getMeasuredHeight();
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- width += child.getMeasuredWidth();
- mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
- break;
- }
-
- if (!expandsHorizontally()) {
- final TextView label = (TextView) child.getTag(R.id.fab_label);
- if (label != null) {
- maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
- }
- }
- }
-
- if (expandsHorizontally()) {
- height = mMaxButtonHeight;
- } else {
- width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
- }
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- height += mButtonSpacing * (mButtonsCount - 1);
- height = adjustForOvershoot(height);
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- width += mButtonSpacing * (mButtonsCount - 1);
- width = adjustForOvershoot(width);
- break;
- }
-
- setMeasuredDimension(width, height);
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (state instanceof SavedState) {
- final SavedState savedState = (SavedState) state;
- mExpanded = savedState.mExpanded;
- mTouchDelegateGroup.setEnabled(mExpanded);
-
- if (mRotatingDrawable != null) {
- mRotatingDrawable.setRotation(mExpanded ? EXPANDED_PLUS_ROTATION : COLLAPSED_PLUS_ROTATION);
- }
-
- super.onRestoreInstanceState(savedState.getSuperState());
- } else {
- super.onRestoreInstanceState(state);
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.mExpanded = mExpanded;
-
- return savedState;
- }
-
- public void removeButton(final LabeledFloatingActionButton button) {
- removeView(button.getLabelView());
- removeView(button);
- button.setTag(R.id.fab_label, null);
- mButtonsCount--;
- }
-
- public void setBehaviorYTranslation(final float behaviorYTranslation) {
- this.behaviorYTranslation = behaviorYTranslation;
- setTranslationY(behaviorYTranslation + scrollYTranslation);
- }
-
- @Override
- public void setEnabled(final boolean enabled) {
- super.setEnabled(enabled);
-
- mAddButton.setEnabled(enabled);
- }
-
- public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
- mListener = listener;
- }
-
- public void setScrollYTranslation(final float scrollYTranslation) {
- this.scrollYTranslation = scrollYTranslation;
- setTranslationY(behaviorYTranslation + scrollYTranslation);
- }
-
- public void toggle() {
- if (mExpanded) {
- collapse();
- } else {
- expand();
- }
- }
-
- public interface OnFloatingActionsMenuUpdateListener {
- void onMenuCollapsed();
-
- void onMenuExpanded();
- }
-
- private static class RotatingDrawable extends LayerDrawable {
- private float mRotation;
-
- RotatingDrawable(final Drawable drawable) {
- super(new Drawable[]{drawable});
- }
-
- @Override
- public void draw(final Canvas canvas) {
- canvas.save();
- canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
- super.draw(canvas);
- canvas.restore();
- }
-
- @SuppressWarnings("UnusedDeclaration")
- public float getRotation() {
- return mRotation;
- }
-
- @Keep
- @SuppressWarnings("UnusedDeclaration")
- public void setRotation(final float rotation) {
- mRotation = rotation;
- invalidateSelf();
- }
- }
-
- public static class SavedState extends BaseSavedState {
- public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
-
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- private boolean mExpanded;
-
- public SavedState(final Parcelable parcel) {
- super(parcel);
- }
-
- private SavedState(final Parcel in) {
- super(in);
- mExpanded = in.readInt() == 1;
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(mExpanded ? 1 : 0);
- }
- }
-
- private class LayoutParams extends ViewGroup.LayoutParams {
-
- private final ObjectAnimator mCollapseAlpha = new ObjectAnimator();
- private final ObjectAnimator mCollapseDir = new ObjectAnimator();
- private final ObjectAnimator mExpandAlpha = new ObjectAnimator();
- private final ObjectAnimator mExpandDir = new ObjectAnimator();
- private boolean animationsSetToPlay;
-
- LayoutParams(final ViewGroup.LayoutParams source) {
- super(source);
-
- mExpandDir.setInterpolator(EXPAND_INTERPOLATOR);
- mExpandAlpha.setInterpolator(ALPHA_EXPAND_INTERPOLATOR);
- mCollapseDir.setInterpolator(COLLAPSE_INTERPOLATOR);
- mCollapseAlpha.setInterpolator(COLLAPSE_INTERPOLATOR);
-
- mCollapseAlpha.setProperty(View.ALPHA);
- mCollapseAlpha.setFloatValues(1f, 0f);
-
- mExpandAlpha.setProperty(View.ALPHA);
- mExpandAlpha.setFloatValues(0f, 1f);
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- mCollapseDir.setProperty(View.TRANSLATION_Y);
- mExpandDir.setProperty(View.TRANSLATION_Y);
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- mCollapseDir.setProperty(View.TRANSLATION_X);
- mExpandDir.setProperty(View.TRANSLATION_X);
- break;
- }
- }
-
- private void addLayerTypeListener(final Animator animator, final View view) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setLayerType(LAYER_TYPE_NONE, null);
- }
-
- @Override
- public void onAnimationStart(final Animator animation) {
- view.setLayerType(LAYER_TYPE_HARDWARE, null);
- }
- });
- }
-
- public void setAnimationsTarget(final View view) {
- mCollapseAlpha.setTarget(view);
- mCollapseDir.setTarget(view);
- mExpandAlpha.setTarget(view);
- mExpandDir.setTarget(view);
-
- // Now that the animations have targets, set them to be played
- if (!animationsSetToPlay) {
- addLayerTypeListener(mExpandDir, view);
- addLayerTypeListener(mCollapseDir, view);
-
- mCollapseAnimation.play(mCollapseAlpha);
- mCollapseAnimation.play(mCollapseDir);
- mExpandAnimation.play(mExpandAlpha);
- mExpandAnimation.play(mExpandDir);
- animationsSetToPlay = true;
- }
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java
deleted file mode 100644
index e1af4484..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import androidx.recyclerview.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()));
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java b/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java
deleted file mode 100644
index be94a6e5..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import androidx.annotation.Nullable;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.wireguard.android.R;
-
-public class LabeledFloatingActionButton extends FloatingActionButton {
-
- @Nullable private final String title;
-
- public LabeledFloatingActionButton(final Context context) {
- this(context, null);
- }
-
- public LabeledFloatingActionButton(final Context context, @Nullable final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LabeledFloatingActionButton(final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
-
- final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.LabeledFloatingActionButton, 0, 0);
- title = attr.getString(R.styleable.LabeledFloatingActionButton_fab_title);
- attr.recycle();
- }
-
- @Nullable
- TextView getLabelView() {
- return (TextView) getTag(R.id.fab_label);
- }
-
- @Nullable
- public String getTitle() {
- return title;
- }
-
- @Override
- public void setVisibility(final int visibility) {
- final TextView label = getLabelView();
- if (label != null) {
- label.setVisibility(visibility);
- }
-
- super.setVisibility(visibility);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java b/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java
deleted file mode 100644
index e16d1d3e..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import android.graphics.Rect;
-import androidx.annotation.Nullable;
-import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-public class TouchDelegateGroup extends TouchDelegate {
- private static final Rect USELESS_HACKY_RECT = new Rect();
- private final Collection<TouchDelegate> mTouchDelegates = new ArrayList<>();
- @Nullable private TouchDelegate mCurrentTouchDelegate;
- private boolean mEnabled;
-
- public TouchDelegateGroup(final View uselessHackyView) {
- super(USELESS_HACKY_RECT, uselessHackyView);
- }
-
- public void addTouchDelegate(final TouchDelegate touchDelegate) {
- mTouchDelegates.add(touchDelegate);
- }
-
- public void clearTouchDelegates() {
- mTouchDelegates.clear();
- mCurrentTouchDelegate = null;
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- if (!mEnabled)
- return false;
-
- TouchDelegate delegate = null;
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- for (final TouchDelegate touchDelegate : mTouchDelegates) {
- if (touchDelegate.onTouchEvent(event)) {
- mCurrentTouchDelegate = touchDelegate;
- return true;
- }
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- delegate = mCurrentTouchDelegate;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- delegate = mCurrentTouchDelegate;
- mCurrentTouchDelegate = null;
- break;
- }
-
- return delegate != null && delegate.onTouchEvent(event);
- }
-
- public void removeTouchDelegate(final TouchDelegate touchDelegate) {
- mTouchDelegates.remove(touchDelegate);
- if (mCurrentTouchDelegate == touchDelegate) {
- mCurrentTouchDelegate = null;
- }
- }
-
- public void setEnabled(final boolean enabled) {
- mEnabled = enabled;
- }
-}