diff --git a/briar-android/res/layout/fragment_blog_post.xml b/briar-android/res/layout/fragment_blog_post.xml
index bc1b87c2ab7a43e35c3190850344563bb49734e7..18ec1f417cd1f72ba54f917eabb8913ea2c04631 100644
--- a/briar-android/res/layout/fragment_blog_post.xml
+++ b/briar-android/res/layout/fragment_blog_post.xml
@@ -20,6 +20,13 @@
 			android:layout_width="wrap_content"
 			android:layout_height="wrap_content"/>
 
+		<ProgressBar
+			android:id="@+id/progressBar"
+			style="?android:attr/progressBarStyleLarge"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:layout_gravity="center"/>
+
 	</FrameLayout>
 
 </ScrollView>
diff --git a/briar-android/res/layout/fragment_blog_post_pager.xml b/briar-android/res/layout/fragment_blog_post_pager.xml
index fe1c2568a1dd52acca61bb24414fd13a106e081b..626dcaf26e21df50e8349d692406ef4f0c4c3a4c 100644
--- a/briar-android/res/layout/fragment_blog_post_pager.xml
+++ b/briar-android/res/layout/fragment_blog_post_pager.xml
@@ -1,6 +1,20 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.v4.view.ViewPager
-	android:id="@+id/pager"
+<FrameLayout
 	xmlns:android="http://schemas.android.com/apk/res/android"
 	android:layout_width="match_parent"
-	android:layout_height="match_parent"/>
\ No newline at end of file
+	android:layout_height="match_parent">
+
+	<android.support.v4.view.ViewPager
+		android:id="@+id/pager"
+		xmlns:android="http://schemas.android.com/apk/res/android"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"/>
+
+	<ProgressBar
+		android:id="@+id/progressBar"
+		style="?android:attr/progressBarStyleLarge"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="center"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 8ba94a5da8a251447bd9aeb4d4225a5eb074f0fe..e866545097665ba4ad001540e73f0e50ce157360 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -73,6 +73,7 @@
 	<string name="offline">Offline</string>
 	<string name="send">Send</string>
 	<string name="no_data">No data</string>
+	<string name="ellipsis">…</string>
 
 	<!-- Contacts and Private Conversations-->
 	<string name="no_contacts">It seems that you are new here and have no contacts yet.\n\nTap the + icon at the top and follow the instructions to add some friends to your list.\n\nPlease remember: You can only add new contacts face-to-face to prevent anyone from impersonating you or reading your messages in the future.</string>
