Compare commits

...

27 Commits

Author SHA1 Message Date
Sebastian Kürten
c85102fe52 Use BriarActivity#signOut() to exit app after updating language 2021-04-06 07:06:20 +02:00
akwizgran
bebf3bbc39 Merge branch '1826-settings-view-model' into 'master'
Finish migrating SettingsFragment to ViewModel

Closes #1942 and #1826

See merge request briar/briar!1350
2021-04-01 13:20:12 +00:00
akwizgran
62cca1335f Bump version numbers for 1.2.20 release. 2021-03-29 13:33:12 +01:00
akwizgran
11a18859fb Update translations. 2021-03-29 13:33:12 +01:00
akwizgran
1116a7e125 Merge branch 'update-bridges-again' into 'master'
Remove a failing bridge

See merge request briar/briar!1422
2021-03-29 12:32:11 +00:00
akwizgran
415b315292 Add a Tor Browser default bridge. 2021-03-29 13:01:27 +01:00
akwizgran
9818ec2b66 Remove a failing bridge. 2021-03-29 12:55:04 +01:00
Torsten Grote
95ef061a34 Pick up screen lock changes when returning to SecurityFragment 2021-03-26 14:33:58 -03:00
Torsten Grote
aaaf8aa66f Go back to security settings when pressing navigation icon in ChangePasswordActivity 2021-03-26 14:12:02 -03:00
Torsten Grote
29965e38d0 Don't show Toast off the UiThread 2021-03-26 14:10:37 -03:00
Torsten Grote
371d49a213 Use SwitchPreferenceCompat for panic preferences
Addresses #1991
2021-03-26 14:10:36 -03:00
Torsten Grote
6ed95e145e Re-open DisplayFragment after changing theme 2021-03-26 13:48:20 -03:00
Torsten Grote
8c025c1173 review: fix nullability and visibility of settings 2021-03-26 13:48:19 -03:00
Torsten Grote
9ce541cc31 Allow settings titles on more than a single line 2021-03-26 13:48:19 -03:00
Torsten Grote
aa57a4c123 lint ignore icon tinting since it seems to work on Android 4 with VectorDrawableCompat 2021-03-26 13:48:18 -03:00
Torsten Grote
58d9deb3b8 Move avatar layout into own preference
which is only shown on main settings fragment
2021-03-26 13:48:18 -03:00
Torsten Grote
f0685c4a43 Get rid of custom switch preference 2021-03-26 13:48:18 -03:00
Torsten Grote
484817db08 Move notifications settings into own screen 2021-03-26 13:48:17 -03:00
Torsten Grote
670bf15d31 Move security settings into own screen 2021-03-26 13:48:17 -03:00
Torsten Grote
6df1e0fd77 Move connections settings into own screen 2021-03-26 13:48:17 -03:00
Torsten Grote
ec910cb80f Move Display category into its own settings screen 2021-03-26 13:48:16 -03:00
akwizgran
372516646d Merge branch '1970-blog-bugs' into 'master'
Fix issues with blogs after refactoring

See merge request briar/briar!1421
2021-03-26 14:32:11 +00:00
Torsten Grote
72e721b0d3 Don't show snackbar about local blog post again after screen rotation 2021-03-26 10:56:57 -03:00
Torsten Grote
6599093611 Improve blog author clickability
resolves issue where clicking reblogged author opened reblogging author's blog
2021-03-26 10:40:51 -03:00
Torsten Grote
dceeecf1fe Open blog posts from blog feed in BlogActivity 2021-03-26 10:23:31 -03:00
Torsten Grote
ace0b9a3d8 Merge branch 'update-bridges' into 'master'
Replace a failing bridge with a Tor Browser default bridge

