mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch '1826-settings-view-model' into 'master'
Finish migrating SettingsFragment to ViewModel Closes #1942 and #1826 See merge request briar/briar!1350
This commit is contained in:
@@ -36,6 +36,10 @@ 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.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;
|
||||
@@ -193,4 +197,12 @@ public interface AndroidComponent
|
||||
void inject(EmojiTextInputView textInputView);
|
||||
|
||||
void inject(BriarModelLoader briarModelLoader);
|
||||
|
||||
void inject(SettingsFragment settingsFragment);
|
||||
|
||||
void inject(ConnectionsFragment connectionsFragment);
|
||||
|
||||
void inject(SecurityFragment securityFragment);
|
||||
|
||||
void inject(NotificationsFragment notificationsFragment);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<Boolean> resultHandler);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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.bramble.api.nullsafety.NotNullByDefault;
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.SwitchPreferenceCompat;
|
||||
|
||||
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 SwitchPreferenceCompat enableBluetooth;
|
||||
private SwitchPreferenceCompat enableWifi;
|
||||
private SwitchPreferenceCompat enableTor;
|
||||
private ListPreference torNetwork;
|
||||
private SwitchPreferenceCompat torMobile;
|
||||
private SwitchPreferenceCompat 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.connectionsManager;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
class ConnectionsManager {
|
||||
|
||||
final ConnectionsStore btStore;
|
||||
final ConnectionsStore wifiStore;
|
||||
final ConnectionsStore torStore;
|
||||
|
||||
private final MutableLiveData<Boolean> btEnabled = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> wifiEnabled =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> torEnabled = new MutableLiveData<>();
|
||||
private final MutableLiveData<String> torNetwork = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> torMobile = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> 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<Boolean> btEnabled() {
|
||||
return btEnabled;
|
||||
}
|
||||
|
||||
LiveData<Boolean> wifiEnabled() {
|
||||
return wifiEnabled;
|
||||
}
|
||||
|
||||
LiveData<Boolean> torEnabled() {
|
||||
return torEnabled;
|
||||
}
|
||||
|
||||
LiveData<String> torNetwork() {
|
||||
return torNetwork;
|
||||
}
|
||||
|
||||
LiveData<Boolean> torMobile() {
|
||||
return torMobile;
|
||||
}
|
||||
|
||||
LiveData<Boolean> torCharging() {
|
||||
return torCharging;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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 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;
|
||||
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 putString(String key, @Nullable String value) {
|
||||
// translate between Android UI pref keys and bramble keys
|
||||
if (key.equals(PREF_KEY_TOR_NETWORK)) {
|
||||
super.putString(PREF_TOR_NETWORK, value);
|
||||
} else {
|
||||
throw new AssertionError(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
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.fragment.app.FragmentActivity;
|
||||
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.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;
|
||||
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 {
|
||||
|
||||
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 = requireNonNull(findPreference(PREF_LANGUAGE));
|
||||
setLanguageEntries(language);
|
||||
language.setOnPreferenceChangeListener(this::onLanguageChanged);
|
||||
|
||||
ListPreference theme = requireNonNull(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<CharSequence> entries = new ArrayList<>(tags.length);
|
||||
List<CharSequence> 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<CharSequence> 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<CharSequence> 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
|
||||
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);
|
||||
intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
// bring this activity back to the foreground
|
||||
intent = new Intent(getActivity(), activity.getClass());
|
||||
intent.putExtra(EXTRA_THEME_CHANGE, true);
|
||||
startActivity(intent);
|
||||
activity.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
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<Boolean> notifyPrivateMessages =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> notifyGroupMessages =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> notifyForumPosts =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> notifyBlogPosts =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> notifyVibration =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> notifySound =
|
||||
new MutableLiveData<>();
|
||||
|
||||
private volatile String ringtoneName, ringtoneUri;
|
||||
|
||||
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<Boolean> getNotifyPrivateMessages() {
|
||||
return notifyPrivateMessages;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getNotifyGroupMessages() {
|
||||
return notifyGroupMessages;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getNotifyForumPosts() {
|
||||
return notifyForumPosts;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getNotifyBlogPosts() {
|
||||
return notifyBlogPosts;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getNotifyVibration() {
|
||||
return notifyVibration;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
LiveData<Boolean> getNotifySound() {
|
||||
return notifySound;
|
||||
}
|
||||
|
||||
String getRingtoneName() {
|
||||
return ringtoneName;
|
||||
}
|
||||
|
||||
String getRingtoneUri() {
|
||||
return ringtoneUri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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.SwitchPreferenceCompat;
|
||||
|
||||
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 SwitchPreferenceCompat 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 {
|
||||
// timeout depends on screenLock and gets disabled automatically
|
||||
LifecycleOwner lifecycleOwner = getViewLifecycleOwner();
|
||||
viewModel.getScreenLockTimeout().observe(lifecycleOwner, value -> {
|
||||
screenLockTimeout.setValue(value);
|
||||
enableAndPersist(screenLockTimeout);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +1,37 @@
|
||||
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.lifecycle.ViewModelProvider;
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentFactory;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
|
||||
|
||||
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 {
|
||||
|
||||
public class SettingsActivity extends BriarActivity {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private SettingsViewModel settingsViewModel;
|
||||
|
||||
@Inject
|
||||
FeatureFlags featureFlags;
|
||||
static final String EXTRA_THEME_CHANGE = "themeChange";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
@@ -44,43 +40,15 @@ public class SettingsActivity extends BriarActivity {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
setContentView(R.layout.activity_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,30 +60,40 @@ public class SettingsActivity extends BriarActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void selectAvatarImage() {
|
||||
Intent intent = UiUtils.createSelectImageIntent(false);
|
||||
startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
|
||||
@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
|
||||
showNextFragment(fragmentManager, fragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
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, f)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
|
||||
onAvatarImageReceived(data);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,761 +1,120 @@
|
||||
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;
|
||||
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.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;
|
||||
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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.TextUtilsCompat;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
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.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;
|
||||
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 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;
|
||||
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.BriarApplication.ENTRY_ACTIVITY;
|
||||
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_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.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent;
|
||||
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 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";
|
||||
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 ListPreference language;
|
||||
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;
|
||||
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, btSettings, wifiSettings, torSettings;
|
||||
private volatile boolean settingsLoaded = false;
|
||||
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
|
||||
volatile SettingsManager settingsManager;
|
||||
@Inject
|
||||
volatile EventBus eventBus;
|
||||
@Inject
|
||||
LocationUtils locationUtils;
|
||||
@Inject
|
||||
CircumventionProvider circumventionProvider;
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private SettingsViewModel viewModel;
|
||||
private AvatarPreference prefAvatar;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
listener = (SettingsActivity) context;
|
||||
listener.getActivityComponent().inject(this);
|
||||
getAndroidComponent(context).inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(SettingsViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
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 =
|
||||
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");
|
||||
|
||||
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);
|
||||
torNetwork.setOnPreferenceChangeListener(this);
|
||||
torMobile.setOnPreferenceChangeListener(this);
|
||||
torOnlyWhenCharging.setOnPreferenceChangeListener(this);
|
||||
screenLock.setOnPreferenceChangeListener(this);
|
||||
screenLockTimeout.setOnPreferenceChangeListener(this);
|
||||
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;
|
||||
});
|
||||
|
||||
if (SDK_INT < 27) {
|
||||
// remove System Default Theme option from preference entries
|
||||
// as it is not functional on this API anyway
|
||||
List<CharSequence> 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<CharSequence> 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"));
|
||||
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 View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
public void onViewCreated(@NonNull View view,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
ColorDrawable divider = new ColorDrawable(
|
||||
ContextCompat.getColor(requireContext(), R.color.divider));
|
||||
setDivider(divider);
|
||||
return view;
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
viewModel.getOwnIdentityInfo().observe(getViewLifecycleOwner(), us ->
|
||||
prefAvatar.setOwnIdentityInfo(us)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
eventBus.addListener(this);
|
||||
setSettingsEnabled(false);
|
||||
loadSettings();
|
||||
requireActivity().setTitle(R.string.settings_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
private void setLanguageEntries() {
|
||||
CharSequence[] tags = language.getEntryValues();
|
||||
List<CharSequence> entries = new ArrayList<>(tags.length);
|
||||
List<CharSequence> 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
|
||||
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();
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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_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)
|
||||
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);
|
||||
notifyForumPosts.setEnabled(enabled);
|
||||
notifyBlogPosts.setEnabled(enabled);
|
||||
notifyVibration.setEnabled(enabled);
|
||||
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) {
|
||||
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 == language) {
|
||||
if (!language.getValue().equals(newValue))
|
||||
languageChanged((String) newValue);
|
||||
return false;
|
||||
} else 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) {
|
||||
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));
|
||||
storeSettings(s);
|
||||
setScreenLockTimeoutSummary(value);
|
||||
} else 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 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);
|
||||
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);
|
||||
}
|
||||
|
||||
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, 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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
|
||||
if (data == null) return;
|
||||
Uri uri = data.getData();
|
||||
if (uri == null) return;
|
||||
|
||||
@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();
|
||||
} 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();
|
||||
}
|
||||
DialogFragment dialog =
|
||||
ConfirmAvatarDialogFragment.newInstance(uri);
|
||||
dialog.show(getParentFragmentManager(),
|
||||
ConfirmAvatarDialogFragment.TAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
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.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;
|
||||
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.
|
||||
*/
|
||||
@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) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Store bool setting: " + key + "=" + value);
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(key, value);
|
||||
storeSettings(s);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putString(String key, @Nullable String value) {
|
||||
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) {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
settingsManager.mergeSettings(s, namespace);
|
||||
logDuration(LOG, "Merging " + namespace + " settings", start);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,17 +3,34 @@ 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;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
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.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.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.api.avatar.AvatarManager;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
@@ -25,66 +42,127 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.annotation.AnyThread;
|
||||
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;
|
||||
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;
|
||||
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
|
||||
class SettingsViewModel extends AndroidViewModel {
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
|
||||
private final static Logger LOG =
|
||||
getLogger(SettingsViewModel.class.getName());
|
||||
|
||||
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 FeatureFlags featureFlags;
|
||||
|
||||
final SettingsStore settingsStore;
|
||||
final TorSummaryProvider torSummaryProvider;
|
||||
final ConnectionsManager connectionsManager;
|
||||
final NotificationsManager notificationsManager;
|
||||
|
||||
private volatile Settings settings;
|
||||
|
||||
private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo =
|
||||
new MutableLiveData<>();
|
||||
|
||||
private final MutableLiveEvent<Boolean> setAvatarFailed =
|
||||
new MutableLiveEvent<>();
|
||||
private final MutableLiveData<Boolean> screenLockEnabled =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<String> screenLockTimeout =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@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,
|
||||
LocationUtils locationUtils,
|
||||
CircumventionProvider circumventionProvider,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
@DatabaseExecutor Executor dbExecutor) {
|
||||
super(application);
|
||||
FeatureFlags featureFlags) {
|
||||
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.featureFlags = featureFlags;
|
||||
settingsStore = new SettingsStore(settingsManager, dbExecutor,
|
||||
SETTINGS_NAMESPACE);
|
||||
torSummaryProvider = new TorSummaryProvider(getApplication(),
|
||||
locationUtils, circumventionProvider);
|
||||
connectionsManager =
|
||||
new ConnectionsManager(settingsManager, dbExecutor);
|
||||
notificationsManager = new NotificationsManager(getApplication(),
|
||||
settingsManager, dbExecutor);
|
||||
|
||||
loadOwnIdentityInfo();
|
||||
eventBus.addListener(this);
|
||||
loadSettings();
|
||||
if (shouldEnableProfilePictures()) loadOwnIdentityInfo();
|
||||
}
|
||||
|
||||
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
|
||||
return ownIdentityInfo;
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
LiveEvent<Boolean> getSetAvatarFailed() {
|
||||
return setAvatarFailed;
|
||||
private void loadSettings() {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
long start = now();
|
||||
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
|
||||
updateSettings(settings);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
boolean shouldEnableProfilePictures() {
|
||||
return featureFlags.shouldEnableProfilePictures();
|
||||
}
|
||||
|
||||
private void loadOwnIdentityInfo() {
|
||||
dbExecutor.execute(() -> {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
LocalAuthor localAuthor = identityManager.getLocalAuthor();
|
||||
AuthorInfo authorInfo = authorManager.getMyAuthorInfo();
|
||||
@@ -96,13 +174,47 @@ 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(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)) {
|
||||
LOG.info("Wifi settings updated");
|
||||
connectionsManager.updateWifiSettings(s.getSettings());
|
||||
} else if (namespace.equals(TOR_NAMESPACE)) {
|
||||
LOG.info("Tor settings updated");
|
||||
connectionsManager.updateTorSettings(s.getSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
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)
|
||||
));
|
||||
notificationsManager.updateSettings(settings);
|
||||
}
|
||||
|
||||
void setAvatar(Uri uri) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
trySetAvatar(uri);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
setAvatarFailed.postEvent(true);
|
||||
onSetAvatarFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -120,15 +232,34 @@ 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();
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
setAvatarFailed.postEvent(true);
|
||||
onSetAvatarFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private void onSetAvatarFailed() {
|
||||
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
|
||||
R.string.change_profile_picture_failed_message, LENGTH_LONG)
|
||||
.show());
|
||||
}
|
||||
|
||||
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
|
||||
return ownIdentityInfo;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getScreenLockEnabled() {
|
||||
return screenLockEnabled;
|
||||
}
|
||||
|
||||
LiveData<String> getScreenLockTimeout() {
|
||||
return screenLockTimeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ListPreference> {
|
||||
|
||||
private final Context ctx;
|
||||
private final LocationUtils locationUtils;
|
||||
private final CircumventionProvider circumventionProvider;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
tools:ignore="NewApi">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,14H9c0,-4.97 4.03,-9 9,-9v2C14.13,7 11,10.13 11,14zM18,11V9c-2.76,0 -5,2.24 -5,5h2C15,12.34 16.34,11 18,11zM7,4c0,-1.11 -0.89,-2 -2,-2S3,2.89 3,4s0.89,2 2,2S7,5.11 7,4zM11.45,4.5h-2C9.21,5.92 7.99,7 6.5,7h-3C2.67,7 2,7.67 2,8.5V11h6V8.74C9.86,8.15 11.25,6.51 11.45,4.5zM19,17c1.11,0 2,-0.89 2,-2s-0.89,-2 -2,-2s-2,0.89 -2,2S17.89,17 19,17zM20.5,18h-3c-1.49,0 -2.71,-1.08 -2.95,-2.5h-2c0.2,2.01 1.59,3.65 3.45,4.24V22h6v-2.5C22,18.67 21.33,18 20.5,18z" />
|
||||
</vector>
|
||||
12
briar-android/src/main/res/drawable/ic_feedback.xml
Normal file
12
briar-android/src/main/res/drawable/ic_feedback.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
tools:ignore="NewApi">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z" />
|
||||
</vector>
|
||||
12
briar-android/src/main/res/drawable/ic_notifications.xml
Normal file
12
briar-android/src/main/res/drawable/ic_notifications.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
tools:ignore="NewApi">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
tools:ignore="NewApi">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02zM8,16h2.5l1.5,1.5 1.5,-1.5L16,16v-2.5l1.5,-1.5 -1.5,-1.5L16,8h-2.5L12,6.5 10.5,8L8,8v2.5L6.5,12 8,13.5L8,16zM12,9c1.66,0 3,1.34 3,3s-1.34,3 -3,3L12,9z" />
|
||||
</vector>
|
||||
12
briar-android/src/main/res/drawable/ic_settings_security.xml
Normal file
12
briar-android/src/main/res/drawable/ic_settings_security.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
tools:ignore="NewApi">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
|
||||
</vector>
|
||||
@@ -1,78 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:name="org.briarproject.briar.android.settings.SettingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/avatarGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarImage"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@mipmap/ic_launcher_round" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toTopOf="@+id/avatarExplanation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatarImage"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="username" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/avatarExplanation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:text="@string/change_profile_picture"
|
||||
android:textColor="@color/briar_text_secondary_inverse"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatarImage"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/fragment"
|
||||
android:name="org.briarproject.briar.android.settings.SettingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
68
briar-android/src/main/res/layout/preference_avatar.xml
Normal file
68
briar-android/src/main/res/layout/preference_avatar.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_width="800dp"
|
||||
tools:layout_height="75dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/avatarGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_scrollFlags="scroll">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarImage"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@mipmap/ic_launcher_round" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toTopOf="@+id/avatarExplanation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatarImage"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="username" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/avatarExplanation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:text="@string/change_profile_picture"
|
||||
android:textColor="@color/briar_text_secondary_inverse"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/avatarImage"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Needed for SwitchPreference on Android 4 (API < 21)-->
|
||||
<androidx.appcompat.widget.SwitchCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/switch_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
tools:targetApi="n" />
|
||||
@@ -468,7 +468,7 @@
|
||||
<string name="pref_theme_light">Light</string>
|
||||
<string name="pref_theme_dark">Dark</string>
|
||||
<string name="pref_theme_auto">Automatic (Daytime)</string>
|
||||
<string name="pref_theme_system">System Default</string>
|
||||
<string name="pref_theme_system">System default</string>
|
||||
|
||||
<!-- Settings Connections -->
|
||||
<string name="network_settings_title">Connections</string>
|
||||
@@ -552,7 +552,6 @@
|
||||
<string name="cannot_load_ringtone">Cannot load ringtone</string>
|
||||
|
||||
<!-- Settings Feedback -->
|
||||
<string name="feedback_settings_title">Feedback</string>
|
||||
<string name="send_feedback">Send feedback</string>
|
||||
|
||||
<!-- Link Warning -->
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_lock"
|
||||
android:summary="@string/panic_setting_signout_summary"
|
||||
android:title="@string/panic_setting_signout_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
@@ -18,15 +18,16 @@
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:key="pref_key_panic_app"
|
||||
android:summary="@string/panic_app_setting_summary"
|
||||
android:title="@string/panic_app_setting_title"/>
|
||||
android:title="@string/panic_app_setting_title" />
|
||||
|
||||
<SwitchPreference
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_purge"
|
||||
android:summary="@string/purge_setting_summary"
|
||||
android:title="@string/purge_setting_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
@@ -1,236 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/display_settings_title">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="default"
|
||||
android:entryValues="@array/pref_language_values"
|
||||
android:key="pref_key_language"
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_language_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<org.briarproject.briar.android.settings.AvatarPreference android:key="pref_key_avatar" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/pref_theme_light_value"
|
||||
android:entries="@array/pref_theme_entries"
|
||||
android:entryValues="@array/pref_theme_values"
|
||||
android:key="pref_key_theme"
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_theme_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<Preference
|
||||
android:title="@string/display_settings_title"
|
||||
app:fragment="org.briarproject.briar.android.settings.DisplayFragment"
|
||||
app:icon="@drawable/ic_settings_brightness" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<Preference
|
||||
android:title="@string/network_settings_title"
|
||||
app:fragment="org.briarproject.briar.android.settings.ConnectionsFragment"
|
||||
app:icon="@drawable/ic_connect_without_contact" />
|
||||
|
||||
<Preference
|
||||
android:title="@string/security_settings_title"
|
||||
app:fragment="org.briarproject.briar.android.settings.SecurityFragment"
|
||||
app:icon="@drawable/ic_settings_security" />
|
||||
|
||||
<Preference
|
||||
android:title="@string/notification_settings_title"
|
||||
app:fragment="org.briarproject.briar.android.settings.NotificationsFragment"
|
||||
app:icon="@drawable/ic_notifications" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_send_feedback"
|
||||
android:title="@string/send_feedback"
|
||||
app:icon="@drawable/ic_feedback" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_dev"
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/network_settings_title">
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_key_bluetooth"
|
||||
android:persistent="false"
|
||||
android:title="@string/bluetooth_setting"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_key_wifi"
|
||||
android:persistent="false"
|
||||
android:title="@string/wifi_setting"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_tor_enable"
|
||||
android:persistent="false"
|
||||
android:title="@string/tor_enable_title"
|
||||
android:summary="@string/tor_enable_summary"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:entries="@array/tor_network_setting_names"
|
||||
android:entryValues="@array/tor_network_setting_values"
|
||||
android:key="pref_key_tor_network"
|
||||
android:persistent="false"
|
||||
android:summary="%s"
|
||||
android:title="@string/tor_network_setting"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:key="pref_key_tor_mobile_data"
|
||||
android:persistent="false"
|
||||
android:title="@string/tor_mobile_data_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:key="pref_key_tor_only_when_charging"
|
||||
android:persistent="false"
|
||||
android:title="@string/tor_only_when_charging_title"
|
||||
android:summary="@string/tor_only_when_charging_summary"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/security_settings_title">
|
||||
|
||||
<SwitchPreference
|
||||
android:enabled="false"
|
||||
android:key="pref_key_lock"
|
||||
android:persistent="false"
|
||||
android:summary="@string/pref_lock_summary"
|
||||
android:title="@string/pref_lock_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/pref_lock_timeout_value_default"
|
||||
android:dependency="pref_key_lock"
|
||||
android:entries="@array/pref_key_lock_timeout_entries"
|
||||
android:entryValues="@array/pref_key_lock_timeout_values"
|
||||
android:key="pref_key_lock_timeout"
|
||||
android:persistent="false"
|
||||
android:summary="@string/pref_lock_timeout_summary"
|
||||
android:title="@string/pref_lock_timeout_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_change_password"
|
||||
android:title="@string/change_password"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.login.ChangePasswordActivity"
|
||||
android:targetPackage="@string/app_package"/>
|
||||
</Preference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/panic_setting_title">
|
||||
|
||||
<Preference
|
||||
android:summary="@string/panic_setting_hint"
|
||||
android:title="@string/panic_setting"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
||||
android:targetPackage="@string/app_package"/>
|
||||
|
||||
</Preference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/notification_settings_title">
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_sign_in"
|
||||
android:summary="@string/notify_sign_in_summary"
|
||||
android:title="@string/notify_sign_in_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_private_messages"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_private_messages_setting_summary"
|
||||
android:title="@string/notify_private_messages_setting_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_group_messages"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_group_messages_setting_summary"
|
||||
android:title="@string/notify_group_messages_setting_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_forum_posts"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_forum_posts_setting_summary"
|
||||
android:title="@string/notify_forum_posts_setting_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_blog_posts"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_blog_posts_setting_summary"
|
||||
android:title="@string/notify_blog_posts_setting_title"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="pref_key_notify_vibration"
|
||||
android:persistent="false"
|
||||
android:title="@string/notify_vibration_setting"
|
||||
android:widgetLayout="@layout/preference_switch_compat"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_notify_sound"
|
||||
android:title="@string/notify_sound_setting"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/feedback_settings_title">
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_send_feedback"
|
||||
android:title="@string/send_feedback"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="Testing">
|
||||
android:title="Developer Options"
|
||||
app:allowDividerAbove="true">
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_test_data"
|
||||
android:title="Create Test Data"
|
||||
app:iconSpaceReserved="false">
|
||||
android:title="Create test data">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.test.TestDataActivity"
|
||||
android:targetPackage="@string/app_package"/>
|
||||
android:targetPackage="@string/app_package" />
|
||||
</Preference>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_explode"
|
||||
android:title="Crash"
|
||||
app:iconSpaceReserved="false"/>
|
||||
android:title="Crash" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
66
briar-android/src/main/res/xml/settings_connections.xml
Normal file
66
briar-android/src/main/res/xml/settings_connections.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_bluetooth"
|
||||
android:persistent="false"
|
||||
android:title="@string/bluetooth_setting"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_wifi"
|
||||
android:persistent="false"
|
||||
android:title="@string/wifi_setting"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_tor_enable"
|
||||
android:persistent="false"
|
||||
android:summary="@string/tor_enable_summary"
|
||||
android:title="@string/tor_enable_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:enabled="false"
|
||||
android:entries="@array/tor_network_setting_names"
|
||||
android:entryValues="@array/tor_network_setting_values"
|
||||
android:key="pref_key_tor_network"
|
||||
android:persistent="false"
|
||||
android:summary="%s"
|
||||
android:title="@string/tor_network_setting"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_tor_mobile_data"
|
||||
android:persistent="false"
|
||||
android:title="@string/tor_mobile_data_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:dependency="pref_key_tor_enable"
|
||||
android:enabled="false"
|
||||
android:key="pref_key_tor_only_when_charging"
|
||||
android:persistent="false"
|
||||
android:summary="@string/tor_only_when_charging_summary"
|
||||
android:title="@string/tor_only_when_charging_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
</PreferenceScreen>
|
||||
24
briar-android/src/main/res/xml/settings_display.xml
Normal file
24
briar-android/src/main/res/xml/settings_display.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="default"
|
||||
android:entryValues="@array/pref_language_values"
|
||||
android:key="pref_key_language"
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_language_title"
|
||||
app:iconSpaceReserved="false"
|
||||
tools:summary="System default" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/pref_theme_light_value"
|
||||
android:entries="@array/pref_theme_entries"
|
||||
android:entryValues="@array/pref_theme_values"
|
||||
android:key="pref_key_theme"
|
||||
android:summary="%s"
|
||||
android:title="@string/pref_theme_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
</PreferenceScreen>
|
||||
67
briar-android/src/main/res/xml/settings_notifications.xml
Normal file
67
briar-android/src/main/res/xml/settings_notifications.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
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:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="notifyPrivateMessages"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_private_messages_setting_summary"
|
||||
android:title="@string/notify_private_messages_setting_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="notifyGroupMessages"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_group_messages_setting_summary"
|
||||
android:title="@string/notify_group_messages_setting_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="notifyForumPosts"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_forum_posts_setting_summary"
|
||||
android:title="@string/notify_forum_posts_setting_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="notifyBlogPosts"
|
||||
android:persistent="false"
|
||||
android:summary="@string/notify_blog_posts_setting_summary"
|
||||
android:title="@string/notify_blog_posts_setting_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:key="notifyVibration"
|
||||
android:persistent="false"
|
||||
android:title="@string/notify_vibration_setting"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<Preference
|
||||
android:key="notifySound"
|
||||
android:title="@string/notify_sound_setting"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
</PreferenceScreen>
|
||||
54
briar-android/src/main/res/xml/settings_security.xml
Normal file
54
briar-android/src/main/res/xml/settings_security.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:enabled="false"
|
||||
android:key="pref_key_lock"
|
||||
android:persistent="false"
|
||||
android:summary="@string/pref_lock_summary"
|
||||
android:title="@string/pref_lock_title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/pref_lock_timeout_value_default"
|
||||
android:dependency="pref_key_lock"
|
||||
android:enabled="false"
|
||||
android:entries="@array/pref_key_lock_timeout_entries"
|
||||
android:entryValues="@array/pref_key_lock_timeout_values"
|
||||
android:key="pref_key_lock_timeout"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_lock_timeout_title"
|
||||
app:iconSpaceReserved="false"
|
||||
tools:summary="@string/pref_lock_timeout_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_change_password"
|
||||
android:title="@string/change_password"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.login.ChangePasswordActivity"
|
||||
android:targetPackage="@string/app_package" />
|
||||
</Preference>
|
||||
|
||||
<PreferenceCategory
|
||||
android:layout="@layout/preferences_category"
|
||||
android:title="@string/panic_setting_title">
|
||||
|
||||
<Preference
|
||||
android:summary="@string/panic_setting_hint"
|
||||
android:title="@string/panic_setting"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<intent
|
||||
android:targetClass="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
||||
android:targetPackage="@string/app_package" />
|
||||
|
||||
</Preference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user