Features:
* Now displays the number of mentions that the user has not seen in the
notification.
* When no mentions are outstanding, display which servers the user is
connected to, not the last message.
* When more than one mention is outstanding, display the names of the
conversations with new mentions, not just the last message received.
* Notifications of mentions are suppressed if you're in the conversation
at the time of the mention.
* Notifications of mentions automatically clear when you bring up the
conversation.
* Vibrate notifications now generate the user's chosen default vibrate
pattern, not a hard-coded one.
* Add ticker text to the notification that's displayed when the IRCService
goes into the foreground, instead of displaying a blank ticker.
To allow for all of this, the implementation moves most of the details
of generating the notification text into the IRCService, which now
exposes addNewMention() and notifyConnected()/notifyDisconnected()
methods instead of the lower-level updateNotification().
---
v2:
* If there's a new message notification, keep showing the "New messages
in" content text, even after a disconnect notification.
* Handle the case of a channel/query name duplicated between two or more
connections more gracefully in new message notifications
* Fix a race in updating notifications
application/res/values/strings.xml | 2 +
.../org/yaaic/activity/ConversationActivity.java | 15 ++-
application/src/org/yaaic/irc/IRCConnection.java | 51 +++++---
application/src/org/yaaic/irc/IRCService.java | 143 +++++++++++++++++---
.../listener/ConversationSelectedListener.java | 15 ++-
application/src/org/yaaic/model/Conversation.java | 25 ++++
application/src/org/yaaic/model/Server.java | 17 +++
7 files changed, 227 insertions(+), 41 deletions(-)
diff --git a/application/res/values/strings.xml
b/application/res/values/strings.xml
index 366112c..56c10cb 100644
--- a/application/res/values/strings.xml
+++ b/application/res/values/strings.xml
@@ -113,8 +113,10 @@
<string name="command_desc_voice">Give a user voice status</string>
<string name="command_desc_whois">Get information about a user</string>
+ <string name="notification_running">Yaaic is running</string>
<string name="notification_connected">Connected to %1$s</string>
<string name="notification_disconnected">Disconnected from %1$s</string>
+ <string name="notification_mentions">New messages in: %1$s</string>
<string name="message_connected">Connected to %1$s</string>
<string name="message_deop">%1$s deops %2$s</string>
diff --git a/application/src/org/yaaic/activity/ConversationActivity.java
b/application/src/org/yaaic/activity/ConversationActivity.java
index 5892215..29bb562 100644
--- a/application/src/org/yaaic/activity/ConversationActivity.java
+++ b/application/src/org/yaaic/activity/ConversationActivity.java
@@ -153,7 +153,7 @@ public class ConversationActivity extends Activity
implements ServiceConnection,
deckAdapter = new DeckAdapter();
deck = (Gallery) findViewById(R.id.deck);
- deck.setOnItemSelectedListener(new
ConversationSelectedListener(server, (TextView) findViewById(R.id.title),
dots));
+ deck.setOnItemSelectedListener(new ConversationSelectedListener(this,
server, (TextView) findViewById(R.id.title), dots));
deck.setAdapter(deckAdapter);
deck.setOnItemClickListener(new ConversationClickListener(deckAdapter,
switcher));
deck.setBackgroundDrawable(new NonScalingBackgroundDrawable(this,
deck, R.drawable.background));
@@ -237,6 +237,15 @@ public class ConversationActivity extends Activity
implements ServiceConnection,
mAdapter.addBulkMessages(conversation.getBuffer());
conversation.clearBuffer();
}
+
+ // Clear new message notifications for the selected conversation
+ if (conversation.getStatus() == Conversation.STATUS_SELECTED &&
conversation.getNewMentions() > 0) {
+ Intent ackIntent = new Intent(this, IRCService.class);
+ ackIntent.setAction(IRCService.ACTION_ACK_NEW_MENTIONS);
+ ackIntent.putExtra(IRCService.EXTRA_ACK_SERVERID, serverId);
+ ackIntent.putExtra(IRCService.EXTRA_ACK_CONVTITLE,
conversation.getName());
+ startService(ackIntent);
+ }
}
// Join channel that has been selected in JoinActivity
(onActivityResult())
@@ -249,6 +258,8 @@ public class ConversationActivity extends Activity
implements ServiceConnection,
}
}.start();
}
+
+ server.setIsForeground(true);
}
/**
@@ -259,6 +270,8 @@ public class ConversationActivity extends Activity
implements ServiceConnection,
{
super.onPause();
+ server.setIsForeground(false);
+
if (binder != null && binder.getService() != null) {
binder.getService().checkServiceStatus();
}
diff --git a/application/src/org/yaaic/irc/IRCConnection.java
b/application/src/org/yaaic/irc/IRCConnection.java
index 9bb81a3..dc031c2 100644
--- a/application/src/org/yaaic/irc/IRCConnection.java
+++ b/application/src/org/yaaic/irc/IRCConnection.java
@@ -141,7 +141,7 @@ public class IRCConnection extends PircBot
Broadcast.createServerIntent(Broadcast.SERVER_UPDATE,
server.getId())
);
-
service.updateNotification(service.getString(R.string.notification_connected,
server.getTitle()));
+ service.notifyConnected(server.getTitle());
Message message = new
Message(service.getString(R.string.message_connected, server.getTitle()));
message.setColor(Message.COLOR_GREEN);
@@ -245,11 +245,15 @@ public class IRCConnection extends PircBot
boolean mentioned = isMentioned(action);
if (mentioned || target.equals(this.getNick())) {
- service.updateNotification(
- target + ": " + sender + " " + action,
- service.getSettings().isVibrateHighlightEnabled(),
- service.getSettings().isSoundHighlightEnabled()
- );
+ if (conversation.getStatus() != Conversation.STATUS_SELECTED ||
!server.getIsForeground()) {
+ service.addNewMention(
+ server.getId(),
+ conversation,
+ conversation.getName() + ": " + sender + " " + action,
+ service.getSettings().isVibrateHighlightEnabled(),
+ service.getSettings().isSoundHighlightEnabled()
+ );
+ }
}
if (mentioned) {
@@ -410,20 +414,25 @@ public class IRCConnection extends PircBot
protected void onMessage(String target, String sender, String login,
String hostname, String text)
{
Message message = new Message(text, sender);
+ Conversation conversation = server.getConversation(target);
if (isMentioned(text)) {
// highlight
message.setColor(Message.COLOR_RED);
- service.updateNotification(
- target + ": <" + sender + "> " + text,
- service.getSettings().isVibrateHighlightEnabled(),
- service.getSettings().isSoundHighlightEnabled()
- );
+ if (conversation.getStatus() != Conversation.STATUS_SELECTED ||
!server.getIsForeground()) {
+ service.addNewMention(
+ server.getId(),
+ conversation,
+ target + ": <" + sender + "> " + text,
+ service.getSettings().isVibrateHighlightEnabled(),
+ service.getSettings().isSoundHighlightEnabled()
+ );
+ }
-
server.getConversation(target).setStatus(Conversation.STATUS_HIGHLIGHT);
+ conversation.setStatus(Conversation.STATUS_HIGHLIGHT);
}
- server.getConversation(target).addMessage(message);
+ conversation.addMessage(message);
Intent intent = Broadcast.createConversationIntent(
Broadcast.CONVERSATION_MESSAGE,
@@ -619,11 +628,15 @@ public class IRCConnection extends PircBot
return;
}
- service.updateNotification(
- "<" + sender + "> " + text,
- service.getSettings().isVibrateHighlightEnabled(),
- service.getSettings().isSoundHighlightEnabled()
- );
+ if (conversation.getStatus() != Conversation.STATUS_SELECTED ||
!server.getIsForeground()) {
+ service.addNewMention(
+ server.getId(),
+ conversation,
+ "<" + sender + "> " + text,
+ service.getSettings().isVibrateHighlightEnabled(),
+ service.getSettings().isSoundHighlightEnabled()
+ );
+ }
if (isMentioned(text)) {
message.setColor(Message.COLOR_RED);
@@ -1091,7 +1104,7 @@ public class IRCConnection extends PircBot
server.setStatus(Status.DISCONNECTED);
}
-
service.updateNotification(service.getString(R.string.notification_disconnected,
server.getTitle()));
+ service.notifyDisconnected(server.getTitle());
Intent sIntent = Broadcast.createServerIntent(Broadcast.SERVER_UPDATE,
server.getId());
service.sendBroadcast(sIntent);
diff --git a/application/src/org/yaaic/irc/IRCService.java
b/application/src/org/yaaic/irc/IRCService.java
index 499de71..29daabc 100644
--- a/application/src/org/yaaic/irc/IRCService.java
+++ b/application/src/org/yaaic/irc/IRCService.java
@@ -24,6 +24,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;
@@ -32,6 +33,7 @@ import org.yaaic.Yaaic;
import org.yaaic.activity.ServersActivity;
import org.yaaic.db.Database;
import org.yaaic.model.Broadcast;
+import org.yaaic.model.Conversation;
import org.yaaic.model.Message;
import org.yaaic.model.Server;
import org.yaaic.model.ServerInfo;
@@ -54,6 +56,11 @@ public class IRCService extends Service
private final IRCBinder binder;
private final HashMap<Integer, IRCConnection> connections;
private boolean foreground = false;
+ private ArrayList<String> connectedServerTitles;
+ private LinkedHashMap<String, Conversation> mentions;
+ private int newMentions = 0;
+
+ private static final int FOREGROUND_NOTIFICATION = 1;
@SuppressWarnings("rawtypes")
private static final Class[] mStartForegroundSignature = new Class[] {
int.class, Notification.class };
@@ -62,6 +69,9 @@ public class IRCService extends Service
public static final String ACTION_FOREGROUND =
"org.yaaic.service.foreground";
public static final String ACTION_BACKGROUND =
"org.yaaic.service.background";
+ public static final String ACTION_ACK_NEW_MENTIONS =
"org.yaaic.service.ack_new_mentions";
+ public static final String EXTRA_ACK_SERVERID =
"org.yaaic.service.ack_serverid";
+ public static final String EXTRA_ACK_CONVTITLE =
"org.yaaic.service.ack_convtitle";
private NotificationManager notificationManager;
private Method mStartForeground;
@@ -80,6 +90,8 @@ public class IRCService extends Service
this.connections = new HashMap<Integer, IRCConnection>();
this.binder = new IRCBinder(this);
+ this.connectedServerTitles = new ArrayList<String>();
+ this.mentions = new LinkedHashMap<String, Conversation>();
}
/**
@@ -166,7 +178,7 @@ public class IRCService extends Service
foreground = true;
// Set the icon, scrolling text and timestamp
- notification = new Notification(R.drawable.icon, "",
System.currentTimeMillis());
+ notification = new Notification(R.drawable.icon,
getText(R.string.notification_running), System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects
this notification
Intent notifyIntent = new Intent(this, ServersActivity.class);
@@ -176,52 +188,143 @@ public class IRCService extends Service
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.app_name),
"", contentIntent);
- startForegroundCompat(R.string.app_name, notification);
+ startForegroundCompat(FOREGROUND_NOTIFICATION, notification);
} else if (ACTION_BACKGROUND.equals(intent.getAction()) &&
!foreground) {
- stopForegroundCompat(R.string.app_name);
+ stopForegroundCompat(FOREGROUND_NOTIFICATION);
+ } else if (ACTION_ACK_NEW_MENTIONS.equals(intent.getAction())) {
+ ackNewMentions(intent.getIntExtra(EXTRA_ACK_SERVERID, -1),
intent.getStringExtra(EXTRA_ACK_CONVTITLE));
}
}
/**
- * Update notification
- *
- * @param text The text to display
- */
- public void updateNotification(String text)
- {
- updateNotification(text, false, false);
- }
-
- /**
* Update notification and vibrate if needed
*
- * @param text The text to display
+ * @param text The ticker text to display
+ * @param contentText The text to display in the notification
dropdown
* @param vibrate True if the device should vibrate, false otherwise
+ * @param sound True if the device should make sound, false otherwise
*/
- public void updateNotification(String text, boolean vibrate, boolean sound)
+ private void updateNotification(String text, String contentText, boolean
vibrate, boolean sound)
{
if (foreground) {
- notificationManager.cancel(R.string.app_name);
notification = new Notification(R.drawable.icon, text,
System.currentTimeMillis());
Intent notifyIntent = new Intent(this, ServersActivity.class);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
notifyIntent, 0);
- notification.setLatestEventInfo(this, getText(R.string.app_name),
text, contentIntent);
+
+ if (contentText == null) {
+ if (newMentions >= 1) {
+ StringBuilder sb = new StringBuilder();
+ for (Conversation conv : mentions.values()) {
+ sb.append(conv.getName() + " (" +
conv.getNewMentions() + "), ");
+ }
+ contentText = getString(R.string.notification_mentions,
sb.substring(0, sb.length()-2));
+ } else if (!connectedServerTitles.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ for (String title : connectedServerTitles) {
+ sb.append(title + ", ");
+ }
+ contentText = getString(R.string.notification_connected,
sb.substring(0, sb.length()-2));
+ } else {
+ contentText = "";
+ }
+ }
+
+ notification.setLatestEventInfo(this, getText(R.string.app_name),
contentText, contentIntent);
if (vibrate) {
- long[] pattern = {0,100,200,300};
- notification.vibrate = pattern;
+ notification.defaults |= Notification.DEFAULT_VIBRATE;
}
if (sound) {
notification.defaults |= Notification.DEFAULT_SOUND;
}
- notificationManager.notify(R.string.app_name, notification);
+ notification.number = newMentions;
+
+ notificationManager.notify(FOREGROUND_NOTIFICATION, notification);
+ }
+ }
+
+ /**
+ * Generates a string uniquely identifying a conversation.
+ */
+ public String getConversationId(int serverId, String title) {
+ return "" + serverId + ":" + title;
+ }
+
+ /**
+ * Notify the service of a new mention (updates the status bar
notification)
+ *
+ * @param conversation The conversation where the new mention occurred
+ * @param msg The text of the new message
+ * @param vibrate Whether the notification should include vibration
+ * @param sound Whether the notification should include sound
+ */
+ public synchronized void addNewMention(int serverId, Conversation
conversation, String msg, boolean vibrate, boolean sound)
+ {
+ if (conversation == null)
+ return;
+
+ conversation.addNewMention();
+ ++newMentions;
+ String convId = getConversationId(serverId, conversation.getName());
+ if (!mentions.containsKey(convId)) {
+ mentions.put(convId, conversation);
+ }
+
+ if (newMentions == 1) {
+ updateNotification(msg, msg, vibrate, sound);
+ } else {
+ updateNotification(msg, null, vibrate, sound);
}
}
/**
+ * Notify the service that new mentions have been viewed (updates the
status bar notification)
+ *
+ * @param convTitle The title of the conversation whose new mentions have
been read
+ */
+ public synchronized void ackNewMentions(int serverId, String convTitle)
+ {
+ if (convTitle == null)
+ return;
+
+ Conversation conversation =
mentions.remove(getConversationId(serverId, convTitle));
+ if (conversation == null)
+ return;
+ newMentions -= conversation.getNewMentions();
+ conversation.clearNewMentions();
+ if (newMentions < 0)
+ newMentions = 0;
+
+ updateNotification(null, null, false, false);
+ }
+
+ /**
+ * Notify the service of connection to a server (updates the status bar
notification)
+ *
+ * @param title The title of the newly connected server
+ */
+ public synchronized void notifyConnected(String title)
+ {
+ connectedServerTitles.add(title);
+ updateNotification(getString(R.string.notification_connected, title),
null, false, false);
+ }
+
+ /**
+ * Notify the service of disconnection from a server (updates the status
bar notification)
+ *
+ * @param title The title of the disconnected server
+ */
+ public synchronized void notifyDisconnected(String title)
+ {
+ connectedServerTitles.remove(title);
+ updateNotification(getString(R.string.notification_disconnected,
title), null, false, false);
+ }
+
+
+ /**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
diff --git
a/application/src/org/yaaic/listener/ConversationSelectedListener.java
b/application/src/org/yaaic/listener/ConversationSelectedListener.java
index 01818b6..26c7d19 100644
--- a/application/src/org/yaaic/listener/ConversationSelectedListener.java
+++ b/application/src/org/yaaic/listener/ConversationSelectedListener.java
@@ -23,11 +23,14 @@ package org.yaaic.listener;
import org.yaaic.model.Conversation;
import org.yaaic.model.Server;
import org.yaaic.view.ConversationSwitcher;
+import org.yaaic.irc.IRCService;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TextView;
+import android.content.Context;
+import android.content.Intent;
/**
* Listener for conversation selections
@@ -36,6 +39,7 @@ import android.widget.TextView;
*/
public class ConversationSelectedListener implements OnItemSelectedListener
{
+ private final Context ctx;
private final Server server;
private final TextView titleView;
private final ConversationSwitcher switcher;
@@ -46,8 +50,9 @@ public class ConversationSelectedListener implements
OnItemSelectedListener
* @param server
* @param titleView
*/
- public ConversationSelectedListener(Server server, TextView titleView,
ConversationSwitcher switcher)
+ public ConversationSelectedListener(Context ctx, Server server, TextView
titleView, ConversationSwitcher switcher)
{
+ this.ctx = ctx;
this.server = server;
this.titleView = titleView;
this.switcher = switcher;
@@ -75,6 +80,14 @@ public class ConversationSelectedListener implements
OnItemSelectedListener
previousConversation.setStatus(Conversation.STATUS_DEFAULT);
}
+ if (conversation.getNewMentions() > 0) {
+ Intent i = new Intent(ctx, IRCService.class);
+ i.setAction(IRCService.ACTION_ACK_NEW_MENTIONS);
+ i.putExtra(IRCService.EXTRA_ACK_SERVERID, server.getId());
+ i.putExtra(IRCService.EXTRA_ACK_CONVTITLE,
conversation.getName());
+ ctx.startService(i);
+ }
+
conversation.setStatus(Conversation.STATUS_SELECTED);
server.setSelectedConversation(conversation.getName());
}
diff --git a/application/src/org/yaaic/model/Conversation.java
b/application/src/org/yaaic/model/Conversation.java
index 43f3013..fc83922 100644
--- a/application/src/org/yaaic/model/Conversation.java
+++ b/application/src/org/yaaic/model/Conversation.java
@@ -47,6 +47,7 @@ public abstract class Conversation
private final LinkedList<Message> history;
private final String name;
private int status = 1;
+ private int newMentions = 0;
/**
* Get the type of conversation (channel, query, ..)
@@ -179,4 +180,28 @@ public abstract class Conversation
{
return status;
}
+
+ /**
+ * Increment the count of unread mentions in this conversation
+ */
+ public void addNewMention()
+ {
+ ++newMentions;
+ }
+
+ /**
+ * Mark all new mentions as unread
+ */
+ public void clearNewMentions()
+ {
+ newMentions = 0;
+ }
+
+ /**
+ * Get the number of unread mentions in this conversation
+ */
+ public int getNewMentions()
+ {
+ return newMentions;
+ }
}
diff --git a/application/src/org/yaaic/model/Server.java
b/application/src/org/yaaic/model/Server.java
index e28e405..2053826 100644
--- a/application/src/org/yaaic/model/Server.java
+++ b/application/src/org/yaaic/model/Server.java
@@ -48,6 +48,7 @@ public class Server
private int status = Status.DISCONNECTED;
private String selected = "";
+ private boolean isForeground = false;
/**
* Create a new server object
@@ -404,4 +405,20 @@ public class Server
return R.drawable.connecting;
}
+
+ /**
+ * Get whether a ConversationActivity for this server is currently in the
foreground.
+ */
+ public boolean getIsForeground()
+ {
+ return isForeground;
+ }
+
+ /**
+ * Set whether a ConversationActivity for this server is currently in the
foreground.
+ */
+ public void setIsForeground(boolean isForeground)
+ {
+ this.isForeground = isForeground;
+ }
}
--
1.7.2.5