See merge request briar/briar!1420
2021-03-26 11:59:03 +00:00
akwizgran
7c45c90de9 Replace a failing bridge with a Tor Browser default bridge. 2021-03-26 09:27:36 +00:00
60 changed files with 1964 additions and 1188 deletions

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10219 versionCode 10220
versionName "1.2.19" versionName "1.2.20"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -179,6 +179,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Stopping services"); LOG.info("Stopping services");
state = STOPPING; state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING)); eventBus.broadcast(new LifecycleEvent(STOPPING));
LOG.info("Sleeping a bit to simulate slowness");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOG.info("Done simulating slowness");
for (Service s : services) { for (Service s : services) {
long start = now(); long start = now();
s.stopService(); s.stopService();

View File

@@ -1,8 +1,8 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1 Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0 Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0 Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0 Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0

View File

@@ -26,8 +26,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10219 versionCode 10220
versionName "1.2.19" versionName "1.2.20"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -36,6 +36,11 @@ import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.settings.ConnectionsFragment;
import org.briarproject.briar.android.settings.DisplayFragment;
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.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -193,4 +198,14 @@ public interface AndroidComponent
void inject(EmojiTextInputView textInputView); void inject(EmojiTextInputView textInputView);
void inject(BriarModelLoader briarModelLoader); void inject(BriarModelLoader briarModelLoader);
void inject(SettingsFragment settingsFragment);
void inject(DisplayFragment displayFragment);
void inject(ConnectionsFragment connectionsFragment);
void inject(SecurityFragment securityFragment);
void inject(NotificationsFragment notificationsFragment);
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.StrictMode; import android.os.StrictMode;
@@ -115,6 +116,11 @@ public class AppModule {
this.application = application; this.application = application;
} }
public static AndroidComponent getAndroidComponent(Context ctx) {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
return app.getApplicationComponent();
}
@Provides @Provides
@Singleton @Singleton
Application providesApplication() { Application providesApplication() {

View File

@@ -12,7 +12,7 @@ import java.util.Locale;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static android.os.Build.VERSION.SDK_INT; 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 @NotNullByDefault
public class Localizer { public class Localizer {
@@ -25,7 +25,7 @@ public class Localizer {
private Localizer(SharedPreferences sharedPreferences) { private Localizer(SharedPreferences sharedPreferences) {
this(Locale.getDefault(), getLocaleFromTag( this(Locale.getDefault(), getLocaleFromTag(
sharedPreferences.getString(LANGUAGE, "default"))); sharedPreferences.getString(PREF_LANGUAGE, "default")));
} }
private Localizer(Locale systemLocale, @Nullable Locale userLocale) { private Localizer(Locale systemLocale, @Nullable Locale userLocale) {

View File

@@ -40,8 +40,8 @@ import static android.os.SystemClock.elapsedRealtime;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; 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.SecurityFragment.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_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;

View File

@@ -6,7 +6,6 @@ public interface RequestCodes {
int REQUEST_INTRODUCTION = 2; int REQUEST_INTRODUCTION = 2;
int REQUEST_GROUP_INVITE = 3; int REQUEST_GROUP_INVITE = 3;
int REQUEST_SHARE_FORUM = 4; int REQUEST_SHARE_FORUM = 4;
int REQUEST_WRITE_BLOG_POST = 5;
int REQUEST_SHARE_BLOG = 6; int REQUEST_SHARE_BLOG = 6;
int REQUEST_RINGTONE = 7; int REQUEST_RINGTONE = 7;
int REQUEST_PERMISSION_CAMERA_LOCATION = 8; int REQUEST_PERMISSION_CAMERA_LOCATION = 8;

View File

@@ -176,10 +176,22 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
return result == null ? null : result.getItems(); return result == null ? null : result.getItems();
} }
/**
* Call this after {@link ListUpdate#getPostAddedWasLocal()} was processed.
* This prevents it from getting processed again.
*/
@UiThread
void resetLocalUpdate() {
LiveResult<ListUpdate> value = blogPosts.getValue();
if (value == null) return;
ListUpdate result = value.getResultOrNull();
result.postAddedWasLocal = null;
}
static class ListUpdate { static class ListUpdate {
@Nullable @Nullable
private final Boolean postAddedWasLocal; private Boolean postAddedWasLocal;
private final List<BlogPostItem> items; private final List<BlogPostItem> items;
ListUpdate(@Nullable Boolean postAddedWasLocal, ListUpdate(@Nullable Boolean postAddedWasLocal,

View File

@@ -6,9 +6,11 @@ import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity; import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
@@ -19,6 +21,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -45,7 +48,10 @@ public class BlogActivity extends BriarActivity
Intent i = getIntent(); Intent i = getIntent();
GroupId groupId = GroupId groupId =
new GroupId(requireNonNull(i.getByteArrayExtra(GROUP_ID))); new GroupId(requireNonNull(i.getByteArrayExtra(GROUP_ID)));
viewModel.setGroupId(groupId); // Get post info from intent
@Nullable byte[] postId = i.getByteArrayExtra(POST_ID);
viewModel.setGroupId(groupId, postId == null);
setContentView(R.layout.activity_fragment_container_toolbar); setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = setUpCustomToolbar(false); Toolbar toolbar = setUpCustomToolbar(false);
@@ -66,7 +72,14 @@ public class BlogActivity extends BriarActivity
); );
if (state == null) { if (state == null) {
showInitialFragment(BlogFragment.newInstance(groupId)); if (postId == null) {
showInitialFragment(BlogFragment.newInstance(groupId));
} else {
MessageId messageId = new MessageId(postId);
BaseFragment f =
BlogPostFragment.newInstance(groupId, messageId);
showInitialFragment(f);
}
} }
} }

View File

@@ -38,7 +38,6 @@ import static android.widget.Toast.LENGTH_SHORT;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG; import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@UiThread @UiThread
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -134,7 +133,7 @@ public class BlogFragment extends BaseFragment
if (itemId == R.id.action_write_blog_post) { if (itemId == R.id.action_write_blog_post) {
Intent i = new Intent(getActivity(), WriteBlogPostActivity.class); Intent i = new Intent(getActivity(), WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_WRITE_BLOG_POST); startActivity(i);
return true; return true;
} else if (itemId == R.id.action_blog_share) { } else if (itemId == R.id.action_blog_share) {
Intent i = new Intent(getActivity(), ShareBlogActivity.class); Intent i = new Intent(getActivity(), ShareBlogActivity.class);
@@ -160,10 +159,7 @@ public class BlogFragment extends BaseFragment
public void onActivityResult(int request, int result, public void onActivityResult(int request, int result,
@Nullable Intent data) { @Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
displaySnackbar(R.string.blogs_blog_post_created, true);
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
displaySnackbar(R.string.blogs_sharing_snackbar, false); displaySnackbar(R.string.blogs_sharing_snackbar, false);
} }
} }
@@ -184,6 +180,7 @@ public class BlogFragment extends BaseFragment
displaySnackbar(R.string.blogs_blog_post_received, displaySnackbar(R.string.blogs_blog_post_received,
true); true);
} }
viewModel.resetLocalUpdate();
list.showData(); list.showData();
}); });
} }
@@ -191,7 +188,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onBlogPostClick(BlogPostItem post) { public void onBlogPostClick(BlogPostItem post) {
BlogPostFragment f = BlogPostFragment f =
BlogPostFragment.newInstance(groupId, post.getId(), false); BlogPostFragment.newInstance(groupId, post.getId());
showNextFragment(f); showNextFragment(f);
} }

View File

