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