1package eu.siacs.conversations.services;
2
3import java.util.ArrayList;
4import java.util.LinkedHashMap;
5import java.util.regex.Matcher;
6import java.util.regex.Pattern;
7
8import android.app.Notification;
9import android.app.NotificationManager;
10import android.app.PendingIntent;
11import android.content.Context;
12import android.content.Intent;
13import android.content.SharedPreferences;
14import android.net.Uri;
15import android.os.PowerManager;
16import android.support.v4.app.NotificationCompat;
17import android.support.v4.app.TaskStackBuilder;
18import android.text.Html;
19import android.util.DisplayMetrics;
20
21import eu.siacs.conversations.R;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Conversation;
24import eu.siacs.conversations.entities.Message;
25import eu.siacs.conversations.ui.ConversationActivity;
26
27public class NotificationService {
28
29 private XmppConnectionService mXmppConnectionService;
30
31 private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
32
33 public int NOTIFICATION_ID = 0x2342;
34 private Conversation mOpenConversation;
35 private boolean mIsInForeground;
36
37 public NotificationService(XmppConnectionService service) {
38 this.mXmppConnectionService = service;
39 }
40
41 public void push(Message message) {
42 PowerManager pm = (PowerManager) mXmppConnectionService
43 .getSystemService(Context.POWER_SERVICE);
44 boolean isScreenOn = pm.isScreenOn();
45
46 if (this.mIsInForeground && isScreenOn
47 && this.mOpenConversation == message.getConversation()) {
48 return;
49 }
50 synchronized (notifications) {
51 String conversationUuid = message.getConversationUuid();
52 if (notifications.containsKey(conversationUuid)) {
53 notifications.get(conversationUuid).add(message);
54 } else {
55 ArrayList<Message> mList = new ArrayList<Message>();
56 mList.add(message);
57 notifications.put(conversationUuid, mList);
58 }
59 Account account = message.getConversation().getAccount();
60 updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
61 && !account.inGracePeriod());
62 }
63
64 }
65
66 public void clear() {
67 synchronized (notifications) {
68 notifications.clear();
69 updateNotification(false);
70 }
71 }
72
73 public void clear(Conversation conversation) {
74 synchronized (notifications) {
75 notifications.remove(conversation.getUuid());
76 updateNotification(false);
77 }
78 }
79
80 private void updateNotification(boolean notify) {
81 NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
82 .getSystemService(Context.NOTIFICATION_SERVICE);
83 SharedPreferences preferences = mXmppConnectionService.getPreferences();
84
85 String ringtone = preferences.getString("notification_ringtone", null);
86 boolean vibrate = preferences.getBoolean("vibrate_on_notification",
87 true);
88
89 if (notifications.size() == 0) {
90 notificationManager.cancel(NOTIFICATION_ID);
91 } else {
92 NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
93 mXmppConnectionService);
94 mBuilder.setSmallIcon(R.drawable.ic_notification);
95 if (notifications.size() == 1) {
96 ArrayList<Message> messages = notifications.values().iterator()
97 .next();
98 if (messages.size() >= 1) {
99 Conversation conversation = messages.get(0)
100 .getConversation();
101 mBuilder.setLargeIcon(mXmppConnectionService
102 .getAvatarService().get(conversation, getPixel(64)));
103 mBuilder.setContentTitle(conversation.getName());
104 StringBuilder text = new StringBuilder();
105 for (int i = 0; i < messages.size(); ++i) {
106 text.append(messages.get(i).getReadableBody(
107 mXmppConnectionService));
108 if (i != messages.size() - 1) {
109 text.append("\n");
110 }
111 }
112 mBuilder.setStyle(new NotificationCompat.BigTextStyle()
113 .bigText(text.toString()));
114 mBuilder.setContentText(messages.get(0).getReadableBody(
115 mXmppConnectionService));
116 if (notify) {
117 mBuilder.setTicker(messages.get(messages.size() - 1)
118 .getReadableBody(mXmppConnectionService));
119 }
120 mBuilder.setContentIntent(createContentIntent(conversation
121 .getUuid()));
122 } else {
123 notificationManager.cancel(NOTIFICATION_ID);
124 return;
125 }
126 } else {
127 NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
128 style.setBigContentTitle(notifications.size()
129 + " "
130 + mXmppConnectionService
131 .getString(R.string.unread_conversations));
132 StringBuilder names = new StringBuilder();
133 Conversation conversation = null;
134 for (ArrayList<Message> messages : notifications.values()) {
135 if (messages.size() > 0) {
136 conversation = messages.get(0).getConversation();
137 String name = conversation.getName();
138 style.addLine(Html.fromHtml("<b>"
139 + name
140 + "</b> "
141 + messages.get(0).getReadableBody(
142 mXmppConnectionService)));
143 names.append(name);
144 names.append(", ");
145 }
146 }
147 if (names.length() >= 2) {
148 names.delete(names.length() - 2, names.length());
149 }
150 mBuilder.setContentTitle(notifications.size()
151 + " "
152 + mXmppConnectionService
153 .getString(R.string.unread_conversations));
154 mBuilder.setContentText(names.toString());
155 mBuilder.setStyle(style);
156 if (conversation != null) {
157 mBuilder.setContentIntent(createContentIntent(conversation
158 .getUuid()));
159 }
160 }
161 if (notify) {
162 if (vibrate) {
163 int dat = 70;
164 long[] pattern = { 0, 3 * dat, dat, dat };
165 mBuilder.setVibrate(pattern);
166 }
167 if (ringtone != null) {
168 mBuilder.setSound(Uri.parse(ringtone));
169 }
170 }
171 mBuilder.setDeleteIntent(createDeleteIntent());
172 mBuilder.setLights(0xffffffff, 2000, 4000);
173 Notification notification = mBuilder.build();
174 notificationManager.notify(NOTIFICATION_ID, notification);
175 }
176 }
177
178 private PendingIntent createContentIntent(String conversationUuid) {
179 TaskStackBuilder stackBuilder = TaskStackBuilder
180 .create(mXmppConnectionService);
181 stackBuilder.addParentStack(ConversationActivity.class);
182
183 Intent viewConversationIntent = new Intent(mXmppConnectionService,
184 ConversationActivity.class);
185 viewConversationIntent.setAction(Intent.ACTION_VIEW);
186 viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
187 conversationUuid);
188 viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
189
190 stackBuilder.addNextIntent(viewConversationIntent);
191
192 PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
193 PendingIntent.FLAG_UPDATE_CURRENT);
194 return resultPendingIntent;
195 }
196
197 private PendingIntent createDeleteIntent() {
198 Intent intent = new Intent(mXmppConnectionService,
199 XmppConnectionService.class);
200 intent.setAction("clear_notification");
201 return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
202 }
203
204 public static boolean wasHighlightedOrPrivate(Message message) {
205 String nick = message.getConversation().getMucOptions().getActualNick();
206 Pattern highlight = generateNickHighlightPattern(nick);
207 if (message.getBody() == null || nick == null) {
208 return false;
209 }
210 Matcher m = highlight.matcher(message.getBody());
211 return (m.find() || message.getType() == Message.TYPE_PRIVATE);
212 }
213
214 private static Pattern generateNickHighlightPattern(String nick) {
215 // We expect a word boundary, i.e. space or start of string, followed by
216 // the
217 // nick (matched in case-insensitive manner), followed by optional
218 // punctuation (for example "bob: i disagree" or "how are you alice?"),
219 // followed by another word boundary.
220 return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
221 Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
222 }
223
224 public void setOpenConversation(Conversation conversation) {
225 this.mOpenConversation = conversation;
226 }
227
228 public void setIsInForeground(boolean foreground) {
229 this.mIsInForeground = foreground;
230 }
231
232 private int getPixel(int dp) {
233 DisplayMetrics metrics = mXmppConnectionService.getResources()
234 .getDisplayMetrics();
235 return ((int) (dp * metrics.density));
236 }
237}