diff --git a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java
index ab513217afa2a58a0dea5220a7b8275e247a3921..3c4f1bc21bbee32c2be22b73ae56f772acb96c5a 100644
--- a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java
@@ -59,6 +59,9 @@ abstract class BaseControllerImpl extends DbControllerImpl
 	@Override
 	@CallSuper
 	public void onStart() {
+		if (listener == null)
+			throw new IllegalStateException(
+					"OnBlogPostAddedListener needs to be attached");
 		eventBus.addListener(this);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
index e340a3fcbacaae6337da5d22c3fc099b88a61ffd..06153e422f286c8278c811af08c4d1b53e32a9d0 100644
--- a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
@@ -3,10 +3,12 @@ package org.briarproject.android.blogs;
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
 import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ProgressBar;
 
 import org.briarproject.R;
 import org.briarproject.android.fragment.BaseFragment;
@@ -14,6 +16,8 @@ import org.briarproject.api.db.DbException;
 
 import java.util.logging.Logger;
 
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
 import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
 
 public abstract class BasePostFragment extends BaseFragment {
@@ -22,6 +26,7 @@ public abstract class BasePostFragment extends BaseFragment {
 			Logger.getLogger(BasePostFragment.class.getName());
 
 	private View view;
+	private ProgressBar progressBar;
 	private BlogPostViewHolder ui;
 	private BlogPostItem post;
 	private Runnable refresher;
@@ -35,6 +40,8 @@ public abstract class BasePostFragment extends BaseFragment {
 
 		view = inflater.inflate(R.layout.fragment_blog_post, container,
 				false);
+		progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
+		progressBar.setVisibility(VISIBLE);
 		ui = new BlogPostViewHolder(view);
 		return view;
 	}
@@ -46,6 +53,7 @@ public abstract class BasePostFragment extends BaseFragment {
 		startPeriodicUpdate();
 	}
 
+	@CallSuper
 	@Override
 	public void onStop() {
 		super.onStop();
@@ -63,12 +71,14 @@ public abstract class BasePostFragment extends BaseFragment {
 		}
 	}
 
+	@UiThread
 	protected void onBlogPostLoaded(BlogPostItem post) {
-		listener.hideLoadingScreen();
+		progressBar.setVisibility(INVISIBLE);
 		this.post = post;
 		ui.bindItem(post);
 	}
 
+	@UiThread
 	protected void onBlogPostLoadException(DbException exception) {
 		// TODO: Decide how to handle errors in the UI
 		finish();
diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
index b24f6be3a9b424b8663a3d620e2507829060197c..e27c1470f5fe09d2e401b53189a5a8da3dfea3c9 100644
--- a/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
@@ -10,10 +10,10 @@ import android.support.v4.view.ViewPager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ProgressBar;
 
 import org.briarproject.R;
 import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
-import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.fragment.BaseFragment;
 import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
@@ -24,13 +24,17 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
 import static org.briarproject.android.blogs.BasePostPagerFragment.BlogPostPagerAdapter.INVALID_POSITION;
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
 
 abstract class BasePostPagerFragment extends BaseFragment
 		implements OnBlogPostAddedListener {
 
+	static final String POST_ID = "briar.POST_ID";
+
 	private ViewPager pager;
+	private ProgressBar progressBar;
 	private BlogPostPagerAdapter postPagerAdapter;
 	private MessageId postId;
 
@@ -49,9 +53,11 @@ abstract class BasePostPagerFragment extends BaseFragment
 
 		View v = inflater.inflate(R.layout.fragment_blog_post_pager, container,
 				false);
+		progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
+		progressBar.setVisibility(VISIBLE);
+
 		pager = (ViewPager) v.findViewById(R.id.pager);
 		postPagerAdapter = new BlogPostPagerAdapter(getChildFragmentManager());
-		listener.showLoadingScreen(false, R.string.progress_title_please_wait);
 
 		return v;
 	}
@@ -82,7 +88,7 @@ abstract class BasePostPagerFragment extends BaseFragment
 
 	abstract void loadBlogPosts(final MessageId select);
 
-	abstract BaseController getController();
+	abstract void loadBlogPost(BlogPostHeader header);
 
 	protected void onBlogPostsLoaded(MessageId select,
 			Collection<BlogPostItem> posts) {
@@ -97,23 +103,6 @@ abstract class BasePostPagerFragment extends BaseFragment
 		finish();
 	}
 
-	private void loadBlogPost(BlogPostHeader header) {
-		getController().loadBlogPost(header,
-				new UiResultExceptionHandler<BlogPostItem, DbException>(
-						getActivity()) {
-					@Override
-					public void onResultUi(BlogPostItem post) {
-						addPost(post);
-					}
-
-					@Override
-					public void onExceptionUi(DbException exception) {
-						// TODO: Decide how to handle errors in the UI
-						finish();
-					}
-				});
-	}
-
 	@Nullable
 	private MessageId getSelectedPost() {
 		if (postPagerAdapter.getCount() == 0) return null;
@@ -124,6 +113,7 @@ abstract class BasePostPagerFragment extends BaseFragment
 	private void selectPost(MessageId m) {
 		int pos = postPagerAdapter.getPostPosition(m);
 		if (pos != INVALID_POSITION) {
+			progressBar.setVisibility(INVISIBLE);
 			pager.setAdapter(postPagerAdapter);
 			pager.setCurrentItem(pos);
 		}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
index 34ba66f98202769f694840933ec0e6247b7f0ee3..bb7a5cc8d3ec4f6f3f86ba492e8606591aaa0fe0 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
@@ -7,10 +7,8 @@ import android.widget.ProgressBar;
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.BriarActivity;
-import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
 import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
 import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
-import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.sync.GroupId;
 
 import javax.inject.Inject;
@@ -19,13 +17,12 @@ import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 public class BlogActivity extends BriarActivity implements
-		OnBlogPostAddedListener, OnBlogPostClickListener, BaseFragmentListener {
+		OnBlogPostClickListener, BaseFragmentListener {
 
 	static final int REQUEST_WRITE_POST = 1;
 	static final int REQUEST_SHARE = 2;
 	static final String BLOG_NAME = "briar.BLOG_NAME";
 	static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
-	static final String POST_ID = "briar.POST_ID";
 
 	private ProgressBar progressBar;
 
@@ -61,22 +58,11 @@ public class BlogActivity extends BriarActivity implements
 		}
 	}
 
-	@Override
-	public void onResume() {
-		super.onResume();
-	}
-
 	@Override
 	public void injectActivity(ActivityComponent component) {
 		component.inject(this);
 	}
 
-	@Override
-	public void onBlogPostAdded(BlogPostHeader header, boolean local) {
-		// all our fragments are implementing and registering that hook,
-		// so we don't need to do that ourselves
-	}
-
 	@Override
 	public void onBlogPostClick(BlogPostItem post) {
 		BlogPostPagerFragment f = BlogPostPagerFragment.newInstance(post.getId());
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
index 55615009f60918b2dc099440f63fca873c48ef4e..ad7b7597d2e40387fb7acb3f0e1f518d68810fa8 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
@@ -35,13 +35,6 @@ public class BlogControllerImpl extends BaseControllerImpl
 
 	@Override
 	public void onActivityCreate() {
-		if (activity instanceof OnBlogPostAddedListener) {
-			listener = (OnBlogPostAddedListener) activity;
-		} else {
-			throw new IllegalStateException(
-					"An activity that injects the BlogController must " +
-							"implement the OnBlogPostAddedListener");
-		}
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
index b70a360625afa89b6754c3212768efa03db68b0f..39c1bf3a5d80e8688e0aa6675b87321f5c22c673 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
@@ -13,7 +13,7 @@ import org.briarproject.api.sync.MessageId;
 
 import javax.inject.Inject;
 
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID;
 
 public class BlogPostFragment extends BasePostFragment {
 
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
index 54e646f9cf58ea37ed1c0b108782ffceefc512ce..3a68030a1ea45446331531d3777025a2884f656a 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
@@ -4,6 +4,7 @@ import android.os.Bundle;
 
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.MessageId;
 
@@ -11,8 +12,6 @@ import java.util.Collection;
 
 import javax.inject.Inject;
 
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
-
 
 public class BlogPostPagerFragment extends BasePostPagerFragment {
 
@@ -42,10 +41,6 @@ public class BlogPostPagerFragment extends BasePostPagerFragment {
 		return TAG;
 	}
 
-	@Override
-	BaseController getController() {
-		return blogController;
-	}
 
 	void loadBlogPosts(final MessageId select) {
 		blogController.loadBlogPosts(
@@ -63,4 +58,21 @@ public class BlogPostPagerFragment extends BasePostPagerFragment {
 				});
 	}
 
+	void loadBlogPost(BlogPostHeader header) {
+		blogController.loadBlogPost(header,
+				new UiResultExceptionHandler<BlogPostItem, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(BlogPostItem post) {
+						addPost(post);
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO: Decide how to handle errors in the UI
+						finish();
+					}
+				});
+	}
+
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
index e9a99449e0fde91ba61b2468449f6f3394fd4956..13a1b3f177215d6b94b9bf23f689bf3891cd4316 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
@@ -6,13 +6,8 @@ import android.content.Intent;
 import android.support.annotation.UiThread;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.ActivityOptionsCompat;
-import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.RecyclerView;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.style.ForegroundColorSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -32,14 +27,14 @@ import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAn
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID;
+import static org.briarproject.android.util.AndroidUtils.TEASER_LENGTH;
+import static org.briarproject.android.util.AndroidUtils.getTeaser;
 import static org.briarproject.api.blogs.MessageType.POST;
 
 @UiThread
 class BlogPostViewHolder extends RecyclerView.ViewHolder {
 
-	private static final int TEASER_LENGTH = 240;
-
 	private final Context ctx;
 	private final ViewGroup layout;
 	private final AuthorView reblogger;
@@ -119,7 +114,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
 		} else {
 			body.setTextIsSelectable(false);
 			if (item.getBody().length() > TEASER_LENGTH)
-				bodyText = getTeaser(item.getBody());
+				bodyText = getTeaser(ctx, item.getBody());
 		}
 		body.setText(bodyText);
 
@@ -176,20 +171,4 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
 			commentContainer.addView(v);
 		}
 	}
-
-	private SpannableStringBuilder getTeaser(String body) {
-		SpannableStringBuilder builder =
-				new SpannableStringBuilder(body.substring(0, TEASER_LENGTH));
-		builder.append("… ");
-
-		Spannable readMore =
-				new SpannableString(ctx.getString(R.string.read_more) + "…");
-		ForegroundColorSpan fg = new ForegroundColorSpan(
-				ContextCompat.getColor(ctx, R.color.briar_text_link));
-		readMore.setSpan(fg, 0, readMore.length(),
-				Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-		builder.append(readMore);
-
-		return builder;
-	}
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
index 5b4841cd4167507839f21fccf10755d99e9d4111..807bcd6e34f69b06d1a971b54f44d2516df0514e 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
@@ -15,7 +15,7 @@ import org.briarproject.api.sync.MessageId;
 import javax.inject.Inject;
 
 import static org.briarproject.android.BriarActivity.GROUP_ID;
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID;
 
 public class FeedPostFragment extends BasePostFragment {
 
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
index db30ac1f3cd06cbeeeeaa8b39af95d275f3101b5..df2c4e33ecc1c33ea7ef4b0c19e973cede142cc5 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
@@ -4,6 +4,7 @@ import android.os.Bundle;
 
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.MessageId;
 
@@ -11,8 +12,6 @@ import java.util.Collection;
 
 import javax.inject.Inject;
 
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
-
 public class FeedPostPagerFragment extends BasePostPagerFragment {
 
 	public final static String TAG = FeedPostPagerFragment.class.getName();
@@ -41,10 +40,6 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
 		return TAG;
 	}
 
-	@Override
-	BaseController getController() {
-		return feedController;
-	}
 
 	void loadBlogPosts(final MessageId select) {
 		feedController.loadBlogPosts(
@@ -62,4 +57,21 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
 				});
 	}
 
+	void loadBlogPost(BlogPostHeader header) {
+		feedController.loadBlogPost(header,
+				new UiResultExceptionHandler<BlogPostItem, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(BlogPostItem post) {
+						addPost(post);
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO: Decide how to handle errors in the UI
+						finish();
+					}
+				});
+	}
+
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java
index 923d520a65053f6d7adfc3c2e7ab03c977acf2f8..6b4afb89bac8121f8dfac031bc6c60b783cf2512 100644
--- a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java
@@ -15,7 +15,7 @@ import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID;
 
 public class ReblogActivity extends BriarActivity implements
 		BaseFragmentListener {
diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java
index 1fee6c6473124c6d2893cbcb5bd01d3e33fe6274..87ac6138050e313e02938d23e2e877f54df0f830 100644
--- a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java
@@ -27,7 +27,7 @@ import static android.view.View.GONE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
-import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID;
 
 public class ReblogFragment extends BaseFragment {
 
diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java
index 610e480e51bb684d2a03cef9f590dc92bb484436..66593d0f8281c64d45d9ea9517bf9655840756c1 100644
--- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java
+++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java
@@ -6,7 +6,12 @@ import android.content.Context;
 import android.os.Build;
 import android.provider.Settings;
 import android.support.design.widget.TextInputLayout;
+import android.support.v4.content.ContextCompat;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
 import android.text.format.DateUtils;
+import android.text.style.ForegroundColorSpan;
 
 import org.briarproject.R;
 import org.briarproject.util.IoUtils;
@@ -31,6 +36,7 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 public class AndroidUtils {
 
 	public static final long MIN_RESOLUTION = MINUTE_IN_MILLIS;
+	public static final int TEASER_LENGTH = 240;
 
 	// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
 	private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
@@ -115,4 +121,25 @@ public class AndroidUtils {
 						MIN_RESOLUTION, flags).toString();
 	}
 
+	public static SpannableStringBuilder getTeaser(Context ctx, String body) {
+		if (body.length() < TEASER_LENGTH)
+			throw new IllegalArgumentException(
+					"String is shorter than TEASER_LENGTH");
+
+		SpannableStringBuilder builder =
+				new SpannableStringBuilder(body.substring(0, TEASER_LENGTH));
+		String ellipsis = ctx.getString(R.string.ellipsis);
+		builder.append(ellipsis).append(" ");
+
+		Spannable readMore = new SpannableString(
+				ctx.getString(R.string.read_more) + ellipsis);
+		ForegroundColorSpan fg = new ForegroundColorSpan(
+				ContextCompat.getColor(ctx, R.color.briar_text_link));
+		readMore.setSpan(fg, 0, readMore.length(),
+				Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+		builder.append(readMore);
+
+		return builder;
+	}
+
 }