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 {
minSdkVersion 16
targetSdkVersion 29
versionCode 10219
versionName "1.2.19"
versionCode 10220
versionName "1.2.20"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -179,6 +179,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Stopping services");
state = 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) {
long start = now();
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 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 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw 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:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10219
versionName "1.2.19"
versionCode 10220
versionName "1.2.20"
applicationId "org.briarproject.briar.android"
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.logging.CachingLogHandler;
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.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
@@ -193,4 +198,14 @@ public interface AndroidComponent
void inject(EmojiTextInputView textInputView);
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;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.StrictMode;
@@ -115,6 +116,11 @@ public class AppModule {
this.application = application;
}
public static AndroidComponent getAndroidComponent(Context ctx) {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
return app.getApplicationComponent();
}
@Provides
@Singleton
Application providesApplication() {

View File

@@ -12,7 +12,7 @@ import java.util.Locale;
import javax.annotation.Nullable;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE;
import static org.briarproject.briar.android.settings.DisplayFragment.PREF_LANGUAGE;
@NotNullByDefault
public class Localizer {
@@ -25,7 +25,7 @@ public class Localizer {
private Localizer(SharedPreferences sharedPreferences) {
this(Locale.getDefault(), getLocaleFromTag(
sharedPreferences.getString(LANGUAGE, "default")));
sharedPreferences.getString(PREF_LANGUAGE, "default")));
}
private Localizer(Locale systemLocale, @Nullable Locale userLocale) {

View File

@@ -40,8 +40,8 @@ import static android.os.SystemClock.elapsedRealtime;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;

View File

@@ -6,7 +6,6 @@ public interface RequestCodes {
int REQUEST_INTRODUCTION = 2;
int REQUEST_GROUP_INVITE = 3;
int REQUEST_SHARE_FORUM = 4;
int REQUEST_WRITE_BLOG_POST = 5;
int REQUEST_SHARE_BLOG = 6;
int REQUEST_RINGTONE = 7;
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();
}
/**
* 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 {
@Nullable
private final Boolean postAddedWasLocal;
private Boolean postAddedWasLocal;
private final List<BlogPostItem> items;
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.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
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.sharing.BlogSharingStatusActivity;
@@ -19,6 +21,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -45,7 +48,10 @@ public class BlogActivity extends BriarActivity
Intent i = getIntent();
GroupId groupId =
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);
Toolbar toolbar = setUpCustomToolbar(false);
@@ -66,7 +72,14 @@ public class BlogActivity extends BriarActivity
);
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 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_WRITE_BLOG_POST;
@UiThread
@MethodsNotNullByDefault
@@ -134,7 +133,7 @@ public class BlogFragment extends BaseFragment
if (itemId == R.id.action_write_blog_post) {
Intent i = new Intent(getActivity(), WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_WRITE_BLOG_POST);
startActivity(i);
return true;
} else if (itemId == R.id.action_blog_share) {
Intent i = new Intent(getActivity(), ShareBlogActivity.class);
@@ -160,10 +159,7 @@ public class BlogFragment extends BaseFragment
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
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) {
if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
displaySnackbar(R.string.blogs_sharing_snackbar, false);
}
}
@@ -184,6 +180,7 @@ public class BlogFragment extends BaseFragment
displaySnackbar(R.string.blogs_blog_post_received,
true);
}
viewModel.resetLocalUpdate();
list.showData();
});
}
@@ -191,7 +188,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onBlogPostClick(BlogPostItem post) {
BlogPostFragment f =
BlogPostFragment.newInstance(groupId, post.getId(), false);
BlogPostFragment.newInstance(groupId, post.getId());
showNextFragment(f);
}

View File

@@ -44,7 +44,6 @@ public class BlogPostFragment extends BaseFragment
private static final Logger LOG = getLogger(TAG);
static final String POST_ID = "briar.POST_ID";
static final String IS_FEED = "briar.IS_FEED";
protected BlogViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper());
@@ -57,13 +56,11 @@ public class BlogPostFragment extends BaseFragment
@Inject
ViewModelProvider.Factory viewModelFactory;
static BlogPostFragment newInstance(GroupId blogId, MessageId postId,
boolean isFeed) {
static BlogPostFragment newInstance(GroupId blogId, MessageId postId) {
BlogPostFragment f = new BlogPostFragment();
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, blogId.getBytes());
bundle.putByteArray(POST_ID, postId.getBytes());
bundle.putBoolean(IS_FEED, isFeed);
f.setArguments(bundle);
return f;
}
@@ -85,13 +82,12 @@ public class BlogPostFragment extends BaseFragment
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
boolean isFeed = args.getBoolean(IS_FEED);
View view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, this, isFeed);
ui = new BlogPostViewHolder(view, true, this, false);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.loadBlogPost(groupId, postId).observe(owner, result ->
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.BlogPostHeader;
import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -81,15 +79,15 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
return "blogPost" + id.hashCode();
}
void bindItem(@Nullable BlogPostItem item) {
if (item == null) return;
void bindItem(BlogPostItem item) {
setTransitionName(item.getId());
if (!fullText) {
layout.setClickable(true);
layout.setOnClickListener(v -> listener.onBlogPostClick(item));
}
boolean isReblog = item instanceof BlogCommentItem;
// author and date
BlogPostHeader post = item.getPostHeader();
author.setAuthor(post.getAuthor(), post.getAuthorInfo());
@@ -97,7 +95,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624
if (authorClickable) {
if (authorClickable && !isReblog) {
author.setAuthorClickable(v -> listener.onAuthorClick(item));
} else {
author.setAuthorNotClickable();
@@ -126,19 +124,21 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// comments
commentContainer.removeAllViews();
if (item instanceof BlogCommentItem) {
onBindComment((BlogCommentItem) item);
if (isReblog) {
onBindComment((BlogCommentItem) item, authorClickable);
} else {
reblogger.setVisibility(GONE);
}
}
private void onBindComment(BlogCommentItem item) {
private void onBindComment(BlogCommentItem item, boolean authorClickable) {
// reblogger
reblogger.setAuthor(item.getAuthor(), item.getAuthorInfo());
reblogger.setDate(item.getTimestamp());
if (!fullText) {
if (authorClickable) {
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
} else {
reblogger.setAuthorNotClickable();
}
reblogger.setVisibility(VISIBLE);
reblogger.setPersona(REBLOGGER);

View File

@@ -111,11 +111,11 @@ class BlogViewModel extends BaseViewModel {
* Set this before calling any other methods.
*/
@UiThread
public void setGroupId(GroupId groupId) {
public void setGroupId(GroupId groupId, boolean loadAllPosts) {
if (this.groupId == groupId) return; // configuration change
this.groupId = groupId;
loadBlog(groupId);
loadBlogPosts(groupId);
if (loadAllPosts) loadBlogPosts(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.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
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 com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -108,6 +110,7 @@ public class FeedFragment extends BaseFragment
} else if (wasLocal != null) {
showSnackBar(R.string.blogs_blog_post_received);
}
viewModel.resetLocalUpdate();
list.showData();
});
}
@@ -145,16 +148,14 @@ public class FeedFragment extends BaseFragment
@Override
public void onBlogPostClick(BlogPostItem post) {
BaseFragment f = BlogPostFragment
.newInstance(post.getGroupId(), post.getId(), true);
showNextFragment(f);
Intent i = getBlogActivityIntent(post.getGroupId());
i.putExtra(POST_ID, post.getId().getBytes());
requireContext().startActivity(i);
}
@Override
public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(requireContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
Intent i = getBlogActivityIntent(post.getGroupId());
requireContext().startActivity(i);
}
@@ -169,6 +170,13 @@ public class FeedFragment extends BaseFragment
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) {
int firstVisible =
layoutManager.findFirstCompletelyVisibleItemPosition();

View File

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

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

View File

@@ -97,8 +97,6 @@ public class NavDrawerActivity extends BriarActivity implements
Uri.parse("briar-content://org.briarproject.briar/blog");
public static Uri CONTACT_ADDED_URI =
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 MutableLiveData<ImageView> torIcon = new MutableLiveData<>();
@@ -224,8 +222,6 @@ public class NavDrawerActivity extends BriarActivity implements
startFragment(ForumListFragment.newInstance(), R.id.nav_btn_forums);
} else if (BLOG_URI.equals(uri)) {
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.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import androidx.preference.SwitchPreferenceCompat;
import info.guardianproject.panic.PanicResponder;
import static android.app.Activity.RESULT_CANCELED;
@@ -40,7 +40,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
Logger.getLogger(PanicPreferencesFragment.class.getName());
private PackageManager pm;
private SwitchPreference lockPref, purgePref;
private SwitchPreferenceCompat lockPref, purgePref;
private ListPreference panicAppPref;
@Override
@@ -51,9 +51,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
private void updatePreferences() {
pm = getActivity().getPackageManager();
lockPref = (SwitchPreference) findPreference(KEY_LOCK);
lockPref = (SwitchPreferenceCompat) findPreference(KEY_LOCK);
panicAppPref = (ListPreference) findPreference(KEY_PANIC_APP);
purgePref = (SwitchPreference) findPreference(KEY_PURGE);
purgePref = (SwitchPreferenceCompat) findPreference(KEY_PURGE);
// check for connect/disconnect intents from panic trigger apps
if (PanicResponder.checkForDisconnectIntent(getActivity())) {

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;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.AuthorView;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import de.hdodenhof.circleimageview.CircleImageView;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SettingsActivity extends BriarActivity
implements OnPreferenceStartFragmentCallback {
public class SettingsActivity extends BriarActivity {
static final String EXTRA_THEME_CHANGE = "themeChange";
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
@Inject
FeatureFlags featureFlags;
private SettingsViewModel viewModel;
@Override
public void onCreate(Bundle bundle) {
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
ActionBar actionBar = getSupportActionBar();
@@ -44,43 +48,24 @@ public class SettingsActivity extends BriarActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
// show display fragment after theme change
Bundle extras = getIntent().getExtras();
if (bundle == null && extras != null &&
extras.getBoolean(EXTRA_THEME_CHANGE, false)) {
FragmentManager fragmentManager = getSupportFragmentManager();
showNextFragment(fragmentManager, new DisplayFragment());
}
setContentView(R.layout.activity_settings);
if (featureFlags.shouldEnableProfilePictures()) {
ViewModelProvider provider =
new ViewModelProvider(this, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
ViewModelProvider provider =
new ViewModelProvider(this, viewModelFactory);
viewModel = provider.get(SettingsViewModel.class);
TextView textViewUserName = findViewById(R.id.username);
CircleImageView imageViewAvatar =
findViewById(R.id.avatarImage);
settingsViewModel.getOwnIdentityInfo().observe(this, us -> {
textViewUserName.setText(us.getLocalAuthor().getName());
AuthorView.setAvatar(imageViewAvatar,
us.getLocalAuthor().getId(), us.getAuthorInfo());
});
settingsViewModel.getSetAvatarFailed()
.observeEvent(this, failed -> {
if (failed) {
Toast.makeText(this,
R.string.change_profile_picture_failed_message,
LENGTH_LONG).show();
}
});
View avatarGroup = findViewById(R.id.avatarGroup);
avatarGroup.setOnClickListener(e -> selectAvatarImage());
} else {
View view = findViewById(R.id.avatarGroup);
view.setVisibility(View.GONE);
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel.getLanguageChange().observeEvent(this, b -> {
signOut(false, false);
finishAffinity();
});
}
@Override
@@ -92,30 +77,40 @@ public class SettingsActivity extends BriarActivity {
return false;
}
private void selectAvatarImage() {
Intent intent = UiUtils.createSelectImageIntent(false);
startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
@Override
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller,
Preference pref) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentFactory fragmentFactory = fragmentManager.getFragmentFactory();
Fragment fragment = fragmentFactory
.instantiate(getClassLoader(), pref.getFragment());
fragment.setTargetFragment(caller, 0);
// Replace the existing Fragment with the new Fragment
showNextFragment(fragmentManager, fragment);
return true;
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
private void showNextFragment(FragmentManager fragmentManager, Fragment f) {
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,
R.anim.step_next_out)
.replace(R.id.fragmentContainer, f)
.addToBackStack(null)
.commit();
}
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
onAvatarImageReceived(data);
/**
* If the preference is not yet enabled, this enables the preference
* and makes it persist changed values.
* Call this after setting the initial value
* to prevent this change from getting persisted in the DB unnecessarily.
*/
static void enableAndPersist(Preference pref) {
if (!pref.isEnabled()) {
pref.setEnabled(true);
pref.setPersistent(true);
}
}
private void onAvatarImageReceived(@Nullable Intent resultData) {
if (resultData == null) return;
Uri uri = resultData.getData();
if (uri == null) return;
ConfirmAvatarDialogFragment dialog =
ConfirmAvatarDialogFragment.newInstance(uri);
dialog.show(getSupportFragmentManager(),
ConfirmAvatarDialogFragment.TAG);
}
}

View File

@@ -1,761 +1,120 @@
package org.briarproject.briar.android.settings;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.util.UiUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.core.text.TextUtilsCompat;
import androidx.preference.ListPreference;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.media.RingtoneManager.ACTION_RINGTONE_PICKER;
import static android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI;
import static android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI;
import static android.media.RingtoneManager.EXTRA_RINGTONE_PICKED_URI;
import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT;
import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE;
import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE;
import static android.media.RingtoneManager.TYPE_NOTIFICATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS;
import static android.provider.Settings.EXTRA_APP_PACKAGE;
import static android.provider.Settings.EXTRA_CHANNEL_ID;
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
import static android.widget.Toast.LENGTH_SHORT;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
import static org.briarproject.briar.android.util.UiUtils.createSelectImageIntent;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_PRIVATE;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_NAME;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_RINGTONE_URI;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_SOUND;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_VIBRATION;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SettingsFragment extends PreferenceFragmentCompat
implements EventListener, OnPreferenceChangeListener {
public class SettingsFragment extends PreferenceFragmentCompat {
public static final String SETTINGS_NAMESPACE = "android-ui";
public static final String LANGUAGE = "pref_key_language";
public static final String PREF_SCREEN_LOCK = "pref_key_lock";
public static final String PREF_SCREEN_LOCK_TIMEOUT =
"pref_key_lock_timeout";
public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in";
private static final String BT_NAMESPACE =
BluetoothConstants.ID.getString();
private static final String BT_ENABLE = "pref_key_bluetooth";
private static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString();
private static final String WIFI_ENABLE = "pref_key_wifi";
private static final String TOR_NAMESPACE = TorConstants.ID.getString();
private static final String TOR_ENABLE = "pref_key_tor_enable";
private static final String TOR_NETWORK = "pref_key_tor_network";
private static final String TOR_MOBILE = "pref_key_tor_mobile_data";
private static final String TOR_ONLY_WHEN_CHARGING =
"pref_key_tor_only_when_charging";
private static final Logger LOG =
Logger.getLogger(SettingsFragment.class.getName());
private SettingsActivity listener;
private ListPreference language;
private SwitchPreference enableBluetooth;
private SwitchPreference enableWifi;
private SwitchPreference enableTor;
private ListPreference torNetwork;
private SwitchPreference torMobile;
private SwitchPreference torOnlyWhenCharging;
private SwitchPreference screenLock;
private ListPreference screenLockTimeout;
private SwitchPreference notifyPrivateMessages;
private SwitchPreference notifyGroupMessages;
private SwitchPreference notifyForumPosts;
private SwitchPreference notifyBlogPosts;
private SwitchPreference notifyVibration;
private Preference notifySound;
// Fields that are accessed from background threads must be volatile
private volatile Settings settings, btSettings, wifiSettings, torSettings;
private volatile boolean settingsLoaded = false;
private static final String PREF_KEY_AVATAR = "pref_key_avatar";
private static final String PREF_KEY_FEEDBACK = "pref_key_send_feedback";
private static final String PREF_KEY_DEV = "pref_key_dev";
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
@Inject
volatile SettingsManager settingsManager;
@Inject
volatile EventBus eventBus;
@Inject
LocationUtils locationUtils;
@Inject
CircumventionProvider circumventionProvider;
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel viewModel;
private AvatarPreference prefAvatar;
@Override
public void onAttach(Context context) {
public void onAttach(@NonNull Context context) {
super.onAttach(context);
listener = (SettingsActivity) context;
listener.getActivityComponent().inject(this);
getAndroidComponent(context).inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(SettingsViewModel.class);
}
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.settings);
language = findPreference(LANGUAGE);
setLanguageEntries();
ListPreference theme = findPreference("pref_key_theme");
enableBluetooth = findPreference(BT_ENABLE);
enableWifi = findPreference(WIFI_ENABLE);
enableTor = findPreference(TOR_ENABLE);
torNetwork = findPreference(TOR_NETWORK);
torMobile = findPreference(TOR_MOBILE);
torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING);
screenLock = findPreference(PREF_SCREEN_LOCK);
screenLockTimeout = findPreference(PREF_SCREEN_LOCK_TIMEOUT);
notifyPrivateMessages =
findPreference("pref_key_notify_private_messages");
notifyGroupMessages = findPreference("pref_key_notify_group_messages");
notifyForumPosts = findPreference("pref_key_notify_forum_posts");
notifyBlogPosts = findPreference("pref_key_notify_blog_posts");
notifyVibration = findPreference("pref_key_notify_vibration");
notifySound = findPreference("pref_key_notify_sound");
language.setOnPreferenceChangeListener(this);
theme.setOnPreferenceChangeListener((preference, newValue) -> {
if (getActivity() != null) {
// activate new theme
UiUtils.setTheme(getActivity(), (String) newValue);
// bring up parent activity, so it can change its theme as well
// upstream bug: https://issuetracker.google.com/issues/38352704
Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY);
intent.setFlags(
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// bring this activity back to the foreground
intent = new Intent(getActivity(), getActivity().getClass());
startActivity(intent);
getActivity().finish();
}
return true;
});
enableBluetooth.setOnPreferenceChangeListener(this);
enableWifi.setOnPreferenceChangeListener(this);
enableTor.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this);
torMobile.setOnPreferenceChangeListener(this);
torOnlyWhenCharging.setOnPreferenceChangeListener(this);
screenLock.setOnPreferenceChangeListener(this);
screenLockTimeout.setOnPreferenceChangeListener(this);
prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
if (viewModel.shouldEnableProfilePictures()) {
prefAvatar.setOnPreferenceClickListener(preference -> {
Intent intent = createSelectImageIntent(false);
startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
return true;
});
} else {
prefAvatar.setVisible(false);
}
Preference prefFeedback =
requireNonNull(findPreference("pref_key_send_feedback"));
requireNonNull(findPreference(PREF_KEY_FEEDBACK));
prefFeedback.setOnPreferenceClickListener(preference -> {
triggerFeedback(requireContext());
return true;
});
if (SDK_INT < 27) {
// remove System Default Theme option from preference entries
// as it is not functional on this API anyway
List<CharSequence> entries =
new ArrayList<>(Arrays.asList(theme.getEntries()));
entries.remove(getString(R.string.pref_theme_system));
theme.setEntries(entries.toArray(new CharSequence[0]));
// also remove corresponding value
List<CharSequence> values =
new ArrayList<>(Arrays.asList(theme.getEntryValues()));
values.remove(getString(R.string.pref_theme_system_value));
theme.setEntryValues(values.toArray(new CharSequence[0]));
}
Preference explode = requireNonNull(findPreference("pref_key_explode"));
Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE));
if (IS_DEBUG_BUILD) {
explode.setOnPreferenceClickListener(preference -> {
throw new RuntimeException("Boom!");
});
} else {
explode.setVisible(false);
findPreference("pref_key_test_data").setVisible(false);
PreferenceGroup testing = explode.getParent();
if (testing == null) throw new AssertionError();
testing.setVisible(false);
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
dev.setVisible(false);
}
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
public void onViewCreated(@NonNull View view,
@Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
ColorDrawable divider = new ColorDrawable(
ContextCompat.getColor(requireContext(), R.color.divider));
setDivider(divider);
return view;
super.onViewCreated(view, savedInstanceState);
viewModel.getOwnIdentityInfo().observe(getViewLifecycleOwner(), us ->
prefAvatar.setOwnIdentityInfo(us)
);
}
@Override
public void onStart() {
super.onStart();
eventBus.addListener(this);
setSettingsEnabled(false);
loadSettings();
requireActivity().setTitle(R.string.settings_button);
}
@Override
public void onStop() {
super.onStop();
eventBus.removeListener(this);
}
private void setLanguageEntries() {
CharSequence[] tags = language.getEntryValues();
List<CharSequence> entries = new ArrayList<>(tags.length);
List<CharSequence> entryValues = new ArrayList<>(tags.length);
for (CharSequence cs : tags) {
String tag = cs.toString();
if (tag.equals("default")) {
entries.add(getString(R.string.pref_language_default));
entryValues.add(tag);
continue;
}
Locale locale = Localizer.getLocaleFromTag(tag);
if (locale == null)
throw new IllegalStateException();
// Exclude RTL locales on API < 17, they won't be laid out correctly
if (SDK_INT < 17 && !isLeftToRight(locale)) {
if (LOG.isLoggable(INFO))
LOG.info("Skipping RTL locale " + tag);
continue;
}
String nativeName = locale.getDisplayName(locale);
// Fallback to English if the name is unknown in both native and
// current locale.
if (nativeName.equals(tag)) {
String tmp = locale.getDisplayLanguage(Locale.ENGLISH);
if (!tmp.isEmpty() && !tmp.equals(nativeName))
nativeName = tmp;
}
// Prefix with LRM marker to prevent any RTL direction
entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase()
+ nativeName.substring(1));
entryValues.add(tag);
}
language.setEntries(entries.toArray(new CharSequence[0]));
language.setEntryValues(entryValues.toArray(new CharSequence[0]));
}
private boolean isLeftToRight(Locale locale) {
// TextUtilsCompat returns the wrong direction for Hebrew on some phones
String language = locale.getLanguage();
if (language.equals("iw") || language.equals("he")) return false;
int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
return direction == LAYOUT_DIRECTION_LTR;
}
private void setTorNetworkSummary(int torNetworkSetting) {
if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) {
torNetwork.setSummary("%s"); // use setting value
return;
}
// Look up country name in the user's chosen language if available
String country = locationUtils.getCurrentCountry();
String countryName = getCountryDisplayName(country);
boolean blocked =
circumventionProvider.isTorProbablyBlocked(country);
boolean useBridges = circumventionProvider.doBridgesWork(country);
String setting =
getString(R.string.tor_network_setting_without_bridges);
if (blocked && useBridges) {
setting = getString(R.string.tor_network_setting_with_bridges);
} else if (blocked) {
setting = getString(R.string.tor_network_setting_never);
}
torNetwork.setSummary(
getString(R.string.tor_network_setting_summary, setting,
countryName));
}
private void loadSettings() {
listener.runOnDbThread(() -> {
try {
long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
btSettings = settingsManager.getSettings(BT_NAMESPACE);
wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE);
torSettings = settingsManager.getSettings(TOR_NAMESPACE);
settingsLoaded = true;
logDuration(LOG, "Loading settings", start);
displaySettings();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateTorSettings(Settings s) {
int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
s.putBoolean(PREF_PLUGIN_ENABLE, false);
// We don't need to save the migrated settings - the Tor plugin is
// responsible for that. This code just handles the case where the
// settings are loaded before the plugin migrates them.
}
return s;
}
private void displaySettings() {
listener.runOnUiThreadUnlessDestroyed(() -> {
// due to events, we might try to display before a load completed
if (!settingsLoaded) return;
boolean btEnabledSetting = btSettings.getBoolean(PREF_PLUGIN_ENABLE,
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableBluetooth.setChecked(btEnabledSetting);
boolean wifiEnabledSetting =
wifiSettings.getBoolean(PREF_PLUGIN_ENABLE,
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableWifi.setChecked(wifiEnabledSetting);
boolean torEnabledSetting =
torSettings.getBoolean(PREF_PLUGIN_ENABLE,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
enableTor.setChecked(torEnabledSetting);
int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK);
torNetwork.setValue(Integer.toString(torNetworkSetting));
setTorNetworkSummary(torNetworkSetting);
boolean torMobileSetting = torSettings.getBoolean(PREF_TOR_MOBILE,
DEFAULT_PREF_TOR_MOBILE);
torMobile.setChecked(torMobileSetting);
boolean torChargingSetting =
torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING);
torOnlyWhenCharging.setChecked(torChargingSetting);
displayScreenLockSetting();
if (SDK_INT < 26) {
notifyPrivateMessages.setChecked(settings.getBoolean(
PREF_NOTIFY_PRIVATE, true));
notifyGroupMessages.setChecked(settings.getBoolean(
PREF_NOTIFY_GROUP, true));
notifyForumPosts.setChecked(settings.getBoolean(
PREF_NOTIFY_FORUM, true));
notifyBlogPosts.setChecked(settings.getBoolean(
PREF_NOTIFY_BLOG, true));
notifyVibration.setChecked(settings.getBoolean(
PREF_NOTIFY_VIBRATION, true));
notifyPrivateMessages.setOnPreferenceChangeListener(this);
notifyGroupMessages.setOnPreferenceChangeListener(this);
notifyForumPosts.setOnPreferenceChangeListener(this);
notifyBlogPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this);
notifySound.setOnPreferenceClickListener(
pref -> onNotificationSoundClicked());
String text;
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
String ringtoneName =
settings.get(PREF_NOTIFY_RINGTONE_NAME);
if (StringUtils.isNullOrEmpty(ringtoneName)) {
text = getString(R.string.notify_sound_setting_default);
} else {
text = ringtoneName;
}
} else {
text = getString(R.string.notify_sound_setting_disabled);
}
notifySound.setSummary(text);
} else {
setupNotificationPreference(notifyPrivateMessages,
CONTACT_CHANNEL_ID,
R.string.notify_private_messages_setting_summary_26);
setupNotificationPreference(notifyGroupMessages,
GROUP_CHANNEL_ID,
R.string.notify_group_messages_setting_summary_26);
setupNotificationPreference(notifyForumPosts, FORUM_CHANNEL_ID,
R.string.notify_forum_posts_setting_summary_26);
setupNotificationPreference(notifyBlogPosts, BLOG_CHANNEL_ID,
R.string.notify_blog_posts_setting_summary_26);
notifyVibration.setVisible(false);
notifySound.setVisible(false);
}
setSettingsEnabled(true);
});
}
private void setSettingsEnabled(boolean enabled) {
// preferences not needed here, because handled by SharedPreferences:
// - pref_key_theme
// - pref_key_notify_sign_in
// preferences partly needed here, because they have their own logic
// - pref_key_lock (screenLock -> displayScreenLockSetting())
// - pref_key_lock_timeout (screenLockTimeout)
enableBluetooth.setEnabled(enabled);
enableWifi.setEnabled(enabled);
enableTor.setEnabled(enabled);
torNetwork.setEnabled(enabled);
torMobile.setEnabled(enabled);
torOnlyWhenCharging.setEnabled(enabled);
if (!enabled) screenLock.setEnabled(false);
notifyPrivateMessages.setEnabled(enabled);
notifyGroupMessages.setEnabled(enabled);
notifyForumPosts.setEnabled(enabled);
notifyBlogPosts.setEnabled(enabled);
notifyVibration.setEnabled(enabled);
notifySound.setEnabled(enabled);
}
private void displayScreenLockSetting() {
if (SDK_INT < 21) {
screenLock.setVisible(false);
screenLockTimeout.setVisible(false);
} else {
if (getActivity() != null && hasScreenLock(getActivity())) {
screenLock.setEnabled(true);
screenLock.setChecked(
settings.getBoolean(PREF_SCREEN_LOCK, false));
screenLock.setSummary(R.string.pref_lock_summary);
} else {
screenLock.setEnabled(false);
screenLock.setChecked(false);
screenLock.setSummary(R.string.pref_lock_disabled_summary);
}
// timeout depends on screenLock and gets disabled automatically
int timeout = settings.getInt(PREF_SCREEN_LOCK_TIMEOUT,
Integer.valueOf(getString(
R.string.pref_lock_timeout_value_default)));
String newValue = String.valueOf(timeout);
screenLockTimeout.setValue(newValue);
setScreenLockTimeoutSummary(newValue);
}
}
private void setScreenLockTimeoutSummary(String timeout) {
String never = getString(R.string.pref_lock_timeout_value_never);
if (timeout.equals(never)) {
screenLockTimeout
.setSummary(R.string.pref_lock_timeout_never_summary);
} else {
screenLockTimeout
.setSummary(R.string.pref_lock_timeout_summary);
}
}
@TargetApi(26)
private void setupNotificationPreference(SwitchPreference pref,
String channelId, @StringRes int summary) {
pref.setWidgetLayoutResource(0);
pref.setSummary(summary);
pref.setOnPreferenceClickListener(clickedPref -> {
String packageName = requireContext().getPackageName();
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(EXTRA_APP_PACKAGE, packageName)
.putExtra(EXTRA_CHANNEL_ID, channelId);
Context ctx = requireContext();
if (intent.resolveActivity(ctx.getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT)
.show();
}
return true;
});
}
private boolean onNotificationSoundClicked() {
String title = getString(R.string.choose_ringtone_title);
Intent i = new Intent(ACTION_RINGTONE_PICKER);
i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
i.putExtra(EXTRA_RINGTONE_TITLE, title);
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI,
DEFAULT_NOTIFICATION_URI);
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
Uri uri;
String ringtoneUri =
settings.get(PREF_NOTIFY_RINGTONE_URI);
if (StringUtils.isNullOrEmpty(ringtoneUri))
uri = DEFAULT_NOTIFICATION_URI;
else uri = Uri.parse(ringtoneUri);
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
}
if (i.resolveActivity(requireActivity().getPackageManager()) != null) {
startActivityForResult(i, REQUEST_RINGTONE);
} else {
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
LENGTH_SHORT).show();
}
return true;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == language) {
if (!language.getValue().equals(newValue))
languageChanged((String) newValue);
return false;
} else if (preference == enableBluetooth) {
boolean btSetting = (Boolean) newValue;
storeBluetoothSetting(btSetting);
} else if (preference == enableWifi) {
boolean wifiSetting = (Boolean) newValue;
storeWifiSetting(wifiSetting);
} else if (preference == enableTor) {
boolean torEnabledSetting = (Boolean) newValue;
storeTorEnabledSetting(torEnabledSetting);
} else if (preference == torNetwork) {
int torNetworkSetting = Integer.valueOf((String) newValue);
storeTorNetworkSetting(torNetworkSetting);
setTorNetworkSummary(torNetworkSetting);
} else if (preference == torMobile) {
boolean torMobileSetting = (Boolean) newValue;
storeTorMobileSetting(torMobileSetting);
} else if (preference == torOnlyWhenCharging) {
boolean torChargingSetting = (Boolean) newValue;
storeTorChargingSetting(torChargingSetting);
} else if (preference == screenLock) {
Settings s = new Settings();
s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue);
storeSettings(s);
} else if (preference == screenLockTimeout) {
Settings s = new Settings();
String value = (String) newValue;
s.putInt(PREF_SCREEN_LOCK_TIMEOUT, Integer.valueOf(value));
storeSettings(s);
setScreenLockTimeoutSummary(value);
} else if (preference == notifyPrivateMessages) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue);
storeSettings(s);
} else if (preference == notifyGroupMessages) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) newValue);
storeSettings(s);
} else if (preference == notifyForumPosts) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) newValue);
storeSettings(s);
} else if (preference == notifyBlogPosts) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) newValue);
storeSettings(s);
} else if (preference == notifyVibration) {
Settings s = new Settings();
s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) newValue);
storeSettings(s);
}
return true;
}
private void languageChanged(String newValue) {
AlertDialog.Builder builder =
new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.pref_language_title);
builder.setMessage(R.string.pref_language_changed);
builder.setPositiveButton(R.string.sign_out_button,
(dialogInterface, i) -> {
language.setValue(newValue);
Intent intent = new Intent(getContext(), ENTRY_ACTIVITY);
intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
intent.setData(SIGN_OUT_URI);
requireActivity().startActivity(intent);
requireActivity().finish();
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
builder.show();
}
private void storeTorEnabledSetting(boolean torEnabledSetting) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeTorNetworkSetting(int torNetworkSetting) {
Settings s = new Settings();
s.putInt(PREF_TOR_NETWORK, torNetworkSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeTorMobileSetting(boolean torMobileSetting) {
Settings s = new Settings();
s.putBoolean(PREF_TOR_MOBILE, torMobileSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeTorChargingSetting(boolean torChargingSetting) {
Settings s = new Settings();
s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, torChargingSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeBluetoothSetting(boolean btSetting) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, btSetting);
mergeSettings(s, BT_NAMESPACE);
}
private void storeWifiSetting(boolean wifiSetting) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, wifiSetting);
mergeSettings(s, WIFI_NAMESPACE);
}
private void storeSettings(Settings s) {
mergeSettings(s, SETTINGS_NAMESPACE);
}
private void mergeSettings(Settings s, String namespace) {
listener.runOnDbThread(() -> {
try {
long start = now();
settingsManager.mergeSettings(s, namespace);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void onActivityResult(int request, int result, Intent data) {
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_RINGTONE && result == RESULT_OK) {
Settings s = new Settings();
Uri uri = data.getParcelableExtra(EXTRA_RINGTONE_PICKED_URI);
if (uri == null) {
// The user chose silence
s.putBoolean(PREF_NOTIFY_SOUND, false);
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
s.put(PREF_NOTIFY_RINGTONE_URI, "");
} else if (RingtoneManager.isDefault(uri)) {
// The user chose the default
s.putBoolean(PREF_NOTIFY_SOUND, true);
s.put(PREF_NOTIFY_RINGTONE_NAME, "");
s.put(PREF_NOTIFY_RINGTONE_URI, "");
} else {
// The user chose a ringtone other than the default
Ringtone r = RingtoneManager.getRingtone(getContext(), uri);
if (r == null || "file".equals(uri.getScheme())) {
Toast.makeText(getContext(), R.string.cannot_load_ringtone,
LENGTH_SHORT).show();
} else {
String name = r.getTitle(getContext());
s.putBoolean(PREF_NOTIFY_SOUND, true);
s.put(PREF_NOTIFY_RINGTONE_NAME, name);
s.put(PREF_NOTIFY_RINGTONE_URI, uri.toString());
}
}
storeSettings(s);
}
}
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
if (data == null) return;
Uri uri = data.getData();
if (uri == null) return;
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
String namespace = s.getNamespace();
if (namespace.equals(SETTINGS_NAMESPACE)) {
LOG.info("Settings updated");
settings = s.getSettings();
displaySettings();
} else if (namespace.equals(BT_NAMESPACE)) {
LOG.info("Bluetooth settings updated");
btSettings = s.getSettings();
displaySettings();
} else if (namespace.equals(WIFI_NAMESPACE)) {
LOG.info("Wifi settings updated");
wifiSettings = s.getSettings();
displaySettings();
} else if (namespace.equals(TOR_NAMESPACE)) {
LOG.info("Tor settings updated");
torSettings = migrateTorSettings(s.getSettings());
displaySettings();
}
DialogFragment dialog =
ConfirmAvatarDialogFragment.newInstance(uri);
dialog.show(getParentFragmentManager(),
ConfirmAvatarDialogFragment.TAG);
}
}

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.content.ContentResolver;
import android.net.Uri;
import android.widget.Toast;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.avatar.AvatarManager;
@@ -25,66 +44,129 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.annotation.AnyThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Arrays.asList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@NotNullByDefault
class SettingsViewModel extends AndroidViewModel {
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class SettingsViewModel extends DbViewModel implements EventListener {
private final static Logger LOG =
getLogger(SettingsViewModel.class.getName());
static final String BT_NAMESPACE =
BluetoothConstants.ID.getString();
static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString();
static final String TOR_NAMESPACE = TorConstants.ID.getString();
private final SettingsManager settingsManager;
private final IdentityManager identityManager;
private final EventBus eventBus;
private final AvatarManager avatarManager;
private final AuthorManager authorManager;
private final ImageCompressor imageCompressor;
@IoExecutor
private final Executor ioExecutor;
@DatabaseExecutor
private final Executor dbExecutor;
private final FeatureFlags featureFlags;
final SettingsStore settingsStore;
final TorSummaryProvider torSummaryProvider;
final ConnectionsManager connectionsManager;
final NotificationsManager notificationsManager;
private volatile Settings settings;
private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> setAvatarFailed =
private final MutableLiveData<Boolean> screenLockEnabled =
new MutableLiveData<>();
private final MutableLiveData<String> screenLockTimeout =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> languageChanged =
new MutableLiveEvent<>();
@Inject
SettingsViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
SettingsManager settingsManager,
IdentityManager identityManager,
EventBus eventBus,
AvatarManager avatarManager,
AuthorManager authorManager,
ImageCompressor imageCompressor,
LocationUtils locationUtils,
CircumventionProvider circumventionProvider,
@IoExecutor Executor ioExecutor,
@DatabaseExecutor Executor dbExecutor) {
super(application);
FeatureFlags featureFlags) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.settingsManager = settingsManager;
this.identityManager = identityManager;
this.eventBus = eventBus;
this.imageCompressor = imageCompressor;
this.avatarManager = avatarManager;
this.authorManager = authorManager;
this.ioExecutor = ioExecutor;
this.dbExecutor = dbExecutor;
this.featureFlags = featureFlags;
settingsStore = new SettingsStore(settingsManager, dbExecutor,
SETTINGS_NAMESPACE);
torSummaryProvider = new TorSummaryProvider(getApplication(),
locationUtils, circumventionProvider);
connectionsManager =
new ConnectionsManager(settingsManager, dbExecutor);
notificationsManager = new NotificationsManager(getApplication(),
settingsManager, dbExecutor);
loadOwnIdentityInfo();
eventBus.addListener(this);
loadSettings();
if (shouldEnableProfilePictures()) loadOwnIdentityInfo();
}
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
return ownIdentityInfo;
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
LiveEvent<Boolean> getSetAvatarFailed() {
return setAvatarFailed;
private void loadSettings() {
runOnDbThread(() -> {
try {
long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
updateSettings(settings);
connectionsManager.updateBtSetting(
settingsManager.getSettings(BT_NAMESPACE));
connectionsManager.updateWifiSettings(
settingsManager.getSettings(WIFI_NAMESPACE));
connectionsManager.updateTorSettings(
settingsManager.getSettings(TOR_NAMESPACE));
logDuration(LOG, "Loading settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
boolean shouldEnableProfilePictures() {
return featureFlags.shouldEnableProfilePictures();
}
private void loadOwnIdentityInfo() {
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
LocalAuthor localAuthor = identityManager.getLocalAuthor();
AuthorInfo authorInfo = authorManager.getMyAuthorInfo();
@@ -96,13 +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) {
ioExecutor.execute(() -> {
try {
trySetAvatar(uri);
} catch (IOException e) {
logException(LOG, WARNING, e);
setAvatarFailed.postEvent(true);
onSetAvatarFailed();
}
});
}
@@ -120,15 +236,42 @@ class SettingsViewModel extends AndroidViewModel {
"ContentResolver returned null when opening InputStream");
InputStream compressed = imageCompressor.compressImage(is, contentType);
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
avatarManager.addAvatar(ImageCompressor.MIME_TYPE, compressed);
loadOwnIdentityInfo();
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
setAvatarFailed.postEvent(true);
onSetAvatarFailed();
}
});
}
@AnyThread
private void onSetAvatarFailed() {
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
R.string.change_profile_picture_failed_message, LENGTH_LONG)
.show());
}
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() {
setClickable(false);
setBackgroundResource(0);
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"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragmentContainer"
android:name="org.briarproject.briar.android.settings.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/avatarGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarImage"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:paddingStart="@dimen/margin_medium"
android:paddingEnd="@dimen/margin_medium"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="@dimen/text_size_medium"
app:layout_constraintBottom_toTopOf="@+id/avatarExplanation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatarImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="username" />
<TextView
android:id="@+id/avatarExplanation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:paddingStart="@dimen/margin_medium"
android:paddingEnd="@dimen/margin_medium"
android:text="@string/change_profile_picture"
android:textColor="@color/briar_text_secondary_inverse"
android:textSize="@dimen/text_size_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatarImage"
app:layout_constraintTop_toBottomOf="@+id/username" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="@+id/fragment"
android:name="org.briarproject.briar.android.settings.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

View File

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

View File

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

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="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="dev_report_user_info">Benutzerinformation</string>
<string name="dev_report_basic_info">Basisinformationen</string>
<string name="dev_report_device_info">Geräteinformationen</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="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="dev_report_user_info">Información de usuario</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_stacktrace">Traza de pila</string>

View File

@@ -467,6 +467,7 @@
<!--Settings Profile Picture-->
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</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>
<!--Settings Display-->
<string name="pref_language_title">زبان و منطقه</string>
@@ -575,6 +576,7 @@
<string name="optional_contact_email">آدرس ایمیل شما (اختیاری)</string>
<string name="include_debug_report_crash">قرار دادن داده های ناشناس مربوط به خرابی</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_device_info">اطلاعات دستگاه</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="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="dev_report_user_info">Renseignements sur lutilisateur</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_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="include_debug_report_crash">Įtraukti anoniminius duomenis apie strigtį</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_device_info">Įrenginio informacija</string>
<string name="dev_report_stacktrace">Dėklo pėdsakas</string>

View File

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

View File

@@ -526,6 +526,7 @@
<string name="optional_contact_email">您的邮箱地址(选填)</string>
<string name="include_debug_report_crash">包含关于本次崩溃的匿名数据</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_device_info">设备信息</string>
<string name="dev_report_stacktrace">堆栈追踪</string>

View File

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

View File

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

View File

@@ -1,236 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/display_settings_title">
<ListPreference
android:defaultValue="default"
android:entryValues="@array/pref_language_values"
android:key="pref_key_language"
android:summary="%s"
android:title="@string/pref_language_title"
app:iconSpaceReserved="false"/>
<org.briarproject.briar.android.settings.AvatarPreference android:key="pref_key_avatar" />
<ListPreference
android:defaultValue="@string/pref_theme_light_value"
android:entries="@array/pref_theme_entries"
android:entryValues="@array/pref_theme_values"
android:key="pref_key_theme"
android:summary="%s"
android:title="@string/pref_theme_title"
app:iconSpaceReserved="false"/>
<Preference
android:title="@string/display_settings_title"
app:fragment="org.briarproject.briar.android.settings.DisplayFragment"
app:icon="@drawable/ic_settings_brightness" />
</PreferenceCategory>
<Preference
android:title="@string/network_settings_title"
app:fragment="org.briarproject.briar.android.settings.ConnectionsFragment"
app:icon="@drawable/ic_connect_without_contact" />
<Preference
android:title="@string/security_settings_title"
app:fragment="org.briarproject.briar.android.settings.SecurityFragment"
app:icon="@drawable/ic_settings_security" />
<Preference
android:title="@string/notification_settings_title"
app:fragment="org.briarproject.briar.android.settings.NotificationsFragment"
app:icon="@drawable/ic_notifications" />
<Preference
android:key="pref_key_send_feedback"
android:title="@string/send_feedback"
app:icon="@drawable/ic_feedback" />
<PreferenceCategory
android:key="pref_key_dev"
android:layout="@layout/preferences_category"
android:title="@string/network_settings_title">
<SwitchPreference
android:defaultValue="false"
android:key="pref_key_bluetooth"
android:persistent="false"
android:title="@string/bluetooth_setting"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="false"
android:key="pref_key_wifi"
android:persistent="false"
android:title="@string/wifi_setting"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_tor_enable"
android:persistent="false"
android:title="@string/tor_enable_title"
android:summary="@string/tor_enable_summary"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<ListPreference
android:defaultValue="0"
android:dependency="pref_key_tor_enable"
android:entries="@array/tor_network_setting_names"
android:entryValues="@array/tor_network_setting_values"
android:key="pref_key_tor_network"
android:persistent="false"
android:summary="%s"
android:title="@string/tor_network_setting"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:dependency="pref_key_tor_enable"
android:key="pref_key_tor_mobile_data"
android:persistent="false"
android:title="@string/tor_mobile_data_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="false"
android:dependency="pref_key_tor_enable"
android:key="pref_key_tor_only_when_charging"
android:persistent="false"
android:title="@string/tor_only_when_charging_title"
android:summary="@string/tor_only_when_charging_summary"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/security_settings_title">
<SwitchPreference
android:enabled="false"
android:key="pref_key_lock"
android:persistent="false"
android:summary="@string/pref_lock_summary"
android:title="@string/pref_lock_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<ListPreference
android:defaultValue="@string/pref_lock_timeout_value_default"
android:dependency="pref_key_lock"
android:entries="@array/pref_key_lock_timeout_entries"
android:entryValues="@array/pref_key_lock_timeout_values"
android:key="pref_key_lock_timeout"
android:persistent="false"
android:summary="@string/pref_lock_timeout_summary"
android:title="@string/pref_lock_timeout_title"
app:iconSpaceReserved="false"/>
<Preference
android:key="pref_key_change_password"
android:title="@string/change_password"
app:iconSpaceReserved="false">
<intent
android:targetClass="org.briarproject.briar.android.login.ChangePasswordActivity"
android:targetPackage="@string/app_package"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/panic_setting_title">
<Preference
android:summary="@string/panic_setting_hint"
android:title="@string/panic_setting"
app:iconSpaceReserved="false">
<intent
android:targetClass="org.briarproject.briar.android.panic.PanicPreferencesActivity"
android:targetPackage="@string/app_package"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/notification_settings_title">
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_sign_in"
android:summary="@string/notify_sign_in_summary"
android:title="@string/notify_sign_in_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_private_messages"
android:persistent="false"
android:summary="@string/notify_private_messages_setting_summary"
android:title="@string/notify_private_messages_setting_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_group_messages"
android:persistent="false"
android:summary="@string/notify_group_messages_setting_summary"
android:title="@string/notify_group_messages_setting_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_forum_posts"
android:persistent="false"
android:summary="@string/notify_forum_posts_setting_summary"
android:title="@string/notify_forum_posts_setting_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_blog_posts"
android:persistent="false"
android:summary="@string/notify_blog_posts_setting_summary"
android:title="@string/notify_blog_posts_setting_title"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="true"
android:key="pref_key_notify_vibration"
android:persistent="false"
android:title="@string/notify_vibration_setting"
android:widgetLayout="@layout/preference_switch_compat"
app:iconSpaceReserved="false"/>
<Preference
android:key="pref_key_notify_sound"
android:title="@string/notify_sound_setting"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="@string/feedback_settings_title">
<Preference
android:key="pref_key_send_feedback"
android:title="@string/send_feedback"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="@layout/preferences_category"
android:title="Testing">
android:title="Developer Options"
app:allowDividerAbove="true">
<Preference
android:key="pref_key_test_data"
android:title="Create Test Data"
app:iconSpaceReserved="false">
android:title="Create test data">
<intent
android:targetClass="org.briarproject.briar.android.test.TestDataActivity"
android:targetPackage="@string/app_package"/>
android:targetPackage="@string/app_package" />
</Preference>
<Preference
android:key="pref_key_explode"
android:title="Crash"
app:iconSpaceReserved="false"/>
android:title="Crash" />
</PreferenceCategory>

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>