From 6df1e0fd77b442ecf11fe553e9c8ac718bf676c6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 13:07:48 -0300 Subject: [PATCH] Move connections settings into own screen --- .../briar/android/AndroidComponent.java | 3 + .../briarproject/briar/android/AppModule.java | 6 + .../android/settings/ConnectionsFragment.java | 118 ++++++++++ .../android/settings/ConnectionsManager.java | 114 ++++++++++ .../android/settings/ConnectionsStore.java | 61 ++++++ .../android/settings/SettingsActivity.java | 13 ++ .../android/settings/SettingsFragment.java | 201 +----------------- .../briar/android/settings/SettingsStore.java | 75 +++++++ .../android/settings/SettingsViewModel.java | 120 +++++++++-- .../android/settings/TorSummaryProvider.java | 57 +++++ .../drawable/ic_connect_without_contact.xml | 10 + briar-android/src/main/res/xml/settings.xml | 64 +----- .../src/main/res/xml/settings_connections.xml | 66 ++++++ 13 files changed, 636 insertions(+), 272 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsManager.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsStore.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsStore.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java create mode 100644 briar-android/src/main/res/drawable/ic_connect_without_contact.xml create mode 100644 briar-android/src/main/res/xml/settings_connections.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 86db83f29..417e1a5a3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -36,6 +36,7 @@ import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.login.SignInReminderReceiver; +import org.briarproject.briar.android.settings.ConnectionsFragment; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; @@ -193,4 +194,6 @@ public interface AndroidComponent void inject(EmojiTextInputView textInputView); void inject(BriarModelLoader briarModelLoader); + + void inject(ConnectionsFragment connectionsFragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index b8db5726f..437d25388 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android; import android.app.Application; +import android.content.Context; import android.content.SharedPreferences; import android.os.StrictMode; @@ -115,6 +116,11 @@ public class AppModule { this.application = application; } + public static AndroidComponent getAndroidComponent(Context ctx) { + BriarApplication app = (BriarApplication) ctx.getApplicationContext(); + return app.getApplicationComponent(); + } + @Provides @Singleton Application providesApplication() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java new file mode 100644 index 000000000..92522c0c0 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsFragment.java @@ -0,0 +1,118 @@ +package org.briarproject.briar.android.settings; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ViewModelProvider; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.SwitchPreference; + +import static org.briarproject.briar.android.AppModule.getAndroidComponent; +import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ConnectionsFragment extends PreferenceFragmentCompat { + + static final String PREF_KEY_BLUETOOTH = "pref_key_bluetooth"; + static final String PREF_KEY_WIFI = "pref_key_wifi"; + static final String PREF_KEY_TOR_ENABLE = "pref_key_tor_enable"; + static final String PREF_KEY_TOR_NETWORK = "pref_key_tor_network"; + static final String PREF_KEY_TOR_MOBILE_DATA = + "pref_key_tor_mobile_data"; + static final String PREF_KEY_TOR_ONLY_WHEN_CHARGING = + "pref_key_tor_only_when_charging"; + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private SettingsViewModel viewModel; + private ConnectionsManager connectionsManager; + + private SwitchPreference enableBluetooth; + private SwitchPreference enableWifi; + private SwitchPreference enableTor; + private ListPreference torNetwork; + private SwitchPreference torMobile; + private SwitchPreference torOnlyWhenCharging; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getAndroidComponent(context).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(SettingsViewModel.class); + connectionsManager = viewModel.getConnectionsManager(); + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings_connections); + + enableBluetooth = findPreference(PREF_KEY_BLUETOOTH); + enableWifi = findPreference(PREF_KEY_WIFI); + enableTor = findPreference(PREF_KEY_TOR_ENABLE); + torNetwork = findPreference(PREF_KEY_TOR_NETWORK); + torMobile = findPreference(PREF_KEY_TOR_MOBILE_DATA); + torOnlyWhenCharging = findPreference(PREF_KEY_TOR_ONLY_WHEN_CHARGING); + + torNetwork.setSummaryProvider(viewModel.torSummaryProvider); + + enableBluetooth.setPreferenceDataStore(connectionsManager.btStore); + enableWifi.setPreferenceDataStore(connectionsManager.wifiStore); + enableTor.setPreferenceDataStore(connectionsManager.torStore); + torNetwork.setPreferenceDataStore(connectionsManager.torStore); + torMobile.setPreferenceDataStore(connectionsManager.torStore); + torOnlyWhenCharging.setPreferenceDataStore(connectionsManager.torStore); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // persist changes after setting initial value and enabling + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + connectionsManager.btEnabled().observe(lifecycleOwner, enabled -> { + enableBluetooth.setChecked(enabled); + enableAndPersist(enableBluetooth); + }); + connectionsManager.wifiEnabled().observe(lifecycleOwner, enabled -> { + enableWifi.setChecked(enabled); + enableAndPersist(enableWifi); + }); + connectionsManager.torEnabled().observe(lifecycleOwner, enabled -> { + enableTor.setChecked(enabled); + enableAndPersist(enableTor); + }); + connectionsManager.torNetwork().observe(lifecycleOwner, value -> { + torNetwork.setValue(value); + enableAndPersist(torNetwork); + }); + connectionsManager.torMobile().observe(lifecycleOwner, enabled -> { + torMobile.setChecked(enabled); + enableAndPersist(torMobile); + }); + connectionsManager.torCharging().observe(lifecycleOwner, enabled -> { + torOnlyWhenCharging.setChecked(enabled); + enableAndPersist(torOnlyWhenCharging); + }); + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.network_settings_title); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsManager.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsManager.java new file mode 100644 index 000000000..8f842cc78 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsManager.java @@ -0,0 +1,114 @@ +package org.briarproject.briar.android.settings; + +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; + +import java.util.concurrent.Executor; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; +import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE; +import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK; +import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; +import static org.briarproject.briar.android.settings.SettingsViewModel.BT_NAMESPACE; +import static org.briarproject.briar.android.settings.SettingsViewModel.TOR_NAMESPACE; +import static org.briarproject.briar.android.settings.SettingsViewModel.WIFI_NAMESPACE; + +class ConnectionsManager { + + final ConnectionsStore btStore; + final ConnectionsStore wifiStore; + final ConnectionsStore torStore; + + private final MutableLiveData btEnabled = new MutableLiveData<>(); + private final MutableLiveData wifiEnabled = + new MutableLiveData<>(); + private final MutableLiveData torEnabled = new MutableLiveData<>(); + private final MutableLiveData torNetwork = new MutableLiveData<>(); + private final MutableLiveData torMobile = new MutableLiveData<>(); + private final MutableLiveData torCharging = + new MutableLiveData<>(); + + ConnectionsManager(SettingsManager settingsManager, + Executor dbExecutor) { + btStore = + new ConnectionsStore(settingsManager, dbExecutor, BT_NAMESPACE); + wifiStore = new ConnectionsStore(settingsManager, dbExecutor, + WIFI_NAMESPACE); + torStore = new ConnectionsStore(settingsManager, dbExecutor, + TOR_NAMESPACE); + } + + void updateBtSetting(Settings btSettings) { + btEnabled.postValue(btSettings.getBoolean(PREF_PLUGIN_ENABLE, + BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE)); + } + + void updateWifiSettings(Settings wifiSettings) { + wifiEnabled.postValue(wifiSettings.getBoolean(PREF_PLUGIN_ENABLE, + LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE)); + } + + void updateTorSettings(Settings settings) { + Settings torSettings = migrateTorSettings(settings); + torEnabled.postValue(torSettings.getBoolean(PREF_PLUGIN_ENABLE, + TorConstants.DEFAULT_PREF_PLUGIN_ENABLE)); + + int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, + DEFAULT_PREF_TOR_NETWORK); + torNetwork.postValue(Integer.toString(torNetworkSetting)); + + torMobile.postValue(torSettings.getBoolean(PREF_TOR_MOBILE, + DEFAULT_PREF_TOR_MOBILE)); + torCharging + .postValue(torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, + DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING)); + } + + // TODO: Remove after a reasonable migration period (added 2020-06-25) + private Settings migrateTorSettings(Settings s) { + int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK); + if (network == PREF_TOR_NETWORK_NEVER) { + s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK); + s.putBoolean(PREF_PLUGIN_ENABLE, false); + // We don't need to save the migrated settings - the Tor plugin is + // responsible for that. This code just handles the case where the + // settings are loaded before the plugin migrates them. + } + return s; + } + + LiveData btEnabled() { + return btEnabled; + } + + LiveData wifiEnabled() { + return wifiEnabled; + } + + LiveData torEnabled() { + return torEnabled; + } + + LiveData torNetwork() { + return torNetwork; + } + + LiveData torMobile() { + return torMobile; + } + + LiveData torCharging() { + return torCharging; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsStore.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsStore.java new file mode 100644 index 000000000..6c1c64b5b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/ConnectionsStore.java @@ -0,0 +1,61 @@ +package org.briarproject.briar.android.settings; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.settings.SettingsManager; + +import java.util.concurrent.Executor; + +import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_BLUETOOTH; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_TOR_ENABLE; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_TOR_MOBILE_DATA; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_TOR_NETWORK; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_TOR_ONLY_WHEN_CHARGING; +import static org.briarproject.briar.android.settings.ConnectionsFragment.PREF_KEY_WIFI; + +@NotNullByDefault +class ConnectionsStore extends SettingsStore { + + ConnectionsStore( + SettingsManager settingsManager, + Executor dbExecutor, + String namespace) { + super(settingsManager, dbExecutor, namespace); + } + + @Override + public void putBoolean(String key, boolean value) { + String newKey; + // translate between Android UI pref keys and bramble keys + switch (key) { + case PREF_KEY_BLUETOOTH: + case PREF_KEY_WIFI: + case PREF_KEY_TOR_ENABLE: + newKey = PREF_PLUGIN_ENABLE; + break; + case PREF_KEY_TOR_MOBILE_DATA: + newKey = PREF_TOR_MOBILE; + break; + case PREF_KEY_TOR_ONLY_WHEN_CHARGING: + newKey = PREF_TOR_ONLY_WHEN_CHARGING; + break; + default: + throw new AssertionError(); + } + super.putBoolean(newKey, value); + } + + @Override + public void putInt(String key, int value) { + // translate between Android UI pref keys and bramble keys + if (key.equals(PREF_KEY_TOR_NETWORK)) { + super.putInt(PREF_TOR_NETWORK, value); + } else { + throw new AssertionError(); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java index 6991e24c0..530e54b4e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java @@ -146,4 +146,17 @@ public class SettingsActivity extends BriarActivity return true; } + /** + * If the preference is not yet enabled, this enables the preference + * and makes it persist changed values. + * Call this after setting the initial value + * to prevent this change from getting persisted in the DB unnecessarily. + */ + static void enableAndPersist(Preference pref) { + if (!pref.isEnabled()) { + pref.setEnabled(true); + pref.setPersistent(true); + } + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index f6609adbb..2f6d70c6b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -19,14 +19,9 @@ import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -import org.briarproject.bramble.api.plugin.BluetoothConstants; -import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; -import org.briarproject.bramble.api.system.LocationUtils; -import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.R; @@ -61,21 +56,11 @@ import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; import static android.widget.Toast.LENGTH_SHORT; import static java.util.Objects.requireNonNull; import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; -import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE; -import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK; -import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; -import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; @@ -102,30 +87,10 @@ public class SettingsFragment extends PreferenceFragmentCompat "pref_key_lock_timeout"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; - private static final String BT_NAMESPACE = - BluetoothConstants.ID.getString(); - private static final String BT_ENABLE = "pref_key_bluetooth"; - - private static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString(); - private static final String WIFI_ENABLE = "pref_key_wifi"; - - private static final String TOR_NAMESPACE = TorConstants.ID.getString(); - private static final String TOR_ENABLE = "pref_key_tor_enable"; - private static final String TOR_NETWORK = "pref_key_tor_network"; - private static final String TOR_MOBILE = "pref_key_tor_mobile_data"; - private static final String TOR_ONLY_WHEN_CHARGING = - "pref_key_tor_only_when_charging"; - private static final Logger LOG = Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; - private SwitchPreference enableBluetooth; - private SwitchPreference enableWifi; - private SwitchPreference enableTor; - private ListPreference torNetwork; - private SwitchPreference torMobile; - private SwitchPreference torOnlyWhenCharging; private SwitchPreference screenLock; private ListPreference screenLockTimeout; private SwitchPreference notifyPrivateMessages; @@ -137,17 +102,13 @@ public class SettingsFragment extends PreferenceFragmentCompat private Preference notifySound; // Fields that are accessed from background threads must be volatile - private volatile Settings settings, btSettings, wifiSettings, torSettings; + private volatile Settings settings; private volatile boolean settingsLoaded = false; @Inject volatile SettingsManager settingsManager; @Inject volatile EventBus eventBus; - @Inject - LocationUtils locationUtils; - @Inject - CircumventionProvider circumventionProvider; @Override public void onAttach(Context context) { @@ -160,12 +121,6 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - enableBluetooth = findPreference(BT_ENABLE); - enableWifi = findPreference(WIFI_ENABLE); - enableTor = findPreference(TOR_ENABLE); - torNetwork = findPreference(TOR_NETWORK); - torMobile = findPreference(TOR_MOBILE); - torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING); screenLock = findPreference(PREF_SCREEN_LOCK); screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT); notifyPrivateMessages = @@ -176,12 +131,6 @@ public class SettingsFragment extends PreferenceFragmentCompat notifyVibration = findPreference("pref_key_notify_vibration"); notifySound = findPreference("pref_key_notify_sound"); - enableBluetooth.setOnPreferenceChangeListener(this); - enableWifi.setOnPreferenceChangeListener(this); - enableTor.setOnPreferenceChangeListener(this); - torNetwork.setOnPreferenceChangeListener(this); - torMobile.setOnPreferenceChangeListener(this); - torOnlyWhenCharging.setOnPreferenceChangeListener(this); screenLock.setOnPreferenceChangeListener(this); screenLockTimeout.setOnPreferenceChangeListener(this); @@ -232,39 +181,11 @@ public class SettingsFragment extends PreferenceFragmentCompat eventBus.removeListener(this); } - private void setTorNetworkSummary(int torNetworkSetting) { - if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) { - torNetwork.setSummary("%s"); // use setting value - return; - } - - // Look up country name in the user's chosen language if available - String country = locationUtils.getCurrentCountry(); - String countryName = getCountryDisplayName(country); - - boolean blocked = - circumventionProvider.isTorProbablyBlocked(country); - boolean useBridges = circumventionProvider.doBridgesWork(country); - String setting = - getString(R.string.tor_network_setting_without_bridges); - if (blocked && useBridges) { - setting = getString(R.string.tor_network_setting_with_bridges); - } else if (blocked) { - setting = getString(R.string.tor_network_setting_never); - } - torNetwork.setSummary( - getString(R.string.tor_network_setting_summary, setting, - countryName)); - } - private void loadSettings() { listener.runOnDbThread(() -> { try { long start = now(); settings = settingsManager.getSettings(SETTINGS_NAMESPACE); - btSettings = settingsManager.getSettings(BT_NAMESPACE); - wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE); - torSettings = settingsManager.getSettings(TOR_NAMESPACE); settingsLoaded = true; logDuration(LOG, "Loading settings", start); displaySettings(); @@ -274,52 +195,11 @@ public class SettingsFragment extends PreferenceFragmentCompat }); } - // TODO: Remove after a reasonable migration period (added 2020-06-25) - private Settings migrateTorSettings(Settings s) { - int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK); - if (network == PREF_TOR_NETWORK_NEVER) { - s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK); - s.putBoolean(PREF_PLUGIN_ENABLE, false); - // We don't need to save the migrated settings - the Tor plugin is - // responsible for that. This code just handles the case where the - // settings are loaded before the plugin migrates them. - } - return s; - } - private void displaySettings() { listener.runOnUiThreadUnlessDestroyed(() -> { // due to events, we might try to display before a load completed if (!settingsLoaded) return; - boolean btEnabledSetting = btSettings.getBoolean(PREF_PLUGIN_ENABLE, - BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE); - enableBluetooth.setChecked(btEnabledSetting); - - boolean wifiEnabledSetting = - wifiSettings.getBoolean(PREF_PLUGIN_ENABLE, - LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE); - enableWifi.setChecked(wifiEnabledSetting); - - boolean torEnabledSetting = - torSettings.getBoolean(PREF_PLUGIN_ENABLE, - TorConstants.DEFAULT_PREF_PLUGIN_ENABLE); - enableTor.setChecked(torEnabledSetting); - - int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, - DEFAULT_PREF_TOR_NETWORK); - torNetwork.setValue(Integer.toString(torNetworkSetting)); - setTorNetworkSummary(torNetworkSetting); - - boolean torMobileSetting = torSettings.getBoolean(PREF_TOR_MOBILE, - DEFAULT_PREF_TOR_MOBILE); - torMobile.setChecked(torMobileSetting); - - boolean torChargingSetting = - torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, - DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING); - torOnlyWhenCharging.setChecked(torChargingSetting); - displayScreenLockSetting(); if (SDK_INT < 26) { @@ -378,12 +258,6 @@ public class SettingsFragment extends PreferenceFragmentCompat // preferences partly needed here, because they have their own logic // - pref_key_lock (screenLock -> displayScreenLockSetting()) // - pref_key_lock_timeout (screenLockTimeout) - enableBluetooth.setEnabled(enabled); - enableWifi.setEnabled(enabled); - enableTor.setEnabled(enabled); - torNetwork.setEnabled(enabled); - torMobile.setEnabled(enabled); - torOnlyWhenCharging.setEnabled(enabled); if (!enabled) screenLock.setEnabled(false); notifyPrivateMessages.setEnabled(enabled); notifyGroupMessages.setEnabled(enabled); @@ -478,33 +352,14 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == enableBluetooth) { - boolean btSetting = (Boolean) newValue; - storeBluetoothSetting(btSetting); - } else if (preference == enableWifi) { - boolean wifiSetting = (Boolean) newValue; - storeWifiSetting(wifiSetting); - } else if (preference == enableTor) { - boolean torEnabledSetting = (Boolean) newValue; - storeTorEnabledSetting(torEnabledSetting); - } else if (preference == torNetwork) { - int torNetworkSetting = Integer.valueOf((String) newValue); - storeTorNetworkSetting(torNetworkSetting); - setTorNetworkSummary(torNetworkSetting); - } else if (preference == torMobile) { - boolean torMobileSetting = (Boolean) newValue; - storeTorMobileSetting(torMobileSetting); - } else if (preference == torOnlyWhenCharging) { - boolean torChargingSetting = (Boolean) newValue; - storeTorChargingSetting(torChargingSetting); - } else if (preference == screenLock) { + if (preference == screenLock) { Settings s = new Settings(); s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue); storeSettings(s); } else if (preference == screenLockTimeout) { Settings s = new Settings(); String value = (String) newValue; - s.putInt(PREF_SCREEN_LOCK_TIMEOUT, Integer.valueOf(value)); + s.putInt(PREF_SCREEN_LOCK_TIMEOUT, Integer.parseInt(value)); storeSettings(s); setScreenLockTimeoutSummary(value); } else if (preference == notifyPrivateMessages) { @@ -531,41 +386,6 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; } - private void storeTorEnabledSetting(boolean torEnabledSetting) { - Settings s = new Settings(); - s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting); - mergeSettings(s, TOR_NAMESPACE); - } - - private void storeTorNetworkSetting(int torNetworkSetting) { - Settings s = new Settings(); - s.putInt(PREF_TOR_NETWORK, torNetworkSetting); - mergeSettings(s, TOR_NAMESPACE); - } - - private void storeTorMobileSetting(boolean torMobileSetting) { - Settings s = new Settings(); - s.putBoolean(PREF_TOR_MOBILE, torMobileSetting); - mergeSettings(s, TOR_NAMESPACE); - } - - private void storeTorChargingSetting(boolean torChargingSetting) { - Settings s = new Settings(); - s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, torChargingSetting); - mergeSettings(s, TOR_NAMESPACE); - } - - private void storeBluetoothSetting(boolean btSetting) { - Settings s = new Settings(); - s.putBoolean(PREF_PLUGIN_ENABLE, btSetting); - mergeSettings(s, BT_NAMESPACE); - } - - private void storeWifiSetting(boolean wifiSetting) { - Settings s = new Settings(); - s.putBoolean(PREF_PLUGIN_ENABLE, wifiSetting); - mergeSettings(s, WIFI_NAMESPACE); - } private void storeSettings(Settings s) { mergeSettings(s, SETTINGS_NAMESPACE); @@ -584,7 +404,8 @@ public class SettingsFragment extends PreferenceFragmentCompat } @Override - public void onActivityResult(int request, int result, Intent data) { + public void onActivityResult(int request, int result, + @Nullable Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_RINGTONE && result == RESULT_OK) { Settings s = new Settings(); @@ -625,18 +446,6 @@ public class SettingsFragment extends PreferenceFragmentCompat LOG.info("Settings updated"); settings = s.getSettings(); displaySettings(); - } else if (namespace.equals(BT_NAMESPACE)) { - LOG.info("Bluetooth settings updated"); - btSettings = s.getSettings(); - displaySettings(); - } else if (namespace.equals(WIFI_NAMESPACE)) { - LOG.info("Wifi settings updated"); - wifiSettings = s.getSettings(); - displaySettings(); - } else if (namespace.equals(TOR_NAMESPACE)) { - LOG.info("Tor settings updated"); - torSettings = migrateTorSettings(s.getSettings()); - displaySettings(); } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsStore.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsStore.java new file mode 100644 index 000000000..c009cdef4 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsStore.java @@ -0,0 +1,75 @@ +package org.briarproject.briar.android.settings; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import androidx.annotation.Nullable; +import androidx.preference.PreferenceDataStore; + +import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; + +/** + * A custom PreferenceDataStore that stores settings in Briar's encrypted DB. + *

+ * Warning: This expects all strings to be integers and stores them as such. + */ +@NotNullByDefault +class SettingsStore extends PreferenceDataStore { + + private final static Logger LOG = getLogger(SettingsStore.class.getName()); + + private final SettingsManager settingsManager; + private final Executor dbExecutor; + private final String namespace; + + SettingsStore(SettingsManager settingsManager, + Executor dbExecutor, + String namespace) { + this.settingsManager = settingsManager; + this.dbExecutor = dbExecutor; + this.namespace = namespace; + } + + @Override + public void putBoolean(String key, boolean value) { + Settings s = new Settings(); + s.putBoolean(key, value); + storeSettings(s); + } + + @Override + public void putInt(String key, int value) { + Settings s = new Settings(); + s.putInt(key, value); + storeSettings(s); + } + + @Override + public void putString(String key, @Nullable String value) { + int integer = Integer.parseInt(requireNonNull(value)); + putInt(key, integer); + } + + private void storeSettings(Settings s) { + dbExecutor.execute(() -> { + try { + long start = now(); + settingsManager.mergeSettings(s, namespace); + logDuration(LOG, "Merging " + namespace + " settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java index 803e2bbf2..23f114c95 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsViewModel.java @@ -6,12 +6,27 @@ import android.net.Uri; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException; import org.briarproject.briar.android.attachment.media.ImageCompressor; +import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.avatar.AvatarManager; @@ -25,7 +40,6 @@ import java.util.logging.Logger; import javax.inject.Inject; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -33,58 +47,102 @@ import static java.util.Arrays.asList; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; +import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; @NotNullByDefault -class SettingsViewModel extends AndroidViewModel { +class SettingsViewModel extends DbViewModel implements EventListener { private final static Logger LOG = getLogger(SettingsViewModel.class.getName()); + public static final String SETTINGS_NAMESPACE = "android-ui"; + static final String BT_NAMESPACE = + BluetoothConstants.ID.getString(); + static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString(); + static final String TOR_NAMESPACE = TorConstants.ID.getString(); + + private final SettingsManager settingsManager; private final IdentityManager identityManager; + private final EventBus eventBus; private final AvatarManager avatarManager; private final AuthorManager authorManager; private final ImageCompressor imageCompressor; - @IoExecutor private final Executor ioExecutor; - @DatabaseExecutor - private final Executor dbExecutor; + private final ConnectionsManager connectionsManager; + + final SettingsStore settingsStore; + final TorSummaryProvider torSummaryProvider; + + private volatile Settings settings; private final MutableLiveData ownIdentityInfo = new MutableLiveData<>(); - private final MutableLiveEvent setAvatarFailed = new MutableLiveEvent<>(); @Inject SettingsViewModel(Application application, + @DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, + TransactionManager db, + AndroidExecutor androidExecutor, + SettingsManager settingsManager, IdentityManager identityManager, + EventBus eventBus, AvatarManager avatarManager, AuthorManager authorManager, ImageCompressor imageCompressor, - @IoExecutor Executor ioExecutor, - @DatabaseExecutor Executor dbExecutor) { - super(application); + LocationUtils locationUtils, + CircumventionProvider circumventionProvider, + @IoExecutor Executor ioExecutor) { + super(application, dbExecutor, lifecycleManager, db, androidExecutor); + this.settingsManager = settingsManager; this.identityManager = identityManager; + this.eventBus = eventBus; this.imageCompressor = imageCompressor; this.avatarManager = avatarManager; this.authorManager = authorManager; this.ioExecutor = ioExecutor; - this.dbExecutor = dbExecutor; + this.settingsStore = new SettingsStore(settingsManager, dbExecutor, + SETTINGS_NAMESPACE); + this.torSummaryProvider = new TorSummaryProvider(getApplication(), + locationUtils, circumventionProvider); + this.connectionsManager = + new ConnectionsManager(settingsManager, dbExecutor); + eventBus.addListener(this); + loadSettings(); loadOwnIdentityInfo(); } - LiveData getOwnIdentityInfo() { - return ownIdentityInfo; + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); } - LiveEvent getSetAvatarFailed() { - return setAvatarFailed; + private void loadSettings() { + runOnDbThread(() -> { + try { + long start = now(); + settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + connectionsManager.updateBtSetting( + settingsManager.getSettings(BT_NAMESPACE)); + connectionsManager.updateWifiSettings( + settingsManager.getSettings(WIFI_NAMESPACE)); + connectionsManager.updateTorSettings( + settingsManager.getSettings(TOR_NAMESPACE)); + logDuration(LOG, "Loading settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); } private void loadOwnIdentityInfo() { - dbExecutor.execute(() -> { + runOnDbThread(() -> { try { LocalAuthor localAuthor = identityManager.getLocalAuthor(); AuthorInfo authorInfo = authorManager.getMyAuthorInfo(); @@ -96,6 +154,24 @@ class SettingsViewModel extends AndroidViewModel { }); } + @Override + public void eventOccurred(Event e) { + if (e instanceof SettingsUpdatedEvent) { + SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; + String namespace = s.getNamespace(); + if (namespace.equals(BT_NAMESPACE)) { + LOG.info("Bluetooth settings updated"); + connectionsManager.updateBtSetting(s.getSettings()); + } else if (namespace.equals(WIFI_NAMESPACE)) { + LOG.info("Wifi settings updated"); + connectionsManager.updateWifiSettings(s.getSettings()); + } else if (namespace.equals(TOR_NAMESPACE)) { + LOG.info("Tor settings updated"); + connectionsManager.updateTorSettings(s.getSettings()); + } + } + } + void setAvatar(Uri uri) { ioExecutor.execute(() -> { try { @@ -120,7 +196,7 @@ class SettingsViewModel extends AndroidViewModel { "ContentResolver returned null when opening InputStream"); InputStream compressed = imageCompressor.compressImage(is, contentType); - dbExecutor.execute(() -> { + runOnDbThread(() -> { try { avatarManager.addAvatar(ImageCompressor.MIME_TYPE, compressed); loadOwnIdentityInfo(); @@ -131,4 +207,16 @@ class SettingsViewModel extends AndroidViewModel { }); } + LiveData getOwnIdentityInfo() { + return ownIdentityInfo; + } + + LiveEvent getSetAvatarFailed() { + return setAvatarFailed; + } + + ConnectionsManager getConnectionsManager() { + return connectionsManager; + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java new file mode 100644 index 000000000..0d4258849 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/TorSummaryProvider.java @@ -0,0 +1,57 @@ +package org.briarproject.briar.android.settings; + +import android.content.Context; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.bramble.plugin.tor.CircumventionProvider; +import org.briarproject.briar.R; + +import androidx.preference.ListPreference; +import androidx.preference.Preference.SummaryProvider; + +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC; +import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName; + +@NotNullByDefault +class TorSummaryProvider implements SummaryProvider { + + private final Context ctx; + private final LocationUtils locationUtils; + private final CircumventionProvider circumventionProvider; + + public TorSummaryProvider(Context ctx, + LocationUtils locationUtils, + CircumventionProvider circumventionProvider) { + this.ctx = ctx; + this.locationUtils = locationUtils; + this.circumventionProvider = circumventionProvider; + } + + @Override + public CharSequence provideSummary(ListPreference preference) { + int torNetworkSetting = Integer.parseInt(preference.getValue()); + + if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) { + return preference.getEntry(); // use setting value + } + + // Look up country name in the user's chosen language if available + String country = locationUtils.getCurrentCountry(); + String countryName = getCountryDisplayName(country); + + boolean blocked = + circumventionProvider.isTorProbablyBlocked(country); + boolean useBridges = circumventionProvider.doBridgesWork(country); + String setting = + ctx.getString(R.string.tor_network_setting_without_bridges); + if (blocked && useBridges) { + setting = ctx.getString(R.string.tor_network_setting_with_bridges); + } else if (blocked) { + setting = ctx.getString(R.string.tor_network_setting_never); + } + return ctx.getString(R.string.tor_network_setting_summary, setting, + countryName); + } + +} diff --git a/briar-android/src/main/res/drawable/ic_connect_without_contact.xml b/briar-android/src/main/res/drawable/ic_connect_without_contact.xml new file mode 100644 index 000000000..35a61e288 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_connect_without_contact.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 5c2a93520..153ae5bed 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -7,66 +7,10 @@ app:fragment="org.briarproject.briar.android.settings.DisplayFragment" app:icon="@drawable/ic_settings_brightness" /> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +