Merge branch '539-564-568-notification-overhaul' into 'master'

Fix various notification bugs

Fixes #539, #564, #568.

Depends on !276.

See merge request !286
This commit is contained in:
akwizgran
2016-08-29 16:27:47 +00:00
18 changed files with 435 additions and 140 deletions

View File

@@ -134,8 +134,10 @@
<string name="introduction_response_accepted_received">%1$s accepted the introduction to %2$s.</string>
<string name="introduction_response_declined_received">%1$s declined the introduction to %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s says that %2$s declined the introduction.</string>
<string name="introduction_success_title">Introduced contact was added</string>
<string name="introduction_success_text">You have been introduced to %1$s.</string>
<plurals name="introduction_notification_text">
<item quantity="one">New contact added.</item>
<item quantity="other">%d new contacts added.</item>
</plurals>
<!-- Forums -->
<string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string>

View File

@@ -2,10 +2,14 @@ package org.briarproject.android;
import android.app.Application;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.UiThread;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
@@ -14,7 +18,6 @@ import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
@@ -59,20 +62,38 @@ import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.BriarActivity.GROUP_ID;
import static org.briarproject.android.NavDrawerActivity.INTENT_BLOGS;
import static org.briarproject.android.NavDrawerActivity.INTENT_CONTACTS;
import static org.briarproject.android.NavDrawerActivity.INTENT_FORUMS;
import static org.briarproject.android.fragment.SettingsFragment.PREF_NOTIFY_BLOG;
import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE;
class AndroidNotificationManagerImpl implements AndroidNotificationManager,
Service, EventListener {
// Notification IDs
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
private static final int FORUM_POST_NOTIFICATION_ID = 4;
private static final int BLOG_POST_NOTIFICATION_ID = 5;
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 6;
// Content URIs to differentiate between pending intents
private static final String CONTACT_URI =
"content://org.briarproject/contact";
private static final String FORUM_URI =
"content://org.briarproject/forum";
private static final String BLOG_URI =
"content://org.briarproject/blog";
// Actions for intents that are broadcast when notifications are dismissed
private static final String CLEAR_PRIVATE_MESSAGE_ACTION =
"org.briarproject.briar.CLEAR_PRIVATE_MESSAGE_NOTIFICATION";
private static final String CLEAR_FORUM_ACTION =
"org.briarproject.briar.CLEAR_FORUM_NOTIFICATION";
private static final String CLEAR_BLOG_ACTION =
"org.briarproject.briar.CLEAR_BLOG_NOTIFICATION";
private static final String CLEAR_INTRODUCTION_ACTION =
"org.briarproject.briar.CLEAR_INTRODUCTION_NOTIFICATION";
private static final Logger LOG =
Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
@@ -82,16 +103,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final MessagingManager messagingManager;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final BroadcastReceiver receiver = new DeleteIntentReceiver();
private final AtomicBoolean used = new AtomicBoolean(false);
// The following must only be accessed on the main UI thread
private final Map<GroupId, Integer> contactCounts = new HashMap<>();
private final Map<GroupId, Integer> forumCounts = new HashMap<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private final Map<GroupId, Integer> blogCounts = new HashMap<>();
private int contactTotal = 0, forumTotal = 0, blogTotal = 0;
private int introductionTotal = 0;
private int nextRequestId = 0;
private GroupId visibleGroup = null;
private boolean blogBlocked = false;
private GroupId blockedGroup = null;
private boolean blockContacts = false, blockForums = false;
private boolean blockBlogs = false, blockIntroductions = false;
private volatile Settings settings = new Settings();
@@ -109,21 +133,22 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Load settings
try {
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
} catch (DbException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
Future<Void> f = androidExecutor.submit(new Callable<Void>() {
// Register a broadcast receiver for notifications being dismissed
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
clearPrivateMessageNotification();
clearForumPostNotification();
clearIntroductionSuccessNotification();
IntentFilter filter = new IntentFilter();
filter.addAction(CLEAR_PRIVATE_MESSAGE_ACTION);
filter.addAction(CLEAR_FORUM_ACTION);
filter.addAction(CLEAR_BLOG_ACTION);
filter.addAction(CLEAR_INTRODUCTION_ACTION);
appContext.registerReceiver(receiver, filter);
return null;
}
});
@@ -134,19 +159,57 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
}
@Override
public void stopService() throws ServiceException {
// Clear all notifications and unregister the broadcast receiver
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
@Override
public Void call() {
clearPrivateMessageNotification();
clearForumPostNotification();
clearBlogPostNotification();
clearIntroductionSuccessNotification();
appContext.unregisterReceiver(receiver);
return null;
}
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
@UiThread
private void clearPrivateMessageNotification() {
contactCounts.clear();
contactTotal = 0;
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
}
@UiThread
private void clearForumPostNotification() {
forumCounts.clear();
forumTotal = 0;
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(FORUM_POST_NOTIFICATION_ID);
}
@UiThread
private void clearBlogPostNotification() {
blogCounts.clear();
blogTotal = 0;
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(BLOG_POST_NOTIFICATION_ID);
}
@UiThread
private void clearIntroductionSuccessNotification() {
introductionTotal = 0;
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
@@ -158,29 +221,28 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(SETTINGS_NAMESPACE)) loadSettings();
} else if (e instanceof PrivateMessageReceivedEvent) {
PrivateMessageReceivedEvent m = (PrivateMessageReceivedEvent) e;
showPrivateMessageNotification(m.getGroupId());
PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e;
showPrivateMessageNotification(p.getGroupId());
} else if (e instanceof ForumPostReceivedEvent) {
ForumPostReceivedEvent m = (ForumPostReceivedEvent) e;
showForumPostNotification(m.getGroupId());
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
showForumPostNotification(f.getGroupId());
} else if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent be = (BlogPostAddedEvent) e;
showBlogPostNotification(be.getGroupId());
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
showBlogPostNotification(b.getGroupId());
} else if (e instanceof IntroductionRequestReceivedEvent) {
ContactId c = ((IntroductionRequestReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} else if (e instanceof IntroductionResponseReceivedEvent) {
ContactId c = ((IntroductionResponseReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} else if (e instanceof IntroductionSucceededEvent) {
Contact c = ((IntroductionSucceededEvent) e).getContact();
showIntroductionSucceededNotification(c);
} else if (e instanceof InvitationReceivedEvent) {
ContactId c = ((InvitationReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} else if (e instanceof InvitationResponseReceivedEvent) {
ContactId c = ((InvitationResponseReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} else if (e instanceof IntroductionSucceededEvent) {
showIntroductionNotification();
}
}
@@ -198,35 +260,35 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void showPrivateMessageNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
private void showPrivateMessageNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (blockContacts) return;
if (g.equals(blockedGroup)) return;
Integer count = contactCounts.get(g);
if (count == null) contactCounts.put(g, 1);
else contactCounts.put(g, count + 1);
contactTotal++;
if (!g.equals(visibleGroup))
updatePrivateMessageNotification();
updatePrivateMessageNotification();
}
});
}
@Override
public void clearPrivateMessageNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
Integer count = contactCounts.remove(g);
if (count == null) return; // Already cleared
contactTotal -= count;
// FIXME: If the notification isn't showing, this may show it
updatePrivateMessageNotification();
}
});
}
@UiThread
private void updatePrivateMessageNotification() {
if (contactTotal == 0) {
clearPrivateMessageNotification();
@@ -245,7 +307,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_PRIVATE_MESSAGE_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
if (contactCounts.size() == 1) {
// Touching the notification shows the relevant conversation
Intent i = new Intent(appContext, ConversationActivity.class);
GroupId g = contactCounts.keySet().iterator().next();
i.putExtra(GROUP_ID, g.getBytes());
@@ -257,9 +325,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
} else {
// Touching the notification shows the contact list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(NavDrawerActivity.INTENT_CONTACTS, true);
i.putExtra(INTENT_CONTACTS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setData(Uri.parse(CONTACT_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
@@ -275,6 +345,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
}
@UiThread
private int getDefaults() {
int defaults = DEFAULT_LIGHTS;
boolean sound = settings.getBoolean("notifySound", true);
@@ -287,34 +358,46 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
@Override
public void showForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
public void clearAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
clearPrivateMessageNotification();
clearIntroductionSuccessNotification();
}
});
}
@UiThread
private void showForumPostNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (blockForums) return;
if (g.equals(blockedGroup)) return;
Integer count = forumCounts.get(g);
if (count == null) forumCounts.put(g, 1);
else forumCounts.put(g, count + 1);
forumTotal++;
if (!g.equals(visibleGroup))
updateForumPostNotification();
updateForumPostNotification();
}
});
}
@Override
public void clearForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
Integer count = forumCounts.remove(g);
if (count == null) return; // Already cleared
forumTotal -= count;
// FIXME: If the notification isn't showing, this may show it
updateForumPostNotification();
}
});
}
@UiThread
private void updateForumPostNotification() {
if (forumTotal == 0) {
clearForumPostNotification();
@@ -332,7 +415,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_FORUM_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
if (forumCounts.size() == 1) {
// Touching the notification shows the relevant forum
Intent i = new Intent(appContext, ForumActivity.class);
GroupId g = forumCounts.keySet().iterator().next();
i.putExtra(GROUP_ID, g.getBytes());
@@ -344,9 +433,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
} else {
// Touching the notification shows the forum list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(NavDrawerActivity.INTENT_FORUMS, true);
i.putExtra(INTENT_FORUMS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setData(Uri.parse(FORUM_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
@@ -363,28 +454,49 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
@Override
public void showBlogPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
public void clearAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (!blogBlocked) {
blogTotal++;
updateBlogPostNotification();
}
clearForumPostNotification();
}
});
}
@UiThread
private void showBlogPostNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (blockBlogs) return;
if (g.equals(blockedGroup)) return;
Integer count = blogCounts.get(g);
if (count == null) blogCounts.put(g, 1);
else blogCounts.put(g, count + 1);
blogTotal++;
updateBlogPostNotification();
}
});
}
@Override
public void clearBlogPostNotification() {
blogTotal = 0;
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(BLOG_POST_NOTIFICATION_ID);
public void clearBlogPostNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
Integer count = blogCounts.remove(g);
if (count == null) return; // Already cleared
blogTotal -= count;
updateBlogPostNotification();
}
});
}
@UiThread
private void updateBlogPostNotification() {
if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
if (blogTotal == 0) {
clearBlogPostNotification();
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.message_notification_icon);
@@ -398,15 +510,20 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counters if the notification is dismissed
Intent clear = new Intent(CLEAR_BLOG_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
// Touching the notification shows the combined blog feed
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(NavDrawerActivity.INTENT_BLOGS, true);
i.putExtra(INTENT_BLOGS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setData(Uri.parse(BLOG_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SOCIAL);
b.setVisibility(VISIBILITY_SECRET);
@@ -418,52 +535,153 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
@Override
public void blockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
public void clearAllBlogPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
visibleGroup = g;
clearBlogPostNotification();
}
});
}
private void showIntroductionNotification() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (blockIntroductions) return;
introductionTotal++;
updateIntroductionNotification();
}
});
}
@UiThread
private void updateIntroductionNotification() {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.introduction_notification);
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.introduction_notification_text, introductionTotal,
introductionTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
// Clear the counter if the notification is dismissed
Intent clear = new Intent(CLEAR_INTRODUCTION_ACTION);
PendingIntent delete = PendingIntent.getBroadcast(appContext, 0,
clear, 0);
b.setDeleteIntent(delete);
// Touching the notification shows the contact list
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_CONTACTS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i.setData(Uri.parse(CONTACT_URI));
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_MESSAGE);
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
}
@Override
public void blockNotification(final GroupId g) {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockedGroup = g;
}
});
}
@Override
public void unblockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (g.equals(visibleGroup)) visibleGroup = null;
if (g.equals(blockedGroup)) blockedGroup = null;
}
});
}
@Override
public void blockBlogNotification() {
androidExecutor.execute(new Runnable() {
public void blockAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blogBlocked = true;
blockContacts = true;
blockIntroductions = true;
}
});
}
@Override
public void unblockBlogNotification() {
androidExecutor.execute(new Runnable() {
public void unblockAllContactNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blogBlocked = false;
blockContacts = false;
blockIntroductions = false;
}
});
}
@Override
public void blockAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockForums = true;
}
});
}
@Override
public void unblockAllForumPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockForums = false;
}
});
}
@Override
public void blockAllBlogPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockBlogs = true;
}
});
}
@Override
public void unblockAllBlogPostNotifications() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
blockBlogs = false;
}
});
}
private void showNotificationForPrivateConversation(final ContactId c) {
androidExecutor.execute(new Runnable() {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
GroupId group = messagingManager.getConversationId(c);
showPrivateMessageNotification(group);
GroupId g = messagingManager.getConversationId(c);
showPrivateMessageNotification(g);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
@@ -472,35 +690,25 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
private void showIntroductionSucceededNotification(final Contact c) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.introduction_notification);
private class DeleteIntentReceiver extends BroadcastReceiver {
b.setContentTitle(appContext
.getString(R.string.introduction_success_title));
b.setContentText(appContext
.getString(R.string.introduction_success_text,
c.getAuthor().getName()));
b.setDefaults(getDefaults());
b.setAutoCancel(true);
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(NavDrawerActivity.INTENT_CONTACTS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
}
});
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
if (CLEAR_PRIVATE_MESSAGE_ACTION.equals(action)) {
clearPrivateMessageNotification();
} else if (CLEAR_FORUM_ACTION.equals(action)) {
clearForumPostNotification();
} else if (CLEAR_BLOG_ACTION.equals(action)) {
clearBlogPostNotification();
} else if (CLEAR_INTRODUCTION_ACTION.equals(action)) {
clearIntroductionSuccessNotification();
}
}
});
}
}
}

View File

@@ -108,7 +108,8 @@ public class BriarService extends Service {
}
private void showStartupFailureNotification(final StartResult result) {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnUiThread(new Runnable() {
@Override
public void run() {
NotificationCompat.Builder b =
new NotificationCompat.Builder(BriarService.this);
@@ -197,11 +198,13 @@ public class BriarService extends Service {
private volatile IBinder binder = null;
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
this.binder = binder;
binderLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {}
/** Waits for the service to connect and returns its binder. */

View File

@@ -87,7 +87,7 @@ public class SplashScreenActivity extends BaseActivity {
}
private void setPreferencesDefaults() {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override
public void run() {
PreferenceManager.setDefaultValues(SplashScreenActivity.this,

View File

@@ -13,10 +13,21 @@ public interface AndroidExecutor {
* Runs the given task on a background thread with a message queue and
* returns a Future for getting the result.
*/
<V> Future<V> submit(Callable<V> c);
<V> Future<V> runOnBackgroundThread(Callable<V> c);
/**
* Runs the given task on a background thread with a message queue.
*/
void execute(Runnable r);
void runOnBackgroundThread(Runnable r);
/**
* Runs the given task on the main UI thread and returns a Future for
* getting the result.
*/
<V> Future<V> runOnUiThread(Callable<V> c);
/**
* Runs the given task on the main UI thread.
*/
void runOnUiThread(Runnable r);
}

View File

@@ -2,26 +2,37 @@ package org.briarproject.android.api;
import org.briarproject.api.sync.GroupId;
/** Manages notifications for private messages and forum posts. */
/**
* Manages notifications for private messages, forum posts, blog posts and
* introductions.
*/
public interface AndroidNotificationManager {
void showPrivateMessageNotification(GroupId g);
void clearPrivateMessageNotification(GroupId g);
void showForumPostNotification(GroupId g);
void clearAllContactNotifications();
void clearForumPostNotification(GroupId g);
void showBlogPostNotification(GroupId g);
void clearAllForumPostNotifications();
void clearBlogPostNotification();
void clearBlogPostNotification(GroupId g);
void clearAllBlogPostNotifications();
void blockNotification(GroupId g);
void unblockNotification(GroupId g);
void blockBlogNotification();
void blockAllContactNotifications();
void unblockBlogNotification();
void unblockAllContactNotifications();
void blockAllForumPostNotifications();
void unblockAllForumPostNotifications();
void blockAllBlogPostNotifications();
void unblockAllBlogPostNotifications();
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.blogs;
import android.app.Activity;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.blogs.Blog;
@@ -38,6 +39,8 @@ public class BlogControllerImpl extends DbControllerImpl
protected Activity activity;
@Inject
protected EventBus eventBus;
@Inject
protected AndroidNotificationManager notificationManager;
@Inject
protected volatile BlogManager blogManager;
@@ -71,11 +74,14 @@ public class BlogControllerImpl extends DbControllerImpl
@Override
public void onActivityResume() {
notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId);
eventBus.addListener(this);
}
@Override
public void onActivityPause() {
notificationManager.unblockNotification(groupId);
eventBus.removeListener(this);
}

View File

@@ -122,6 +122,7 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setGroupId(groupId);
}
@Override

View File

@@ -33,12 +33,13 @@ public class FeedControllerImpl extends DbControllerImpl
@SuppressWarnings("WeakerAccess")
@Inject
AndroidNotificationManager notificationManager;
@Inject
protected EventBus eventBus;
@Inject
protected volatile BlogManager blogManager;
@Inject
protected volatile IdentityManager identityManager;
@Inject
protected volatile EventBus eventBus;
private volatile OnBlogPostAddedListener listener;
@@ -48,14 +49,14 @@ public class FeedControllerImpl extends DbControllerImpl
@Override
public void onResume() {
notificationManager.blockBlogNotification();
notificationManager.clearBlogPostNotification();
notificationManager.blockAllBlogPostNotifications();
notificationManager.clearAllBlogPostNotifications();
eventBus.addListener(this);
}
@Override
public void onPause() {
notificationManager.unblockBlogNotification();
notificationManager.unblockAllBlogPostNotifications();
eventBus.removeListener(this);
}

View File

@@ -18,6 +18,7 @@ import android.widget.TextView.OnEditorActionListener;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPost;
@@ -47,6 +48,9 @@ public class WriteBlogPostActivity extends BriarActivity
Logger.getLogger(WriteBlogPostActivity.class.getName());
private static final String contentType = "text/plain";
@Inject
protected AndroidNotificationManager notificationManager;
private TextInputEditText titleInput;
private EditText bodyInput;
private Button publishButton;
@@ -69,13 +73,8 @@ public class WriteBlogPostActivity extends BriarActivity
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group in intent.");
groupId = new GroupId(b);
// String blogName = i.getStringExtra(BLOG_NAME);
// if (blogName != null) setTitle(blogName);
setContentView(R.layout.activity_write_blog_post);
// String title =
// getTitle() + ": " + getString(R.string.blogs_write_blog_post);
// setTitle(title);
TextInputLayout titleLayout =
(TextInputLayout) findViewById(R.id.titleLayout);
@@ -116,6 +115,18 @@ public class WriteBlogPostActivity extends BriarActivity
progressBar = (ProgressBar) findViewById(R.id.progressBar);
}
@Override
public void onPause() {
super.onPause();
notificationManager.unblockNotification(groupId);
}
@Override
public void onResume() {
super.onResume();
notificationManager.blockNotification(groupId);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

View File

@@ -17,6 +17,7 @@ import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.keyagreement.KeyAgreementActivity;
import org.briarproject.android.util.BriarRecyclerView;
@@ -75,6 +76,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ConnectionRegistry connectionRegistry;
@Inject
protected EventBus eventBus;
@Inject
protected AndroidNotificationManager notificationManager;
private ContactListAdapter adapter = null;
private BriarRecyclerView list = null;
@@ -184,6 +187,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
@Override
public void onResume() {
super.onResume();
notificationManager.blockAllContactNotifications();
notificationManager.clearAllContactNotifications();
eventBus.addListener(this);
loadContacts();
list.startPeriodicUpdate();
@@ -193,6 +198,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
public void onPause() {
super.onPause();
eventBus.removeListener(this);
notificationManager.unblockAllContactNotifications();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();

View File

@@ -15,6 +15,7 @@ import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.sharing.InvitationsForumActivity;
import org.briarproject.android.util.BriarRecyclerView;
@@ -55,9 +56,14 @@ public class ForumListFragment extends BaseEventFragment implements
private ForumListAdapter adapter;
private Snackbar snackbar;
@Inject
protected AndroidNotificationManager notificationManager;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumManager forumManager;
@Inject protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile ForumManager forumManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
public static ForumListFragment newInstance() {
@@ -109,6 +115,8 @@ public class ForumListFragment extends BaseEventFragment implements
public void onResume() {
super.onResume();
notificationManager.blockAllForumPostNotifications();
notificationManager.clearAllForumPostNotifications();
loadForumHeaders();
loadAvailableForums();
list.startPeriodicUpdate();
@@ -118,6 +126,7 @@ public class ForumListFragment extends BaseEventFragment implements
public void onPause() {
super.onPause();
notificationManager.unblockAllForumPostNotifications();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();

View File

@@ -173,6 +173,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void loadSettings() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
@@ -195,6 +196,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void displaySettings() {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
enableBluetooth.setValue(Boolean.toString(bluetoothSetting));
torOverMobile.setValue(Boolean.toString(torSetting));
@@ -228,7 +230,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
}
private void triggerFeedback() {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override
public void run() {
ACRA.getErrorReporter().handleException(new UserFeedback(),
false);
@@ -268,7 +271,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void enableOrDisableBluetooth(final boolean enable) {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override
public void run() {
if (enable) adapter.enable();
else adapter.disable();
@@ -279,6 +283,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void storeTorSettings() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Settings s = new Settings();
@@ -298,6 +303,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void storeBluetoothSettings() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Settings s = new Settings();
@@ -317,6 +323,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void storeSettings(final Settings settings) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();

View File

@@ -144,7 +144,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null && !adapter.isEnabled()) {
waitingForBluetooth = true;
androidExecutor.execute(new Runnable() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override
public void run() {
adapter.enable();

View File

@@ -107,7 +107,7 @@ public class PanicResponderActivity extends BriarActivity {
}
private void deleteAllData() {
androidExecutor.execute(new Runnable() {
androidExecutor.runOnBackgroundThread(new Runnable() {
@Override
public void run() {
configController.deleteAccount(PanicResponderActivity.this);

View File

@@ -126,12 +126,13 @@ class DroidtoothPlugin implements DuplexPlugin {
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
try {
adapter = androidExecutor.submit(new Callable<BluetoothAdapter>() {
@Override
public BluetoothAdapter call() throws Exception {
return BluetoothAdapter.getDefaultAdapter();
}
}).get();
adapter = androidExecutor.runOnBackgroundThread(
new Callable<BluetoothAdapter>() {
@Override
public BluetoothAdapter call() throws Exception {
return BluetoothAdapter.getDefaultAdapter();
}
}).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while getting BluetoothAdapter");

View File

@@ -1,5 +1,6 @@
package org.briarproject.system;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
@@ -16,18 +17,21 @@ import javax.inject.Inject;
class AndroidExecutorImpl implements AndroidExecutor {
private final Handler uiHandler;
private final Runnable loop;
private final AtomicBoolean started = new AtomicBoolean(false);
private final CountDownLatch startLatch = new CountDownLatch(1);
private volatile Handler handler = null;
private volatile Handler backgroundHandler = null;
@Inject
AndroidExecutorImpl() {
AndroidExecutorImpl(Application app) {
uiHandler = new Handler(app.getApplicationContext().getMainLooper());
loop = new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler();
backgroundHandler = new Handler();
startLatch.countDown();
Looper.loop();
}
@@ -46,14 +50,28 @@ class AndroidExecutorImpl implements AndroidExecutor {
}
}
public <V> Future<V> submit(Callable<V> c) {
@Override
public <V> Future<V> runOnBackgroundThread(Callable<V> c) {
FutureTask<V> f = new FutureTask<>(c);
execute(f);
runOnBackgroundThread(f);
return f;
}
public void execute(Runnable r) {
@Override
public void runOnBackgroundThread(Runnable r) {
startIfNecessary();
handler.post(r);
backgroundHandler.post(r);
}
@Override
public <V> Future<V> runOnUiThread(Callable<V> c) {
FutureTask<V> f = new FutureTask<>(c);
runOnUiThread(f);
return f;
}
@Override
public void runOnUiThread(Runnable r) {
uiHandler.post(r);
}
}

View File

@@ -27,7 +27,7 @@ public class AndroidSystemModule {
@Provides
@Singleton
public AndroidExecutor provideAndroidExecutor() {
return new AndroidExecutorImpl();
public AndroidExecutor provideAndroidExecutor(Application app) {
return new AndroidExecutorImpl(app);
}
}