Compare commits

...

23 Commits

Author SHA1 Message Date
Michael Rogers
a9437f7985 Bumped version number for beta release. 2017-07-28 18:01:19 +01:00
akwizgran
8141a97fc9 Merge branch '1015-recent-emoji-crash' into 'master'
Prevent a crash caused by empty emoji

Closes #1015

See merge request !571
2017-07-28 16:59:02 +00:00
Torsten Grote
db842bd7e4 Prevent a crash caused by empty emoji
The crash happens because the serialization of recently used emoji uses
';' to separate the emojis.
One of the ASCII emojis however has a ';' in the beginning.
When this one is used by the user,
it causes an empty string to be returned when deserializing.

This commit prevents the crash by changing the separator to a tab.
It uses a different settings string to store the emoji,
so users will lose the list of recently used emoji when they update to
this version.

PS. That wasn't my idea ;)
2017-07-28 13:49:51 -03:00
Torsten Grote
6dbec3a864 Merge branch 'enable-logging-for-beta-builds' into 'master'
Enable logging for beta builds

See merge request !573
2017-07-28 15:58:01 +00:00
akwizgran
29f658cf4d Merge branch '1006-blog-crash' into 'master'
Prevent crash in blog by ensuring a listener always exists

Closes #1006

See merge request !574
2017-07-28 15:53:43 +00:00
akwizgran
ca83744a84 Merge branch 'close-feed-stream' into 'master'
Close InputStream from RSS feed and prevent NPE

See merge request !572
2017-07-28 15:48:01 +00:00
Torsten Grote
d91a9e2be4 Prevent crash in blog by ensuring a listener always exists 2017-07-28 12:42:56 -03:00
akwizgran
8408c3f467 Enable logging for beta builds.
Some devices were logging and others not, due to the log level being set in the SplashScreenActivity constructor.
2017-07-28 16:41:24 +01:00
Torsten Grote
544c83a64c Close InputStream from RSS feed and prevent NPE 2017-07-28 10:38:01 -03:00
Michael Rogers
3800cd5e4f Bumped version number for beta release. 2017-07-28 11:17:09 +01:00
akwizgran
259f2cd419 Merge branch '993-fix-full-text-blog-posts' into 'master'
Show blog posts with full text when clicked

Closes #993

See merge request !570
2017-07-26 11:01:38 +00:00
Torsten Grote
20eb022c36 Show blog posts with full text when clicked
This fixes a regression that was introduced in !551.
2017-07-25 15:50:04 -03:00
akwizgran
531e555b52 Bumped version number for beta release. 2017-07-25 18:43:19 +01:00
akwizgran
a9024aa34b Merge branch '955-shared-with-update' into 'master'
Fix "shared with" counter not being updated

Closes #955

See merge request !569
2017-07-25 17:40:40 +00:00
akwizgran
d4e3b7842c Merge branch 'blog-sharing-tests' into 'master'
Add unit tests for BlogSharingManager

See merge request !567
2017-07-25 17:40:29 +00:00
Torsten Grote
167fddfbcc Add unit tests for BlogSharingManager 2017-07-25 12:45:36 -03:00
Torsten Grote
a48d642648 Fix UI bug in CreateForumActivity and adapt group creation 2017-07-25 12:32:53 -03:00
Torsten Grote
9a70f054c7 Use proper GroupId when reacting to accepted invitations
Fixes #955
2017-07-25 10:03:13 -03:00
Torsten Grote
ca43d13bd6 Merge branch 'inject-properties-module-eager-singletons' into 'master'
Inject properties module's eager singletons

See merge request !568
2017-07-25 12:55:59 +00:00
akwizgran
5b71004179 Inject properties module's eager singletons. 2017-07-25 13:49:15 +01:00
akwizgran
63befccdbf Bumped expiry time and version number for beta release. 2017-07-21 11:52:09 +01:00
akwizgran
4ecf7c02d0 Merge branch '979-duplicate-blog-session' into 'master'
Fix Blog Sharing Sessions

Closes #979

See merge request !566
2017-07-21 10:27:21 +00:00
Torsten Grote
f25badc18c Move responsibility for pre-sharing blogs to sharing manager
to have all the code related to that in one place,
so it is easier to maintain and to spot bugs.

