diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2020-03-09 19:06:11 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2020-03-09 19:24:27 +0530 |
commit | 7d48bef70a56d4370856eedab619b1f83ac3d0d0 (patch) | |
tree | 76fd859578e499cd3a8fd2f402652530ea36a72d /app/src/main/java/com/wireguard | |
parent | 6bc3e257f80a273d35d07099bd4ed99eb45163bf (diff) |
Rename app module to ui
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/com/wireguard')
52 files changed, 0 insertions, 6014 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 2ebeb69d..00000000 --- a/app/src/main/java/com/wireguard/android/Application.java +++ /dev/null @@ -1,171 +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 android.os.StrictMode; -import android.util.Log; - -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.ExceptionLoggers; -import com.wireguard.android.util.ModuleLoader; -import com.wireguard.android.util.RootShell; -import com.wireguard.android.util.ToolsInstaller; - -import java.lang.ref.WeakReference; -import java.util.Locale; - -import java9.util.concurrent.CompletableFuture; - -public class Application extends android.app.Application { - private static final String TAG = "WireGuard/" + Application.class.getSimpleName(); - public static final String USER_AGENT; - - static { - String preferredAbi = "unknown ABI"; - if (Build.SUPPORTED_ABIS.length > 0) - preferredAbi = Build.SUPPORTED_ABIS[0]; - USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, preferredAbi, Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT); - } - - @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 (!ModuleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) { - try { - app.rootShell.start(); - didStartRootShell = true; - app.moduleLoader.loadModule(); - } catch (final Exception ignored) { - } - } - if (ModuleLoader.isModuleLoaded()) { - try { - if (!didStartRootShell) - app.rootShell.start(); - backend = new WgQuickBackend(app.getApplicationContext(), app.rootShell, app.toolsInstaller); - } catch (final Exception ignored) { - } - } - if (backend == null) { - backend = new GoBackend(app.getApplicationContext()); - GoBackend.setAlwaysOnCallback(() -> { - get().tunnelManager.restoreState(true).whenComplete(ExceptionLoggers.D); - }); - } - 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); - } - - if (BuildConfig.DEBUG) { - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); - } - } - - @Override - public void onCreate() { - Log.i(TAG, USER_AGENT); - super.onCreate(); - - asyncWorker = new AsyncWorker(AsyncTask.SERIAL_EXECUTOR, new Handler(Looper.getMainLooper())); - rootShell = new RootShell(getApplicationContext()); - toolsInstaller = new ToolsInstaller(getApplicationContext(), rootShell); - moduleLoader = new ModuleLoader(getApplicationContext(), rootShell, USER_AGENT); - - 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 66aecec3..00000000 --- a/app/src/main/java/com/wireguard/android/QuickTileService.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android; - -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 androidx.annotation.RequiresApi; -import android.util.Log; - -import com.wireguard.android.activity.MainActivity; -import com.wireguard.android.activity.TunnelToggleActivity; -import com.wireguard.android.backend.Tunnel.State; -import com.wireguard.android.model.ObservableTunnel; -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. - */ - -@RequiresApi(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 ObservableTunnel 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) { - unlockAndRun(() -> { - final Tile tile = getQsTile(); - if (tile != null) { - tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn); - tile.updateTile(); - } - tunnel.setState(State.TOGGLE).whenComplete((v, t) -> { - if (t == null) { - updateTile(); - } else { - final Intent toggleIntent = new Intent(this, TunnelToggleActivity.class); - toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(toggleIntent); - } - }); - }); - } 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 updateTile() { - // Update the tunnel. - final ObservableTunnel 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() == 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 8ec58ee8..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.ObservableTunnel; - -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 ObservableTunnel selectedTunnel; - - public void addOnSelectedTunnelChangedListener(final OnSelectedTunnelChangedListener listener) { - selectionChangeRegistry.add(listener); - } - - @Nullable - public ObservableTunnel 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 ObservableTunnel oldTunnel, @Nullable ObservableTunnel newTunnel); - - public void removeOnSelectedTunnelChangedListener( - final OnSelectedTunnelChangedListener listener) { - selectionChangeRegistry.remove(listener); - } - - public void setSelectedTunnel(@Nullable final ObservableTunnel tunnel) { - final ObservableTunnel oldTunnel = selectedTunnel; - if (Objects.equals(oldTunnel, tunnel)) - return; - selectedTunnel = tunnel; - onSelectedTunnelChanged(oldTunnel, tunnel); - selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, tunnel); - } - - public interface OnSelectedTunnelChangedListener { - void onSelectedTunnelChanged(@Nullable ObservableTunnel oldTunnel, @Nullable ObservableTunnel newTunnel); - } - - private static final class SelectionChangeNotifier - extends NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel> { - @Override - public void onNotifyCallback(final OnSelectedTunnelChangedListener listener, - final ObservableTunnel oldTunnel, final int ignored, - final ObservableTunnel newTunnel) { - listener.onSelectedTunnelChanged(oldTunnel, newTunnel); - } - } - - private static final class SelectionChangeRegistry - extends CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel> { - 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 4c33f000..00000000 --- a/app/src/main/java/com/wireguard/android/activity/MainActivity.java +++ /dev/null @@ -1,144 +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.Fragment; -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.OnApplyWindowInsetsListener; -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.model.ObservableTunnel; - -import java.util.List; - -/** - * 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; - - @Override - public void onBackPressed() { - final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount(); - // 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) { - getSupportFragmentManager().popBackStack(); - 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; - getSupportFragmentManager().addOnBackStackChangedListener(this); - onBackStackChanged(); - // Dispatch insets on back stack change - // This is required to ensure replaced fragments are also able to consume insets - findViewById(R.id.master_detail_wrapper).setOnApplyWindowInsetsListener((OnApplyWindowInsetsListener) (v, insets) -> { - final FragmentManager fragmentManager = getSupportFragmentManager(); - fragmentManager.addOnBackStackChangedListener(() -> { - final List<Fragment> fragments = fragmentManager.getFragments(); - for (int i = 0; i < fragments.size(); i++) { - fragments.get(i).requireView().dispatchApplyWindowInsets(insets); - } - }); - return insets; - }); - } - - @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 ObservableTunnel oldTunnel, - @Nullable final ObservableTunnel 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 f545c371..00000000 --- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java +++ /dev/null @@ -1,129 +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 com.wireguard.android.util.ModuleLoader; - -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 (ModuleLoader.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 602ad37c..00000000 --- a/app/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java +++ /dev/null @@ -1,47 +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(); - - @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)) { - AppCompatDelegate.setDefaultNightMode( - sharedPreferences.getBoolean(key, false) ? - AppCompatDelegate.MODE_NIGHT_YES : - AppCompatDelegate.MODE_NIGHT_NO); - } - } -} 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 c87ec537..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.ObservableTunnel; - -/** - * 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 ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { - finish(); - } -} diff --git a/app/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java b/app/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java deleted file mode 100644 index 09a34bf7..00000000 --- a/app/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.activity; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatActivity; - -import android.content.ComponentName; -import android.os.Bundle; -import android.os.Build; -import android.service.quicksettings.TileService; -import android.util.Log; -import android.widget.Toast; - -import com.wireguard.android.Application; -import com.wireguard.android.QuickTileService; -import com.wireguard.android.R; -import com.wireguard.android.model.ObservableTunnel; -import com.wireguard.android.backend.Tunnel.State; -import com.wireguard.android.util.ErrorMessages; - -@RequiresApi(Build.VERSION_CODES.N) -public class TunnelToggleActivity extends AppCompatActivity { - private static final String TAG = "WireGuard/" + TunnelToggleActivity.class.getSimpleName(); - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final ObservableTunnel tunnel = Application.getTunnelManager().getLastUsedTunnel(); - if (tunnel == null) - return; - tunnel.setState(State.TOGGLE).whenComplete((v, t) -> { - TileService.requestListeningState(this, new ComponentName(this, QuickTileService.class)); - onToggleFinished(t); - finishAffinity(); - }); - } - - private void onToggleFinished(@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(); - } -} 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/AddTunnelsSheet.kt b/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt deleted file mode 100644 index 3df141be..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package com.wireguard.android.fragment - -import android.content.Intent -import android.graphics.drawable.GradientDrawable -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewTreeObserver -import android.widget.FrameLayout -import androidx.fragment.app.Fragment -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.zxing.integration.android.IntentIntegrator -import com.wireguard.android.R -import com.wireguard.android.activity.TunnelCreatorActivity -import com.wireguard.android.util.resolveAttribute - -class AddTunnelsSheet : BottomSheetDialogFragment() { - - private lateinit var behavior: BottomSheetBehavior<FrameLayout> - private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) { - } - - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == BottomSheetBehavior.STATE_COLLAPSED) { - dismiss() - } - } - } - - override fun getTheme(): Int { - return R.style.BottomSheetDialogTheme - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - if (savedInstanceState != null) dismiss() - return inflater.inflate(R.layout.add_tunnels_bottom_sheet, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - view.viewTreeObserver.removeOnGlobalLayoutListener(this) - val dialog = dialog as BottomSheetDialog? ?: return - behavior = dialog.behavior - behavior.state = BottomSheetBehavior.STATE_EXPANDED - behavior.peekHeight = 0 - behavior.addBottomSheetCallback(bottomSheetCallback) - dialog.findViewById<View>(R.id.create_empty)?.setOnClickListener { - dismiss() - onRequestCreateConfig() - } - dialog.findViewById<View>(R.id.create_from_file)?.setOnClickListener { - dismiss() - onRequestImportConfig() - } - dialog.findViewById<View>(R.id.create_from_qrcode)?.setOnClickListener { - dismiss() - onRequestScanQRCode() - } - } - }) - val gradientDrawable = GradientDrawable().apply { - setColor(requireContext().resolveAttribute(R.attr.colorBackground)) - } - view.background = gradientDrawable - } - - override fun dismiss() { - super.dismiss() - behavior.removeBottomSheetCallback(bottomSheetCallback) - } - - private fun requireTargetFragment(): Fragment { - return requireNotNull(targetFragment) { "A target fragment should always be set" } - } - - private fun onRequestCreateConfig() { - startActivity(Intent(activity, TunnelCreatorActivity::class.java)) - } - - private fun onRequestImportConfig() { - val intent = Intent(Intent.ACTION_GET_CONTENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "*/*" - } - requireTargetFragment().startActivityForResult(intent, TunnelListFragment.REQUEST_IMPORT) - } - - private fun onRequestScanQRCode() { - val integrator = IntentIntegrator.forSupportFragment(requireTargetFragment()).apply { - setOrientationLocked(false) - setBeepEnabled(false) - setPrompt(getString(R.string.qr_code_hint)) - } - integrator.initiateScan(listOf(IntentIntegrator.QR_CODE)) - } -} 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 43178665..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.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.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; -import java9.util.stream.Collectors; -import java9.util.stream.StreamSupport; - -public class AppListDialogFragment extends DialogFragment { - - private static final String KEY_EXCLUDED_APPS = "excludedApps"; - private final ObservableKeyedList<String, ApplicationData> appData = new ObservableKeyedArrayList<>(); - private List<String> currentlyExcludedApps = Collections.emptyList(); - - 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> applicationData = new ArrayList<>(); - for (ResolveInfo resolveInfo : resolveInfos) { - String packageName = resolveInfo.activityInfo.packageName; - applicationData.add(new ApplicationData(resolveInfo.loadIcon(pm), resolveInfo.loadLabel(pm).toString(), packageName, currentlyExcludedApps.contains(packageName))); - } - - Collections.sort(applicationData, Comparators.comparing(ApplicationData::getName, String.CASE_INSENSITIVE_ORDER)); - return applicationData; - }).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); - final List<String> excludedApps = requireArguments().getStringArrayList(KEY_EXCLUDED_APPS); - currentlyExcludedApps = (excludedApps != null) ? excludedApps : Collections.emptyList(); - } - - @Override - public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { - final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireActivity()); - alertDialogBuilder.setTitle(R.string.excluded_applications); - - final AppListDialogFragmentBinding binding = AppListDialogFragmentBinding.inflate(requireActivity().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.toggle_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 -> { - final List<ApplicationData> selectedItems = StreamSupport.stream(appData) - .filter(ApplicationData::isExcludedFromTunnel) - .collect(Collectors.toList()); - final boolean excludeAll = selectedItems.isEmpty(); - for (final ApplicationData app : appData) - app.setExcludedFromTunnel(excludeAll); - })); - return dialog; - } - - private 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 23bf44e7..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.ObservableTunnel; -import com.wireguard.android.backend.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 ObservableTunnel pendingTunnel; - @Nullable private Boolean pendingTunnelUp; - - @Nullable - protected ObservableTunnel 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 ObservableTunnel tunnel) { - if (activity != null) - activity.setSelectedTunnel(tunnel); - } - - public void setTunnelState(final View view, final boolean checked) { - final ViewDataBinding binding = DataBindingUtil.findBinding(view); - final ObservableTunnel 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 ObservableTunnel 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 = requireContext().getString(messageResId, error); - final View view = getView(); - if (view != null) - Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); - else - Toast.makeText(requireContext(), 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 effa0593..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 = requireActivity(); - - 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 8d90fa7e..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.java +++ /dev/null @@ -1,162 +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 androidx.databinding.DataBindingUtil; - -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.databinding.TunnelDetailPeerBinding; -import com.wireguard.android.model.ObservableTunnel; -import com.wireguard.android.backend.Tunnel.State; -import com.wireguard.android.ui.EdgeToEdge; -import com.wireguard.crypto.Key; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * Fragment that shows details about a specific tunnel. - */ - -public class TunnelDetailFragment extends BaseFragment { - @Nullable private TunnelDetailFragmentBinding binding; - @Nullable private Timer timer; - @Nullable private State lastState = State.TOGGLE; - - @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 void onStop() { - super.onStop(); - if (timer != null) { - timer.cancel(); - timer = null; - } - } - - @Override - public void onResume() { - super.onResume(); - timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - updateStats(); - } - }, 0, 1000); - } - - @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(); - EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot()); - EdgeToEdge.setUpScrollingContent((ViewGroup) binding.getRoot(), null); - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } - - @Override - public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel newTunnel) { - if (binding == null) - return; - binding.setTunnel(newTunnel); - if (newTunnel == null) - binding.setConfig(null); - else - newTunnel.getConfigAsync().thenAccept(binding::setConfig); - lastState = State.TOGGLE; - updateStats(); - } - - @Override - public void onViewStateRestored(@Nullable final Bundle savedInstanceState) { - if (binding == null) { - return; - } - - binding.setFragment(this); - onSelectedTunnelChanged(null, getSelectedTunnel()); - super.onViewStateRestored(savedInstanceState); - } - - private String formatBytes(final long bytes) { - if (bytes < 1024) - return requireContext().getString(R.string.transfer_bytes, bytes); - else if (bytes < 1024*1024) - return requireContext().getString(R.string.transfer_kibibytes, bytes/1024.0); - else if (bytes < 1024*1024*1024) - return requireContext().getString(R.string.transfer_mibibytes, bytes/(1024.0*1024.0)); - else if (bytes < 1024*1024*1024*1024) - return requireContext().getString(R.string.transfer_gibibytes, bytes/(1024.0*1024.0*1024.0)); - return requireContext().getString(R.string.transfer_tibibytes, bytes/(1024.0*1024.0*1024.0)/1024.0); - } - - private void updateStats() { - if (binding == null || !isResumed()) - return; - final ObservableTunnel tunnel = binding.getTunnel(); - if (tunnel == null) - return; - final State state = tunnel.getState(); - if (state != State.UP && lastState == state) - return; - lastState = state; - tunnel.getStatisticsAsync().whenComplete((statistics, throwable) -> { - if (throwable != null) { - for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) { - final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)); - if (peer == null) - continue; - peer.transferLabel.setVisibility(View.GONE); - peer.transferText.setVisibility(View.GONE); - } - return; - } - for (int i = 0; i < binding.peersLayout.getChildCount(); ++i) { - final TunnelDetailPeerBinding peer = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i)); - if (peer == null) - continue; - final Key publicKey = peer.getItem().getPublicKey(); - final long rx = statistics.peerRx(publicKey); - final long tx = statistics.peerTx(publicKey); - if (rx == 0 && tx == 0) { - peer.transferLabel.setVisibility(View.GONE); - peer.transferText.setVisibility(View.GONE); - continue; - } - peer.transferText.setText(requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))); - peer.transferLabel.setVisibility(View.VISIBLE); - peer.transferText.setVisibility(View.VISIBLE); - } - }); - } -} 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 92aeb52a..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ /dev/null @@ -1,264 +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 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.ObservableTunnel; -import com.wireguard.android.model.TunnelManager; -import com.wireguard.android.ui.EdgeToEdge; -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 ObservableTunnel tunnel; - - private void onConfigLoaded(final Config config) { - if (binding != null) { - binding.setConfig(new ConfigProxy(config)); - } - } - - private void onConfigSaved(final ObservableTunnel 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(requireContext(), 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(); - EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot()); - EdgeToEdge.setUpScrollingContent(binding.mainContainer, null); - 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 ObservableTunnel 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) { - if (binding != null) { - final ArrayList<String> excludedApps = new ArrayList<>(binding.getConfig().getInterface().getExcludedApplications()); - final AppListDialogFragment fragment = AppListDialogFragment.newInstance(excludedApps, this); - fragment.show(getParentFragmentManager(), 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 ObservableTunnel oldTunnel, - @Nullable final ObservableTunnel 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 ObservableTunnel 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(requireContext(), 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 ObservableTunnel 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 21618e60..00000000 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java +++ /dev/null @@ -1,449 +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.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.ObservableTunnel; -import com.wireguard.android.ui.EdgeToEdge; -import com.wireguard.android.util.ErrorMessages; -import com.wireguard.android.widget.MultiselectableRelativeLayout; -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 { - public static final int REQUEST_IMPORT = 1; - private static final int REQUEST_TARGET_FRAGMENT = 2; - private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName(); - - private final ActionModeListener actionModeListener = new ActionModeListener(); - @Nullable private ActionMode actionMode; - @Nullable private TunnelListFragmentBinding binding; - - 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… - ConfigNamingDialogFragment.newInstance(configText).show(getParentFragmentManager(), 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<ObservableTunnel>> 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<ObservableTunnel> tunnels = new ArrayList<>(futureTunnels.size()); - for (final CompletableFuture<ObservableTunnel> futureTunnel : futureTunnels) { - ObservableTunnel 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); - } - } - - @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.createFab.setOnClickListener(v -> { - final AddTunnelsSheet bottomSheet = new AddTunnelsSheet(); - bottomSheet.setTargetFragment(this, REQUEST_TARGET_FRAGMENT); - bottomSheet.show(getParentFragmentManager(), "BOTTOM_SHEET"); - }); - binding.executePendingBindings(); - EdgeToEdge.setUpRoot((ViewGroup) binding.getRoot()); - EdgeToEdge.setUpFAB(binding.createFab); - EdgeToEdge.setUpScrollingContent(binding.tunnelList, binding.createFab); - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } - - @Override - public void onPause() { - super.onPause(); - } - - public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) { - startActivity(new Intent(getActivity(), TunnelCreatorActivity.class)); - } - - @Override - public void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putIntegerArrayList("CHECKED_ITEMS", actionModeListener.getCheckedItems()); - } - - @Override - public void onSelectedTunnelChanged(@Nullable final ObservableTunnel oldTunnel, @Nullable final ObservableTunnel 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 showSnackbar(final CharSequence message) { - if (binding != null) { - final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG); - snackbar.setAnchorView(binding.createFab); - snackbar.show(); - } - } - - 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); - } - showSnackbar(message); - } - - private void onTunnelImportFinished(final List<ObservableTunnel> 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()); - - showSnackbar(message); - } - - @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, ObservableTunnel>) (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 ObservableTunnel 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<ObservableTunnel> tunnelsToDelete = new ArrayList<>(); - for (final Integer position : copyCheckedItems) - tunnelsToDelete.add(tunnels.get(position)); - - final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete) - .map(ObservableTunnel::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/ObservableTunnel.java b/app/src/main/java/com/wireguard/android/model/ObservableTunnel.java deleted file mode 100644 index ce3197f2..00000000 --- a/app/src/main/java/com/wireguard/android/model/ObservableTunnel.java +++ /dev/null @@ -1,142 +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.backend.Statistics; -import com.wireguard.android.backend.Tunnel; -import com.wireguard.android.util.ExceptionLoggers; -import com.wireguard.config.Config; -import com.wireguard.util.Keyed; - -import java9.util.concurrent.CompletableFuture; -import java9.util.concurrent.CompletionStage; - -/** - * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel. - */ - -public class ObservableTunnel extends BaseObservable implements Keyed<String>, Tunnel { - private final TunnelManager manager; - @Nullable private Config config; - private State state; - private String name; - @Nullable private Statistics statistics; - - ObservableTunnel(final TunnelManager manager, final String name, - @Nullable final Config config, final State state) { - this.name = name; - this.manager = manager; - this.config = config; - this.state = state; - } - - 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; - } - - @Override - @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() { - if (statistics == null || statistics.isStale()) - TunnelManager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E); - return statistics; - } - - public CompletionStage<Statistics> getStatisticsAsync() { - if (statistics == null || statistics.isStale()) - return TunnelManager.getTunnelStatistics(this); - return CompletableFuture.completedFuture(statistics); - } - - Config onConfigChanged(final Config config) { - this.config = config; - notifyPropertyChanged(BR.config); - return config; - } - - 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; - } - - @Override - public void onStateChange(final State newState) { - onStateChanged(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); - } -} 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 35d56c81..00000000 --- a/app/src/main/java/com/wireguard/android/model/TunnelManager.java +++ /dev/null @@ -1,301 +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.backend.Tunnel; -import com.wireguard.android.backend.Tunnel.State; -import com.wireguard.android.backend.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, ObservableTunnel>> 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, ObservableTunnel> tunnels = new ObservableSortedKeyedArrayList<>(COMPARATOR); - private boolean haveLoaded; - @Nullable private ObservableTunnel lastUsedTunnel; - - public TunnelManager(final ConfigStore configStore) { - this.configStore = configStore; - } - - static CompletionStage<State> getTunnelState(final ObservableTunnel tunnel) { - return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getState(tunnel)) - .thenApply(tunnel::onStateChanged); - } - - static CompletionStage<Statistics> getTunnelStatistics(final ObservableTunnel tunnel) { - return Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getStatistics(tunnel)) - .thenApply(tunnel::onStatisticsChanged); - } - - private ObservableTunnel addToList(final String name, @Nullable final Config config, final State state) { - final ObservableTunnel tunnel = new ObservableTunnel(this, name, config, state); - tunnels.add(tunnel); - return tunnel; - } - - public CompletionStage<ObservableTunnel> 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 ObservableTunnel 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, null); - try { - configStore.delete(tunnel.getName()); - } catch (final Exception e) { - if (originalState == State.UP) - Application.getBackend().setState(tunnel, State.UP, tunnel.getConfig()); - // 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 ObservableTunnel getLastUsedTunnel() { - return lastUsedTunnel; - } - - CompletionStage<Config> getTunnelConfig(final ObservableTunnel tunnel) { - return Application.getAsyncWorker().supplyAsync(() -> configStore.load(tunnel.getName())) - .thenApply(tunnel::onConfigChanged); - } - - public CompletableFuture<ObservableSortedKeyedList<String, ObservableTunnel>> getTunnels() { - return completableTunnels; - } - - public void onCreate() { - Application.getAsyncWorker().supplyAsync(configStore::enumerate) - .thenAcceptBoth(Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().getRunningTunnelNames()), 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().getRunningTunnelNames()) - .thenAccept(running -> { - for (final ObservableTunnel 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(ObservableTunnel::getName) - .collect(Collectors.toUnmodifiableSet()); - Application.getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, runningTunnels).apply(); - } - - private void setLastUsedTunnel(@Nullable final ObservableTunnel 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 ObservableTunnel tunnel, final Config config) { - return Application.getAsyncWorker().supplyAsync(() -> { - Application.getBackend().setState(tunnel, tunnel.getState(), config); - return configStore.save(tunnel.getName(), config); - }).thenApply(tunnel::onConfigChanged); - } - - CompletionStage<String> setTunnelName(final ObservableTunnel 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, null); - configStore.rename(tunnel.getName(), name); - final String newName = tunnel.onNameChanged(name); - if (originalState == State.UP) - Application.getBackend().setState(tunnel, State.UP, tunnel.getConfig()); - 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 ObservableTunnel tunnel, final State state) { - // Ensure the configuration is loaded before trying to use it. - return tunnel.getConfigAsync().thenCompose(config -> - Application.getAsyncWorker().supplyAsync(() -> Application.getBackend().setState(tunnel, state, config)) - ).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 ObservableTunnel 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 aac649dd..00000000 --- a/app/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java +++ /dev/null @@ -1,92 +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.ErrorMessages; -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(), ErrorMessages.get(throwable), 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 7e95a8ae..00000000 --- a/app/src/main/java/com/wireguard/android/preference/VersionPreference.java +++ /dev/null @@ -1,71 +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 com.wireguard.android.backend.Backend; -import com.wireguard.android.backend.GoBackend; -import com.wireguard.android.backend.WgQuickBackend; - -import java.util.Locale; - -public class VersionPreference extends Preference { - @Nullable private String versionSummary; - - private String getBackendPrettyName(final Context context, final Backend backend) { - if (backend instanceof GoBackend) - return context.getString(R.string.type_name_kernel_module); - if (backend instanceof WgQuickBackend) - return context.getString(R.string.type_name_go_userspace); - return ""; - } - - public VersionPreference(final Context context, final AttributeSet attrs) { - super(context, attrs); - - Application.getBackendAsync().thenAccept(backend -> { - versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH)); - Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> { - versionSummary = exception == null - ? getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version) - : getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).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 3af412a5..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.ObservableTunnel; -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<ObservableTunnel> tunnels) { - final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size()); - for (final ObservableTunnel 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/ui/EdgeToEdge.kt b/app/src/main/java/com/wireguard/android/ui/EdgeToEdge.kt deleted file mode 100644 index 52a19657..00000000 --- a/app/src/main/java/com/wireguard/android/ui/EdgeToEdge.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright © 2017-2020 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package com.wireguard.android.ui - -import android.view.View -import android.view.ViewGroup -import androidx.core.view.marginBottom -import androidx.core.view.marginLeft -import androidx.core.view.marginRight -import androidx.core.view.marginTop -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton - -/** - * A utility for edge-to-edge display. It provides several features needed to make the app - * displayed edge-to-edge on Android Q with gestural navigation. - */ - -object EdgeToEdge { - - @JvmStatic - fun setUpRoot(root: ViewGroup) { - root.systemUiVisibility = - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE - } - - @JvmStatic - fun setUpScrollingContent(scrollingContent: ViewGroup, fab: ExtendedFloatingActionButton?) { - val originalPaddingLeft = scrollingContent.paddingLeft - val originalPaddingRight = scrollingContent.paddingRight - val originalPaddingBottom = scrollingContent.paddingBottom - - val fabPaddingBottom = fab?.height ?: 0 - - val originalMarginTop = scrollingContent.marginTop - - scrollingContent.setOnApplyWindowInsetsListener { _, windowInsets -> - scrollingContent.updatePadding( - left = originalPaddingLeft + windowInsets.systemWindowInsetLeft, - right = originalPaddingRight + windowInsets.systemWindowInsetRight, - bottom = originalPaddingBottom + fabPaddingBottom + windowInsets.systemWindowInsetBottom - ) - scrollingContent.updateLayoutParams<ViewGroup.MarginLayoutParams> { - topMargin = originalMarginTop + windowInsets.systemWindowInsetTop - } - windowInsets - } - } - - @JvmStatic - fun setUpFAB(fab: ExtendedFloatingActionButton) { - val originalMarginLeft = fab.marginLeft - val originalMarginRight = fab.marginRight - val originalMarginBottom = fab.marginBottom - fab.setOnApplyWindowInsetsListener { _, windowInsets -> - fab.updateLayoutParams<ViewGroup.MarginLayoutParams> { - leftMargin = originalMarginLeft + windowInsets.systemWindowInsetLeft - rightMargin = originalMarginRight + windowInsets.systemWindowInsetRight - bottomMargin = originalMarginBottom + windowInsets.systemWindowInsetBottom - } - windowInsets - } - } -} 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 7db46fa9..00000000 --- a/app/src/main/java/com/wireguard/android/util/DownloadsFileSaver.java +++ /dev/null @@ -1,98 +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)); - @SuppressWarnings("deprecation") - 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 { - @SuppressWarnings("deprecation") - 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 481a6ffb..00000000 --- a/app/src/main/java/com/wireguard/android/util/ErrorMessages.java +++ /dev/null @@ -1,160 +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 android.os.RemoteException; - -import androidx.annotation.Nullable; - -import com.wireguard.android.Application; -import com.wireguard.android.R; -import com.wireguard.android.backend.BackendException; -import com.wireguard.android.util.RootShell.RootShellException; -import com.wireguard.config.BadConfigException; -import com.wireguard.config.BadConfigException.Location; -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<BadConfigException.Reason, Integer> BCE_REASON_MAP = new EnumMap<>(Maps.of( - BadConfigException.Reason.INVALID_KEY, R.string.bad_config_reason_invalid_key, - BadConfigException.Reason.INVALID_NUMBER, R.string.bad_config_reason_invalid_number, - BadConfigException.Reason.INVALID_VALUE, R.string.bad_config_reason_invalid_value, - BadConfigException.Reason.MISSING_ATTRIBUTE, R.string.bad_config_reason_missing_attribute, - BadConfigException.Reason.MISSING_SECTION, R.string.bad_config_reason_missing_section, - BadConfigException.Reason.MISSING_VALUE, R.string.bad_config_reason_missing_value, - BadConfigException.Reason.SYNTAX_ERROR, R.string.bad_config_reason_syntax_error, - BadConfigException.Reason.UNKNOWN_ATTRIBUTE, R.string.bad_config_reason_unknown_attribute, - BadConfigException.Reason.UNKNOWN_SECTION, R.string.bad_config_reason_unknown_section - )); - private static final Map<BackendException.Reason, Integer> BE_REASON_MAP = new EnumMap<>(Maps.of( - BackendException.Reason.UNKNOWN_KERNEL_MODULE_NAME, R.string.module_version_error, - BackendException.Reason.WG_QUICK_CONFIG_ERROR_CODE, R.string.tunnel_config_error, - BackendException.Reason.TUNNEL_MISSING_CONFIG, R.string.no_config_error, - BackendException.Reason.VPN_NOT_AUTHORIZED, R.string.vpn_not_authorized_error, - BackendException.Reason.UNABLE_TO_START_VPN, R.string.vpn_start_error, - BackendException.Reason.TUN_CREATION_ERROR, R.string.tun_create_error, - BackendException.Reason.GO_ACTIVATION_ERROR_CODE, R.string.tunnel_on_error - )); - private static final Map<RootShellException.Reason, Integer> RSE_REASON_MAP = new EnumMap<>(Maps.of( - RootShellException.Reason.NO_ROOT_ACCESS, R.string.error_root, - RootShellException.Reason.SHELL_MARKER_COUNT_ERROR, R.string.shell_marker_count_error, - RootShellException.Reason.SHELL_EXIT_STATUS_READ_ERROR, R.string.shell_exit_status_read_error, - RootShellException.Reason.SHELL_START_ERROR, R.string.shell_start_error, - RootShellException.Reason.CREATE_BIN_DIR_ERROR, R.string.create_bin_dir_error, - RootShellException.Reason.CREATE_TEMP_DIR_ERROR, R.string.create_temp_dir_error - )); - 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 instanceof BackendException) { - final BackendException be = (BackendException) rootCause; - message = resources.getString(BE_REASON_MAP.get(be.getReason()), be.getFormat()); - } else if (rootCause instanceof RootShellException) { - final RootShellException rse = (RootShellException) rootCause; - message = resources.getString(RSE_REASON_MAP.get(rse.getReason()), rse.getFormat()); - } 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 || cause instanceof BackendException || - cause instanceof RootShellException) - break; - final Throwable nextCause = cause.getCause(); - if (nextCause instanceof RemoteException) - break; - cause = nextCause; - } - 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/Extensions.kt b/app/src/main/java/com/wireguard/android/util/Extensions.kt deleted file mode 100644 index 6b528a85..00000000 --- a/app/src/main/java/com/wireguard/android/util/Extensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright © 2020 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.android.util - -import android.content.Context -import android.util.TypedValue -import androidx.annotation.AttrRes - -fun Context.resolveAttribute(@AttrRes attrRes: Int): Int { - val typedValue = TypedValue() - theme.resolveAttribute(attrRes, typedValue, true) - return typedValue.data -} 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 bf094a5e..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.util.RootShell.RootShellException; - -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.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 RootShell rootShell; - private final String userAgent; - private final File moduleDir; - private final File tmpDir; - - public ModuleLoader(final Context context, final RootShell rootShell, final String userAgent) { - moduleDir = new File(context.getCacheDir(), "kmod"); - tmpDir = new File(context.getCacheDir(), "tmp"); - this.rootShell = rootShell; - this.userAgent = userAgent; - } - - public boolean moduleMightExist() { - return moduleDir.exists() && moduleDir.isDirectory(); - } - - public void loadModule() throws IOException, RootShellException { - rootShell.run(null, String.format("insmod \"%s/wireguard-$(sha256sum /proc/version|cut -d ' ' -f 1).ko\"", moduleDir.getAbsolutePath())); - } - - public static 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, RootShellException, NoSuchAlgorithmException { - final List<String> output = new ArrayList<>(); - rootShell.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)); - 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 FileOutputStream 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); - } - outputStream.getFD().sync(); - } - 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/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 030be25a..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.backend.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 e020aa81..00000000 --- a/app/src/main/java/com/wireguard/android/widget/SlashDrawable.java +++ /dev/null @@ -1,217 +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.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 androidx.annotation.RequiresApi; -import android.util.FloatProperty; - -@RequiresApi(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(); - } - - @SuppressWarnings("deprecation") - @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/util/Keyed.java b/app/src/main/java/com/wireguard/util/Keyed.java deleted file mode 100644 index f31a43a2..00000000 --- a/app/src/main/java/com/wireguard/util/Keyed.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.util; - -/** - * Interface for objects that have a identifying key of the given type. - */ - -public interface Keyed<K> { - K getKey(); -} diff --git a/app/src/main/java/com/wireguard/util/KeyedList.java b/app/src/main/java/com/wireguard/util/KeyedList.java deleted file mode 100644 index c116c1da..00000000 --- a/app/src/main/java/com/wireguard/util/KeyedList.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.util; - -import androidx.annotation.Nullable; - -import java.util.Collection; -import java.util.List; - -/** - * A list containing elements that can be looked up by key. A {@code KeyedList} cannot contain - * {@code null} elements. - */ - -public interface KeyedList<K, E extends Keyed<? extends K>> extends List<E> { - boolean containsAllKeys(Collection<K> keys); - - boolean containsKey(K key); - - @Nullable - E get(K key); - - @Nullable - E getLast(K key); - - int indexOfKey(K key); - - int lastIndexOfKey(K key); -} diff --git a/app/src/main/java/com/wireguard/util/SortedKeyedList.java b/app/src/main/java/com/wireguard/util/SortedKeyedList.java deleted file mode 100644 index b144fc85..00000000 --- a/app/src/main/java/com/wireguard/util/SortedKeyedList.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.wireguard.util; - -import androidx.annotation.Nullable; - -import java.util.Collection; -import java.util.Comparator; -import java.util.Set; - -/** - * A keyed list where all elements are sorted by the comparator returned by {@code comparator()} - * applied to their keys. - */ - -public interface SortedKeyedList<K, E extends Keyed<? extends K>> extends KeyedList<K, E> { - Comparator<? super K> comparator(); - - @Nullable - K firstKey(); - - Set<K> keySet(); - - @Nullable - K lastKey(); - - Collection<E> values(); -} |