@@ -44,7 +44,6 @@ public class BlogPostFragment extends BaseFragment
private static final Logger LOG = getLogger(TAG); private static final Logger LOG = getLogger(TAG);
static final String POST_ID = "briar.POST_ID"; static final String POST_ID = "briar.POST_ID";
static final String IS_FEED = "briar.IS_FEED";
protected BlogViewModel viewModel; protected BlogViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper()); private final Handler handler = new Handler(Looper.getMainLooper());
@@ -57,13 +56,11 @@ public class BlogPostFragment extends BaseFragment
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
static BlogPostFragment newInstance(GroupId blogId, MessageId postId, static BlogPostFragment newInstance(GroupId blogId, MessageId postId) {
boolean isFeed) {
BlogPostFragment f = new BlogPostFragment(); BlogPostFragment f = new BlogPostFragment();
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, blogId.getBytes()); bundle.putByteArray(GROUP_ID, blogId.getBytes());
bundle.putByteArray(POST_ID, postId.getBytes()); bundle.putByteArray(POST_ID, postId.getBytes());
bundle.putBoolean(IS_FEED, isFeed);
f.setArguments(bundle); f.setArguments(bundle);
return f; return f;
} }
@@ -85,13 +82,12 @@ public class BlogPostFragment extends BaseFragment
new GroupId(requireNonNull(args.getByteArray(GROUP_ID))); new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId = MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID))); new MessageId(requireNonNull(args.getByteArray(POST_ID)));
boolean isFeed = args.getBoolean(IS_FEED);
View view = inflater.inflate(R.layout.fragment_blog_post, container, View view = inflater.inflate(R.layout.fragment_blog_post, container,
false); false);
progressBar = view.findViewById(R.id.progressBar); progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, this, isFeed); ui = new BlogPostViewHolder(view, true, this, false);
LifecycleOwner owner = getViewLifecycleOwner(); LifecycleOwner owner = getViewLifecycleOwner();
viewModel.loadBlogPost(groupId, postId).observe(owner, result -> viewModel.loadBlogPost(groupId, postId).observe(owner, result ->
result.onError(this::handleException) result.onError(this::handleException)

View File

@@ -16,8 +16,6 @@ import org.briarproject.briar.android.view.AuthorView;
import org.briarproject.briar.api.blog.BlogCommentHeader; import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogPostHeader; import org.briarproject.briar.api.blog.BlogPostHeader;
import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -81,15 +79,15 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
return "blogPost" + id.hashCode(); return "blogPost" + id.hashCode();
} }
void bindItem(@Nullable BlogPostItem item) { void bindItem(BlogPostItem item) {
if (item == null) return;
setTransitionName(item.getId()); setTransitionName(item.getId());
if (!fullText) { if (!fullText) {
layout.setClickable(true); layout.setClickable(true);
layout.setOnClickListener(v -> listener.onBlogPostClick(item)); layout.setOnClickListener(v -> listener.onBlogPostClick(item));
} }
boolean isReblog = item instanceof BlogCommentItem;
// author and date // author and date
BlogPostHeader post = item.getPostHeader(); BlogPostHeader post = item.getPostHeader();
author.setAuthor(post.getAuthor(), post.getAuthorInfo()); author.setAuthor(post.getAuthor(), post.getAuthorInfo());
@@ -97,7 +95,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setPersona( author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL); item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624 // TODO make author clickable more often #624
if (authorClickable) { if (authorClickable && !isReblog) {
author.setAuthorClickable(v -> listener.onAuthorClick(item)); author.setAuthorClickable(v -> listener.onAuthorClick(item));
} else { } else {
author.setAuthorNotClickable(); author.setAuthorNotClickable();
@@ -126,19 +124,21 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// comments // comments
commentContainer.removeAllViews(); commentContainer.removeAllViews();
if (item instanceof BlogCommentItem) { if (isReblog) {
onBindComment((BlogCommentItem) item); onBindComment((BlogCommentItem) item, authorClickable);
} else { } else {
reblogger.setVisibility(GONE); reblogger.setVisibility(GONE);
} }
} }
private void onBindComment(BlogCommentItem item) { private void onBindComment(BlogCommentItem item, boolean authorClickable) {
// reblogger // reblogger
reblogger.setAuthor(item.getAuthor(), item.getAuthorInfo()); reblogger.setAuthor(item.getAuthor(), item.getAuthorInfo());
reblogger.setDate(item.getTimestamp()); reblogger.setDate(item.getTimestamp());
if (!fullText) { if (authorClickable) {
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item)); reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
} else {
reblogger.setAuthorNotClickable();
} }
reblogger.setVisibility(VISIBLE); reblogger.setVisibility(VISIBLE);
reblogger.setPersona(REBLOGGER); reblogger.setPersona(REBLOGGER);

View File

@@ -111,11 +111,11 @@ class BlogViewModel extends BaseViewModel {
* Set this before calling any other methods. * Set this before calling any other methods.
*/ */
@UiThread @UiThread
public void setGroupId(GroupId groupId) { public void setGroupId(GroupId groupId, boolean loadAllPosts) {
if (this.groupId == groupId) return; // configuration change if (this.groupId == groupId) return; // configuration change
this.groupId = groupId; this.groupId = groupId;
loadBlog(groupId); loadBlog(groupId);
loadBlogPosts(groupId); if (loadAllPosts) loadBlogPosts(groupId);
loadSharingContacts(groupId); loadSharingContacts(groupId);
} }

View File

@@ -11,6 +11,7 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate; import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
@@ -29,6 +30,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG; import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -108,6 +110,7 @@ public class FeedFragment extends BaseFragment
} else if (wasLocal != null) { } else if (wasLocal != null) {
showSnackBar(R.string.blogs_blog_post_received); showSnackBar(R.string.blogs_blog_post_received);
} }
viewModel.resetLocalUpdate();
list.showData(); list.showData();
}); });
} }
@@ -145,16 +148,14 @@ public class FeedFragment extends BaseFragment
@Override @Override
public void onBlogPostClick(BlogPostItem post) { public void onBlogPostClick(BlogPostItem post) {
BaseFragment f = BlogPostFragment Intent i = getBlogActivityIntent(post.getGroupId());
.newInstance(post.getGroupId(), post.getId(), true); i.putExtra(POST_ID, post.getId().getBytes());
showNextFragment(f); requireContext().startActivity(i);
} }
@Override @Override
public void onAuthorClick(BlogPostItem post) { public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(requireContext(), BlogActivity.class); Intent i = getBlogActivityIntent(post.getGroupId());
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
requireContext().startActivity(i); requireContext().startActivity(i);
} }
@@ -169,6 +170,13 @@ public class FeedFragment extends BaseFragment
return TAG; return TAG;
} }
private Intent getBlogActivityIntent(GroupId groupId) {
Intent i = new Intent(requireContext(), BlogActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
return i;
}
private void showSnackBar(int stringRes) { private void showSnackBar(int stringRes) {
int firstVisible = int firstVisible =
layoutManager.findFirstCompletelyVisibleItemPosition(); layoutManager.findFirstCompletelyVisibleItemPosition();

View File

@@ -4,6 +4,7 @@ import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
@@ -24,7 +25,6 @@ import javax.inject.Inject;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
@@ -56,14 +56,18 @@ public class ChangePasswordActivity extends BriarActivity
@VisibleForTesting @VisibleForTesting
ChangePasswordViewModel viewModel; ChangePasswordViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ChangePasswordViewModel.class);
}
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_change_password); setContentView(R.layout.activity_change_password);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ChangePasswordViewModel.class);
currentPasswordEntryWrapper = currentPasswordEntryWrapper =
findViewById(R.id.current_password_entry_wrapper); findViewById(R.id.current_password_entry_wrapper);
newPasswordEntryWrapper = findViewById(R.id.new_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); progress = findViewById(R.id.progress_wheel);
TextWatcher tw = new TextWatcher() { TextWatcher tw = new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, public void beforeTextChanged(CharSequence s, int start, int count,
int after) { int after) {
@@ -102,8 +105,12 @@ public class ChangePasswordActivity extends BriarActivity
} }
@Override @Override
public void injectActivity(ActivityComponent component) { public boolean onOptionsItemSelected(MenuItem item) {
component.inject(this); if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
} }
private void enableOrDisableContinueButton() { private void enableOrDisableContinueButton() {

View File

@@ -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);
}

View File

