From 484817db08c7bff08f0baf2b9bde395a7c9ff6a8 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 22 Jan 2021 15:39:46 -0300 Subject: [PATCH] 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 @@ + + + + + + + + + + + + + + + + + +