This also checks that only blogs without an existing sharing session
are shared and initialized again.
It extends an existing test to catch the missing check.

This removes some debugging information from the previous commit
to not leak private information via the sharing sessions.

Fixes #979
2017-07-17 14:07:47 -03:00
27 changed files with 441 additions and 141 deletions

View File

@@ -54,6 +54,7 @@ public class BrambleCoreModule {
c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());
c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());

View File

@@ -78,8 +78,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 1601
versionName "0.16.1"
versionCode 1605
versionName "0.16.5"
applicationId "org.briarproject.briar.beta"
resValue "string", "app_package", "org.briarproject.briar.beta"
buildConfigField "String", "GitHash", "\"${getGitHash()}\""

View File

@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
*/
public interface BriarApplication {
// This build expires on 15 October 2017
long EXPIRY_DATE = 1508022000 * 1000L;
// This build expires on 21 October 2017
long EXPIRY_DATE = 1508544000 * 1000L;
AndroidComponent getApplicationComponent();

View File

@@ -2,6 +2,9 @@ package org.briarproject.briar.android;
import android.app.Application;
import android.content.Context;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
@@ -33,6 +36,8 @@ import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes(
reportPrimerClass = BriarReportPrimer.class,
@@ -72,6 +77,9 @@ public class BriarApplicationImpl extends Application
@Override
public void onCreate() {
super.onCreate();
if (IS_DEBUG_BUILD) enableStrictMode();
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
LOG.info("Created");
applicationComponent = DaggerAndroidComponent.builder()
@@ -85,6 +93,17 @@ public class BriarApplicationImpl extends Application
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
}
private void enableStrictMode() {
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
threadPolicy.detectAll();
threadPolicy.penaltyLog();
StrictMode.setThreadPolicy(threadPolicy.build());
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
vmPolicy.detectAll();
vmPolicy.penaltyLog();
StrictMode.setVmPolicy(vmPolicy.build());
}
@Override
public AndroidComponent getApplicationComponent() {
return applicationComponent;

View File

@@ -10,13 +10,21 @@ import static java.util.logging.Level.OFF;
public interface TestingConstants {
/**
* Whether this is an alpha or beta build. This should be set to false for
* Whether this is a debug build.
*/
boolean IS_DEBUG_BUILD = BuildConfig.DEBUG;
/**
* Whether this is a beta build. This should be set to false for final
* release builds.
*/
boolean TESTING = BuildConfig.DEBUG;
boolean IS_BETA_BUILD = true;
/** Default log level. */
Level DEFAULT_LOG_LEVEL = TESTING ? INFO : OFF;
/**
* Default log level. Disable logging for final release builds.
*/
@SuppressWarnings("ConstantConditions")
Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
/**
* Whether to prevent screenshots from being taken. Setting this to true
@@ -24,5 +32,5 @@ public interface TestingConstants {
* Unfortunately this also prevents the user from taking screenshots
* intentionally.
*/
boolean PREVENT_SCREENSHOTS = !TESTING;
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
}

View File

@@ -60,8 +60,7 @@ abstract class BasePostFragment extends BaseFragment {
false);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view);
ui.setOnBlogPostClickListener(new OnBlogPostClickListener() {
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there

View File

@@ -106,7 +106,7 @@ class BlogControllerImpl extends BaseControllerImpl
BlogInvitationResponseReceivedEvent b =
(BlogInvitationResponseReceivedEvent) e;
InvitationResponse r = b.getResponse();
if (r.getGroupId().equals(groupId) && r.wasAccepted()) {
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
LOG.info("Blog invitation accepted");
onBlogInvitationAccepted(b.getContactId());
}

View File

@@ -23,8 +23,7 @@ class BlogPostAdapter
int viewType) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog_post, parent, false);
BlogPostViewHolder ui = new BlogPostViewHolder(v);
ui.setOnBlogPostClickListener(listener);
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
return ui;
}

View File

@@ -8,14 +8,16 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseController.BlogListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.api.blog.BlogPostHeader;
import javax.inject.Inject;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogPostFragment extends BasePostFragment {
public class BlogPostFragment extends BasePostFragment implements BlogListener {
private static final String TAG = BlogPostFragment.class.getName();
@@ -40,6 +42,7 @@ public class BlogPostFragment extends BasePostFragment {
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setBlogListener(this);
}
@Override
@@ -59,4 +62,15 @@ public class BlogPostFragment extends BasePostFragment {
}
});
}
@Override
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
// doesn't matter here
}
@Override
public void onBlogRemoved() {
finish();
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
@@ -47,12 +48,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final ImageView reblogButton;
private final TextView body;
private final ViewGroup commentContainer;
private final boolean fullText;
@Nullable
private OnBlogPostClickListener listener;
@NonNull
private final OnBlogPostClickListener listener;
BlogPostViewHolder(View v) {
BlogPostViewHolder(View v, boolean fullText,
@NonNull OnBlogPostClickListener listener) {
super(v);
this.fullText = fullText;
this.listener = listener;
ctx = v.getContext();
layout = (ViewGroup) v.findViewById(R.id.postLayout);
@@ -64,10 +69,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
(ViewGroup) v.findViewById(R.id.commentContainer);
}
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
this.listener = listener;
}
void setVisibility(int visibility) {
layout.setVisibility(visibility);
}
@@ -92,7 +93,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
if (item == null) return;
setTransitionName(item.getId());
if (listener != null) {
if (!fullText) {
layout.setClickable(true);
layout.setOnClickListener(new OnClickListener() {
@Override
@@ -111,7 +112,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
author.setPersona(
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
// TODO make author clickable more often #624
if (listener != null && item.getHeader().getType() == POST) {
if (!fullText && item.getHeader().getType() == POST) {
author.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -124,7 +125,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// post body
Spanned bodyText = getSpanned(item.getBody());
if (listener == null) {
if (fullText) {
body.setText(bodyText);
body.setTextIsSelectable(true);
makeLinksClickable(body);
@@ -170,7 +171,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogger.setAuthor(item.getAuthor());
reblogger.setAuthorStatus(item.getAuthorStatus());
reblogger.setDate(item.getTimestamp());
if (listener != null) {
if (!fullText) {
reblogger.setAuthorClickable(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -200,7 +201,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// TODO make author clickable #624
body.setText(c.getComment());
if (listener == null) body.setTextIsSelectable(true);
if (fullText) body.setTextIsSelectable(true);
commentContainer.addView(v);
}

View File

@@ -161,7 +161,18 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
private ViewHolder(View v) {
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
});
input = (TextInputView) v.findViewById(R.id.inputText);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -43,10 +44,10 @@ public class CreateForumActivity extends BriarActivity {
private static final Logger LOG =
Logger.getLogger(CreateForumActivity.class.getName());
private TextInputLayout nameEntryLayout;
private EditText nameEntry;
private Button createForumButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -58,6 +59,8 @@ public class CreateForumActivity extends BriarActivity {
setContentView(R.layout.activity_create_forum);
nameEntryLayout =
(TextInputLayout) findViewById(R.id.createForumNameLayout);
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
nameEntry.addTextChangedListener(new TextWatcher() {
@@ -85,8 +88,6 @@ public class CreateForumActivity extends BriarActivity {
}
});
feedback = (TextView) findViewById(R.id.createForumFeedback);
createForumButton = (Button) findViewById(R.id.createForumButton);
createForumButton.setOnClickListener(new OnClickListener() {
@Override
@@ -118,10 +119,10 @@ public class CreateForumActivity extends BriarActivity {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_FORUM_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
nameEntryLayout.setError(getString(R.string.name_too_long));
return false;
}
feedback.setText("");
nameEntryLayout.setError(null);
return length > 0;
}

View File

@@ -85,7 +85,7 @@ class ForumControllerImpl extends
(ForumInvitationResponseReceivedEvent) e;
ForumInvitationResponse r =
(ForumInvitationResponse) f.getResponse();
if (r.getGroupId().equals(getGroupId()) && r.wasAccepted()) {
if (r.getShareableId().equals(getGroupId()) && r.wasAccepted()) {
LOG.info("Forum invitation was accepted");
onForumInvitationAccepted(r.getContactId());
}

View File

@@ -108,7 +108,7 @@ class GroupControllerImpl extends
(GroupInvitationResponseReceivedEvent) e;
final GroupInvitationResponse r =
(GroupInvitationResponse) g.getResponse();
if (getGroupId().equals(r.getGroupId()) && r.wasAccepted()) {
if (getGroupId().equals(r.getShareableId()) && r.wasAccepted()) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Context;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@@ -11,6 +12,7 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -19,6 +21,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
public class CreateGroupFragment extends BaseFragment {
@@ -28,7 +32,8 @@ public class CreateGroupFragment extends BaseFragment {
private CreateGroupListener listener;
private EditText nameEntry;
private Button createGroupButton;
private TextView feedback;
private TextInputLayout nameLayout;
private ProgressBar progress;
@Override
public void onAttach(Context context) {
@@ -69,7 +74,7 @@ public class CreateGroupFragment extends BaseFragment {
}
});
feedback = (TextView) v.findViewById(R.id.feedback);
nameLayout = (TextInputLayout) v.findViewById(R.id.nameLayout);
createGroupButton = (Button) v.findViewById(R.id.button);
createGroupButton.setOnClickListener(new OnClickListener() {
@@ -79,6 +84,8 @@ public class CreateGroupFragment extends BaseFragment {
}
});
progress = (ProgressBar) v.findViewById(R.id.progressBar);
return v;
}
@@ -107,16 +114,18 @@ public class CreateGroupFragment extends BaseFragment {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_GROUP_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
nameLayout.setError(getString(R.string.name_too_long));
return false;
}
feedback.setText("");
nameLayout.setError(null);
return length > 0;
}
private void createGroup() {
if (!validateName()) return;
listener.hideSoftKeyboard(nameEntry);
createGroupButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
listener.onGroupNameChosen(nameEntry.getText().toString());
}
}

View File

@@ -4,9 +4,6 @@ import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy;
import android.support.v7.preference.PreferenceManager;
import android.transition.Fade;
@@ -23,8 +20,6 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
import static org.briarproject.briar.android.TestingConstants.TESTING;
public class SplashScreenActivity extends BaseActivity {
@@ -36,11 +31,6 @@ public class SplashScreenActivity extends BaseActivity {
@Inject
protected AndroidExecutor androidExecutor;
public SplashScreenActivity() {
Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
enableStrictMode();
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
@@ -81,19 +71,6 @@ public class SplashScreenActivity extends BaseActivity {
}
}
private void enableStrictMode() {
if (TESTING) {
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
threadPolicy.detectAll();
threadPolicy.penaltyLog();
StrictMode.setThreadPolicy(threadPolicy.build());
VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
vmPolicy.detectAll();
vmPolicy.penaltyLog();
StrictMode.setVmPolicy(vmPolicy.build());
}
}
private void setPreferencesDefaults() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override

View File

@@ -104,8 +104,9 @@ public class EmojiPageView extends FrameLayout {
emojiSize + 2 * pad));
view = emojiView;
}
String emoji = model.getEmoji()[position];
view.setEmoji(emoji);
view.setEmoji(model.getEmoji()[position]);
return view;
}
}

View File

@@ -32,7 +32,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
private static final Logger LOG =
Logger.getLogger(RecentEmojiPageModel.class.getName());
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent";
private static final String EMOJI_LRU_PREFERENCE = "pref_emoji_recent2";
private static final int EMOJI_LRU_SIZE = 50;
private final LinkedHashSet<String> recentlyUsed; // UI thread
@@ -98,12 +98,12 @@ public class RecentEmojiPageModel implements EmojiPageModel {
}
private String serialize(LinkedHashSet<String> emojis) {
return StringUtils.join(emojis, ";");
return StringUtils.join(emojis, "\t");
}
private LinkedHashSet<String> deserialize(@Nullable String serialized) {
if (serialized == null) return new LinkedHashSet<>();
String[] list = serialized.split(";");
String[] list = serialized.split("\t");
LinkedHashSet<String> result = new LinkedHashSet<>(list.length);
Collections.addAll(result, list);
return result;

View File

@@ -1,37 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="@dimen/margin_large">
android:padding="@dimen/margin_large">
<EditText
<android.support.design.widget.TextInputLayout
android:id="@+id/createForumNameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/createForumNameEntry"
android:maxLines="1"
android:inputType="text|textCapSentences"
android:hint="@string/choose_forum_hint" />
app:errorEnabled="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/createForumFeedback"
android:gravity="center" />
<EditText
android:id="@+id/createForumNameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/choose_forum_hint"
android:inputType="text|textCapSentences"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
style="@style/BriarButton"
android:id="@+id/createForumButton"
style="@style/BriarButton"
android:enabled="false"
android:text="@string/create_forum_button" />
android:text="@string/create_forum_button"/>
<ProgressBar
android:id="@+id/createForumProgressBar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

View File

@@ -1,30 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_margin="@dimen/margin_large">
android:padding="@dimen/margin_large">
<EditText
android:id="@+id/name"
<android.support.design.widget.TextInputLayout
android:id="@+id/nameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:inputType="text|textCapSentences"
android:hint="@string/groups_create_group_hint"/>
app:errorEnabled="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/feedback"
android:gravity="center" />
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/groups_create_group_hint"
android:inputType="text|textCapSentences"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
style="@style/BriarButton"
android:id="@+id/button"
style="@style/BriarButton"
android:enabled="false"
android:text="@string/groups_create_group_button"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>

View File

@@ -48,9 +48,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
import static org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
@@ -72,7 +70,7 @@ import static org.briarproject.briar.blog.BlogPostValidator.authorToBdfDictionar
@NotNullByDefault
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
AddContactHook, RemoveContactHook, Client {
RemoveContactHook, Client {
private final IdentityManager identityManager;
private final BlogFactory blogFactory;
@@ -96,26 +94,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
// Create our personal blog if necessary
LocalAuthor a = identityManager.getLocalAuthor(txn);
Blog b = blogFactory.createBlog(a);
db.addGroup(txn, b.getGroup());
// Ensure that we have the personal blogs of all contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Add the personal blog of the contact and share it with the contact
Blog b = blogFactory.createBlog(c.getAuthor());
addBlog(txn, b);
db.setGroupVisibility(txn, c.getId(), b.getId(), SHARED);
// Share our personal blog with the contact
LocalAuthor a = identityManager.getLocalAuthor(txn);
Blog b2 = blogFactory.createBlog(a);
db.setGroupVisibility(txn, c.getId(), b2.getId(), SHARED);
db.addGroup(txn, b.getGroup()); // does nothing, if group exists
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
Blog b = blogFactory.createBlog(c.getAuthor());
// TODO we might want to reconsider removing b, if otherwise shared
if (db.containsGroup(txn, b.getId())) removeBlog(txn, b);
}

View File

@@ -38,7 +38,6 @@ public class BlogModule {
ValidationManager validationManager) {
lifecycleManager.registerClient(blogManager);
contactManager.registerAddContactHook(blogManager);
contactManager.registerRemoveContactHook(blogManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, blogManager);
return blogManager;

View File

@@ -59,6 +59,7 @@ import okhttp3.Dns;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.WARNING;
@@ -334,7 +335,9 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
private SyndFeed fetchSyndFeed(String url)
throws FeedException, IOException {
// fetch feed
SyndFeed f = getSyndFeed(getFeedInputStream(url));
InputStream stream = getFeedInputStream(url);
SyndFeed f = getSyndFeed(stream);
stream.close();
if (f.getEntries().size() == 0)
throw new FeedException("Feed has no entries");
@@ -387,7 +390,9 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
// Execute Request
Response response = client.newCall(request).execute();
return response.body().byteStream();
ResponseBody body = response.body();
if (body != null) return body.byteStream();
throw new IOException("Empty response body");
}
private SyndFeed getSyndFeed(InputStream stream)
@@ -433,7 +438,6 @@ class FeedManagerImpl implements FeedManager, Client, EventListener,
// build post body
StringBuilder b = new StringBuilder();
b.append("<h3>").append(feed.getTitle()).append("</h3>");
if (!StringUtils.isNullOrEmpty(entry.getTitle())) {
b.append("<h1>").append(entry.getTitle()).append("</h1>");

View File

@@ -51,15 +51,23 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog>
return CLIENT_ID;
}
/**
* This is called during each startup for each existing Contact.
*/
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// creates a group to share with the contact
super.addingContact(txn, c);
// get our blog and that of Contact c
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
Blog ourBlog = blogManager.getPersonalBlog(localAuthor);
Blog theirBlog = blogManager.getPersonalBlog(c.getAuthor());
// pre-share both blogs, if they have not been shared already
try {
initializeSharedSession(txn, c, ourBlog);
initializeSharedSession(txn, c, theirBlog);
preShareShareable(txn, c, ourBlog);
preShareShareable(txn, c, theirBlog);
} catch (FormatException e) {
throw new DbException(e);
}

View File

@@ -139,9 +139,24 @@ abstract class SharingManagerImpl<S extends Shareable>
return false;
}
void initializeSharedSession(Transaction txn, Contact c, S shareable)
/**
* Adds the given Shareable and initializes a session between us
* and the Contact c in state SHARING.
* If a session already exists, this does nothing.
*/
void preShareShareable(Transaction txn, Contact c, S shareable)
throws DbException, FormatException {
// return if a session already exists with that Contact
GroupId contactGroupId = getContactGroup(c).getId();
StoredSession existingSession = getSession(txn, contactGroupId,
getSessionId(shareable.getId()));
if (existingSession != null) return;
// add and shares the shareable with the Contact
db.addGroup(txn, shareable.getGroup());
db.setGroupVisibility(txn, c.getId(), shareable.getId(), SHARED);
// initialize session in sharing state
Session session = new Session(SHARING, contactGroupId,
shareable.getId(), null, null, 0, 0);
MessageId storageId = createStorageId(txn, contactGroupId);

View File

@@ -31,15 +31,11 @@ import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
@@ -117,29 +113,12 @@ public class BlogManagerImplTest extends BriarTestCase {
public void testCreateLocalState() throws DbException {
final Transaction txn = new Transaction(null, false);
final ContactId contactId = new ContactId(0);
Contact contact = new Contact(contactId, blog2.getAuthor(),
blog1.getAuthor().getId(), true, true);
final Collection<Contact> contacts = Collections.singletonList(contact);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).addGroup(txn, blog1.getGroup());
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(blogFactory).createBlog(blog2.getAuthor());
will(returnValue(blog2));
oneOf(db).addGroup(txn, blog2.getGroup());
oneOf(db).setGroupVisibility(txn, contactId, blog2.getId(), SHARED);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(blogFactory).createBlog(blog1.getAuthor());
will(returnValue(blog1));
oneOf(db).setGroupVisibility(txn, contactId, blog1.getId(), SHARED);
}});
blogManager.createLocalState(txn);

View File

@@ -0,0 +1,253 @@
package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogInvitationResponse;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
public class BlogSharingManagerImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final BlogSharingManagerImpl blogSharingManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final SessionEncoder sessionEncoder =
context.mock(SessionEncoder.class);
private final SessionParser sessionParser =
context.mock(SessionParser.class);
private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class);
private final BlogManager blogManager = context.mock(BlogManager.class);
private final AuthorId localAuthorId = new AuthorId(getRandomId());
private final ContactId contactId = new ContactId(0);
private final AuthorId authorId = new AuthorId(getRandomId());
private final Author author = new Author(authorId, "Author",
getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
private final Contact contact =
new Contact(contactId, author, localAuthorId, true, true);
private final Collection<Contact> contacts =
Collections.singletonList(contact);
private final Group contactGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID,
getRandomBytes(42));
private final Group blogGroup =
new Group(new GroupId(getRandomId()), BlogManager.CLIENT_ID,
getRandomBytes(42));
private final Blog blog = new Blog(blogGroup, author, false);
@SuppressWarnings("unchecked")
private final ProtocolEngine<Blog> engine =
context.mock(ProtocolEngine.class);
@SuppressWarnings("unchecked")
public BlogSharingManagerImplTest() {
MetadataParser metadataParser = context.mock(MetadataParser.class);
MessageTracker messageTracker = context.mock(MessageTracker.class);
MessageParser<Blog> messageParser = context.mock(MessageParser.class);
InvitationFactory<Blog, BlogInvitationResponse> invitationFactory =
context.mock(InvitationFactory.class);
blogSharingManager =
new BlogSharingManagerImpl(db, clientHelper, metadataParser,
messageParser, sessionEncoder, sessionParser,
messageTracker, contactGroupFactory,
engine, invitationFactory, identityManager,
blogManager);
}
@Test
public void testAddingContactFreshState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(0);
testAddingContact(sessions);
}
@Test
public void testAddingContactExistingState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(1);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testAddingContact(sessions);
}
@Test(expected = DbException.class)
public void testAddingContactMultipleSessions() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(2);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testAddingContact(sessions);
}
@Test
public void testRemovingBlogFreshState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(0);
testRemovingBlog(sessions);
}
@Test
public void testRemovingBlogExistingState() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(1);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testRemovingBlog(sessions);
}
@Test(expected = DbException.class)
public void testRemovingBlogMultipleSessions() throws Exception {
Map<MessageId, BdfDictionary> sessions =
new HashMap<MessageId, BdfDictionary>(2);
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
sessions.put(new MessageId(getRandomId()), new BdfDictionary());
testRemovingBlog(sessions);
}
private void testAddingContact(final Map<MessageId, BdfDictionary> sessions)
throws Exception {
final Transaction txn = new Transaction(null, false);
final LocalAuthor localAuthor =
new LocalAuthor(localAuthorId, "Local Author",
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
System.currentTimeMillis());
final BdfDictionary meta = BdfDictionary
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, contactId.getInt()));
final Group localBlogGroup =
new Group(new GroupId(getRandomId()), BlogManager.CLIENT_ID,
getRandomBytes(42));
final Blog localBlog = new Blog(localBlogGroup, localAuthor, false);
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contactId, contactGroup.getId(),
SHARED);
oneOf(clientHelper)
.mergeGroupMetadata(txn, contactGroup.getId(), meta);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(blogManager).getPersonalBlog(localAuthor);
will(returnValue(localBlog));
oneOf(blogManager).getPersonalBlog(author);
will(returnValue(blog));
}});
expectPreShareShareable(txn, contact, localBlog, sessions);
expectPreShareShareable(txn, contact, blog, sessions);
blogSharingManager.createLocalState(txn);
}
private void expectPreShareShareable(final Transaction txn,
final Contact contact, final Blog blog,
final Map<MessageId, BdfDictionary> sessions) throws Exception {
final Group contactGroup =
new Group(new GroupId(getRandomId()), CLIENT_ID,
getRandomBytes(42));
final BdfDictionary sessionDict = new BdfDictionary();
final Message message =
new Message(new MessageId(getRandomId()), contactGroup.getId(),
42L, getRandomBytes(1337));
context.checking(new Expectations() {{
oneOf(contactGroupFactory)
.createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.getSessionQuery(new SessionId(blog.getId().getBytes()));
will(returnValue(sessionDict));
oneOf(clientHelper)
.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
sessionDict);
will(returnValue(sessions));
if (sessions.size() == 0) {
oneOf(db).addGroup(txn, blog.getGroup());
oneOf(db).setGroupVisibility(txn, contact.getId(),
blog.getGroup().getId(), SHARED);
oneOf(clientHelper)
.createMessageForStoringMetadata(contactGroup.getId());
will(returnValue(message));
oneOf(db).addLocalMessage(txn, message, new Metadata(), false);
oneOf(sessionEncoder).encodeSession(with(any(Session.class)));
will(returnValue(sessionDict));
oneOf(clientHelper).mergeMessageMetadata(txn, message.getId(),
sessionDict);
}
}});
}
private void testRemovingBlog(final Map<MessageId, BdfDictionary> sessions)
throws Exception {
final Transaction txn = new Transaction(null, false);
final BdfDictionary sessionDict = new BdfDictionary();
final Session session = new Session(contactGroup.getId(), blog.getId());
context.checking(new Expectations() {{
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
will(returnValue(contactGroup));
oneOf(sessionParser)
.getSessionQuery(new SessionId(blog.getId().getBytes()));
will(returnValue(sessionDict));
oneOf(clientHelper)
.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
sessionDict);
will(returnValue(sessions));
if (sessions.size() == 1) {
oneOf(sessionParser)
.parseSession(contactGroup.getId(), sessionDict);
will(returnValue(session));
oneOf(engine).onLeaveAction(txn, session);
will(returnValue(session));
oneOf(sessionEncoder).encodeSession(session);
will(returnValue(sessionDict));
oneOf(clientHelper).mergeMessageMetadata(txn,
sessions.keySet().iterator().next(), sessionDict);
}
}});
blogSharingManager.removingBlog(txn, blog);
}
}