@@ -14,7 +14,7 @@ import javax.inject.Inject;
import static android.content.Intent.ACTION_BOOT_COMPLETED; import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static android.content.Intent.ACTION_MY_PACKAGE_REPLACED; 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; import static org.briarproject.briar.api.android.AndroidNotificationManager.ACTION_DISMISS_REMINDER;
public class SignInReminderReceiver extends BroadcastReceiver { public class SignInReminderReceiver extends BroadcastReceiver {
@@ -37,7 +37,7 @@ public class SignInReminderReceiver extends BroadcastReceiver {
if (accountManager.accountExists() && if (accountManager.accountExists() &&
!accountManager.hasDatabaseKey()) { !accountManager.hasDatabaseKey()) {
SharedPreferences prefs = app.getDefaultSharedPreferences(); SharedPreferences prefs = app.getDefaultSharedPreferences();
if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) { if (prefs.getBoolean(PREF_NOTIFY_SIGN_IN, true)) {
notificationManager.showSignInNotification(); notificationManager.showSignInNotification();
} }
} }

View File

@@ -97,8 +97,6 @@ public class NavDrawerActivity extends BriarActivity implements
Uri.parse("briar-content://org.briarproject.briar/blog"); Uri.parse("briar-content://org.briarproject.briar/blog");
public static Uri CONTACT_ADDED_URI = public static Uri CONTACT_ADDED_URI =
Uri.parse("briar-content://org.briarproject.briar/contact/added"); Uri.parse("briar-content://org.briarproject.briar/contact/added");
public static Uri SIGN_OUT_URI =
Uri.parse("briar-content://org.briarproject.briar/sign-out");
private final List<Transport> transports = new ArrayList<>(3); private final List<Transport> transports = new ArrayList<>(3);
private final MutableLiveData<ImageView> torIcon = new MutableLiveData<>(); private final MutableLiveData<ImageView> torIcon = new MutableLiveData<>();
@@ -224,8 +222,6 @@ public class NavDrawerActivity extends BriarActivity implements
startFragment(ForumListFragment.newInstance(), R.id.nav_btn_forums); startFragment(ForumListFragment.newInstance(), R.id.nav_btn_forums);
} else if (BLOG_URI.equals(uri)) { } else if (BLOG_URI.equals(uri)) {
startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs); startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs);
} else if (SIGN_OUT_URI.equals(uri)) {
signOut(false, false);
} }
} }

View File

