1package eu.siacs.conversations.services;
2
3import android.app.Notification;
4import android.app.PendingIntent;
5import android.content.Intent;
6import android.content.SharedPreferences;
7import android.graphics.Bitmap;
8import android.graphics.Typeface;
9import android.net.Uri;
10import android.os.Build;
11import android.os.SystemClock;
12import android.support.v4.app.NotificationCompat;
13import android.support.v4.app.NotificationCompat.BigPictureStyle;
14import android.support.v4.app.NotificationCompat.Builder;
15import android.support.v4.app.NotificationManagerCompat;
16import android.support.v4.app.RemoteInput;
17import android.text.Html;
18import android.text.SpannableString;
19import android.text.style.StyleSpan;
20import android.util.DisplayMetrics;
21import android.util.Log;
22
23import java.io.FileNotFoundException;
24import java.util.ArrayList;
25import java.util.Calendar;
26import java.util.LinkedHashMap;
27import java.util.List;
28import java.util.Map;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31
32import eu.siacs.conversations.Config;
33import eu.siacs.conversations.R;
34import eu.siacs.conversations.entities.Account;
35import eu.siacs.conversations.entities.Contact;
36import eu.siacs.conversations.entities.Conversation;
37import eu.siacs.conversations.entities.Message;
38import eu.siacs.conversations.ui.ConversationActivity;
39import eu.siacs.conversations.ui.ManageAccountActivity;
40import eu.siacs.conversations.ui.SettingsActivity;
41import eu.siacs.conversations.ui.TimePreference;
42import eu.siacs.conversations.utils.GeoHelper;
43import eu.siacs.conversations.utils.UIHelper;
44
45public class NotificationService {
46
47 private static final String CONVERSATIONS_GROUP = "eu.siacs.conversations";
48 private final XmppConnectionService mXmppConnectionService;
49
50 private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
51
52 public static final int NOTIFICATION_ID = 0x2342;
53 public static final int FOREGROUND_NOTIFICATION_ID = 0x8899;
54 public static final int ERROR_NOTIFICATION_ID = 0x5678;
55
56 private Conversation mOpenConversation;
57 private boolean mIsInForeground;
58 private long mLastNotification;
59
60 public NotificationService(final XmppConnectionService service) {
61 this.mXmppConnectionService = service;
62 }
63
64 public boolean notify(final Message message) {
65 return (message.getStatus() == Message.STATUS_RECEIVED)
66 && notificationsEnabled()
67 && !message.getConversation().isMuted()
68 && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message)
69 );
70 }
71
72 public boolean notificationsEnabled() {
73 return mXmppConnectionService.getPreferences().getBoolean("show_notification", true);
74 }
75
76 public boolean isQuietHours() {
77 if (!mXmppConnectionService.getPreferences().getBoolean("enable_quiet_hours", false)) {
78 return false;
79 }
80 final long startTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
81 final long endTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
82 final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY;
83
84 if (endTime < startTime) {
85 return nowTime > startTime || nowTime < endTime;
86 } else {
87 return nowTime > startTime && nowTime < endTime;
88 }
89 }
90
91 public void pushFromBacklog(final Message message) {
92 if (notify(message)) {
93 synchronized (notifications) {
94 pushToStack(message);
95 }
96 }
97 }
98
99 public void pushFromDirectReply(final Message message) {
100 synchronized (notifications) {
101 pushToStack(message);
102 updateNotification(false);
103 }
104 }
105
106 public void finishBacklog(boolean notify, Account account) {
107 synchronized (notifications) {
108 mXmppConnectionService.updateUnreadCountBadge();
109 if (account == null || !notify) {
110 updateNotification(notify);
111 } else {
112 boolean hasPendingMessages = false;
113 for(ArrayList<Message> messages : notifications.values()) {
114 if (messages.size() > 0 && messages.get(0).getConversation().getAccount() == account) {
115 hasPendingMessages = true;
116 break;
117 }
118 }
119 updateNotification(hasPendingMessages);
120 }
121 }
122 }
123
124 public void finishBacklog(boolean notify) {
125 finishBacklog(notify,null);
126 }
127
128 private void pushToStack(final Message message) {
129 final String conversationUuid = message.getConversationUuid();
130 if (notifications.containsKey(conversationUuid)) {
131 notifications.get(conversationUuid).add(message);
132 } else {
133 final ArrayList<Message> mList = new ArrayList<>();
134 mList.add(message);
135 notifications.put(conversationUuid, mList);
136 }
137 }
138
139 public void push(final Message message) {
140 mXmppConnectionService.updateUnreadCountBadge();
141 if (!notify(message)) {
142 Log.d(Config.LOGTAG,message.getConversation().getAccount().getJid().toBareJid()+": suppressing notification because turned off");
143 return;
144 }
145 final boolean isScreenOn = mXmppConnectionService.isInteractive();
146 if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
147 Log.d(Config.LOGTAG,message.getConversation().getAccount().getJid().toBareJid()+": suppressing notification because conversation is open");
148 return;
149 }
150 synchronized (notifications) {
151 pushToStack(message);
152 final Account account = message.getConversation().getAccount();
153 final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
154 && !account.inGracePeriod()
155 && !this.inMiniGracePeriod(account);
156 updateNotification(doNotify);
157 }
158 }
159
160 public void clear() {
161 synchronized (notifications) {
162 for(ArrayList<Message> messages : notifications.values()) {
163 markAsReadIfHasDirectReply(messages);
164 }
165 notifications.clear();
166 updateNotification(false);
167 }
168 }
169
170 public void clear(final Conversation conversation) {
171 synchronized (notifications) {
172 markAsReadIfHasDirectReply(conversation);
173 notifications.remove(conversation.getUuid());
174 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
175 notificationManager.cancel(conversation.getUuid(), NOTIFICATION_ID);
176 updateNotification(false);
177 }
178 }
179
180 private void markAsReadIfHasDirectReply(final Conversation conversation) {
181 markAsReadIfHasDirectReply(notifications.get(conversation.getUuid()));
182 }
183
184 private void markAsReadIfHasDirectReply(final ArrayList<Message> messages) {
185 if (messages != null && messages.size() > 0) {
186 Message last = messages.get(messages.size() - 1);
187 if (last.getStatus() != Message.STATUS_RECEIVED) {
188 mXmppConnectionService.markRead(last.getConversation(), false);
189 }
190 }
191 }
192
193 private void setNotificationColor(final Builder mBuilder) {
194 mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary500));
195 }
196
197 public void updateNotification(final boolean notify) {
198 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
199 final SharedPreferences preferences = mXmppConnectionService.getPreferences();
200
201 if (notifications.size() == 0) {
202 notificationManager.cancel(NOTIFICATION_ID);
203 } else {
204 if (notify) {
205 this.markLastNotification();
206 }
207 final Builder mBuilder;
208 if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
209 mBuilder = buildSingleConversations(notifications.values().iterator().next());
210 modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
211 notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
212 } else {
213 mBuilder = buildMultipleConversation();
214 modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
215 notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
216 for(Map.Entry<String,ArrayList<Message>> entry : notifications.entrySet()) {
217 Builder singleBuilder = buildSingleConversations(entry.getValue());
218 singleBuilder.setGroup(CONVERSATIONS_GROUP);
219 modifyForSoundVibrationAndLight(singleBuilder,notify,preferences);
220 notificationManager.notify(entry.getKey(), NOTIFICATION_ID ,singleBuilder.build());
221 }
222 }
223 }
224 }
225
226
227 private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, SharedPreferences preferences) {
228 final String ringtone = preferences.getString("notification_ringtone", null);
229 final boolean vibrate = preferences.getBoolean("vibrate_on_notification", true);
230 final boolean led = preferences.getBoolean("led", true);
231 if (notify && !isQuietHours()) {
232 if (vibrate) {
233 final int dat = 70;
234 final long[] pattern = {0, 3 * dat, dat, dat};
235 mBuilder.setVibrate(pattern);
236 } else {
237 mBuilder.setVibrate(new long[]{0});
238 }
239 if (ringtone != null) {
240 mBuilder.setSound(Uri.parse(ringtone));
241 }
242 }
243 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
244 mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
245 }
246 mBuilder.setPriority(notify ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_LOW);
247 setNotificationColor(mBuilder);
248 mBuilder.setDefaults(0);
249 if (led) {
250 mBuilder.setLights(0xff00FF00, 2000, 3000);
251 }
252 }
253
254 private Builder buildMultipleConversation() {
255 final Builder mBuilder = new NotificationCompat.Builder(
256 mXmppConnectionService);
257 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
258 style.setBigContentTitle(notifications.size()
259 + " "
260 + mXmppConnectionService
261 .getString(R.string.unread_conversations));
262 final StringBuilder names = new StringBuilder();
263 Conversation conversation = null;
264 for (final ArrayList<Message> messages : notifications.values()) {
265 if (messages.size() > 0) {
266 conversation = messages.get(0).getConversation();
267 final String name = conversation.getName();
268 SpannableString styledString;
269 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
270 int count = messages.size();
271 styledString = new SpannableString(name + ": " + mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count));
272 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
273 style.addLine(styledString);
274 } else {
275 styledString = new SpannableString(name + ": " + UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first);
276 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
277 style.addLine(styledString);
278 }
279 names.append(name);
280 names.append(", ");
281 }
282 }
283 if (names.length() >= 2) {
284 names.delete(names.length() - 2, names.length());
285 }
286 mBuilder.setContentTitle(notifications.size()
287 + " "
288 + mXmppConnectionService
289 .getString(R.string.unread_conversations));
290 mBuilder.setContentText(names.toString());
291 mBuilder.setStyle(style);
292 if (conversation != null) {
293 mBuilder.setContentIntent(createContentIntent(conversation));
294 }
295 mBuilder.setGroupSummary(true);
296 mBuilder.setGroup(CONVERSATIONS_GROUP);
297 mBuilder.setDeleteIntent(createDeleteIntent(null));
298 mBuilder.setSmallIcon(R.drawable.ic_notification);
299 return mBuilder;
300 }
301
302 private Builder buildSingleConversations(final ArrayList<Message> messages) {
303 final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
304 if (messages.size() >= 1) {
305 final Conversation conversation = messages.get(0).getConversation();
306 mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
307 .get(conversation, getPixel(64)));
308 mBuilder.setContentTitle(conversation.getName());
309 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
310 int count = messages.size();
311 mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count));
312 } else {
313 Message message;
314 if ((message = getImage(messages)) != null) {
315 modifyForImage(mBuilder, message, messages);
316 } else {
317 modifyForTextOnly(mBuilder, messages);
318 }
319 RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
320 NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation, false)).addRemoteInput(remoteInput).build();
321 NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build();
322 mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
323 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
324 mBuilder.addAction(replyAction);
325 }
326 if ((message = getFirstDownloadableMessage(messages)) != null) {
327 mBuilder.addAction(
328 Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
329 R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download,
330 mXmppConnectionService.getResources().getString(R.string.download_x_file,
331 UIHelper.getFileDescriptionString(mXmppConnectionService, message)),
332 createDownloadIntent(message)
333 );
334 }
335 if ((message = getFirstLocationMessage(messages)) != null) {
336 mBuilder.addAction(R.drawable.ic_room_white_24dp,
337 mXmppConnectionService.getString(R.string.show_location),
338 createShowLocationIntent(message));
339 }
340 }
341 if (conversation.getMode() == Conversation.MODE_SINGLE) {
342 Contact contact = conversation.getContact();
343 Uri systemAccount = contact.getSystemAccount();
344 if (systemAccount != null) {
345 mBuilder.addPerson(systemAccount.toString());
346 }
347 }
348 mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
349 mBuilder.setSmallIcon(R.drawable.ic_notification);
350 mBuilder.setDeleteIntent(createDeleteIntent(conversation));
351 mBuilder.setContentIntent(createContentIntent(conversation));
352 }
353 return mBuilder;
354 }
355
356 private void modifyForImage(final Builder builder, final Message message,
357 final ArrayList<Message> messages) {
358 try {
359 final Bitmap bitmap = mXmppConnectionService.getFileBackend()
360 .getThumbnail(message, getPixel(288), false);
361 final ArrayList<Message> tmp = new ArrayList<>();
362 for (final Message msg : messages) {
363 if (msg.getType() == Message.TYPE_TEXT
364 && msg.getTransferable() == null) {
365 tmp.add(msg);
366 }
367 }
368 final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
369 bigPictureStyle.bigPicture(bitmap);
370 if (tmp.size() > 0) {
371 CharSequence text = getMergedBodies(tmp);
372 bigPictureStyle.setSummaryText(text);
373 builder.setContentText(text);
374 } else {
375 builder.setContentText(mXmppConnectionService.getString(
376 R.string.received_x_file,
377 UIHelper.getFileDescriptionString(mXmppConnectionService, message)));
378 }
379 builder.setStyle(bigPictureStyle);
380 } catch (final FileNotFoundException e) {
381 modifyForTextOnly(builder, messages);
382 }
383 }
384
385 private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages) {
386 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
387 NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(mXmppConnectionService.getString(R.string.me));
388 Conversation conversation = messages.get(0).getConversation();
389 if (conversation.getMode() == Conversation.MODE_MULTI) {
390 messagingStyle.setConversationTitle(conversation.getName());
391 }
392 for (Message message : messages) {
393 String sender = message.getStatus() == Message.STATUS_RECEIVED ? UIHelper.getMessageDisplayName(message) : null;
394 messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService,message).first, message.getTimeSent(), sender);
395 }
396 builder.setStyle(messagingStyle);
397 } else {
398 if(messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
399 builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
400 builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get((messages.size() - 1))).first);
401 }
402 else {
403 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
404 SpannableString styledString;
405 for (Message message : messages) {
406 final String name = UIHelper.getMessageDisplayName(message);
407 styledString = new SpannableString(name + ": " + message.getBody());
408 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
409 style.addLine(styledString);
410 }
411 builder.setStyle(style);
412 if(messages.size() == 1) {
413 final String name = UIHelper.getMessageDisplayName(messages.get(0));
414 styledString = new SpannableString(name + ": " + messages.get(0).getBody());
415 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
416 builder.setContentText(styledString);
417 }
418 else {
419 builder.setContentText(messages.size() + " " + mXmppConnectionService.getString(R.string.unread_conversations));
420 }
421 }
422 }
423 }
424
425 private Message getImage(final Iterable<Message> messages) {
426 Message image = null;
427 for (final Message message : messages) {
428 if (message.getStatus() != Message.STATUS_RECEIVED) {
429 return null;
430 }
431 if (message.getType() != Message.TYPE_TEXT
432 && message.getTransferable() == null
433 && message.getEncryption() != Message.ENCRYPTION_PGP
434 && message.getFileParams().height > 0) {
435 image = message;
436 }
437 }
438 return image;
439 }
440
441 private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
442 for (final Message message : messages) {
443 if (message.getTransferable() != null
444 && (message.getType() == Message.TYPE_FILE
445 || message.getType() == Message.TYPE_IMAGE
446 || message.treatAsDownloadable() != Message.Decision.NEVER)) {
447 return message;
448 }
449 }
450 return null;
451 }
452
453 private Message getFirstLocationMessage(final Iterable<Message> messages) {
454 for (final Message message : messages) {
455 if (GeoHelper.isGeoUri(message.getBody())) {
456 return message;
457 }
458 }
459 return null;
460 }
461
462 private CharSequence getMergedBodies(final ArrayList<Message> messages) {
463 final StringBuilder text = new StringBuilder();
464 for (int i = 0; i < messages.size(); ++i) {
465 text.append(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(i)).first);
466 if (i != messages.size() - 1) {
467 text.append("\n");
468 }
469 }
470 return text.toString();
471 }
472
473 private PendingIntent createShowLocationIntent(final Message message) {
474 Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message);
475 for (Intent intent : intents) {
476 if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
477 return PendingIntent.getActivity(mXmppConnectionService, 18, intent, PendingIntent.FLAG_UPDATE_CURRENT);
478 }
479 }
480 return createOpenConversationsIntent();
481 }
482
483 private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
484 final Intent viewConversationIntent = new Intent(mXmppConnectionService,ConversationActivity.class);
485 viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION);
486 viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, conversationUuid);
487 if (downloadMessageUuid != null) {
488 viewConversationIntent.putExtra(ConversationActivity.EXTRA_DOWNLOAD_UUID, downloadMessageUuid);
489 return PendingIntent.getActivity(mXmppConnectionService,
490 conversationUuid.hashCode() % 389782,
491 viewConversationIntent,
492 PendingIntent.FLAG_UPDATE_CURRENT);
493 } else {
494 return PendingIntent.getActivity(mXmppConnectionService,
495 conversationUuid.hashCode() % 936236,
496 viewConversationIntent,
497 PendingIntent.FLAG_UPDATE_CURRENT);
498 }
499 }
500
501 private PendingIntent createDownloadIntent(final Message message) {
502 return createContentIntent(message.getConversationUuid(), message.getUuid());
503 }
504
505 private PendingIntent createContentIntent(final Conversation conversation) {
506 return createContentIntent(conversation.getUuid(), null);
507 }
508
509 private PendingIntent createDeleteIntent(Conversation conversation) {
510 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
511 intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
512 if (conversation != null) {
513 intent.putExtra("uuid", conversation.getUuid());
514 return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 247527, intent, 0);
515 }
516 return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
517 }
518
519 private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) {
520 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
521 intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
522 intent.putExtra("uuid",conversation.getUuid());
523 intent.putExtra("dismiss_notification",dismissAfterReply);
524 int id = conversation.getUuid().hashCode() % (dismissAfterReply ? 402359 : 426583);
525 return PendingIntent.getService(mXmppConnectionService, id, intent, 0);
526 }
527
528 private PendingIntent createDisableForeground() {
529 final Intent intent = new Intent(mXmppConnectionService,
530 XmppConnectionService.class);
531 intent.setAction(XmppConnectionService.ACTION_DISABLE_FOREGROUND);
532 return PendingIntent.getService(mXmppConnectionService, 34, intent, 0);
533 }
534
535 private PendingIntent createTryAgainIntent() {
536 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
537 intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
538 return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
539 }
540
541 private PendingIntent createDismissErrorIntent() {
542 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
543 intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS);
544 return PendingIntent.getService(mXmppConnectionService, 69, intent, 0);
545 }
546
547 private boolean wasHighlightedOrPrivate(final Message message) {
548 final String nick = message.getConversation().getMucOptions().getActualNick();
549 final Pattern highlight = generateNickHighlightPattern(nick);
550 if (message.getBody() == null || nick == null) {
551 return false;
552 }
553 final Matcher m = highlight.matcher(message.getBody());
554 return (m.find() || message.getType() == Message.TYPE_PRIVATE);
555 }
556
557 public static Pattern generateNickHighlightPattern(final String nick) {
558 // We expect a word boundary, i.e. space or start of string, followed by
559 // the
560 // nick (matched in case-insensitive manner), followed by optional
561 // punctuation (for example "bob: i disagree" or "how are you alice?"),
562 // followed by another word boundary.
563 return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
564 Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
565 }
566
567 public void setOpenConversation(final Conversation conversation) {
568 this.mOpenConversation = conversation;
569 }
570
571 public void setIsInForeground(final boolean foreground) {
572 this.mIsInForeground = foreground;
573 }
574
575 private int getPixel(final int dp) {
576 final DisplayMetrics metrics = mXmppConnectionService.getResources()
577 .getDisplayMetrics();
578 return ((int) (dp * metrics.density));
579 }
580
581 private void markLastNotification() {
582 this.mLastNotification = SystemClock.elapsedRealtime();
583 }
584
585 private boolean inMiniGracePeriod(final Account account) {
586 final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD
587 : Config.MINI_GRACE_PERIOD * 2;
588 return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
589 }
590
591 public Notification createForegroundNotification() {
592 final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
593
594 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service));
595 if (Config.SHOW_CONNECTED_ACCOUNTS) {
596 List<Account> accounts = mXmppConnectionService.getAccounts();
597 int enabled = 0;
598 int connected = 0;
599 for (Account account : accounts) {
600 if (account.isOnlineAndConnected()) {
601 connected++;
602 enabled++;
603 } else if (!account.isOptionSet(Account.OPTION_DISABLED)) {
604 enabled++;
605 }
606 }
607 mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
608 } else {
609 mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
610 }
611 mBuilder.setContentIntent(createOpenConversationsIntent());
612 mBuilder.setWhen(0);
613 mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN);
614 mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
615 if (Config.SHOW_DISABLE_FOREGROUND) {
616 final int cancelIcon;
617 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
618 mBuilder.setCategory(Notification.CATEGORY_SERVICE);
619 cancelIcon = R.drawable.ic_cancel_white_24dp;
620 } else {
621 cancelIcon = R.drawable.ic_action_cancel;
622 }
623 mBuilder.addAction(cancelIcon,
624 mXmppConnectionService.getString(R.string.disable_foreground_service),
625 createDisableForeground());
626 }
627 return mBuilder.build();
628 }
629
630 private PendingIntent createOpenConversationsIntent() {
631 return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationActivity.class), 0);
632 }
633
634 public void updateErrorNotification() {
635 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
636 final List<Account> errors = new ArrayList<>();
637 for (final Account account : mXmppConnectionService.getAccounts()) {
638 if (account.hasErrorStatus() && account.showErrorNotification()) {
639 errors.add(account);
640 }
641 }
642 if (mXmppConnectionService.getPreferences().getBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, false)) {
643 notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
644 }
645 final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
646 if (errors.size() == 0) {
647 notificationManager.cancel(ERROR_NOTIFICATION_ID);
648 return;
649 } else if (errors.size() == 1) {
650 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account));
651 mBuilder.setContentText(errors.get(0).getJid().toBareJid().toString());
652 } else {
653 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
654 mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
655 }
656 mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
657 mXmppConnectionService.getString(R.string.try_again),
658 createTryAgainIntent());
659 mBuilder.setDeleteIntent(createDismissErrorIntent());
660 mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
661 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
662 mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
663 } else {
664 mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
665 }
666 mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService,
667 145,
668 new Intent(mXmppConnectionService,ManageAccountActivity.class),
669 PendingIntent.FLAG_UPDATE_CURRENT));
670 notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build());
671 }
672}