diff options
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 { |