@@ -20,7 +20,7 @@ import javax.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreferenceCompat;
import info.guardianproject.panic.PanicResponder; import info.guardianproject.panic.PanicResponder;
import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_CANCELED;
@@ -40,7 +40,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
Logger.getLogger(PanicPreferencesFragment.class.getName()); Logger.getLogger(PanicPreferencesFragment.class.getName());
private PackageManager pm; private PackageManager pm;
private SwitchPreference lockPref, purgePref; private SwitchPreferenceCompat lockPref, purgePref;
private ListPreference panicAppPref; private ListPreference panicAppPref;
@Override @Override
@@ -51,9 +51,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
private void updatePreferences() { private void updatePreferences() {
pm = getActivity().getPackageManager(); pm = getActivity().getPackageManager();
lockPref = (SwitchPreference) findPreference(KEY_LOCK); lockPref = (SwitchPreferenceCompat) findPreference(KEY_LOCK);
panicAppPref = (ListPreference) findPreference(KEY_PANIC_APP); 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 // check for connect/disconnect intents from panic trigger apps
if (PanicResponder.checkForDisconnectIntent(getActivity())) { if (PanicResponder.checkForDisconnectIntent(getActivity())) {

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,177 @@
package org.briarproject.briar.android.settings;
import android.app.AlertDialog;
import android.content.Context;
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 javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.core.text.TextUtilsCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
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_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.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
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());
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel viewModel;
@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_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);
viewModel.languageChanged();
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
builder.show();
}
return false;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,41 +1,45 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; 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.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; 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 javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import de.hdodenhof.circleimageview.CircleImageView; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
import static android.widget.Toast.LENGTH_LONG; @MethodsNotNullByDefault
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; @ParametersNotNullByDefault
public class SettingsActivity extends BriarActivity
implements OnPreferenceStartFragmentCallback {
public class SettingsActivity extends BriarActivity { static final String EXTRA_THEME_CHANGE = "themeChange";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
@Inject private SettingsViewModel viewModel;
FeatureFlags featureFlags;
@Override @Override
public void onCreate(Bundle bundle) { public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
@@ -44,43 +48,24 @@ public class SettingsActivity extends BriarActivity {
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
} }
// show display fragment after theme change
Bundle extras = getIntent().getExtras();
if (bundle == null && extras != null &&
extras.getBoolean(EXTRA_THEME_CHANGE, false)) {
FragmentManager fragmentManager = getSupportFragmentManager();
showNextFragment(fragmentManager, new DisplayFragment());
}
setContentView(R.layout.activity_settings); setContentView(R.layout.activity_settings);
if (featureFlags.shouldEnableProfilePictures()) { ViewModelProvider provider =
ViewModelProvider provider = new ViewModelProvider(this, viewModelFactory);
new ViewModelProvider(this, viewModelFactory); viewModel = provider.get(SettingsViewModel.class);
settingsViewModel = provider.get(SettingsViewModel.class);
TextView textViewUserName = findViewById(R.id.username); viewModel.getLanguageChange().observeEvent(this, b -> {
CircleImageView imageViewAvatar = signOut(false, false);
findViewById(R.id.avatarImage); finishAffinity();
});
settingsViewModel.getOwnIdentityInfo().observe(this, us -> {
textViewUserName.setText(us.getLocalAuthor().getName());
AuthorView.setAvatar(imageViewAvatar,
us.getLocalAuthor().getId(), us.getAuthorInfo());
});
settingsViewModel.getSetAvatarFailed()
.observeEvent(this, failed -> {
if (failed) {
Toast.makeText(this,
R.string.change_profile_picture_failed_message,
LENGTH_LONG).show();
}
});
View avatarGroup = findViewById(R.id.avatarGroup);
avatarGroup.setOnClickListener(e -> selectAvatarImage());
} else {
View view = findViewById(R.id.avatarGroup);
view.setVisibility(View.GONE);
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
} }
@Override @Override
@@ -92,30 +77,40 @@ public class SettingsActivity extends BriarActivity {
return false; return false;
} }
private void selectAvatarImage() { @Override
Intent intent = UiUtils.createSelectImageIntent(false); public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller,
startActivityForResult(intent, REQUEST_AVATAR_IMAGE); 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 private void showNextFragment(FragmentManager fragmentManager, Fragment f) {
protected void onActivityResult(int request, int result, fragmentManager.beginTransaction()
@Nullable Intent data) { .setCustomAnimations(R.anim.step_next_in,
super.onActivityResult(request, result, data); 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);
}
} }

View File

@@ -1,761 +1,120 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View; 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; 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.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 javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.fragment.app.DialogFragment;
import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider;
import androidx.core.text.TextUtilsCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
import static android.app.Activity.RESULT_OK; 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.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
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.TestingConstants.IS_DEBUG_BUILD; 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.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.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 @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SettingsFragment extends PreferenceFragmentCompat public class SettingsFragment extends PreferenceFragmentCompat {
implements EventListener, OnPreferenceChangeListener {
public static final String SETTINGS_NAMESPACE = "android-ui"; 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 = private static final String PREF_KEY_AVATAR = "pref_key_avatar";
BluetoothConstants.ID.getString(); private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
private static final String BT_ENABLE = "pref_key_bluetooth"; private static final String PREF_KEY_DEV = "pref_key_dev";
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
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;
@Inject @Inject
volatile SettingsManager settingsManager; ViewModelProvider.Factory viewModelFactory;
@Inject
volatile EventBus eventBus; private SettingsViewModel viewModel;
@Inject private AvatarPreference prefAvatar;
LocationUtils locationUtils;
@Inject
CircumventionProvider circumventionProvider;
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
listener = (SettingsActivity) context; getAndroidComponent(context).inject(this);
listener.getActivityComponent().inject(this); viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(SettingsViewModel.class);
} }
@Override @Override
public void onCreatePreferences(Bundle bundle, String s) { public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.settings); addPreferencesFromResource(R.xml.settings);
language = findPreference(LANGUAGE); prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
setLanguageEntries(); if (viewModel.shouldEnableProfilePictures()) {
ListPreference theme = findPreference("pref_key_theme"); prefAvatar.setOnPreferenceClickListener(preference -> {
enableBluetooth = findPreference(BT_ENABLE); Intent intent = createSelectImageIntent(false);
enableWifi = findPreference(WIFI_ENABLE); startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
enableTor = findPreference(TOR_ENABLE); return true;
torNetwork = findPreference(TOR_NETWORK); });
torMobile = findPreference(TOR_MOBILE); } else {
torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING); prefAvatar.setVisible(false);
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);
Preference prefFeedback = Preference prefFeedback =
requireNonNull(findPreference("pref_key_send_feedback")); requireNonNull(findPreference(PREF_KEY_FEEDBACK));
prefFeedback.setOnPreferenceClickListener(preference -> { prefFeedback.setOnPreferenceClickListener(preference -> {
triggerFeedback(requireContext()); triggerFeedback(requireContext());
return true; return true;
}); });
if (SDK_INT < 27) { Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE));
// 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"));
if (IS_DEBUG_BUILD) { if (IS_DEBUG_BUILD) {
explode.setOnPreferenceClickListener(preference -> { explode.setOnPreferenceClickListener(preference -> {
throw new RuntimeException("Boom!"); throw new RuntimeException("Boom!");
}); });
} else { } else {
explode.setVisible(false); PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
findPreference("pref_key_test_data").setVisible(false); dev.setVisible(false);
PreferenceGroup testing = explode.getParent();
if (testing == null) throw new AssertionError();
testing.setVisible(false);
} }
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public void onViewCreated(@NonNull View view,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState); super.onViewCreated(view, savedInstanceState);
ColorDrawable divider = new ColorDrawable(
ContextCompat.getColor(requireContext(), R.color.divider)); viewModel.getOwnIdentityInfo().observe(getViewLifecycleOwner(), us ->
setDivider(divider); prefAvatar.setOwnIdentityInfo(us)
return view; );
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this); requireActivity().setTitle(R.string.settings_button);
setSettingsEnabled(false);
loadSettings();
} }
@Override @Override
public void onStop() { public void onActivityResult(int request, int result,
super.onStop(); @Nullable Intent data) {
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) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_RINGTONE && result == RESULT_OK) { if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
Settings s = new Settings(); if (data == null) return;
Uri uri = data.getParcelableExtra(EXTRA_RINGTONE_PICKED_URI); Uri uri = data.getData();
if (uri == null) { if (uri == null) return;
// The user chose silence
s.putBoolean(PREF_NOTIFY_SOUND, false);
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
s.put(PREF_NOTIFY_RINGTONE_URI, "");
} else if (RingtoneManager.isDefault(uri)) {
// The user chose the default
s.putBoolean(PREF_NOTIFY_SOUND, true);
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
s.put(PREF_NOTIFY_RINGTONE_URI, "");
} else {
// The user chose a ringtone other than the default
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
if (r == null || "file".equals(uri.getScheme())) {
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
LENGTH_SHORT).show();
} else {
String name = r.getTitle(getContext());
s.putBoolean(PREF_NOTIFY_SOUND, true);
s.put(PREF_NOTIFY_RINGTONE_NAME, name);
s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString());
}
}
storeSettings(s);
}
}
@Override DialogFragment dialog =
public void eventOccurred(Event e) { ConfirmAvatarDialogFragment.newInstance(uri);
if (e instanceof SettingsUpdatedEvent) { dialog.show(getParentFragmentManager(),
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; ConfirmAvatarDialogFragment.TAG);
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();
}
} }
} }

View File

@@ -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);
}
});
}
}

View File

