summaryrefslogtreecommitdiffhomepage
path: root/ui/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/main')
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/BaseActivity.java102
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt79
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/MainActivity.java147
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/MainActivity.kt126
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/SettingsActivity.java134
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt115
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java47
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt42
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java37
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt27
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java53
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt44
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.kt17
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt17
14 files changed, 455 insertions, 532 deletions
diff --git a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.java b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.java
deleted file mode 100644
index 704ca766..00000000
--- a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.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.activity;
-
-import android.os.Bundle;
-
-import com.wireguard.android.Application;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.util.NonNullForAll;
-
-import java.util.Objects;
-
-import androidx.annotation.Nullable;
-import androidx.databinding.CallbackRegistry;
-import androidx.databinding.CallbackRegistry.NotifierCallback;
-
-/**
- * Base class for activities that need to remember the currently-selected tunnel.
- */
-
-@NonNullForAll
-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/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
new file mode 100644
index 00000000..ebf2e161
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.databinding.CallbackRegistry
+import androidx.databinding.CallbackRegistry.NotifierCallback
+import com.wireguard.android.Application
+import com.wireguard.android.model.ObservableTunnel
+
+/**
+ * Base class for activities that need to remember the currently-selected tunnel.
+ */
+abstract class BaseActivity : ThemeChangeAwareActivity() {
+ private val selectionChangeRegistry = SelectionChangeRegistry()
+ var selectedTunnel: ObservableTunnel? = null
+ set(value) {
+ val oldTunnel = field
+ if (oldTunnel == value) return
+ field = value
+ onSelectedTunnelChanged(oldTunnel, value)
+ selectionChangeRegistry.notifyCallbacks(oldTunnel, 0, value)
+ }
+ fun addOnSelectedTunnelChangedListener(listener: OnSelectedTunnelChangedListener) {
+ selectionChangeRegistry.add(listener)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // Restore the saved tunnel if there is one; otherwise grab it from the arguments.
+ val savedTunnelName = when {
+ savedInstanceState != null -> savedInstanceState.getString(KEY_SELECTED_TUNNEL)
+ intent != null -> intent.getStringExtra(KEY_SELECTED_TUNNEL)
+ else -> null
+ }
+ if (savedTunnelName != null) {
+ Application.getTunnelManager()
+ .tunnels
+ .thenAccept { selectedTunnel = it[savedTunnelName] }
+ }
+
+ // The selected tunnel must be set before the superclass method recreates fragments.
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ if (selectedTunnel != null) outState.putString(KEY_SELECTED_TUNNEL, selectedTunnel!!.name)
+ super.onSaveInstanceState(outState)
+ }
+
+ protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?)
+ fun removeOnSelectedTunnelChangedListener(
+ listener: OnSelectedTunnelChangedListener) {
+ selectionChangeRegistry.remove(listener)
+ }
+
+ interface OnSelectedTunnelChangedListener {
+ fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?)
+ }
+
+ private class SelectionChangeNotifier : NotifierCallback<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>() {
+ override fun onNotifyCallback(
+ listener: OnSelectedTunnelChangedListener,
+ oldTunnel: ObservableTunnel?,
+ ignored: Int,
+ newTunnel: ObservableTunnel?
+ ) {
+ listener.onSelectedTunnelChanged(oldTunnel, newTunnel)
+ }
+ }
+
+ private class SelectionChangeRegistry :
+ CallbackRegistry<OnSelectedTunnelChangedListener, ObservableTunnel, ObservableTunnel>(SelectionChangeNotifier())
+
+ companion object {
+ private const val KEY_SELECTED_TUNNEL = "selected_tunnel"
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/activity/MainActivity.java b/ui/src/main/java/com/wireguard/android/activity/MainActivity.java
deleted file mode 100644
index 61ae63e3..00000000
--- a/ui/src/main/java/com/wireguard/android/activity/MainActivity.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.activity;
-
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-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 com.wireguard.util.NonNullForAll;
-
-import java.util.List;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-
-/**
- * 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.
- */
-
-@NonNullForAll
-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((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
- @SuppressWarnings("UnnecessaryFullyQualifiedName")
- 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/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt b/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt
new file mode 100644
index 00000000..237a75f4
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/MainActivity.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.activity
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.widget.LinearLayout
+import androidx.appcompat.app.ActionBar
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import com.wireguard.android.R
+import com.wireguard.android.fragment.TunnelDetailFragment
+import com.wireguard.android.fragment.TunnelEditorFragment
+import com.wireguard.android.model.ObservableTunnel
+
+/**
+ * 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.
+ */
+class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
+ private var actionBar: ActionBar? = null
+ private var isTwoPaneLayout = false
+
+ override fun onBackPressed() {
+ val backStackEntries = supportFragmentManager.backStackEntryCount
+ // 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) {
+ supportFragmentManager.popBackStack()
+ selectedTunnel = null
+ return
+ }
+ super.onBackPressed()
+ }
+
+ override fun onBackStackChanged() {
+ if (actionBar == null) return
+ // Do not show the home menu when the two-pane layout is at the detail view (see above).
+ val backStackEntries = supportFragmentManager.backStackEntryCount
+ val minBackStackEntries = if (isTwoPaneLayout) 2 else 1
+ actionBar!!.setDisplayHomeAsUpEnabled(backStackEntries >= minBackStackEntries)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.main_activity)
+ actionBar = supportActionBar
+ isTwoPaneLayout = findViewById<View>(R.id.master_detail_wrapper) is LinearLayout
+ supportFragmentManager.addOnBackStackChangedListener(this)
+ onBackStackChanged()
+ // Dispatch insets on back stack change
+ // This is required to ensure replaced fragments are also able to consume insets
+ findViewById<View>(R.id.master_detail_wrapper).setOnApplyWindowInsetsListener { _, insets ->
+ val fragmentManager = supportFragmentManager
+ fragmentManager.addOnBackStackChangedListener {
+ fragmentManager.fragments.forEach {
+ it.requireView().dispatchApplyWindowInsets(insets)
+ }
+ }
+ insets
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.main_activity, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ android.R.id.home -> {
+ // The back arrow in the action bar should act the same as the back button.
+ onBackPressed()
+ true
+ }
+ R.id.menu_action_edit -> {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.detail_container, TunnelEditorFragment())
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+ .addToBackStack(null)
+ .commit()
+ true
+ }
+ // This menu item is handled by the editor fragment.
+ R.id.menu_action_save -> false
+ R.id.menu_settings -> {
+ startActivity(Intent(this, SettingsActivity::class.java))
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?,
+ newTunnel: ObservableTunnel?) {
+ val fragmentManager = supportFragmentManager
+ val backStackEntries = fragmentManager.backStackEntryCount
+ 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, TunnelDetailFragment())
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+ .addToBackStack(null)
+ .commit()
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.java
deleted file mode 100644
index a0726d55..00000000
--- a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.java
+++ /dev/null
@@ -1,134 +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 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 com.wireguard.util.NonNullForAll;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-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;
-
-/**
- * Interface for changing application-global persistent settings.
- */
-
-@NonNullForAll
-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
- @SuppressWarnings("UnnecessaryFullyQualifiedName")
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- finish();
- return true;
- }
- 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"),
- getPreferenceManager().findPreference("multiple_tunnels")
- };
- 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");
- final Preference kernelModuleDisabler = getPreferenceManager().findPreference("kernel_module_disabler");
- moduleInstaller.setVisible(false);
- if (ModuleLoader.isModuleLoaded()) {
- screen.removePreference(moduleInstaller);
- } else {
- screen.removePreference(kernelModuleDisabler);
- Application.getAsyncWorker().runAsync(Application.getRootShell()::start).whenComplete((v, e) -> {
- if (e == null)
- moduleInstaller.setVisible(true);
- else
- screen.removePreference(moduleInstaller);
- });
- }
- }
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
new file mode 100644
index 00000000..c7453617
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
@@ -0,0 +1,115 @@
+/*
+ * 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 android.util.SparseArray
+import android.view.MenuItem
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import com.wireguard.android.Application
+import com.wireguard.android.R
+import com.wireguard.android.backend.Backend
+import com.wireguard.android.backend.WgQuickBackend
+import com.wireguard.android.util.ModuleLoader
+import java.util.ArrayList
+import java.util.Arrays
+
+/**
+ * Interface for changing application-global persistent settings.
+ */
+class SettingsActivity : ThemeChangeAwareActivity() {
+ private val permissionRequestCallbacks = SparseArray<PermissionRequestCallback>()
+ private var permissionRequestCounter = 0
+
+ fun ensurePermissions(permissions: Array<String>, cb: PermissionRequestCallback) {
+ val needPermissions: MutableList<String> = ArrayList(permissions.size)
+ permissions.forEach {
+ if (ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED) {
+ needPermissions.add(it)
+ }
+ }
+ if (needPermissions.isEmpty()) {
+ val granted = IntArray(permissions.size)
+ Arrays.fill(granted, PackageManager.PERMISSION_GRANTED)
+ cb.done(permissions, granted)
+ return
+ }
+ val idx = permissionRequestCounter++
+ permissionRequestCallbacks.put(idx, cb)
+ ActivityCompat.requestPermissions(this,
+ needPermissions.toTypedArray(), idx)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
+ supportFragmentManager.beginTransaction()
+ .add(android.R.id.content, SettingsFragment())
+ .commit()
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ finish()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int,
+ permissions: Array<String>,
+ grantResults: IntArray) {
+ val f = permissionRequestCallbacks[requestCode]
+ if (f != null) {
+ permissionRequestCallbacks.remove(requestCode)
+ f.done(permissions, grantResults)
+ }
+ }
+
+ interface PermissionRequestCallback {
+ fun done(permissions: Array<String>, grantResults: IntArray)
+ }
+
+ class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
+ addPreferencesFromResource(R.xml.preferences)
+ val screen = preferenceScreen
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) screen.removePreference(preferenceManager.findPreference("dark_theme"))
+ val wgQuickOnlyPrefs = arrayOf(
+ preferenceManager.findPreference("tools_installer"),
+ preferenceManager.findPreference("restore_on_boot"),
+ preferenceManager.findPreference<Preference>("multiple_tunnels")
+ ).filterNotNull()
+ wgQuickOnlyPrefs.forEach { it.isVisible = false }
+ Application.getBackendAsync().thenAccept { backend ->
+ if (backend is WgQuickBackend) {
+ wgQuickOnlyPrefs.forEach { it.isVisible = true }
+ } else {
+ wgQuickOnlyPrefs.forEach { screen.removePreference(it) }
+ }
+ }
+ val moduleInstaller = preferenceManager.findPreference<Preference>("module_downloader")
+ val kernelModuleDisabler = preferenceManager.findPreference<Preference>("kernel_module_disabler")
+ moduleInstaller?.isVisible = false
+ if (ModuleLoader.isModuleLoaded()) {
+ screen.removePreference(moduleInstaller)
+ } else {
+ screen.removePreference(kernelModuleDisabler)
+ Application.getAsyncWorker().runAsync(Application.getRootShell()::start).whenComplete { _, e ->
+ if (e == null)
+ moduleInstaller?.isVisible = true
+ else
+ screen.removePreference(moduleInstaller)
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.java
deleted file mode 100644
index abe053e9..00000000
--- a/ui/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.os.Build;
-import android.os.Bundle;
-
-import com.wireguard.android.Application;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.app.AppCompatDelegate;
-
-@NonNullForAll
-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);
- recreate();
- }
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt
new file mode 100644
index 00000000..bd124cbc
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.SharedPreferences.OnSharedPreferenceChangeListener
+import android.os.Build
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatDelegate
+import com.wireguard.android.Application
+
+abstract class ThemeChangeAwareActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this)
+ }
+ }
+
+ override fun onDestroy() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this)
+ }
+ super.onDestroy()
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ when (key) {
+ "dark_theme" -> {
+ AppCompatDelegate.setDefaultNightMode(if (sharedPreferences.getBoolean(key, false)) {
+ AppCompatDelegate.MODE_NIGHT_YES
+ } else {
+ AppCompatDelegate.MODE_NIGHT_NO
+ })
+ recreate()
+ }
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java b/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.java
deleted file mode 100644
index 3dee0c00..00000000
--- a/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.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.activity;
-
-import android.os.Bundle;
-
-import com.wireguard.android.fragment.TunnelEditorFragment;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-
-/**
- * Standalone activity for creating tunnels.
- */
-
-@NonNullForAll
-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/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt
new file mode 100644
index 00000000..d3d4d4f9
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/TunnelCreatorActivity.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.activity
+
+import android.os.Bundle
+import com.wireguard.android.fragment.TunnelEditorFragment
+import com.wireguard.android.model.ObservableTunnel
+
+/**
+ * Standalone activity for creating tunnels.
+ */
+class TunnelCreatorActivity : BaseActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
+ supportFragmentManager.beginTransaction()
+ .add(android.R.id.content, TunnelEditorFragment())
+ .commit()
+ }
+ }
+
+ override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
+ finish()
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.java
deleted file mode 100644
index 82c79b6d..00000000
--- a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.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.activity;
-
-import android.content.ComponentName;
-import android.os.Build;
-import android.os.Bundle;
-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.backend.Tunnel.State;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.android.util.ErrorMessages;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.appcompat.app.AppCompatActivity;
-
-@RequiresApi(Build.VERSION_CODES.N)
-@NonNullForAll
-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/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
new file mode 100644
index 00000000..928a1108
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.activity
+
+import android.content.ComponentName
+import android.os.Build
+import android.os.Bundle
+import android.service.quicksettings.TileService
+import android.util.Log
+import android.widget.Toast
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AppCompatActivity
+import com.wireguard.android.Application
+import com.wireguard.android.QuickTileService
+import com.wireguard.android.R
+import com.wireguard.android.backend.Tunnel
+import com.wireguard.android.util.ErrorMessages
+
+@RequiresApi(Build.VERSION_CODES.N)
+class TunnelToggleActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return
+ tunnel.setState(Tunnel.State.TOGGLE).whenComplete { _, t ->
+ TileService.requestListeningState(this, ComponentName(this, QuickTileService::class.java))
+ onToggleFinished(t)
+ finishAffinity()
+ }
+ }
+
+ private fun onToggleFinished(throwable: Throwable?) {
+ if (throwable == null) return
+ val error = ErrorMessages.get(throwable)
+ val message = getString(R.string.toggle_error, error)
+ Log.e(TAG, message, throwable)
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+ }
+
+ companion object {
+ private val TAG = "WireGuard/" + TunnelToggleActivity::class.java.simpleName
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.kt
index b4671309..e49de5e2 100644
--- a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.kt
@@ -13,6 +13,7 @@ import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import com.wireguard.android.Application
import com.wireguard.android.R
+import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.util.DownloadsFileSaver
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.FragmentUtils
@@ -80,12 +81,16 @@ class LogExporterPreference(context: Context, attrs: AttributeSet?) : Preference
override fun getTitle() = context.getString(R.string.log_export_title)
override fun onClick() {
- FragmentUtils.getPrefActivity(this).ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, granted ->
- if (granted.isNotEmpty() && granted[0] == PackageManager.PERMISSION_GRANTED) {
- isEnabled = false
- exportLog()
- }
- }
+ FragmentUtils.getPrefActivity(this)
+ .ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ object: SettingsActivity.PermissionRequestCallback {
+ override fun done(permissions: Array<String>, grantResults: IntArray) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ isEnabled = false
+ exportLog()
+ }
+ }
+ })
}
companion object {
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
index 2a77f36c..db1b7735 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
@@ -13,6 +13,7 @@ import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import com.wireguard.android.Application
import com.wireguard.android.R
+import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.DownloadsFileSaver
import com.wireguard.android.util.ErrorMessages
@@ -84,12 +85,16 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
override fun getTitle() = context.getString(R.string.zip_export_title)
override fun onClick() {
- FragmentUtils.getPrefActivity(this).ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, granted ->
- if (granted.isNotEmpty() && granted[0] == PackageManager.PERMISSION_GRANTED) {
- isEnabled = false
- exportZip()
- }
- }
+ FragmentUtils.getPrefActivity(this)
+ .ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ object : SettingsActivity.PermissionRequestCallback {
+ override fun done(permissions: Array<String>, grantResults: IntArray) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ isEnabled = false
+ exportZip()
+ }
+ }
+ })
}
companion object {