From ec910cb80fb631f9c3fbe6593df7cdb1cf32d317 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 21 Jan 2021 16:59:59 -0300 Subject: [PATCH 01/14] Move Display category into its own settings screen --- .../briarproject/briar/android/Localizer.java | 4 +- .../android/settings/DisplayFragment.java | 160 ++++++++++++++++++ .../android/settings/SettingsActivity.java | 54 ++++-- .../android/settings/SettingsFragment.java | 122 +------------ .../res/drawable/ic_settings_brightness.xml | 10 ++ .../src/main/res/layout/activity_settings.xml | 4 +- briar-android/src/main/res/values/strings.xml | 2 +- briar-android/src/main/res/xml/settings.xml | 72 +++----- .../src/main/res/xml/settings_display.xml | 24 +++ 9 files changed, 269 insertions(+), 183 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java create mode 100644 briar-android/src/main/res/drawable/ic_settings_brightness.xml create mode 100644 briar-android/src/main/res/xml/settings_display.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java index 5e99eba79..015a96d98 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java @@ -12,7 +12,7 @@ import java.util.Locale; import javax.annotation.Nullable; import static android.os.Build.VERSION.SDK_INT; -import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE; +import static org.briarproject.briar.android.settings.DisplayFragment.PREF_LANGUAGE; @NotNullByDefault public class Localizer { @@ -25,7 +25,7 @@ public class Localizer { private Localizer(SharedPreferences sharedPreferences) { this(Locale.getDefault(), getLocaleFromTag( - sharedPreferences.getString(LANGUAGE, "default"))); + sharedPreferences.getString(PREF_LANGUAGE, "default"))); } private Localizer(Locale systemLocale, @Nullable Locale userLocale) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java new file mode 100644 index 000000000..6ef75cefb --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java @@ -0,0 +1,160 @@ +package org.briarproject.briar.android.settings; + +import android.app.AlertDialog; +import android.content.Intent; +import android.os.Bundle; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.Localizer; +import org.briarproject.briar.android.util.UiUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + +import androidx.core.text.TextUtilsCompat; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.VERSION.SDK_INT; +import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; +import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; + +@NotNullByDefault +public class DisplayFragment extends PreferenceFragmentCompat { + + public static final String PREF_LANGUAGE = "pref_key_language"; + private static final String PREF_THEME = "pref_key_theme"; + + private static final Logger LOG = + getLogger(DisplayFragment.class.getName()); + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings_display); + + ListPreference language = findPreference(PREF_LANGUAGE); + setLanguageEntries(language); + language.setOnPreferenceChangeListener(this::onLanguageChanged); + + ListPreference theme = findPreference(PREF_THEME); + setThemeEntries(theme); + theme.setOnPreferenceChangeListener(this::onThemeChanged); + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.display_settings_title); + } + + private void setLanguageEntries(ListPreference language) { + CharSequence[] tags = language.getEntryValues(); + List entries = new ArrayList<>(tags.length); + List entryValues = new ArrayList<>(tags.length); + for (CharSequence cs : tags) { + String tag = cs.toString(); + if (tag.equals("default")) { + entries.add(getString(R.string.pref_language_default)); + entryValues.add(tag); + continue; + } + Locale locale = Localizer.getLocaleFromTag(tag); + if (locale == null) + throw new IllegalStateException(); + // Exclude RTL locales on API < 17, they won't be laid out correctly + if (SDK_INT < 17 && !isLeftToRight(locale)) { + if (LOG.isLoggable(INFO)) + LOG.info("Skipping RTL locale " + tag); + continue; + } + String nativeName = locale.getDisplayName(locale); + // Fallback to English if the name is unknown in both native and + // current locale. + if (nativeName.equals(tag)) { + String tmp = locale.getDisplayLanguage(Locale.ENGLISH); + if (!tmp.isEmpty() && !tmp.equals(nativeName)) + nativeName = tmp; + } + // Prefix with LRM marker to prevent any RTL direction + entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() + + nativeName.substring(1)); + entryValues.add(tag); + } + language.setEntries(entries.toArray(new CharSequence[0])); + language.setEntryValues(entryValues.toArray(new CharSequence[0])); + } + + private boolean isLeftToRight(Locale locale) { + // TextUtilsCompat returns the wrong direction for Hebrew on some phones + String language = locale.getLanguage(); + if (language.equals("iw") || language.equals("he")) return false; + int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); + return direction == LAYOUT_DIRECTION_LTR; + } + + private void setThemeEntries(ListPreference theme) { + if (SDK_INT < 27) { + // remove System Default Theme option from preference entries + // as it is not functional on this API anyway + List entries = + new ArrayList<>(Arrays.asList(theme.getEntries())); + entries.remove(getString(R.string.pref_theme_system)); + theme.setEntries(entries.toArray(new CharSequence[0])); + // also remove corresponding value + List values = + new ArrayList<>(Arrays.asList(theme.getEntryValues())); + values.remove(getString(R.string.pref_theme_system_value)); + theme.setEntryValues(values.toArray(new CharSequence[0])); + } + } + + private boolean onThemeChanged(Preference preference, Object newValue) { + // activate new theme + UiUtils.setTheme(getActivity(), (String) newValue); + // bring up parent activity, so it can change its theme as well + // upstream bug: https://issuetracker.google.com/issues/38352704 + Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); + intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + // bring this activity back to the foreground + // TODO maybe tell the activity here to relaunch this fragment? + intent = new Intent(getActivity(), getActivity().getClass()); + startActivity(intent); + getActivity().finish(); + return true; + } + + private boolean onLanguageChanged(Preference preference, Object newValue) { + ListPreference language = (ListPreference) preference; + if (!language.getValue().equals(newValue)) { + AlertDialog.Builder builder = + new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pref_language_title); + builder.setMessage(R.string.pref_language_changed); + builder.setPositiveButton(R.string.sign_out_button, (d, i) -> { + language.setValue((String) newValue); + Intent intent = new Intent(getContext(), ENTRY_ACTIVITY); + intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + intent.setData(SIGN_OUT_URI); + requireActivity().startActivity(intent); + requireActivity().finish(); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setCancelable(false); + builder.show(); + } + return false; + } + +} 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 747bc9895..6991e24c0 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 @@ -9,6 +9,8 @@ import android.widget.TextView; import android.widget.Toast; import org.briarproject.bramble.api.FeatureFlags; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -19,23 +21,39 @@ import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentFactory; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; import de.hdodenhof.circleimageview.CircleImageView; import static android.widget.Toast.LENGTH_LONG; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; -public class SettingsActivity extends BriarActivity { +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class SettingsActivity extends BriarActivity + implements OnPreferenceStartFragmentCallback { @Inject ViewModelProvider.Factory viewModelFactory; - private SettingsViewModel settingsViewModel; - @Inject FeatureFlags featureFlags; + private SettingsViewModel settingsViewModel; + @Override - public void onCreate(Bundle bundle) { + public void injectActivity(ActivityComponent component) { + component.inject(this); + settingsViewModel = new ViewModelProvider(this, viewModelFactory) + .get(SettingsViewModel.class); + } + + @Override + public void onCreate(@Nullable Bundle bundle) { super.onCreate(bundle); ActionBar actionBar = getSupportActionBar(); @@ -47,10 +65,6 @@ public class SettingsActivity extends BriarActivity { setContentView(R.layout.activity_settings); if (featureFlags.shouldEnableProfilePictures()) { - ViewModelProvider provider = - new ViewModelProvider(this, viewModelFactory); - settingsViewModel = provider.get(SettingsViewModel.class); - TextView textViewUserName = findViewById(R.id.username); CircleImageView imageViewAvatar = findViewById(R.id.avatarImage); @@ -78,11 +92,6 @@ public class SettingsActivity extends BriarActivity { } } - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -118,4 +127,23 @@ public class SettingsActivity extends BriarActivity { ConfirmAvatarDialogFragment.TAG); } + @Override + public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, + Preference pref) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentFactory fragmentFactory = fragmentManager.getFragmentFactory(); + Fragment fragment = fragmentFactory + .instantiate(getClassLoader(), pref.getFragment()); + fragment.setTargetFragment(caller, 0); + // Replace the existing Fragment with the new Fragment + fragmentManager.beginTransaction() + .setCustomAnimations(R.anim.step_next_in, + R.anim.step_previous_out, R.anim.step_previous_in, + R.anim.step_next_out) + .replace(R.id.fragmentContainer, fragment) + .addToBackStack(null) + .commit(); + return 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 7412b213f..f6609adbb 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 @@ -1,7 +1,6 @@ package org.briarproject.briar.android.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.graphics.drawable.ColorDrawable; @@ -30,13 +29,7 @@ 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; -import org.briarproject.briar.android.Localizer; -import org.briarproject.briar.android.util.UiUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; import java.util.logging.Logger; import javax.inject.Inject; @@ -44,7 +37,6 @@ import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; -import androidx.core.text.TextUtilsCompat; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; @@ -53,9 +45,6 @@ import androidx.preference.PreferenceGroup; import androidx.preference.SwitchPreference; import static android.app.Activity.RESULT_OK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.media.RingtoneManager.ACTION_RINGTONE_PICKER; import static android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI; import static android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI; @@ -70,9 +59,7 @@ import static android.provider.Settings.EXTRA_APP_PACKAGE; import static android.provider.Settings.EXTRA_CHANNEL_ID; import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; import static android.widget.Toast.LENGTH_SHORT; -import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.INFO; 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; @@ -86,10 +73,8 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE 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.BriarApplication.ENTRY_ACTIVITY; 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.navdrawer.NavDrawerActivity.SIGN_OUT_URI; 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; @@ -112,7 +97,6 @@ public class SettingsFragment extends PreferenceFragmentCompat implements EventListener, OnPreferenceChangeListener { public static final String SETTINGS_NAMESPACE = "android-ui"; - public static final String LANGUAGE = "pref_key_language"; public static final String PREF_SCREEN_LOCK = "pref_key_lock"; public static final String PREF_SCREEN_LOCK_TIMEOUT = "pref_key_lock_timeout"; @@ -136,7 +120,6 @@ public class SettingsFragment extends PreferenceFragmentCompat Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; - private ListPreference language; private SwitchPreference enableBluetooth; private SwitchPreference enableWifi; private SwitchPreference enableTor; @@ -177,9 +160,6 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - language = findPreference(LANGUAGE); - setLanguageEntries(); - ListPreference theme = findPreference("pref_key_theme"); enableBluetooth = findPreference(BT_ENABLE); enableWifi = findPreference(WIFI_ENABLE); enableTor = findPreference(TOR_ENABLE); @@ -196,24 +176,6 @@ public class SettingsFragment extends PreferenceFragmentCompat notifyVibration = findPreference("pref_key_notify_vibration"); notifySound = findPreference("pref_key_notify_sound"); - language.setOnPreferenceChangeListener(this); - theme.setOnPreferenceChangeListener((preference, newValue) -> { - if (getActivity() != null) { - // activate new theme - UiUtils.setTheme(getActivity(), (String) newValue); - // bring up parent activity, so it can change its theme as well - // upstream bug: https://issuetracker.google.com/issues/38352704 - Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); - intent.setFlags( - FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - // bring this activity back to the foreground - intent = new Intent(getActivity(), getActivity().getClass()); - startActivity(intent); - getActivity().finish(); - } - return true; - }); enableBluetooth.setOnPreferenceChangeListener(this); enableWifi.setOnPreferenceChangeListener(this); enableTor.setOnPreferenceChangeListener(this); @@ -230,19 +192,6 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; }); - if (SDK_INT < 27) { - // remove System Default Theme option from preference entries - // as it is not functional on this API anyway - List entries = - new ArrayList<>(Arrays.asList(theme.getEntries())); - entries.remove(getString(R.string.pref_theme_system)); - theme.setEntries(entries.toArray(new CharSequence[0])); - // also remove corresponding value - List values = - new ArrayList<>(Arrays.asList(theme.getEntryValues())); - values.remove(getString(R.string.pref_theme_system_value)); - theme.setEntryValues(values.toArray(new CharSequence[0])); - } Preference explode = requireNonNull(findPreference("pref_key_explode")); if (IS_DEBUG_BUILD) { explode.setOnPreferenceClickListener(preference -> { @@ -271,6 +220,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public void onStart() { super.onStart(); + requireActivity().setTitle(R.string.settings_button); eventBus.addListener(this); setSettingsEnabled(false); loadSettings(); @@ -282,51 +232,6 @@ public class SettingsFragment extends PreferenceFragmentCompat eventBus.removeListener(this); } - private void setLanguageEntries() { - CharSequence[] tags = language.getEntryValues(); - List entries = new ArrayList<>(tags.length); - List entryValues = new ArrayList<>(tags.length); - for (CharSequence cs : tags) { - String tag = cs.toString(); - if (tag.equals("default")) { - entries.add(getString(R.string.pref_language_default)); - entryValues.add(tag); - continue; - } - Locale locale = Localizer.getLocaleFromTag(tag); - if (locale == null) - throw new IllegalStateException(); - // Exclude RTL locales on API < 17, they won't be laid out correctly - if (SDK_INT < 17 && !isLeftToRight(locale)) { - if (LOG.isLoggable(INFO)) - LOG.info("Skipping RTL locale " + tag); - continue; - } - String nativeName = locale.getDisplayName(locale); - // Fallback to English if the name is unknown in both native and - // current locale. - if (nativeName.equals(tag)) { - String tmp = locale.getDisplayLanguage(Locale.ENGLISH); - if (!tmp.isEmpty() && !tmp.equals(nativeName)) - nativeName = tmp; - } - // Prefix with LRM marker to prevent any RTL direction - entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() - + nativeName.substring(1)); - entryValues.add(tag); - } - language.setEntries(entries.toArray(new CharSequence[0])); - language.setEntryValues(entryValues.toArray(new CharSequence[0])); - } - - private boolean isLeftToRight(Locale locale) { - // TextUtilsCompat returns the wrong direction for Hebrew on some phones - String language = locale.getLanguage(); - if (language.equals("iw") || language.equals("he")) return false; - int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); - return direction == LAYOUT_DIRECTION_LTR; - } - private void setTorNetworkSummary(int torNetworkSetting) { if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) { torNetwork.setSummary("%s"); // use setting value @@ -573,11 +478,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == language) { - if (!language.getValue().equals(newValue)) - languageChanged((String) newValue); - return false; - } else if (preference == enableBluetooth) { + if (preference == enableBluetooth) { boolean btSetting = (Boolean) newValue; storeBluetoothSetting(btSetting); } else if (preference == enableWifi) { @@ -630,25 +531,6 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; } - private void languageChanged(String newValue) { - AlertDialog.Builder builder = - new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.pref_language_title); - builder.setMessage(R.string.pref_language_changed); - builder.setPositiveButton(R.string.sign_out_button, - (dialogInterface, i) -> { - language.setValue(newValue); - Intent intent = new Intent(getContext(), ENTRY_ACTIVITY); - intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - intent.setData(SIGN_OUT_URI); - requireActivity().startActivity(intent); - requireActivity().finish(); - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setCancelable(false); - builder.show(); - } - private void storeTorEnabledSetting(boolean torEnabledSetting) { Settings s = new Settings(); s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting); diff --git a/briar-android/src/main/res/drawable/ic_settings_brightness.xml b/briar-android/src/main/res/drawable/ic_settings_brightness.xml new file mode 100644 index 000000000..a0b720816 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_settings_brightness.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/layout/activity_settings.xml b/briar-android/src/main/res/layout/activity_settings.xml index 88bf95130..180e2728e 100644 --- a/briar-android/src/main/res/layout/activity_settings.xml +++ b/briar-android/src/main/res/layout/activity_settings.xml @@ -68,8 +68,8 @@ - Light Dark Automatic (Daytime) - System Default + System default Connections diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 21acdd98f..5c2a93520 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -1,29 +1,11 @@ - - - - - - - + + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> @@ -97,7 +79,7 @@ android:summary="@string/pref_lock_summary" android:title="@string/pref_lock_title" android:widgetLayout="@layout/preference_switch_compat" - app:iconSpaceReserved="false"/> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + android:targetPackage="@string/app_package" /> @@ -133,7 +115,7 @@ + android:targetPackage="@string/app_package" /> @@ -149,7 +131,7 @@ android:summary="@string/notify_sign_in_summary" android:title="@string/notify_sign_in_title" android:widgetLayout="@layout/preference_switch_compat" - app:iconSpaceReserved="false"/> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> @@ -209,7 +191,7 @@ + app:iconSpaceReserved="false" /> @@ -224,13 +206,13 @@ + android:targetPackage="@string/app_package" /> + app:iconSpaceReserved="false" /> diff --git a/briar-android/src/main/res/xml/settings_display.xml b/briar-android/src/main/res/xml/settings_display.xml new file mode 100644 index 000000000..4a7d146a9 --- /dev/null +++ b/briar-android/src/main/res/xml/settings_display.xml @@ -0,0 +1,24 @@ + + + + + + + + From 6df1e0fd77b442ecf11fe553e9c8ac718bf676c6 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 13:07:48 -0300 Subject: [PATCH 02/14] 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" /> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + From 670bf15d31c189b0297a55c68b1faf17317598ea Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 14:07:10 -0300 Subject: [PATCH 03/14] Move security settings into own screen --- .../briar/android/AndroidComponent.java | 3 + .../android/account/LockManagerImpl.java | 4 +- .../android/settings/SecurityFragment.java | 104 ++++++++++++++++++ .../android/settings/SettingsFragment.java | 67 +---------- .../briar/android/settings/SettingsStore.java | 5 + .../android/settings/SettingsViewModel.java | 32 +++++- .../res/drawable/ic_settings_security.xml | 10 ++ briar-android/src/main/res/xml/settings.xml | 56 +--------- .../src/main/res/xml/settings_security.xml | 54 +++++++++ 9 files changed, 214 insertions(+), 121 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java create mode 100644 briar-android/src/main/res/drawable/ic_settings_security.xml create mode 100644 briar-android/src/main/res/xml/settings_security.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 417e1a5a3..8a9315e3a 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 @@ -37,6 +37,7 @@ 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.settings.SecurityFragment; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; @@ -196,4 +197,6 @@ public interface AndroidComponent void inject(BriarModelLoader briarModelLoader); void inject(ConnectionsFragment connectionsFragment); + + void inject(SecurityFragment securityFragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java index fb978bd6a..e2e396958 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/account/LockManagerImpl.java @@ -40,8 +40,8 @@ import static android.os.SystemClock.elapsedRealtime; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; -import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK; -import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK_TIMEOUT; +import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK; +import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java new file mode 100644 index 000000000..241e5812d --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java @@ -0,0 +1,104 @@ +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 android.os.Build.VERSION.SDK_INT; +import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; +import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist; +import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class SecurityFragment extends PreferenceFragmentCompat { + + public static final String PREF_SCREEN_LOCK = "pref_key_lock"; + public static final String PREF_SCREEN_LOCK_TIMEOUT = + "pref_key_lock_timeout"; + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private SettingsViewModel viewModel; + private SwitchPreference screenLock; + private ListPreference screenLockTimeout; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getAndroidComponent(context).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(SettingsViewModel.class); + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings_security); + getPreferenceManager().setPreferenceDataStore(viewModel.settingsStore); + + screenLock = findPreference(PREF_SCREEN_LOCK); + screenLockTimeout = + requireNonNull(findPreference(PREF_SCREEN_LOCK_TIMEOUT)); + + screenLockTimeout.setSummaryProvider(preference -> { + CharSequence timeout = screenLockTimeout.getValue(); + String never = getString(R.string.pref_lock_timeout_value_never); + if (timeout.equals(never)) { + return getString(R.string.pref_lock_timeout_never_summary); + } else { + return getString(R.string.pref_lock_timeout_summary, + screenLockTimeout.getEntry()); + } + }); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (SDK_INT < 21) { + screenLock.setVisible(false); + screenLockTimeout.setVisible(false); + } else { + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + if (getActivity() != null && hasScreenLock(getActivity())) { + viewModel.getScreenLockEnabled().observe(lifecycleOwner, on -> { + screenLock.setChecked(on); + enableAndPersist(screenLock); + }); + screenLock.setSummary(R.string.pref_lock_summary); + } else { + screenLock.setEnabled(false); + screenLock.setChecked(false); + screenLock.setSummary(R.string.pref_lock_disabled_summary); + } + // timeout depends on screenLock and gets disabled automatically + viewModel.getScreenLockTimeout().observe(lifecycleOwner, value -> { + screenLockTimeout.setValue(value); + enableAndPersist(screenLockTimeout); + }); + } + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.security_settings_title); + } + +} 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 2f6d70c6b..d45014313 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 @@ -32,7 +32,6 @@ import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; -import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceFragmentCompat; @@ -61,7 +60,6 @@ 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.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID; @@ -82,17 +80,12 @@ public class SettingsFragment extends PreferenceFragmentCompat implements EventListener, OnPreferenceChangeListener { public static final String SETTINGS_NAMESPACE = "android-ui"; - public static final String PREF_SCREEN_LOCK = "pref_key_lock"; - public static final String PREF_SCREEN_LOCK_TIMEOUT = - "pref_key_lock_timeout"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; private static final Logger LOG = Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; - private SwitchPreference screenLock; - private ListPreference screenLockTimeout; private SwitchPreference notifyPrivateMessages; private SwitchPreference notifyGroupMessages; private SwitchPreference notifyForumPosts; @@ -121,8 +114,6 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - screenLock = findPreference(PREF_SCREEN_LOCK); - screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT); notifyPrivateMessages = findPreference("pref_key_notify_private_messages"); notifyGroupMessages = findPreference("pref_key_notify_group_messages"); @@ -131,9 +122,6 @@ public class SettingsFragment extends PreferenceFragmentCompat notifyVibration = findPreference("pref_key_notify_vibration"); notifySound = findPreference("pref_key_notify_sound"); - screenLock.setOnPreferenceChangeListener(this); - screenLockTimeout.setOnPreferenceChangeListener(this); - Preference prefFeedback = requireNonNull(findPreference("pref_key_send_feedback")); prefFeedback.setOnPreferenceClickListener(preference -> { @@ -200,8 +188,6 @@ public class SettingsFragment extends PreferenceFragmentCompat // due to events, we might try to display before a load completed if (!settingsLoaded) return; - displayScreenLockSetting(); - if (SDK_INT < 26) { notifyPrivateMessages.setChecked(settings.getBoolean( PREF_NOTIFY_PRIVATE, true)); @@ -253,12 +239,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private void setSettingsEnabled(boolean enabled) { // preferences not needed here, because handled by SharedPreferences: - // - pref_key_theme // - pref_key_notify_sign_in - // preferences partly needed here, because they have their own logic - // - pref_key_lock (screenLock -> displayScreenLockSetting()) - // - pref_key_lock_timeout (screenLockTimeout) - if (!enabled) screenLock.setEnabled(false); notifyPrivateMessages.setEnabled(enabled); notifyGroupMessages.setEnabled(enabled); notifyForumPosts.setEnabled(enabled); @@ -267,42 +248,6 @@ public class SettingsFragment extends PreferenceFragmentCompat notifySound.setEnabled(enabled); } - private void displayScreenLockSetting() { - if (SDK_INT < 21) { - screenLock.setVisible(false); - screenLockTimeout.setVisible(false); - } else { - if (getActivity() != null && hasScreenLock(getActivity())) { - screenLock.setEnabled(true); - screenLock.setChecked( - settings.getBoolean(PREF_SCREEN_LOCK, false)); - screenLock.setSummary(R.string.pref_lock_summary); - } else { - screenLock.setEnabled(false); - screenLock.setChecked(false); - screenLock.setSummary(R.string.pref_lock_disabled_summary); - } - // timeout depends on screenLock and gets disabled automatically - int timeout = settings.getInt(PREF_SCREEN_LOCK_TIMEOUT, - Integer.valueOf(getString( - R.string.pref_lock_timeout_value_default))); - String newValue = String.valueOf(timeout); - screenLockTimeout.setValue(newValue); - setScreenLockTimeoutSummary(newValue); - } - } - - private void setScreenLockTimeoutSummary(String timeout) { - String never = getString(R.string.pref_lock_timeout_value_never); - if (timeout.equals(never)) { - screenLockTimeout - .setSummary(R.string.pref_lock_timeout_never_summary); - } else { - screenLockTimeout - .setSummary(R.string.pref_lock_timeout_summary); - } - } - @TargetApi(26) private void setupNotificationPreference(SwitchPreference pref, String channelId, @StringRes int summary) { @@ -352,17 +297,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - 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.parseInt(value)); - storeSettings(s); - setScreenLockTimeoutSummary(value); - } else if (preference == notifyPrivateMessages) { + if (preference == notifyPrivateMessages) { Settings s = new Settings(); s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue); storeSettings(s); 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 index c009cdef4..23f9f3a11 100644 --- 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 @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceDataStore; import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.LogUtils.logDuration; @@ -42,6 +43,8 @@ class SettingsStore extends PreferenceDataStore { @Override public void putBoolean(String key, boolean value) { + if (LOG.isLoggable(INFO)) + LOG.info("Store bool setting: " + key + "=" + value); Settings s = new Settings(); s.putBoolean(key, value); storeSettings(s); @@ -49,6 +52,8 @@ class SettingsStore extends PreferenceDataStore { @Override public void putInt(String key, int value) { + if (LOG.isLoggable(INFO)) + LOG.info("Store int setting: " + key + "=" + value); Settings s = new Settings(); s.putInt(key, value); storeSettings(s); 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 23f114c95..805c854d5 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 @@ -24,6 +24,7 @@ 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.R; import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException; import org.briarproject.briar.android.attachment.media.ImageCompressor; import org.briarproject.briar.android.viewmodel.DbViewModel; @@ -50,6 +51,8 @@ import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageConten 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.settings.SecurityFragment.PREF_SCREEN_LOCK; +import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT; @NotNullByDefault class SettingsViewModel extends DbViewModel implements EventListener { @@ -81,6 +84,10 @@ class SettingsViewModel extends DbViewModel implements EventListener { new MutableLiveData<>(); private final MutableLiveEvent setAvatarFailed = new MutableLiveEvent<>(); + private final MutableLiveData screenLockEnabled = + new MutableLiveData<>(); + private final MutableLiveData screenLockTimeout = + new MutableLiveData<>(); @Inject SettingsViewModel(Application application, @@ -128,6 +135,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { try { long start = now(); settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + updateSettings(settings); connectionsManager.updateBtSetting( settingsManager.getSettings(BT_NAMESPACE)); connectionsManager.updateWifiSettings( @@ -159,7 +167,11 @@ class SettingsViewModel extends DbViewModel implements EventListener { if (e instanceof SettingsUpdatedEvent) { SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; String namespace = s.getNamespace(); - if (namespace.equals(BT_NAMESPACE)) { + if (namespace.equals(SETTINGS_NAMESPACE)) { + LOG.info("Settings updated"); + settings = s.getSettings(); + updateSettings(settings); + } else if (namespace.equals(BT_NAMESPACE)) { LOG.info("Bluetooth settings updated"); connectionsManager.updateBtSetting(s.getSettings()); } else if (namespace.equals(WIFI_NAMESPACE)) { @@ -172,6 +184,16 @@ class SettingsViewModel extends DbViewModel implements EventListener { } } + private void updateSettings(Settings settings) { + screenLockEnabled.postValue(settings.getBoolean(PREF_SCREEN_LOCK, + false)); + int defaultTimeout = Integer.parseInt(getApplication() + .getString(R.string.pref_lock_timeout_value_default)); + screenLockTimeout.postValue(String.valueOf( + settings.getInt(PREF_SCREEN_LOCK_TIMEOUT, defaultTimeout) + )); + } + void setAvatar(Uri uri) { ioExecutor.execute(() -> { try { @@ -215,6 +237,14 @@ class SettingsViewModel extends DbViewModel implements EventListener { return setAvatarFailed; } + LiveData getScreenLockEnabled() { + return screenLockEnabled; + } + + LiveData getScreenLockTimeout() { + return screenLockTimeout; + } + ConnectionsManager getConnectionsManager() { return connectionsManager; } diff --git a/briar-android/src/main/res/drawable/ic_settings_security.xml b/briar-android/src/main/res/drawable/ic_settings_security.xml new file mode 100644 index 000000000..f44f9f25d --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_settings_security.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 153ae5bed..566d73023 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -12,58 +12,10 @@ app:fragment="org.briarproject.briar.android.settings.ConnectionsFragment" app:icon="@drawable/ic_connect_without_contact" /> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + From 484817db08c7bff08f0baf2b9bde395a7c9ff6a8 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 15:39:46 -0300 Subject: [PATCH 04/14] Move notifications settings into own screen --- .../briar/android/AndroidComponent.java | 3 + .../android/login/SignInReminderReceiver.java | 4 +- .../android/settings/ConnectionsFragment.java | 2 +- .../android/settings/ConnectionsManager.java | 2 + .../settings/NotificationsFragment.java | 235 ++++++++++++ .../settings/NotificationsManager.java | 159 +++++++++ .../android/settings/SettingsFragment.java | 336 +----------------- .../android/settings/SettingsViewModel.java | 22 +- .../main/res/drawable/ic_notifications.xml | 10 + briar-android/src/main/res/values/strings.xml | 1 - briar-android/src/main/res/xml/settings.xml | 88 +---- .../main/res/xml/settings_notifications.xml | 61 ++++ 12 files changed, 497 insertions(+), 426 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java create mode 100644 briar-android/src/main/res/drawable/ic_notifications.xml create mode 100644 briar-android/src/main/res/xml/settings_notifications.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 8a9315e3a..08cf0d02e 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 @@ -37,6 +37,7 @@ 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.settings.NotificationsFragment; import org.briarproject.briar.android.settings.SecurityFragment; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -199,4 +200,6 @@ public interface AndroidComponent void inject(ConnectionsFragment connectionsFragment); void inject(SecurityFragment securityFragment); + + void inject(NotificationsFragment notificationsFragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java index 994825bdc..a1237f616 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SignInReminderReceiver.java @@ -14,7 +14,7 @@ import javax.inject.Inject; import static android.content.Intent.ACTION_BOOT_COMPLETED; import static android.content.Intent.ACTION_MY_PACKAGE_REPLACED; -import static org.briarproject.briar.android.settings.SettingsFragment.NOTIFY_SIGN_IN; +import static org.briarproject.briar.android.settings.NotificationsFragment.PREF_NOTIFY_SIGN_IN; import static org.briarproject.briar.api.android.AndroidNotificationManager.ACTION_DISMISS_REMINDER; public class SignInReminderReceiver extends BroadcastReceiver { @@ -37,7 +37,7 @@ public class SignInReminderReceiver extends BroadcastReceiver { if (accountManager.accountExists() && !accountManager.hasDatabaseKey()) { SharedPreferences prefs = app.getDefaultSharedPreferences(); - if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) { + if (prefs.getBoolean(PREF_NOTIFY_SIGN_IN, true)) { notificationManager.showSignInNotification(); } } 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 index 92522c0c0..7b612809d 100644 --- 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 @@ -53,7 +53,7 @@ public class ConnectionsFragment extends PreferenceFragmentCompat { getAndroidComponent(context).inject(this); viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) .get(SettingsViewModel.class); - connectionsManager = viewModel.getConnectionsManager(); + connectionsManager = viewModel.connectionsManager; } @Override 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 index 8f842cc78..61fbfd6e4 100644 --- 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 @@ -1,5 +1,6 @@ package org.briarproject.briar.android.settings; +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; @@ -23,6 +24,7 @@ import static org.briarproject.briar.android.settings.SettingsViewModel.BT_NAMES import static org.briarproject.briar.android.settings.SettingsViewModel.TOR_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsViewModel.WIFI_NAMESPACE; +@NotNullByDefault class ConnectionsManager { final ConnectionsStore btStore; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsFragment.java new file mode 100644 index 000000000..b39192743 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsFragment.java @@ -0,0 +1,235 @@ +package org.briarproject.briar.android.settings; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.Toast; + +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.annotation.StringRes; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ViewModelProvider; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.SwitchPreferenceCompat; + +import static android.app.Activity.RESULT_OK; +import static android.media.RingtoneManager.ACTION_RINGTONE_PICKER; +import static android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI; +import static android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI; +import static android.media.RingtoneManager.EXTRA_RINGTONE_PICKED_URI; +import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT; +import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE; +import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE; +import static android.media.RingtoneManager.TYPE_NOTIFICATION; +import static android.os.Build.VERSION.SDK_INT; +import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS; +import static android.provider.Settings.EXTRA_APP_PACKAGE; +import static android.provider.Settings.EXTRA_CHANNEL_ID; +import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; +import static android.widget.Toast.LENGTH_SHORT; +import static java.util.Objects.requireNonNull; +import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; +import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist; +import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; +import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID; +import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID; +import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_CHANNEL_ID; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class NotificationsFragment extends PreferenceFragmentCompat { + + public static final String PREF_NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; + private static final int NOTIFICATION_CHANNEL_API = 26; + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private SettingsViewModel viewModel; + private NotificationsManager nm; + + private SwitchPreferenceCompat notifyPrivateMessages; + private SwitchPreferenceCompat notifyGroupMessages; + private SwitchPreferenceCompat notifyForumPosts; + private SwitchPreferenceCompat notifyBlogPosts; + private SwitchPreferenceCompat notifyVibration; + + private Preference notifySound; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getAndroidComponent(context).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(SettingsViewModel.class); + nm = viewModel.notificationsManager; + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings_notifications); + + notifyPrivateMessages = findPreference(PREF_NOTIFY_PRIVATE); + notifyGroupMessages = findPreference(PREF_NOTIFY_GROUP); + notifyForumPosts = findPreference(PREF_NOTIFY_FORUM); + notifyBlogPosts = findPreference(PREF_NOTIFY_BLOG); + notifyVibration = findPreference(PREF_NOTIFY_VIBRATION); + notifySound = findPreference(PREF_NOTIFY_SOUND); + + if (SDK_INT < NOTIFICATION_CHANNEL_API) { + // NOTIFY_SIGN_IN gets stored in Android's SharedPreferences + notifyPrivateMessages + .setPreferenceDataStore(viewModel.settingsStore); + notifyGroupMessages.setPreferenceDataStore(viewModel.settingsStore); + notifyForumPosts.setPreferenceDataStore(viewModel.settingsStore); + notifyBlogPosts.setPreferenceDataStore(viewModel.settingsStore); + notifyVibration.setPreferenceDataStore(viewModel.settingsStore); + + notifySound.setOnPreferenceClickListener(pref -> + onNotificationSoundClicked() + ); + } else { + setupNotificationPreference(notifyPrivateMessages, + CONTACT_CHANNEL_ID, + R.string.notify_private_messages_setting_summary_26); + setupNotificationPreference(notifyGroupMessages, + GROUP_CHANNEL_ID, + R.string.notify_group_messages_setting_summary_26); + setupNotificationPreference(notifyForumPosts, FORUM_CHANNEL_ID, + R.string.notify_forum_posts_setting_summary_26); + setupNotificationPreference(notifyBlogPosts, BLOG_CHANNEL_ID, + R.string.notify_blog_posts_setting_summary_26); + + notifyVibration.setVisible(false); + notifySound.setVisible(false); + } + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (SDK_INT < NOTIFICATION_CHANNEL_API) { + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + nm.getNotifyPrivateMessages().observe(lifecycleOwner, enabled -> { + notifyPrivateMessages.setChecked(enabled); + enableAndPersist(notifyPrivateMessages); + }); + nm.getNotifyGroupMessages().observe(lifecycleOwner, enabled -> { + notifyGroupMessages.setChecked(enabled); + enableAndPersist(notifyGroupMessages); + }); + nm.getNotifyForumPosts().observe(lifecycleOwner, enabled -> { + notifyForumPosts.setChecked(enabled); + enableAndPersist(notifyForumPosts); + }); + nm.getNotifyBlogPosts().observe(lifecycleOwner, enabled -> { + notifyBlogPosts.setChecked(enabled); + enableAndPersist(notifyBlogPosts); + }); + nm.getNotifyVibration().observe(lifecycleOwner, enabled -> { + notifyVibration.setChecked(enabled); + enableAndPersist(notifyVibration); + }); + nm.getNotifySound().observe(lifecycleOwner, enabled -> { + String text; + if (enabled) { + String ringtoneName = nm.getRingtoneName(); + if (isNullOrEmpty(ringtoneName)) { + text = getString(R.string.notify_sound_setting_default); + } else { + text = ringtoneName; + } + } else { + text = getString(R.string.notify_sound_setting_disabled); + } + notifySound.setSummary(text); + notifySound.setEnabled(true); + }); + } + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.notification_settings_title); + } + + @Override + public void onActivityResult(int request, int result, + @Nullable Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_RINGTONE && result == RESULT_OK && + data != null) { + Uri uri = data.getParcelableExtra(EXTRA_RINGTONE_PICKED_URI); + nm.onRingtoneSet(uri); + } + } + + @TargetApi(NOTIFICATION_CHANNEL_API) + private void setupNotificationPreference(SwitchPreferenceCompat pref, + String channelId, @StringRes int summary) { + pref.setWidgetLayoutResource(0); + pref.setSummary(summary); + pref.setEnabled(true); + pref.setOnPreferenceClickListener(clickedPref -> { + String packageName = requireContext().getPackageName(); + Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(EXTRA_APP_PACKAGE, packageName) + .putExtra(EXTRA_CHANNEL_ID, channelId); + Context ctx = requireContext(); + if (intent.resolveActivity(ctx.getPackageManager()) != null) { + startActivity(intent); + } else { + Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT) + .show(); + } + return true; + }); + } + + private boolean onNotificationSoundClicked() { + String title = getString(R.string.choose_ringtone_title); + Intent i = new Intent(ACTION_RINGTONE_PICKER); + i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION); + i.putExtra(EXTRA_RINGTONE_TITLE, title); + i.putExtra(EXTRA_RINGTONE_DEFAULT_URI, + DEFAULT_NOTIFICATION_URI); + i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true); + if (requireNonNull(nm.getNotifySound().getValue())) { + Uri uri; + String ringtoneUri = nm.getRingtoneUri(); + if (isNullOrEmpty(ringtoneUri)) + uri = DEFAULT_NOTIFICATION_URI; + else uri = Uri.parse(ringtoneUri); + i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri); + } + if (i.resolveActivity(requireActivity().getPackageManager()) != null) { + startActivityForResult(i, REQUEST_RINGTONE); + } else { + Toast.makeText(getContext(), R.string.cannot_load_ringtone, + LENGTH_SHORT).show(); + } + return true; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java new file mode 100644 index 000000000..40f8995ab --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java @@ -0,0 +1,159 @@ +package org.briarproject.briar.android.settings; + +import android.content.Context; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.widget.Toast; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.briar.R; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static android.widget.Toast.LENGTH_SHORT; +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; +import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND; +import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION; + + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class NotificationsManager { + + private final static Logger LOG = + getLogger(NotificationsManager.class.getName()); + + private final Context ctx; + private final SettingsManager settingsManager; + private final Executor dbExecutor; + + private final MutableLiveData notifyPrivateMessages = + new MutableLiveData<>(); + private final MutableLiveData notifyGroupMessages = + new MutableLiveData<>(); + private final MutableLiveData notifyForumPosts = + new MutableLiveData<>(); + private final MutableLiveData notifyBlogPosts = + new MutableLiveData<>(); + private final MutableLiveData notifyVibration = + new MutableLiveData<>(); + private final MutableLiveData notifySound = + new MutableLiveData<>(); + + private volatile String ringtoneName, ringtoneUri; + + public NotificationsManager(Context ctx, + SettingsManager settingsManager, + Executor dbExecutor) { + this.ctx = ctx; + this.settingsManager = settingsManager; + this.dbExecutor = dbExecutor; + } + + void updateSettings(Settings settings) { + notifyPrivateMessages.postValue(settings.getBoolean( + PREF_NOTIFY_PRIVATE, true)); + notifyGroupMessages.postValue(settings.getBoolean( + PREF_NOTIFY_GROUP, true)); + notifyForumPosts.postValue(settings.getBoolean( + PREF_NOTIFY_FORUM, true)); + notifyBlogPosts.postValue(settings.getBoolean( + PREF_NOTIFY_BLOG, true)); + notifyVibration.postValue(settings.getBoolean( + PREF_NOTIFY_VIBRATION, true)); + ringtoneName = settings.get(PREF_NOTIFY_RINGTONE_NAME); + ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI); + notifySound.postValue(settings.getBoolean(PREF_NOTIFY_SOUND, true)); + } + + void onRingtoneSet(@Nullable Uri uri) { + Settings s = new Settings(); + if (uri == null) { + // The user chose silence + s.putBoolean(PREF_NOTIFY_SOUND, false); + s.put(PREF_NOTIFY_RINGTONE_NAME, ""); + s.put(PREF_NOTIFY_RINGTONE_URI, ""); + } else if (RingtoneManager.isDefault(uri)) { + // The user chose the default + s.putBoolean(PREF_NOTIFY_SOUND, true); + s.put(PREF_NOTIFY_RINGTONE_NAME, ""); + s.put(PREF_NOTIFY_RINGTONE_URI, ""); + } else { + // The user chose a ringtone other than the default + Ringtone r = RingtoneManager.getRingtone(ctx, uri); + if (r == null || "file".equals(uri.getScheme())) { + Toast.makeText(ctx, R.string.cannot_load_ringtone, LENGTH_SHORT) + .show(); + } else { + String name = r.getTitle(ctx); + s.putBoolean(PREF_NOTIFY_SOUND, true); + s.put(PREF_NOTIFY_RINGTONE_NAME, name); + s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString()); + } + } + dbExecutor.execute(() -> { + try { + long start = now(); + settingsManager.mergeSettings(s, SETTINGS_NAMESPACE); + logDuration(LOG, "Merging notification settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData getNotifyPrivateMessages() { + return notifyPrivateMessages; + } + + LiveData getNotifyGroupMessages() { + return notifyGroupMessages; + } + + LiveData getNotifyForumPosts() { + return notifyForumPosts; + } + + LiveData getNotifyBlogPosts() { + return notifyBlogPosts; + } + + LiveData getNotifyVibration() { + return notifyVibration; + } + + @NonNull + LiveData getNotifySound() { + return notifySound; + } + + String getRingtoneName() { + return ringtoneName; + } + + public String getRingtoneUri() { + return ringtoneUri; + } +} 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 d45014313..8b5de3b4c 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 @@ -1,127 +1,29 @@ package org.briarproject.briar.android.settings; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.ColorDrawable; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; -import org.briarproject.bramble.api.db.DbException; -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.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; -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.util.StringUtils; import org.briarproject.briar.R; -import java.util.logging.Logger; - -import javax.inject.Inject; - -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; import androidx.preference.Preference; -import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceGroup; -import androidx.preference.SwitchPreference; -import static android.app.Activity.RESULT_OK; -import static android.media.RingtoneManager.ACTION_RINGTONE_PICKER; -import static android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI; -import static android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI; -import static android.media.RingtoneManager.EXTRA_RINGTONE_PICKED_URI; -import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT; -import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE; -import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE; -import static android.media.RingtoneManager.TYPE_NOTIFICATION; -import static android.os.Build.VERSION.SDK_INT; -import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS; -import static android.provider.Settings.EXTRA_APP_PACKAGE; -import static android.provider.Settings.EXTRA_CHANNEL_ID; -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.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.triggerFeedback; -import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; -import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID; -import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID; -import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_CHANNEL_ID; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND; -import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class SettingsFragment extends PreferenceFragmentCompat - implements EventListener, OnPreferenceChangeListener { +public class SettingsFragment extends PreferenceFragmentCompat { public static final String SETTINGS_NAMESPACE = "android-ui"; - public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; - - private static final Logger LOG = - Logger.getLogger(SettingsFragment.class.getName()); - - private SettingsActivity listener; - private SwitchPreference notifyPrivateMessages; - private SwitchPreference notifyGroupMessages; - private SwitchPreference notifyForumPosts; - private SwitchPreference notifyBlogPosts; - private SwitchPreference notifyVibration; - - private Preference notifySound; - - // Fields that are accessed from background threads must be volatile - private volatile Settings settings; - private volatile boolean settingsLoaded = false; - - @Inject - volatile SettingsManager settingsManager; - @Inject - volatile EventBus eventBus; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - listener = (SettingsActivity) context; - listener.getActivityComponent().inject(this); - } @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - notifyPrivateMessages = - findPreference("pref_key_notify_private_messages"); - notifyGroupMessages = findPreference("pref_key_notify_group_messages"); - notifyForumPosts = findPreference("pref_key_notify_forum_posts"); - notifyBlogPosts = findPreference("pref_key_notify_blog_posts"); - notifyVibration = findPreference("pref_key_notify_vibration"); - notifySound = findPreference("pref_key_notify_sound"); - Preference prefFeedback = requireNonNull(findPreference("pref_key_send_feedback")); prefFeedback.setOnPreferenceClickListener(preference -> { @@ -143,246 +45,10 @@ public class SettingsFragment extends PreferenceFragmentCompat } } - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); - ColorDrawable divider = new ColorDrawable( - ContextCompat.getColor(requireContext(), R.color.divider)); - setDivider(divider); - return view; - } - @Override public void onStart() { super.onStart(); requireActivity().setTitle(R.string.settings_button); - eventBus.addListener(this); - setSettingsEnabled(false); - loadSettings(); - } - - @Override - public void onStop() { - super.onStop(); - eventBus.removeListener(this); - } - - private void loadSettings() { - listener.runOnDbThread(() -> { - try { - long start = now(); - settings = settingsManager.getSettings(SETTINGS_NAMESPACE); - settingsLoaded = true; - logDuration(LOG, "Loading settings", start); - displaySettings(); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - private void displaySettings() { - listener.runOnUiThreadUnlessDestroyed(() -> { - // due to events, we might try to display before a load completed - if (!settingsLoaded) return; - - if (SDK_INT < 26) { - notifyPrivateMessages.setChecked(settings.getBoolean( - PREF_NOTIFY_PRIVATE, true)); - notifyGroupMessages.setChecked(settings.getBoolean( - PREF_NOTIFY_GROUP, true)); - notifyForumPosts.setChecked(settings.getBoolean( - PREF_NOTIFY_FORUM, true)); - notifyBlogPosts.setChecked(settings.getBoolean( - PREF_NOTIFY_BLOG, true)); - notifyVibration.setChecked(settings.getBoolean( - PREF_NOTIFY_VIBRATION, true)); - notifyPrivateMessages.setOnPreferenceChangeListener(this); - notifyGroupMessages.setOnPreferenceChangeListener(this); - notifyForumPosts.setOnPreferenceChangeListener(this); - notifyBlogPosts.setOnPreferenceChangeListener(this); - notifyVibration.setOnPreferenceChangeListener(this); - notifySound.setOnPreferenceClickListener( - pref -> onNotificationSoundClicked()); - String text; - if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) { - String ringtoneName = - settings.get(PREF_NOTIFY_RINGTONE_NAME); - if (StringUtils.isNullOrEmpty(ringtoneName)) { - text = getString(R.string.notify_sound_setting_default); - } else { - text = ringtoneName; - } - } else { - text = getString(R.string.notify_sound_setting_disabled); - } - notifySound.setSummary(text); - } else { - setupNotificationPreference(notifyPrivateMessages, - CONTACT_CHANNEL_ID, - R.string.notify_private_messages_setting_summary_26); - setupNotificationPreference(notifyGroupMessages, - GROUP_CHANNEL_ID, - R.string.notify_group_messages_setting_summary_26); - setupNotificationPreference(notifyForumPosts, FORUM_CHANNEL_ID, - R.string.notify_forum_posts_setting_summary_26); - setupNotificationPreference(notifyBlogPosts, BLOG_CHANNEL_ID, - R.string.notify_blog_posts_setting_summary_26); - notifyVibration.setVisible(false); - notifySound.setVisible(false); - } - setSettingsEnabled(true); - }); - } - - private void setSettingsEnabled(boolean enabled) { - // preferences not needed here, because handled by SharedPreferences: - // - pref_key_notify_sign_in - notifyPrivateMessages.setEnabled(enabled); - notifyGroupMessages.setEnabled(enabled); - notifyForumPosts.setEnabled(enabled); - notifyBlogPosts.setEnabled(enabled); - notifyVibration.setEnabled(enabled); - notifySound.setEnabled(enabled); - } - - @TargetApi(26) - private void setupNotificationPreference(SwitchPreference pref, - String channelId, @StringRes int summary) { - pref.setWidgetLayoutResource(0); - pref.setSummary(summary); - pref.setOnPreferenceClickListener(clickedPref -> { - String packageName = requireContext().getPackageName(); - Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS) - .putExtra(EXTRA_APP_PACKAGE, packageName) - .putExtra(EXTRA_CHANNEL_ID, channelId); - Context ctx = requireContext(); - if (intent.resolveActivity(ctx.getPackageManager()) != null) { - startActivity(intent); - } else { - Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT) - .show(); - } - return true; - }); - } - - private boolean onNotificationSoundClicked() { - String title = getString(R.string.choose_ringtone_title); - Intent i = new Intent(ACTION_RINGTONE_PICKER); - i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION); - i.putExtra(EXTRA_RINGTONE_TITLE, title); - i.putExtra(EXTRA_RINGTONE_DEFAULT_URI, - DEFAULT_NOTIFICATION_URI); - i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true); - if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) { - Uri uri; - String ringtoneUri = - settings.get(PREF_NOTIFY_RINGTONE_URI); - if (StringUtils.isNullOrEmpty(ringtoneUri)) - uri = DEFAULT_NOTIFICATION_URI; - else uri = Uri.parse(ringtoneUri); - i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri); - } - if (i.resolveActivity(requireActivity().getPackageManager()) != null) { - startActivityForResult(i, REQUEST_RINGTONE); - } else { - Toast.makeText(getContext(), R.string.cannot_load_ringtone, - LENGTH_SHORT).show(); - } - return true; - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == notifyPrivateMessages) { - Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue); - storeSettings(s); - } else if (preference == notifyGroupMessages) { - Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) newValue); - storeSettings(s); - } else if (preference == notifyForumPosts) { - Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) newValue); - storeSettings(s); - } else if (preference == notifyBlogPosts) { - Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) newValue); - storeSettings(s); - } else if (preference == notifyVibration) { - Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) newValue); - storeSettings(s); - } - return true; - } - - - private void storeSettings(Settings s) { - mergeSettings(s, SETTINGS_NAMESPACE); - } - - private void mergeSettings(Settings s, String namespace) { - listener.runOnDbThread(() -> { - try { - long start = now(); - settingsManager.mergeSettings(s, namespace); - logDuration(LOG, "Merging settings", start); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - @Override - 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(); - Uri uri = data.getParcelableExtra(EXTRA_RINGTONE_PICKED_URI); - if (uri == null) { - // The user chose silence - s.putBoolean(PREF_NOTIFY_SOUND, false); - s.put(PREF_NOTIFY_RINGTONE_NAME, ""); - s.put(PREF_NOTIFY_RINGTONE_URI, ""); - } else if (RingtoneManager.isDefault(uri)) { - // The user chose the default - s.putBoolean(PREF_NOTIFY_SOUND, true); - s.put(PREF_NOTIFY_RINGTONE_NAME, ""); - s.put(PREF_NOTIFY_RINGTONE_URI, ""); - } else { - // The user chose a ringtone other than the default - Ringtone r = RingtoneManager.getRingtone(getContext(), uri); - if (r == null || "file".equals(uri.getScheme())) { - Toast.makeText(getContext(), R.string.cannot_load_ringtone, - LENGTH_SHORT).show(); - } else { - String name = r.getTitle(getContext()); - s.putBoolean(PREF_NOTIFY_SOUND, true); - s.put(PREF_NOTIFY_RINGTONE_NAME, name); - s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString()); - } - } - storeSettings(s); - } - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof SettingsUpdatedEvent) { - SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; - String namespace = s.getNamespace(); - if (namespace.equals(SETTINGS_NAMESPACE)) { - LOG.info("Settings updated"); - settings = s.getSettings(); - displaySettings(); - } - } } } 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 805c854d5..d0123ecd3 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 @@ -14,7 +14,8 @@ 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.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; @@ -53,14 +54,15 @@ import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK; import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT; +import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; -@NotNullByDefault +@MethodsNotNullByDefault +@ParametersNotNullByDefault 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(); @@ -73,10 +75,11 @@ class SettingsViewModel extends DbViewModel implements EventListener { private final AuthorManager authorManager; private final ImageCompressor imageCompressor; private final Executor ioExecutor; - private final ConnectionsManager connectionsManager; final SettingsStore settingsStore; final TorSummaryProvider torSummaryProvider; + final ConnectionsManager connectionsManager; + final NotificationsManager notificationsManager; private volatile Settings settings; @@ -114,10 +117,12 @@ class SettingsViewModel extends DbViewModel implements EventListener { this.ioExecutor = ioExecutor; this.settingsStore = new SettingsStore(settingsManager, dbExecutor, SETTINGS_NAMESPACE); - this.torSummaryProvider = new TorSummaryProvider(getApplication(), + torSummaryProvider = new TorSummaryProvider(getApplication(), locationUtils, circumventionProvider); - this.connectionsManager = + connectionsManager = new ConnectionsManager(settingsManager, dbExecutor); + notificationsManager = new NotificationsManager(getApplication(), + settingsManager, dbExecutor); eventBus.addListener(this); loadSettings(); @@ -192,6 +197,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { screenLockTimeout.postValue(String.valueOf( settings.getInt(PREF_SCREEN_LOCK_TIMEOUT, defaultTimeout) )); + notificationsManager.updateSettings(settings); } void setAvatar(Uri uri) { @@ -245,8 +251,4 @@ class SettingsViewModel extends DbViewModel implements EventListener { return screenLockTimeout; } - ConnectionsManager getConnectionsManager() { - return connectionsManager; - } - } diff --git a/briar-android/src/main/res/drawable/ic_notifications.xml b/briar-android/src/main/res/drawable/ic_notifications.xml new file mode 100644 index 000000000..9b98606f9 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_notifications.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 4de25404d..a5887a24a 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -552,7 +552,6 @@ Cannot load ringtone - Feedback Send feedback diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 566d73023..d98dc01af 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -17,88 +17,23 @@ app:fragment="org.briarproject.briar.android.settings.SecurityFragment" app:icon="@drawable/ic_settings_security" /> - + - - - - - - - - - - - - - - - + - - - - - - + android:title="Developer Options" + app:allowDividerAbove="true"> + android:title="Create test data"> + android:title="Crash" /> diff --git a/briar-android/src/main/res/xml/settings_notifications.xml b/briar-android/src/main/res/xml/settings_notifications.xml new file mode 100644 index 000000000..9e36183f7 --- /dev/null +++ b/briar-android/src/main/res/xml/settings_notifications.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + From f0685c4a43dc43a1b97039cc38e129c24937b84c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 15:45:32 -0300 Subject: [PATCH 05/14] Get rid of custom switch preference --- .../android/settings/ConnectionsFragment.java | 12 ++++++------ .../briar/android/settings/SecurityFragment.java | 4 ++-- .../main/res/layout/list_item_transport_card.xml | 1 - .../main/res/layout/preference_switch_compat.xml | 11 ----------- .../src/main/res/xml/settings_connections.xml | 15 +++++---------- .../src/main/res/xml/settings_security.xml | 3 +-- 6 files changed, 14 insertions(+), 32 deletions(-) delete mode 100644 briar-android/src/main/res/layout/preference_switch_compat.xml 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 index 7b612809d..f669636f6 100644 --- 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 @@ -16,7 +16,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.settings.SettingsActivity.enableAndPersist; @@ -40,12 +40,12 @@ public class ConnectionsFragment extends PreferenceFragmentCompat { private SettingsViewModel viewModel; private ConnectionsManager connectionsManager; - private SwitchPreference enableBluetooth; - private SwitchPreference enableWifi; - private SwitchPreference enableTor; + private SwitchPreferenceCompat enableBluetooth; + private SwitchPreferenceCompat enableWifi; + private SwitchPreferenceCompat enableTor; private ListPreference torNetwork; - private SwitchPreference torMobile; - private SwitchPreference torOnlyWhenCharging; + private SwitchPreferenceCompat torMobile; + private SwitchPreferenceCompat torOnlyWhenCharging; @Override public void onAttach(@NonNull Context context) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java index 241e5812d..89ba2482e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java @@ -16,7 +16,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import static android.os.Build.VERSION.SDK_INT; import static java.util.Objects.requireNonNull; @@ -36,7 +36,7 @@ public class SecurityFragment extends PreferenceFragmentCompat { ViewModelProvider.Factory viewModelFactory; private SettingsViewModel viewModel; - private SwitchPreference screenLock; + private SwitchPreferenceCompat screenLock; private ListPreference screenLockTimeout; @Override diff --git a/briar-android/src/main/res/layout/list_item_transport_card.xml b/briar-android/src/main/res/layout/list_item_transport_card.xml index 638ef70a8..2ad07a951 100644 --- a/briar-android/src/main/res/layout/list_item_transport_card.xml +++ b/briar-android/src/main/res/layout/list_item_transport_card.xml @@ -44,7 +44,6 @@ android:layout_height="wrap_content" android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/text_size_medium" - android:widgetLayout="@layout/preference_switch_compat" tools:checked="true" tools:text="@string/tor_enable_title" /> diff --git a/briar-android/src/main/res/layout/preference_switch_compat.xml b/briar-android/src/main/res/layout/preference_switch_compat.xml deleted file mode 100644 index 7ebf4bbbe..000000000 --- a/briar-android/src/main/res/layout/preference_switch_compat.xml +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/briar-android/src/main/res/xml/settings_connections.xml b/briar-android/src/main/res/xml/settings_connections.xml index 0fcbda021..0ef427868 100644 --- a/briar-android/src/main/res/xml/settings_connections.xml +++ b/briar-android/src/main/res/xml/settings_connections.xml @@ -2,32 +2,29 @@ - - - - - diff --git a/briar-android/src/main/res/xml/settings_security.xml b/briar-android/src/main/res/xml/settings_security.xml index aea43dc5a..568efc714 100644 --- a/briar-android/src/main/res/xml/settings_security.xml +++ b/briar-android/src/main/res/xml/settings_security.xml @@ -3,13 +3,12 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - Date: Fri, 22 Jan 2021 17:03:09 -0300 Subject: [PATCH 06/14] Move avatar layout into own preference which is only shown on main settings fragment --- .../briar/android/AndroidComponent.java | 3 + .../android/settings/AvatarPreference.java | 45 +++++++++++ .../android/settings/SettingsActivity.java | 77 ------------------ .../android/settings/SettingsFragment.java | 81 +++++++++++++++++-- .../android/settings/SettingsViewModel.java | 32 +++++--- .../src/main/res/drawable/ic_feedback.xml | 10 +++ .../src/main/res/layout/activity_settings.xml | 80 ++---------------- .../src/main/res/layout/preference_avatar.xml | 68 ++++++++++++++++ briar-android/src/main/res/xml/settings.xml | 6 +- 9 files changed, 230 insertions(+), 172 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java create mode 100644 briar-android/src/main/res/drawable/ic_feedback.xml create mode 100644 briar-android/src/main/res/layout/preference_avatar.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 08cf0d02e..5e04f88eb 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 @@ -39,6 +39,7 @@ import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.settings.ConnectionsFragment; import org.briarproject.briar.android.settings.NotificationsFragment; import org.briarproject.briar.android.settings.SecurityFragment; +import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; @@ -197,6 +198,8 @@ public interface AndroidComponent void inject(BriarModelLoader briarModelLoader); + void inject(SettingsFragment settingsFragment); + void inject(ConnectionsFragment connectionsFragment); void inject(SecurityFragment securityFragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java new file mode 100644 index 000000000..e3f30f981 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java @@ -0,0 +1,45 @@ +package org.briarproject.briar.android.settings; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import org.briarproject.briar.R; + +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; +import de.hdodenhof.circleimageview.CircleImageView; + +import static org.briarproject.briar.android.view.AuthorView.setAvatar; + +public class AvatarPreference extends Preference { + + @Nullable + private OwnIdentityInfo info; + + public AvatarPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.preference_avatar); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + View v = holder.itemView; + if (info != null) { + TextView textViewUserName = v.findViewById(R.id.username); + CircleImageView imageViewAvatar = v.findViewById(R.id.avatarImage); + textViewUserName.setText(info.getLocalAuthor().getName()); + setAvatar(imageViewAvatar, info.getLocalAuthor().getId(), + info.getAuthorInfo()); + } + } + + void setOwnIdentityInfo(OwnIdentityInfo info) { + this.info = info; + notifyChanged(); + } + +} 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 530e54b4e..afb93173f 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 @@ -1,55 +1,31 @@ package org.briarproject.briar.android.settings; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; -import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; -import org.briarproject.briar.android.util.UiUtils; -import org.briarproject.briar.android.view.AuthorView; - -import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; -import de.hdodenhof.circleimageview.CircleImageView; - -import static android.widget.Toast.LENGTH_LONG; -import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; @MethodsNotNullByDefault @ParametersNotNullByDefault public class SettingsActivity extends BriarActivity implements OnPreferenceStartFragmentCallback { - @Inject - ViewModelProvider.Factory viewModelFactory; - @Inject - FeatureFlags featureFlags; - - private SettingsViewModel settingsViewModel; - @Override public void injectActivity(ActivityComponent component) { component.inject(this); - settingsViewModel = new ViewModelProvider(this, viewModelFactory) - .get(SettingsViewModel.class); } @Override @@ -63,33 +39,6 @@ public class SettingsActivity extends BriarActivity } setContentView(R.layout.activity_settings); - - if (featureFlags.shouldEnableProfilePictures()) { - TextView textViewUserName = findViewById(R.id.username); - CircleImageView imageViewAvatar = - findViewById(R.id.avatarImage); - - settingsViewModel.getOwnIdentityInfo().observe(this, us -> { - textViewUserName.setText(us.getLocalAuthor().getName()); - AuthorView.setAvatar(imageViewAvatar, - us.getLocalAuthor().getId(), us.getAuthorInfo()); - }); - - settingsViewModel.getSetAvatarFailed() - .observeEvent(this, failed -> { - if (failed) { - Toast.makeText(this, - R.string.change_profile_picture_failed_message, - LENGTH_LONG).show(); - } - }); - - View avatarGroup = findViewById(R.id.avatarGroup); - avatarGroup.setOnClickListener(e -> selectAvatarImage()); - } else { - View view = findViewById(R.id.avatarGroup); - view.setVisibility(View.GONE); - } } @Override @@ -101,32 +50,6 @@ public class SettingsActivity extends BriarActivity return false; } - private void selectAvatarImage() { - Intent intent = UiUtils.createSelectImageIntent(false); - startActivityForResult(intent, REQUEST_AVATAR_IMAGE); - } - - @Override - protected void onActivityResult(int request, int result, - @Nullable Intent data) { - super.onActivityResult(request, result, data); - - if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) { - onAvatarImageReceived(data); - } - } - - private void onAvatarImageReceived(@Nullable Intent resultData) { - if (resultData == null) return; - Uri uri = resultData.getData(); - if (uri == null) return; - - ConfirmAvatarDialogFragment dialog = - ConfirmAvatarDialogFragment.newInstance(uri); - dialog.show(getSupportFragmentManager(), - ConfirmAvatarDialogFragment.TAG); - } - @Override public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { 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 8b5de3b4c..e3769e9e2 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 @@ -1,17 +1,31 @@ package org.briarproject.briar.android.settings; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; 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.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceGroup; +import static android.app.Activity.RESULT_OK; import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; +import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; @MethodsNotNullByDefault @@ -20,35 +34,88 @@ public class SettingsFragment extends PreferenceFragmentCompat { public static final String SETTINGS_NAMESPACE = "android-ui"; + private static final String PREF_KEY_AVATAR = "pref_key_avatar"; + private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback"; + private static final String PREF_KEY_DEV = "pref_key_dev"; + private static final String PREF_KEY_EXPLODE = "pref_key_explode"; + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private SettingsViewModel viewModel; + private AvatarPreference prefAvatar; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + getAndroidComponent(context).inject(this); + viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) + .get(SettingsViewModel.class); + } + @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); + prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR)); + if (viewModel.shouldEnableProfilePictures()) { + prefAvatar.setOnPreferenceClickListener(preference -> { + Intent intent = createSelectImageIntent(false); + startActivityForResult(intent, REQUEST_AVATAR_IMAGE); + return true; + }); + } else { + prefAvatar.setVisible(false); + } + Preference prefFeedback = - requireNonNull(findPreference("pref_key_send_feedback")); + requireNonNull(findPreference(PREF_KEY_FEEDBACK)); prefFeedback.setOnPreferenceClickListener(preference -> { triggerFeedback(requireContext()); return true; }); - Preference explode = requireNonNull(findPreference("pref_key_explode")); + Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE)); if (IS_DEBUG_BUILD) { explode.setOnPreferenceClickListener(preference -> { throw new RuntimeException("Boom!"); }); } else { - explode.setVisible(false); - findPreference("pref_key_test_data").setVisible(false); - PreferenceGroup testing = explode.getParent(); - if (testing == null) throw new AssertionError(); - testing.setVisible(false); + PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV)); + dev.setVisible(false); } } + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + viewModel.getOwnIdentityInfo().observe(getViewLifecycleOwner(), us -> + prefAvatar.setOwnIdentityInfo(us) + ); + } + @Override public void onStart() { super.onStart(); requireActivity().setTitle(R.string.settings_button); } + @Override + public void onActivityResult(int request, int result, + @Nullable Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) { + if (data == null) return; + Uri uri = data.getData(); + if (uri == null) return; + + DialogFragment dialog = + ConfirmAvatarDialogFragment.newInstance(uri); + dialog.show(getParentFragmentManager(), + ConfirmAvatarDialogFragment.TAG); + } + } + } 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 d0123ecd3..707d63b1a 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 @@ -3,7 +3,9 @@ package org.briarproject.briar.android.settings; import android.app.Application; import android.content.ContentResolver; import android.net.Uri; +import android.widget.Toast; +import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.TransactionManager; @@ -29,8 +31,6 @@ import org.briarproject.briar.R; 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; import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorManager; @@ -45,6 +45,7 @@ import javax.inject.Inject; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.widget.Toast.LENGTH_LONG; import static java.util.Arrays.asList; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; @@ -75,6 +76,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { private final AuthorManager authorManager; private final ImageCompressor imageCompressor; private final Executor ioExecutor; + private final FeatureFlags featureFlags; final SettingsStore settingsStore; final TorSummaryProvider torSummaryProvider; @@ -85,8 +87,6 @@ class SettingsViewModel extends DbViewModel implements EventListener { private final MutableLiveData ownIdentityInfo = new MutableLiveData<>(); - private final MutableLiveEvent setAvatarFailed = - new MutableLiveEvent<>(); private final MutableLiveData screenLockEnabled = new MutableLiveData<>(); private final MutableLiveData screenLockTimeout = @@ -106,7 +106,8 @@ class SettingsViewModel extends DbViewModel implements EventListener { ImageCompressor imageCompressor, LocationUtils locationUtils, CircumventionProvider circumventionProvider, - @IoExecutor Executor ioExecutor) { + @IoExecutor Executor ioExecutor, + FeatureFlags featureFlags) { super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.settingsManager = settingsManager; this.identityManager = identityManager; @@ -115,6 +116,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { this.avatarManager = avatarManager; this.authorManager = authorManager; this.ioExecutor = ioExecutor; + this.featureFlags = featureFlags; this.settingsStore = new SettingsStore(settingsManager, dbExecutor, SETTINGS_NAMESPACE); torSummaryProvider = new TorSummaryProvider(getApplication(), @@ -126,7 +128,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { eventBus.addListener(this); loadSettings(); - loadOwnIdentityInfo(); + if (shouldEnableProfilePictures()) loadOwnIdentityInfo(); } @Override @@ -154,6 +156,10 @@ class SettingsViewModel extends DbViewModel implements EventListener { }); } + boolean shouldEnableProfilePictures() { + return featureFlags.shouldEnableProfilePictures(); + } + private void loadOwnIdentityInfo() { runOnDbThread(() -> { try { @@ -206,7 +212,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { trySetAvatar(uri); } catch (IOException e) { logException(LOG, WARNING, e); - setAvatarFailed.postEvent(true); + onSetAvatarFailed(); } }); } @@ -230,17 +236,19 @@ class SettingsViewModel extends DbViewModel implements EventListener { loadOwnIdentityInfo(); } catch (IOException | DbException e) { logException(LOG, WARNING, e); - setAvatarFailed.postEvent(true); + onSetAvatarFailed(); } }); } - LiveData getOwnIdentityInfo() { - return ownIdentityInfo; + private void onSetAvatarFailed() { + Toast.makeText(getApplication(), + R.string.change_profile_picture_failed_message, + LENGTH_LONG).show(); } - LiveEvent getSetAvatarFailed() { - return setAvatarFailed; + LiveData getOwnIdentityInfo() { + return ownIdentityInfo; } LiveData getScreenLockEnabled() { diff --git a/briar-android/src/main/res/drawable/ic_feedback.xml b/briar-android/src/main/res/drawable/ic_feedback.xml new file mode 100644 index 000000000..b7e7b41f4 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_feedback.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/layout/activity_settings.xml b/briar-android/src/main/res/layout/activity_settings.xml index 180e2728e..90826e8ba 100644 --- a/briar-android/src/main/res/layout/activity_settings.xml +++ b/briar-android/src/main/res/layout/activity_settings.xml @@ -1,78 +1,8 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> diff --git a/briar-android/src/main/res/layout/preference_avatar.xml b/briar-android/src/main/res/layout/preference_avatar.xml new file mode 100644 index 000000000..61173ea4a --- /dev/null +++ b/briar-android/src/main/res/layout/preference_avatar.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index d98dc01af..b3cbe0e53 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -2,6 +2,8 @@ + + + android:title="@string/send_feedback" + app:icon="@drawable/ic_feedback" /> From aa57a4c123892475c41e39c9b5fbd0ebb6ad35ed Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 17:16:53 -0300 Subject: [PATCH 07/14] lint ignore icon tinting since it seems to work on Android 4 with VectorDrawableCompat --- .../src/main/res/drawable/ic_connect_without_contact.xml | 4 +++- briar-android/src/main/res/drawable/ic_feedback.xml | 4 +++- briar-android/src/main/res/drawable/ic_notifications.xml | 4 +++- .../src/main/res/drawable/ic_settings_brightness.xml | 4 +++- briar-android/src/main/res/drawable/ic_settings_security.xml | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) 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 index 35a61e288..38c9f9901 100644 --- a/briar-android/src/main/res/drawable/ic_connect_without_contact.xml +++ b/briar-android/src/main/res/drawable/ic_connect_without_contact.xml @@ -1,9 +1,11 @@ + android:viewportHeight="24" + tools:ignore="NewApi"> diff --git a/briar-android/src/main/res/drawable/ic_feedback.xml b/briar-android/src/main/res/drawable/ic_feedback.xml index b7e7b41f4..7c69a8240 100644 --- a/briar-android/src/main/res/drawable/ic_feedback.xml +++ b/briar-android/src/main/res/drawable/ic_feedback.xml @@ -1,9 +1,11 @@ + android:viewportHeight="24" + tools:ignore="NewApi"> diff --git a/briar-android/src/main/res/drawable/ic_notifications.xml b/briar-android/src/main/res/drawable/ic_notifications.xml index 9b98606f9..2bc13c6a0 100644 --- a/briar-android/src/main/res/drawable/ic_notifications.xml +++ b/briar-android/src/main/res/drawable/ic_notifications.xml @@ -1,9 +1,11 @@ + android:viewportHeight="24" + tools:ignore="NewApi"> diff --git a/briar-android/src/main/res/drawable/ic_settings_brightness.xml b/briar-android/src/main/res/drawable/ic_settings_brightness.xml index a0b720816..7fb6eb7ed 100644 --- a/briar-android/src/main/res/drawable/ic_settings_brightness.xml +++ b/briar-android/src/main/res/drawable/ic_settings_brightness.xml @@ -1,9 +1,11 @@ + android:viewportHeight="24" + tools:ignore="NewApi"> diff --git a/briar-android/src/main/res/drawable/ic_settings_security.xml b/briar-android/src/main/res/drawable/ic_settings_security.xml index f44f9f25d..0746b87ff 100644 --- a/briar-android/src/main/res/drawable/ic_settings_security.xml +++ b/briar-android/src/main/res/drawable/ic_settings_security.xml @@ -1,9 +1,11 @@ + android:viewportHeight="24.0" + tools:ignore="NewApi"> From 9ce541cc31b835bd8904107fcd12ffd8d4ffd534 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 4 Feb 2021 10:30:59 -0300 Subject: [PATCH 08/14] Allow settings titles on more than a single line --- .../src/main/res/xml/settings_connections.xml | 15 ++++++++++----- .../main/res/xml/settings_notifications.xml | 18 ++++++++++++------ .../src/main/res/xml/settings_security.xml | 3 ++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/briar-android/src/main/res/xml/settings_connections.xml b/briar-android/src/main/res/xml/settings_connections.xml index 0ef427868..2a25f7e40 100644 --- a/briar-android/src/main/res/xml/settings_connections.xml +++ b/briar-android/src/main/res/xml/settings_connections.xml @@ -8,7 +8,8 @@ android:key="pref_key_bluetooth" android:persistent="false" android:title="@string/bluetooth_setting" - app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> diff --git a/briar-android/src/main/res/xml/settings_notifications.xml b/briar-android/src/main/res/xml/settings_notifications.xml index 9e36183f7..fede5dd56 100644 --- a/briar-android/src/main/res/xml/settings_notifications.xml +++ b/briar-android/src/main/res/xml/settings_notifications.xml @@ -7,7 +7,8 @@ android:key="pref_key_notify_sign_in" android:summary="@string/notify_sign_in_summary" android:title="@string/notify_sign_in_title" - app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> Date: Fri, 5 Feb 2021 10:26:56 -0300 Subject: [PATCH 09/14] review: fix nullability and visibility of settings --- .../briar/android/settings/AvatarPreference.java | 2 ++ .../briar/android/settings/ConnectionsStore.java | 8 +++++--- .../briar/android/settings/DisplayFragment.java | 13 ++++++++----- .../android/settings/NotificationsManager.java | 5 ++--- .../briar/android/settings/SettingsStore.java | 10 +++++----- .../briar/android/settings/SettingsViewModel.java | 4 +++- .../briar/android/settings/TorSummaryProvider.java | 2 +- 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java index e3f30f981..77812b14f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/AvatarPreference.java @@ -5,6 +5,7 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; import androidx.annotation.Nullable; @@ -14,6 +15,7 @@ import de.hdodenhof.circleimageview.CircleImageView; import static org.briarproject.briar.android.view.AuthorView.setAvatar; +@NotNullByDefault public class AvatarPreference extends Preference { @Nullable 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 index 6c1c64b5b..94a12d53b 100644 --- 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 @@ -5,6 +5,8 @@ import org.briarproject.bramble.api.settings.SettingsManager; import java.util.concurrent.Executor; +import androidx.annotation.Nullable; + 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; @@ -49,12 +51,12 @@ class ConnectionsStore extends SettingsStore { } @Override - public void putInt(String key, int value) { + public void putString(String key, @Nullable String value) { // translate between Android UI pref keys and bramble keys if (key.equals(PREF_KEY_TOR_NETWORK)) { - super.putInt(PREF_TOR_NETWORK, value); + super.putString(PREF_TOR_NETWORK, value); } else { - throw new AssertionError(); + throw new AssertionError(key); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java index 6ef75cefb..8fd21c207 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java @@ -16,6 +16,7 @@ import java.util.Locale; import java.util.logging.Logger; import androidx.core.text.TextUtilsCompat; +import androidx.fragment.app.FragmentActivity; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -25,6 +26,7 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Build.VERSION.SDK_INT; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; +import static java.util.Objects.requireNonNull; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; @@ -43,11 +45,11 @@ public class DisplayFragment extends PreferenceFragmentCompat { public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings_display); - ListPreference language = findPreference(PREF_LANGUAGE); + ListPreference language = requireNonNull(findPreference(PREF_LANGUAGE)); setLanguageEntries(language); language.setOnPreferenceChangeListener(this::onLanguageChanged); - ListPreference theme = findPreference(PREF_THEME); + ListPreference theme = requireNonNull(findPreference(PREF_THEME)); setThemeEntries(theme); theme.setOnPreferenceChangeListener(this::onThemeChanged); } @@ -121,7 +123,8 @@ public class DisplayFragment extends PreferenceFragmentCompat { private boolean onThemeChanged(Preference preference, Object newValue) { // activate new theme - UiUtils.setTheme(getActivity(), (String) newValue); + FragmentActivity activity = requireActivity(); + UiUtils.setTheme(activity, (String) newValue); // bring up parent activity, so it can change its theme as well // upstream bug: https://issuetracker.google.com/issues/38352704 Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); @@ -129,9 +132,9 @@ public class DisplayFragment extends PreferenceFragmentCompat { startActivity(intent); // bring this activity back to the foreground // TODO maybe tell the activity here to relaunch this fragment? - intent = new Intent(getActivity(), getActivity().getClass()); + intent = new Intent(getActivity(), activity.getClass()); startActivity(intent); - getActivity().finish(); + activity.finish(); return true; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java index 40f8995ab..83a18c0ce 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/NotificationsManager.java @@ -37,7 +37,6 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND; import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION; - @MethodsNotNullByDefault @ParametersNotNullByDefault class NotificationsManager { @@ -64,7 +63,7 @@ class NotificationsManager { private volatile String ringtoneName, ringtoneUri; - public NotificationsManager(Context ctx, + NotificationsManager(Context ctx, SettingsManager settingsManager, Executor dbExecutor) { this.ctx = ctx; @@ -153,7 +152,7 @@ class NotificationsManager { return ringtoneName; } - public String getRingtoneUri() { + String getRingtoneUri() { return ringtoneUri; } } 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 index 23f9f3a11..b5752de51 100644 --- 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 @@ -11,7 +11,6 @@ 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.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; @@ -21,8 +20,6 @@ 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 { @@ -61,8 +58,11 @@ class SettingsStore extends PreferenceDataStore { @Override public void putString(String key, @Nullable String value) { - int integer = Integer.parseInt(requireNonNull(value)); - putInt(key, integer); + if (LOG.isLoggable(INFO)) + LOG.info("Store string setting: " + key + "=" + value); + Settings s = new Settings(); + s.put(key, value); + storeSettings(s); } private void storeSettings(Settings s) { 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 707d63b1a..9b0392d72 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 @@ -42,6 +42,7 @@ import java.util.logging.Logger; import javax.inject.Inject; +import androidx.annotation.AnyThread; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -117,7 +118,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { this.authorManager = authorManager; this.ioExecutor = ioExecutor; this.featureFlags = featureFlags; - this.settingsStore = new SettingsStore(settingsManager, dbExecutor, + settingsStore = new SettingsStore(settingsManager, dbExecutor, SETTINGS_NAMESPACE); torSummaryProvider = new TorSummaryProvider(getApplication(), locationUtils, circumventionProvider); @@ -195,6 +196,7 @@ class SettingsViewModel extends DbViewModel implements EventListener { } } + @AnyThread private void updateSettings(Settings settings) { screenLockEnabled.postValue(settings.getBoolean(PREF_SCREEN_LOCK, false)); 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 index 0d4258849..5e0ba6e8b 100644 --- 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 @@ -20,7 +20,7 @@ class TorSummaryProvider implements SummaryProvider { private final LocationUtils locationUtils; private final CircumventionProvider circumventionProvider; - public TorSummaryProvider(Context ctx, + TorSummaryProvider(Context ctx, LocationUtils locationUtils, CircumventionProvider circumventionProvider) { this.ctx = ctx; From 6ed95e145e4b6f7102472a1a804af0bed43ba4f1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 8 Feb 2021 13:59:50 -0300 Subject: [PATCH 10/14] Re-open DisplayFragment after changing theme --- .../android/settings/DisplayFragment.java | 3 ++- .../android/settings/SettingsActivity.java | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java index 8fd21c207..3103a6804 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java @@ -31,6 +31,7 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; +import static org.briarproject.briar.android.settings.SettingsActivity.EXTRA_THEME_CHANGE; @NotNullByDefault public class DisplayFragment extends PreferenceFragmentCompat { @@ -131,8 +132,8 @@ public class DisplayFragment extends PreferenceFragmentCompat { intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); startActivity(intent); // bring this activity back to the foreground - // TODO maybe tell the activity here to relaunch this fragment? intent = new Intent(getActivity(), activity.getClass()); + intent.putExtra(EXTRA_THEME_CHANGE, true); startActivity(intent); activity.finish(); return true; 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 afb93173f..18b3c0f6c 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 @@ -23,6 +23,8 @@ import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCal public class SettingsActivity extends BriarActivity implements OnPreferenceStartFragmentCallback { + static final String EXTRA_THEME_CHANGE = "themeChange"; + @Override public void injectActivity(ActivityComponent component) { component.inject(this); @@ -38,6 +40,14 @@ public class SettingsActivity extends BriarActivity actionBar.setDisplayHomeAsUpEnabled(true); } + // show display fragment after theme change + Bundle extras = getIntent().getExtras(); + if (bundle == null && extras != null && + extras.getBoolean(EXTRA_THEME_CHANGE, false)) { + FragmentManager fragmentManager = getSupportFragmentManager(); + showNextFragment(fragmentManager, new DisplayFragment()); + } + setContentView(R.layout.activity_settings); } @@ -59,14 +69,18 @@ public class SettingsActivity extends BriarActivity .instantiate(getClassLoader(), pref.getFragment()); fragment.setTargetFragment(caller, 0); // Replace the existing Fragment with the new Fragment + showNextFragment(fragmentManager, fragment); + return true; + } + + private void showNextFragment(FragmentManager fragmentManager, Fragment f) { fragmentManager.beginTransaction() .setCustomAnimations(R.anim.step_next_in, R.anim.step_previous_out, R.anim.step_previous_in, R.anim.step_next_out) - .replace(R.id.fragmentContainer, fragment) + .replace(R.id.fragmentContainer, f) .addToBackStack(null) .commit(); - return true; } /** From 371d49a213931781290d27bffd6fe8a3509c0349 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 26 Mar 2021 13:52:50 -0300 Subject: [PATCH 11/14] Use SwitchPreferenceCompat for panic preferences Addresses #1991 --- .../android/panic/PanicPreferencesFragment.java | 8 ++++---- .../src/main/res/xml/panic_preferences.xml | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicPreferencesFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicPreferencesFragment.java index e37fbaa5b..ff13f6dbf 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicPreferencesFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/panic/PanicPreferencesFragment.java @@ -20,7 +20,7 @@ import javax.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import info.guardianproject.panic.PanicResponder; import static android.app.Activity.RESULT_CANCELED; @@ -40,7 +40,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat Logger.getLogger(PanicPreferencesFragment.class.getName()); private PackageManager pm; - private SwitchPreference lockPref, purgePref; + private SwitchPreferenceCompat lockPref, purgePref; private ListPreference panicAppPref; @Override @@ -51,9 +51,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat private void updatePreferences() { pm = getActivity().getPackageManager(); - lockPref = (SwitchPreference) findPreference(KEY_LOCK); + lockPref = (SwitchPreferenceCompat) findPreference(KEY_LOCK); panicAppPref = (ListPreference) findPreference(KEY_PANIC_APP); - purgePref = (SwitchPreference) findPreference(KEY_PURGE); + purgePref = (SwitchPreferenceCompat) findPreference(KEY_PURGE); // check for connect/disconnect intents from panic trigger apps if (PanicResponder.checkForDisconnectIntent(getActivity())) { diff --git a/briar-android/src/main/res/xml/panic_preferences.xml b/briar-android/src/main/res/xml/panic_preferences.xml index 67887eb26..75c6e195b 100644 --- a/briar-android/src/main/res/xml/panic_preferences.xml +++ b/briar-android/src/main/res/xml/panic_preferences.xml @@ -1,14 +1,14 @@ - - + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> + android:title="@string/panic_app_setting_title" /> - + app:iconSpaceReserved="false" + app:singleLineTitle="false" /> From 29965e38d082a106a60aa0f1e4baa973782203e7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 26 Mar 2021 14:07:35 -0300 Subject: [PATCH 12/14] Don't show Toast off the UiThread --- .../briar/android/settings/SettingsViewModel.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 9b0392d72..9d08c2905 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 @@ -243,10 +243,11 @@ class SettingsViewModel extends DbViewModel implements EventListener { }); } + @AnyThread private void onSetAvatarFailed() { - Toast.makeText(getApplication(), - R.string.change_profile_picture_failed_message, - LENGTH_LONG).show(); + androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), + R.string.change_profile_picture_failed_message, LENGTH_LONG) + .show()); } LiveData getOwnIdentityInfo() { From aaaf8aa66f7474ea4c1a4e57ee47f724616e95e5 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 26 Mar 2021 14:12:02 -0300 Subject: [PATCH 13/14] Go back to security settings when pressing navigation icon in ChangePasswordActivity --- .../android/login/ChangePasswordActivity.java | 21 ++++++++++++------- .../login/ChangePasswordController.java | 14 ------------- 2 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java index 7aadbef8b..88fe2dbac 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -24,7 +25,6 @@ import javax.inject.Inject; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelProviders; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; @@ -56,14 +56,18 @@ public class ChangePasswordActivity extends BriarActivity @VisibleForTesting ChangePasswordViewModel viewModel; + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + viewModel = new ViewModelProvider(this, viewModelFactory) + .get(ChangePasswordViewModel.class); + } + @Override public void onCreate(Bundle state) { super.onCreate(state); setContentView(R.layout.activity_change_password); - viewModel = ViewModelProviders.of(this, viewModelFactory) - .get(ChangePasswordViewModel.class); - currentPasswordEntryWrapper = findViewById(R.id.current_password_entry_wrapper); newPasswordEntryWrapper = findViewById(R.id.new_password_entry_wrapper); @@ -77,7 +81,6 @@ public class ChangePasswordActivity extends BriarActivity progress = findViewById(R.id.progress_wheel); TextWatcher tw = new TextWatcher() { - @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -102,8 +105,12 @@ public class ChangePasswordActivity extends BriarActivity } @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); } private void enableOrDisableContinueButton() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java b/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java deleted file mode 100644 index 54e8ab55d..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/ChangePasswordController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.briarproject.briar.android.login; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.android.controller.handler.ResultHandler; - -@NotNullByDefault -public interface ChangePasswordController { - - float estimatePasswordStrength(String password); - - void changePassword(String oldPassword, String newPassword, - ResultHandler resultHandler); - -} From 95ef061a34fb501cb32c0607038a71fdd84d905b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 26 Mar 2021 14:33:58 -0300 Subject: [PATCH 14/14] Pick up screen lock changes when returning to SecurityFragment --- .../android/settings/SecurityFragment.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java index 89ba2482e..36a10f228 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SecurityFragment.java @@ -75,19 +75,8 @@ public class SecurityFragment extends PreferenceFragmentCompat { screenLock.setVisible(false); screenLockTimeout.setVisible(false); } else { - LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); - if (getActivity() != null && hasScreenLock(getActivity())) { - viewModel.getScreenLockEnabled().observe(lifecycleOwner, on -> { - screenLock.setChecked(on); - enableAndPersist(screenLock); - }); - screenLock.setSummary(R.string.pref_lock_summary); - } else { - screenLock.setEnabled(false); - screenLock.setChecked(false); - screenLock.setSummary(R.string.pref_lock_disabled_summary); - } // timeout depends on screenLock and gets disabled automatically + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); viewModel.getScreenLockTimeout().observe(lifecycleOwner, value -> { screenLockTimeout.setValue(value); enableAndPersist(screenLockTimeout); @@ -99,6 +88,25 @@ public class SecurityFragment extends PreferenceFragmentCompat { public void onStart() { super.onStart(); requireActivity().setTitle(R.string.security_settings_title); + checkScreenLock(); + } + + private void checkScreenLock() { + if (SDK_INT < 21) return; + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + viewModel.getScreenLockEnabled().removeObservers(lifecycleOwner); + if (hasScreenLock(requireActivity())) { + viewModel.getScreenLockEnabled().observe(lifecycleOwner, on -> { + screenLock.setChecked(on); + enableAndPersist(screenLock); + }); + screenLock.setSummary(R.string.pref_lock_summary); + } else { + screenLock.setEnabled(false); + screenLock.setPersistent(false); + screenLock.setChecked(false); + screenLock.setSummary(R.string.pref_lock_disabled_summary); + } } }