@@ -3,15 +3,34 @@ package org.briarproject.briar.android.settings;
import android.app.Application; import android.app.Application;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.net.Uri; 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.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; 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.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.UnsupportedMimeTypeException;
import org.briarproject.briar.android.attachment.media.ImageCompressor; import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.avatar.AvatarManager; import org.briarproject.briar.api.avatar.AvatarManager;
@@ -25,66 +44,129 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel; import androidx.annotation.AnyThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; 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.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@NotNullByDefault @MethodsNotNullByDefault
class SettingsViewModel extends AndroidViewModel { @ParametersNotNullByDefault
class SettingsViewModel extends DbViewModel implements EventListener {
private final static Logger LOG = private final static Logger LOG =
getLogger(SettingsViewModel.class.getName()); 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 IdentityManager identityManager;
private final EventBus eventBus;
private final AvatarManager avatarManager; private final AvatarManager avatarManager;
private final AuthorManager authorManager; private final AuthorManager authorManager;
private final ImageCompressor imageCompressor; private final ImageCompressor imageCompressor;
@IoExecutor
private final Executor ioExecutor; private final Executor ioExecutor;
@DatabaseExecutor private final FeatureFlags featureFlags;
private final Executor dbExecutor;
final SettingsStore settingsStore;
final TorSummaryProvider torSummaryProvider;
final ConnectionsManager connectionsManager;
final NotificationsManager notificationsManager;
private volatile Settings settings;
private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo = private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> screenLockEnabled =
private final MutableLiveEvent<Boolean> setAvatarFailed = new MutableLiveData<>();
private final MutableLiveData<String> screenLockTimeout =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> languageChanged =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@Inject @Inject
SettingsViewModel(Application application, SettingsViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
SettingsManager settingsManager,
IdentityManager identityManager, IdentityManager identityManager,
EventBus eventBus,
AvatarManager avatarManager, AvatarManager avatarManager,
AuthorManager authorManager, AuthorManager authorManager,
ImageCompressor imageCompressor, ImageCompressor imageCompressor,
LocationUtils locationUtils,
CircumventionProvider circumventionProvider,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
@DatabaseExecutor Executor dbExecutor) { FeatureFlags featureFlags) {
super(application); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.settingsManager = settingsManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.eventBus = eventBus;
this.imageCompressor = imageCompressor; this.imageCompressor = imageCompressor;
this.avatarManager = avatarManager; this.avatarManager = avatarManager;
this.authorManager = authorManager; this.authorManager = authorManager;
this.ioExecutor = ioExecutor; 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() { @Override
return ownIdentityInfo; protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
} }
LiveEvent<Boolean> getSetAvatarFailed() { private void loadSettings() {
return setAvatarFailed; 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() { private void loadOwnIdentityInfo() {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
LocalAuthor localAuthor = identityManager.getLocalAuthor(); LocalAuthor localAuthor = identityManager.getLocalAuthor();
AuthorInfo authorInfo = authorManager.getMyAuthorInfo(); AuthorInfo authorInfo = authorManager.getMyAuthorInfo();
@@ -96,13 +178,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) { void setAvatar(Uri uri) {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
trySetAvatar(uri); trySetAvatar(uri);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
setAvatarFailed.postEvent(true); onSetAvatarFailed();
} }
}); });
} }
@@ -120,15 +236,42 @@ class SettingsViewModel extends AndroidViewModel {
"ContentResolver returned null when opening InputStream"); "ContentResolver returned null when opening InputStream");
InputStream compressed = imageCompressor.compressImage(is, contentType); InputStream compressed = imageCompressor.compressImage(is, contentType);
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
avatarManager.addAvatar(ImageCompressor.MIME_TYPE, compressed); avatarManager.addAvatar(ImageCompressor.MIME_TYPE, compressed);
loadOwnIdentityInfo(); loadOwnIdentityInfo();
} catch (IOException | DbException e) { } catch (IOException | DbException e) {
logException(LOG, WARNING, 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());
}
void languageChanged() {
languageChanged.setEvent(true);
}
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
return ownIdentityInfo;
}
LiveData<Boolean> getScreenLockEnabled() {
return screenLockEnabled;
}
LiveData<String> getScreenLockTimeout() {
return screenLockTimeout;
}
LiveEvent<Boolean> getLanguageChange() {
return languageChanged;
}
} }

View File

@@ -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);
}
}

View File

@@ -135,9 +135,10 @@ public class AuthorView extends ConstraintLayout {
} }
public void setAuthorNotClickable() { public void setAuthorNotClickable() {
setClickable(false);
setBackgroundResource(0);
setOnClickListener(null); setOnClickListener(null);
setClickable(false);
setFocusable(false);
setBackgroundResource(0);
} }
/** /**

View 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="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>

View 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>

View 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>

View 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="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>

View 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>

View File

@@ -1,78 +1,8 @@
<?xml version="1.0" encoding="utf-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: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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<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>

View File

@@ -16,27 +16,18 @@
android:id="@+id/rebloggerView" android:id="@+id/rebloggerView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/listitem_vertical_margin" android:padding="@dimen/listitem_vertical_margin"
android:layout_marginLeft="@dimen/listitem_vertical_margin"
android:layout_marginTop="@dimen/listitem_vertical_margin"
android:layout_marginEnd="@dimen/listitem_vertical_margin"
android:layout_marginRight="@dimen/listitem_vertical_margin"
android:layout_marginBottom="@dimen/listitem_horizontal_margin"
app:layout_constraintEnd_toStartOf="@+id/commentView" app:layout_constraintEnd_toStartOf="@+id/commentView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:persona="reblogger" /> app:persona="reblogger"
tools:visibility="visible" />
<org.briarproject.briar.android.view.AuthorView <org.briarproject.briar.android.view.AuthorView
android:id="@+id/authorView" android:id="@+id/authorView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/listitem_vertical_margin" android:padding="@dimen/listitem_vertical_margin"
android:layout_marginLeft="@dimen/listitem_vertical_margin"
android:layout_marginTop="@dimen/listitem_vertical_margin"
android:layout_marginEnd="@dimen/listitem_vertical_margin"
android:layout_marginRight="@dimen/listitem_vertical_margin"
android:layout_marginBottom="@dimen/listitem_horizontal_margin"
app:layout_constraintEnd_toStartOf="@+id/commentView" app:layout_constraintEnd_toStartOf="@+id/commentView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rebloggerView" /> app:layout_constraintTop_toBottomOf="@+id/rebloggerView" />

View File

@@ -44,7 +44,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium" android:textSize="@dimen/text_size_medium"
android:widgetLayout="@layout/preference_switch_compat"
tools:checked="true" tools:checked="true"
tools:text="@string/tor_enable_title" /> tools:text="@string/tor_enable_title" />

View 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>

View File

@@ -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" />

View File

@@ -536,6 +536,7 @@
<string name="optional_contact_email">Deine E-Mail-Adresse (optional)</string> <string name="optional_contact_email">Deine E-Mail-Adresse (optional)</string>
<string name="include_debug_report_crash">Anonymisierte Daten über den Absturz anhängen</string> <string name="include_debug_report_crash">Anonymisierte Daten über den Absturz anhängen</string>
<string name="include_debug_report_feedback">Anonymisierte Daten über dieses Gerät anhängen</string> <string name="include_debug_report_feedback">Anonymisierte Daten über dieses Gerät anhängen</string>
<string name="dev_report_user_info">Benutzerinformation</string>
<string name="dev_report_basic_info">Basisinformationen</string> <string name="dev_report_basic_info">Basisinformationen</string>
<string name="dev_report_device_info">Geräteinformationen</string> <string name="dev_report_device_info">Geräteinformationen</string>
<string name="dev_report_stacktrace">Stacktrace</string> <string name="dev_report_stacktrace">Stacktrace</string>

View File

@@ -536,6 +536,7 @@
<string name="optional_contact_email">Tu correo electrónico (opcional)</string> <string name="optional_contact_email">Tu correo electrónico (opcional)</string>
<string name="include_debug_report_crash">Incluir datos anónimos sobre la falla</string> <string name="include_debug_report_crash">Incluir datos anónimos sobre la falla</string>
<string name="include_debug_report_feedback">Incluir datos anónimos sobre este dispositivo</string> <string name="include_debug_report_feedback">Incluir datos anónimos sobre este dispositivo</string>
<string name="dev_report_user_info">Información de usuario</string>
<string name="dev_report_basic_info">Información básica</string> <string name="dev_report_basic_info">Información básica</string>
<string name="dev_report_device_info">Información del dispositivo</string> <string name="dev_report_device_info">Información del dispositivo</string>
<string name="dev_report_stacktrace">Traza de pila</string> <string name="dev_report_stacktrace">Traza de pila</string>

View File

@@ -467,6 +467,7 @@
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string> <string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string>
<string name="dialog_confirm_profile_picture_title">تغییر تصویر نمایه</string> <string name="dialog_confirm_profile_picture_title">تغییر تصویر نمایه</string>
<string name="dialog_confirm_profile_picture_remark">تنها مخاطبین شما می‌توانند این تصویر را مشاهده کنند.</string>
<string name="change_profile_picture_failed_message">تاسفیم اما هنگام بروزرسانی تصویر نمایه شما مشکلی رخ داد.</string> <string name="change_profile_picture_failed_message">تاسفیم اما هنگام بروزرسانی تصویر نمایه شما مشکلی رخ داد.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">زبان و منطقه</string> <string name="pref_language_title">زبان و منطقه</string>
@@ -575,6 +576,7 @@
<string name="optional_contact_email">آدرس ایمیل شما (اختیاری)</string> <string name="optional_contact_email">آدرس ایمیل شما (اختیاری)</string>
<string name="include_debug_report_crash">قرار دادن داده های ناشناس مربوط به خرابی</string> <string name="include_debug_report_crash">قرار دادن داده های ناشناس مربوط به خرابی</string>
<string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string> <string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string>
<string name="dev_report_user_info">اطلاعات کاربر</string>
<string name="dev_report_basic_info">اطلاعات پایه</string> <string name="dev_report_basic_info">اطلاعات پایه</string>
<string name="dev_report_device_info">اطلاعات دستگاه</string> <string name="dev_report_device_info">اطلاعات دستگاه</string>
<string name="dev_report_stacktrace">Stacktrace</string> <string name="dev_report_stacktrace">Stacktrace</string>

View File

@@ -536,6 +536,7 @@
<string name="optional_contact_email">Votre adresse courriel (facultative)</string> <string name="optional_contact_email">Votre adresse courriel (facultative)</string>
<string name="include_debug_report_crash">Inclure des données anonymes concernant le plantage</string> <string name="include_debug_report_crash">Inclure des données anonymes concernant le plantage</string>
<string name="include_debug_report_feedback">Inclure des données anonymes concernant cet appareil</string> <string name="include_debug_report_feedback">Inclure des données anonymes concernant cet appareil</string>
<string name="dev_report_user_info">Renseignements sur lutilisateur</string>
<string name="dev_report_basic_info">Renseignements de base</string> <string name="dev_report_basic_info">Renseignements de base</string>
<string name="dev_report_device_info">Renseignements sur lappareil</string> <string name="dev_report_device_info">Renseignements sur lappareil</string>
<string name="dev_report_stacktrace">Trace dappels</string> <string name="dev_report_stacktrace">Trace dappels</string>

View File

@@ -556,6 +556,7 @@
<string name="optional_contact_email">Jūsų el. pašto adresas (nebūtina)</string> <string name="optional_contact_email">Jūsų el. pašto adresas (nebūtina)</string>
<string name="include_debug_report_crash">Įtraukti anoniminius duomenis apie strigtį</string> <string name="include_debug_report_crash">Įtraukti anoniminius duomenis apie strigtį</string>
<string name="include_debug_report_feedback">Įtraukti anoniminius duomenis apie šį įrenginį</string> <string name="include_debug_report_feedback">Įtraukti anoniminius duomenis apie šį įrenginį</string>
<string name="dev_report_user_info">Naudotojo informacija</string>
<string name="dev_report_basic_info">Pagrindinė informacija</string> <string name="dev_report_basic_info">Pagrindinė informacija</string>
<string name="dev_report_device_info">Įrenginio informacija</string> <string name="dev_report_device_info">Įrenginio informacija</string>
<string name="dev_report_stacktrace">Dėklo pėdsakas</string> <string name="dev_report_stacktrace">Dėklo pėdsakas</string>

View File

@@ -219,9 +219,9 @@
<string name="copy_button">Копировать</string> <string name="copy_button">Копировать</string>
<string name="share_button">Поделиться</string> <string name="share_button">Поделиться</string>
<string name="send_link_title">Обмен ссылками</string> <string name="send_link_title">Обмен ссылками</string>
<string name="add_contact_choose_nickname">Выберите ник</string> <string name="add_contact_choose_nickname">Выберите псевдоним</string>
<string name="add_contact_choose_a_nickname">Введите ник</string> <string name="add_contact_choose_a_nickname">Введите псевдоним</string>
<string name="nickname_intro">Дайте вашему контакту ник. Увидеть его сможете только вы.</string> <string name="nickname_intro">Дайте вашему контакту псевдоним. Увидеть его сможете только вы.</string>
<string name="your_link">Передайте эту ссылку контакту, который вы хотите добавить.</string> <string name="your_link">Передайте эту ссылку контакту, который вы хотите добавить.</string>
<string name="link_clip_label">Ссылка Briar</string> <string name="link_clip_label">Ссылка Briar</string>
<string name="link_copied_toast">Ссылка скопирована</string> <string name="link_copied_toast">Ссылка скопирована</string>
@@ -236,7 +236,7 @@
<string name="dialog_title_remove_pending_contact">Подтвердите удаление</string> <string name="dialog_title_remove_pending_contact">Подтвердите удаление</string>
<string name="dialog_message_remove_pending_contact">Этот контакт находится в процессе добавления. Если вы удалите его сейчас, он не будет добавлен.</string> <string name="dialog_message_remove_pending_contact">Этот контакт находится в процессе добавления. Если вы удалите его сейчас, он не будет добавлен.</string>
<string name="own_link_error">Введите ссылку вашего контакта, а не свою</string> <string name="own_link_error">Введите ссылку вашего контакта, а не свою</string>
<string name="nickname_missing">Пожалуйста, введите ник</string> <string name="nickname_missing">Пожалуйста, введите псевдоним</string>
<string name="invalid_link">Неправильная ссылка</string> <string name="invalid_link">Неправильная ссылка</string>
<string name="unsupported_link">Эта ссылка из более новой версии Briar. Пожалуйста, обновитесь до последней версии и попробуйте снова.</string> <string name="unsupported_link">Эта ссылка из более новой версии Briar. Пожалуйста, обновитесь до последней версии и попробуйте снова.</string>
<string name="intent_own_link">Вы открыли свою собственную ссылку. Используйте контакт, который вы хотите добавить!</string> <string name="intent_own_link">Вы открыли свою собственную ссылку. Используйте контакт, который вы хотите добавить!</string>
@@ -558,6 +558,7 @@
<string name="optional_contact_email">Ваш адрес email (необязательно)</string> <string name="optional_contact_email">Ваш адрес email (необязательно)</string>
<string name="include_debug_report_crash">Включить анонимные данные о сбое</string> <string name="include_debug_report_crash">Включить анонимные данные о сбое</string>
<string name="include_debug_report_feedback">Включить анонимные данные об этом устройстве</string> <string name="include_debug_report_feedback">Включить анонимные данные об этом устройстве</string>
<string name="dev_report_user_info">Информация о пользователе</string>
<string name="dev_report_basic_info">Основная информация</string> <string name="dev_report_basic_info">Основная информация</string>
<string name="dev_report_device_info">Информация об устройстве</string> <string name="dev_report_device_info">Информация об устройстве</string>
<string name="dev_report_stacktrace">Трассировки стека</string> <string name="dev_report_stacktrace">Трассировки стека</string>

View File

@@ -536,6 +536,7 @@
<string name="optional_contact_email">E-posta adresiniz (isteğe bağlı)</string> <string name="optional_contact_email">E-posta adresiniz (isteğe bağlı)</string>
<string name="include_debug_report_crash">Çökme ile ilgili anonim verileri ekle</string> <string name="include_debug_report_crash">Çökme ile ilgili anonim verileri ekle</string>
<string name="include_debug_report_feedback">Bu aygıtla ilgili anonim verileri ekle</string> <string name="include_debug_report_feedback">Bu aygıtla ilgili anonim verileri ekle</string>
<string name="dev_report_user_info">Kullanıcı bilgisi</string>
<string name="dev_report_basic_info">Temel bilgi</string> <string name="dev_report_basic_info">Temel bilgi</string>
<string name="dev_report_device_info">Aygıt bilgisi</string> <string name="dev_report_device_info">Aygıt bilgisi</string>
<string name="dev_report_stacktrace">Yığın izleme</string> <string name="dev_report_stacktrace">Yığın izleme</string>

View File

@@ -526,6 +526,7 @@
<string name="optional_contact_email">您的邮箱地址(选填)</string> <string name="optional_contact_email">您的邮箱地址(选填)</string>
<string name="include_debug_report_crash">包含关于本次崩溃的匿名数据</string> <string name="include_debug_report_crash">包含关于本次崩溃的匿名数据</string>
<string name="include_debug_report_feedback">包含关于本设备的匿名数据</string> <string name="include_debug_report_feedback">包含关于本设备的匿名数据</string>
<string name="dev_report_user_info">用户信息</string>
<string name="dev_report_basic_info">基础信息</string> <string name="dev_report_basic_info">基础信息</string>
<string name="dev_report_device_info">设备信息</string> <string name="dev_report_device_info">设备信息</string>
<string name="dev_report_stacktrace">堆栈追踪</string> <string name="dev_report_stacktrace">堆栈追踪</string>

View File

@@ -468,7 +468,7 @@
<string name="pref_theme_light">Light</string> <string name="pref_theme_light">Light</string>
<string name="pref_theme_dark">Dark</string> <string name="pref_theme_dark">Dark</string>
<string name="pref_theme_auto">Automatic (Daytime)</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 --> <!-- Settings Connections -->
<string name="network_settings_title">Connections</string> <string name="network_settings_title">Connections</string>
@@ -552,7 +552,6 @@
<string name="cannot_load_ringtone">Cannot load ringtone</string> <string name="cannot_load_ringtone">Cannot load ringtone</string>
<!-- Settings Feedback --> <!-- Settings Feedback -->
<string name="feedback_settings_title">Feedback</string>
<string name="send_feedback">Send feedback</string> <string name="send_feedback">Send feedback</string>
<!-- Link Warning --> <!-- Link Warning -->

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference <SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:key="pref_key_lock" android:key="pref_key_lock"
android:summary="@string/panic_setting_signout_summary" android:summary="@string/panic_setting_signout_summary"
android:title="@string/panic_setting_signout_title" android:title="@string/panic_setting_signout_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"
app:singleLineTitle="false" />
<PreferenceCategory <PreferenceCategory
android:layout="@layout/preferences_category" android:layout="@layout/preferences_category"
@@ -18,15 +18,16 @@
android:icon="@android:drawable/ic_menu_close_clear_cancel" android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:key="pref_key_panic_app" android:key="pref_key_panic_app"
android:summary="@string/panic_app_setting_summary" 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:defaultValue="false"
android:enabled="false" android:enabled="false"
android:key="pref_key_purge" android:key="pref_key_purge"
android:summary="@string/purge_setting_summary" android:summary="@string/purge_setting_summary"
android:title="@string/purge_setting_title" android:title="@string/purge_setting_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"
app:singleLineTitle="false" />
</PreferenceCategory> </PreferenceCategory>

View File

@@ -1,236 +1,52 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/display_settings_title">
<ListPreference <org.briarproject.briar.android.settings.AvatarPreference android:key="pref_key_avatar" />
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"/>
<ListPreference <Preference
android:defaultValue="@string/pref_theme_light_value" android:title="@string/display_settings_title"
android:entries="@array/pref_theme_entries" app:fragment="org.briarproject.briar.android.settings.DisplayFragment"
android:entryValues="@array/pref_theme_values" app:icon="@drawable/ic_settings_brightness" />
android:key="pref_key_theme"
android:summary="%s"
android:title="@string/pref_theme_title"
app:iconSpaceReserved="false"/>
</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 <PreferenceCategory
android:key="pref_key_dev"
android:layout="@layout/preferences_category" android:layout="@layout/preferences_category"
android:title="@string/network_settings_title"> android:title="Developer Options"
app:allowDividerAbove="true">
<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">
<Preference <Preference
android:key="pref_key_test_data" android:key="pref_key_test_data"
android:title="Create Test Data" android:title="Create test data">
app:iconSpaceReserved="false">
<intent <intent
android:targetClass="org.briarproject.briar.android.test.TestDataActivity" android:targetClass="org.briarproject.briar.android.test.TestDataActivity"
android:targetPackage="@string/app_package"/> android:targetPackage="@string/app_package" />
</Preference> </Preference>
<Preference <Preference
android:key="pref_key_explode" android:key="pref_key_explode"
android:title="Crash" android:title="Crash" />
app:iconSpaceReserved="false"/>
</PreferenceCategory> </PreferenceCategory>

View 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>

View 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>

View 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>

View 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>