1package eu.siacs.conversations.services;
2
3import android.app.Notification;
4import android.app.NotificationChannel;
5import android.app.NotificationChannelGroup;
6import android.app.NotificationManager;
7import android.app.PendingIntent;
8import android.content.Context;
9import android.content.Intent;
10import android.content.SharedPreferences;
11import android.content.res.Resources;
12import android.graphics.Bitmap;
13import android.graphics.Typeface;
14import android.media.AudioAttributes;
15import android.media.RingtoneManager;
16import android.net.Uri;
17import android.os.Build;
18import android.os.SystemClock;
19import android.preference.PreferenceManager;
20import android.support.annotation.RequiresApi;
21import android.support.v4.app.NotificationCompat;
22import android.support.v4.app.NotificationCompat.BigPictureStyle;
23import android.support.v4.app.NotificationCompat.Builder;
24import android.support.v4.app.NotificationManagerCompat;
25import android.support.v4.app.Person;
26import android.support.v4.app.RemoteInput;
27import android.support.v4.content.ContextCompat;
28import android.support.v4.graphics.drawable.IconCompat;
29import android.text.SpannableString;
30import android.text.style.StyleSpan;
31import android.util.DisplayMetrics;
32import android.util.Log;
33
34import java.io.File;
35import java.io.IOException;
36import java.util.ArrayList;
37import java.util.Calendar;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.Iterator;
41import java.util.LinkedHashMap;
42import java.util.List;
43import java.util.Map;
44import java.util.concurrent.atomic.AtomicInteger;
45import java.util.regex.Matcher;
46import java.util.regex.Pattern;
47
48import eu.siacs.conversations.Config;
49import eu.siacs.conversations.R;
50import eu.siacs.conversations.entities.Account;
51import eu.siacs.conversations.entities.Contact;
52import eu.siacs.conversations.entities.Conversation;
53import eu.siacs.conversations.entities.Conversational;
54import eu.siacs.conversations.entities.Message;
55import eu.siacs.conversations.persistance.FileBackend;
56import eu.siacs.conversations.ui.ConversationsActivity;
57import eu.siacs.conversations.ui.EditAccountActivity;
58import eu.siacs.conversations.ui.RtpSessionActivity;
59import eu.siacs.conversations.ui.TimePreference;
60import eu.siacs.conversations.utils.AccountUtils;
61import eu.siacs.conversations.utils.Compatibility;
62import eu.siacs.conversations.utils.GeoHelper;
63import eu.siacs.conversations.utils.UIHelper;
64import eu.siacs.conversations.xmpp.XmppConnection;
65import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
66
67public class NotificationService {
68
69 public static final Object CATCHUP_LOCK = new Object();
70
71 private static final int LED_COLOR = 0xff00ff00;
72
73 private static final String CONVERSATIONS_GROUP = "eu.siacs.conversations";
74 private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
75 static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
76 private static final int NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 2;
77 private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6;
78 private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
79 private static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
80 private final XmppConnectionService mXmppConnectionService;
81 private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
82 private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
83 private Conversation mOpenConversation;
84 private boolean mIsInForeground;
85 private long mLastNotification;
86
87 NotificationService(final XmppConnectionService service) {
88 this.mXmppConnectionService = service;
89 }
90
91 private static boolean displaySnoozeAction(List<Message> messages) {
92 int numberOfMessagesWithoutReply = 0;
93 for (Message message : messages) {
94 if (message.getStatus() == Message.STATUS_RECEIVED) {
95 ++numberOfMessagesWithoutReply;
96 } else {
97 return false;
98 }
99 }
100 return numberOfMessagesWithoutReply >= 3;
101 }
102
103 public static Pattern generateNickHighlightPattern(final String nick) {
104 return Pattern.compile("(?<=(^|\\s))" + Pattern.quote(nick) + "(?=\\s|$|\\p{Punct})");
105 }
106
107 private static boolean isImageMessage(Message message) {
108 return message.getType() != Message.TYPE_TEXT
109 && message.getTransferable() == null
110 && !message.isDeleted()
111 && message.getEncryption() != Message.ENCRYPTION_PGP
112 && message.getFileParams().height > 0;
113 }
114
115 @RequiresApi(api = Build.VERSION_CODES.O)
116 void initializeChannels() {
117 final Context c = mXmppConnectionService;
118 final NotificationManager notificationManager = c.getSystemService(NotificationManager.class);
119 if (notificationManager == null) {
120 return;
121 }
122
123 notificationManager.deleteNotificationChannel("export");
124
125 notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("status", c.getString(R.string.notification_group_status_information)));
126 notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("chats", c.getString(R.string.notification_group_messages)));
127 notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("calls", c.getString(R.string.notification_group_calls)));
128 final NotificationChannel foregroundServiceChannel = new NotificationChannel("foreground",
129 c.getString(R.string.foreground_service_channel_name),
130 NotificationManager.IMPORTANCE_MIN);
131 foregroundServiceChannel.setDescription(c.getString(R.string.foreground_service_channel_description));
132 foregroundServiceChannel.setShowBadge(false);
133 foregroundServiceChannel.setGroup("status");
134 notificationManager.createNotificationChannel(foregroundServiceChannel);
135 final NotificationChannel errorChannel = new NotificationChannel("error",
136 c.getString(R.string.error_channel_name),
137 NotificationManager.IMPORTANCE_LOW);
138 errorChannel.setDescription(c.getString(R.string.error_channel_description));
139 errorChannel.setShowBadge(false);
140 errorChannel.setGroup("status");
141 notificationManager.createNotificationChannel(errorChannel);
142
143 final NotificationChannel videoCompressionChannel = new NotificationChannel("compression",
144 c.getString(R.string.video_compression_channel_name),
145 NotificationManager.IMPORTANCE_LOW);
146 videoCompressionChannel.setShowBadge(false);
147 videoCompressionChannel.setGroup("status");
148 notificationManager.createNotificationChannel(videoCompressionChannel);
149
150 final NotificationChannel exportChannel = new NotificationChannel("backup",
151 c.getString(R.string.backup_channel_name),
152 NotificationManager.IMPORTANCE_LOW);
153 exportChannel.setShowBadge(false);
154 exportChannel.setGroup("status");
155 notificationManager.createNotificationChannel(exportChannel);
156
157 final NotificationChannel incomingCallsChannel = new NotificationChannel("incoming_calls",
158 c.getString(R.string.incoming_calls_channel_name),
159 NotificationManager.IMPORTANCE_HIGH);
160 incomingCallsChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), new AudioAttributes.Builder()
161 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
162 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
163 .build());
164 incomingCallsChannel.setShowBadge(false);
165 incomingCallsChannel.setLightColor(LED_COLOR);
166 incomingCallsChannel.enableLights(true);
167 incomingCallsChannel.setGroup("calls");
168 notificationManager.createNotificationChannel(incomingCallsChannel);
169
170 final NotificationChannel ongoingCallsChannel = new NotificationChannel("ongoing_calls",
171 c.getString(R.string.ongoing_calls_channel_name),
172 NotificationManager.IMPORTANCE_LOW);
173 ongoingCallsChannel.setShowBadge(false);
174 ongoingCallsChannel.setGroup("calls");
175 notificationManager.createNotificationChannel(ongoingCallsChannel);
176
177
178 final NotificationChannel messagesChannel = new NotificationChannel("messages",
179 c.getString(R.string.messages_channel_name),
180 NotificationManager.IMPORTANCE_HIGH);
181 messagesChannel.setShowBadge(true);
182 messagesChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), new AudioAttributes.Builder()
183 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
184 .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
185 .build());
186 messagesChannel.setLightColor(LED_COLOR);
187 final int dat = 70;
188 final long[] pattern = {0, 3 * dat, dat, dat};
189 messagesChannel.setVibrationPattern(pattern);
190 messagesChannel.enableVibration(true);
191 messagesChannel.enableLights(true);
192 messagesChannel.setGroup("chats");
193 notificationManager.createNotificationChannel(messagesChannel);
194 final NotificationChannel silentMessagesChannel = new NotificationChannel("silent_messages",
195 c.getString(R.string.silent_messages_channel_name),
196 NotificationManager.IMPORTANCE_LOW);
197 silentMessagesChannel.setDescription(c.getString(R.string.silent_messages_channel_description));
198 silentMessagesChannel.setShowBadge(true);
199 silentMessagesChannel.setLightColor(LED_COLOR);
200 silentMessagesChannel.enableLights(true);
201 silentMessagesChannel.setGroup("chats");
202 notificationManager.createNotificationChannel(silentMessagesChannel);
203
204 final NotificationChannel quietHoursChannel = new NotificationChannel("quiet_hours",
205 c.getString(R.string.title_pref_quiet_hours),
206 NotificationManager.IMPORTANCE_LOW);
207 quietHoursChannel.setShowBadge(true);
208 quietHoursChannel.setLightColor(LED_COLOR);
209 quietHoursChannel.enableLights(true);
210 quietHoursChannel.setGroup("chats");
211 quietHoursChannel.enableVibration(false);
212 quietHoursChannel.setSound(null, null);
213
214 notificationManager.createNotificationChannel(quietHoursChannel);
215 }
216
217 public boolean notify(final Message message) {
218 final Conversation conversation = (Conversation) message.getConversation();
219 return message.getStatus() == Message.STATUS_RECEIVED
220 && !conversation.isMuted()
221 && (conversation.alwaysNotify() || wasHighlightedOrPrivate(message))
222 && (!conversation.isWithStranger() || notificationsFromStrangers());
223 }
224
225 private boolean notificationsFromStrangers() {
226 return mXmppConnectionService.getBooleanPreference("notifications_from_strangers", R.bool.notifications_from_strangers);
227 }
228
229 private boolean isQuietHours() {
230 if (!mXmppConnectionService.getBooleanPreference("enable_quiet_hours", R.bool.enable_quiet_hours)) {
231 return false;
232 }
233 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
234 final long startTime = TimePreference.minutesToTimestamp(preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE));
235 final long endTime = TimePreference.minutesToTimestamp(preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE));
236 final long nowTime = Calendar.getInstance().getTimeInMillis();
237
238 if (endTime < startTime) {
239 return nowTime > startTime || nowTime < endTime;
240 } else {
241 return nowTime > startTime && nowTime < endTime;
242 }
243 }
244
245 public void pushFromBacklog(final Message message) {
246 if (notify(message)) {
247 synchronized (notifications) {
248 getBacklogMessageCounter((Conversation) message.getConversation()).incrementAndGet();
249 pushToStack(message);
250 }
251 }
252 }
253
254 private AtomicInteger getBacklogMessageCounter(Conversation conversation) {
255 synchronized (mBacklogMessageCounter) {
256 if (!mBacklogMessageCounter.containsKey(conversation)) {
257 mBacklogMessageCounter.put(conversation, new AtomicInteger(0));
258 }
259 return mBacklogMessageCounter.get(conversation);
260 }
261 }
262
263 void pushFromDirectReply(final Message message) {
264 synchronized (notifications) {
265 pushToStack(message);
266 updateNotification(false);
267 }
268 }
269
270 public void finishBacklog(boolean notify, Account account) {
271 synchronized (notifications) {
272 mXmppConnectionService.updateUnreadCountBadge();
273 if (account == null || !notify) {
274 updateNotification(notify);
275 } else {
276 final int count;
277 final List<String> conversations;
278 synchronized (this.mBacklogMessageCounter) {
279 conversations = getBacklogConversations(account);
280 count = getBacklogMessageCount(account);
281 }
282 updateNotification(count > 0, conversations);
283 }
284 }
285 }
286
287 private List<String> getBacklogConversations(Account account) {
288 final List<String> conversations = new ArrayList<>();
289 for (Map.Entry<Conversation, AtomicInteger> entry : mBacklogMessageCounter.entrySet()) {
290 if (entry.getKey().getAccount() == account) {
291 conversations.add(entry.getKey().getUuid());
292 }
293 }
294 return conversations;
295 }
296
297 private int getBacklogMessageCount(Account account) {
298 int count = 0;
299 for (Iterator<Map.Entry<Conversation, AtomicInteger>> it = mBacklogMessageCounter.entrySet().iterator(); it.hasNext(); ) {
300 Map.Entry<Conversation, AtomicInteger> entry = it.next();
301 if (entry.getKey().getAccount() == account) {
302 count += entry.getValue().get();
303 it.remove();
304 }
305 }
306 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": backlog message count=" + count);
307 return count;
308 }
309
310 void finishBacklog(boolean notify) {
311 finishBacklog(notify, null);
312 }
313
314 private void pushToStack(final Message message) {
315 final String conversationUuid = message.getConversationUuid();
316 if (notifications.containsKey(conversationUuid)) {
317 notifications.get(conversationUuid).add(message);
318 } else {
319 final ArrayList<Message> mList = new ArrayList<>();
320 mList.add(message);
321 notifications.put(conversationUuid, mList);
322 }
323 }
324
325 public void push(final Message message) {
326 synchronized (CATCHUP_LOCK) {
327 final XmppConnection connection = message.getConversation().getAccount().getXmppConnection();
328 if (connection != null && connection.isWaitingForSmCatchup()) {
329 connection.incrementSmCatchupMessageCounter();
330 pushFromBacklog(message);
331 } else {
332 pushNow(message);
333 }
334 }
335 }
336
337 public void showIncomingCallNotification(AbstractJingleConnection.Id id) {
338 final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
339 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
340 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
341 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
342 fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
343 fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
344 final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "incoming_calls");
345 builder.setSmallIcon(R.drawable.ic_call_white_24dp);
346 builder.setContentTitle(mXmppConnectionService.getString(R.string.rtp_state_incoming_call));
347 builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
348 builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
349 builder.setPriority(NotificationCompat.PRIORITY_HIGH);
350 builder.setCategory(NotificationCompat.CATEGORY_CALL);
351 builder.setFullScreenIntent(createPendingRtpSession(id, Intent.ACTION_VIEW, 101), true);
352 builder.setOngoing(true);
353 builder.addAction(new NotificationCompat.Action.Builder(
354 R.drawable.ic_call_end_white_48dp,
355 mXmppConnectionService.getString(R.string.dismiss_call),
356 createCallAction(id.sessionId, XmppConnectionService.ACTION_DISMISS_CALL, 102))
357 .build());
358 builder.addAction(new NotificationCompat.Action.Builder(
359 R.drawable.ic_call_white_24dp,
360 mXmppConnectionService.getString(R.string.answer_call),
361 createPendingRtpSession(id, RtpSessionActivity.ACTION_ACCEPT_CALL, 103))
362 .build());
363 final Notification notification = builder.build();
364 notification.flags = notification.flags | Notification.FLAG_INSISTENT;
365 notify(INCOMING_CALL_NOTIFICATION_ID, builder.build());
366 }
367
368 public void showOngoingCallNotification(final AbstractJingleConnection.Id id) {
369 final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls");
370 builder.setSmallIcon(R.drawable.ic_call_white_24dp);
371 builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
372 builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
373 builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
374 builder.setPriority(NotificationCompat.PRIORITY_HIGH);
375 builder.setCategory(NotificationCompat.CATEGORY_CALL);
376 builder.setContentIntent(createPendingRtpSession(id, Intent.ACTION_VIEW, 101));
377 builder.setOngoing(true);
378 builder.addAction(new NotificationCompat.Action.Builder(
379 R.drawable.ic_call_end_white_48dp,
380 mXmppConnectionService.getString(R.string.hang_up),
381 createCallAction(id.sessionId, XmppConnectionService.ACTION_END_CALL, 104))
382 .build());
383 final Notification notification = builder.build();
384 notification.flags = notification.flags | Notification.FLAG_INSISTENT;
385 notify(ONGOING_CALL_NOTIFICATION_ID, builder.build());
386 }
387
388 private PendingIntent createPendingRtpSession(final AbstractJingleConnection.Id id, final String action, final int requestCode) {
389 final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
390 fullScreenIntent.setAction(action);
391 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
392 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
393 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
394 //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
395 //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
396 return PendingIntent.getActivity(mXmppConnectionService, requestCode, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
397 }
398
399 public void cancelIncomingCallNotification() {
400 cancel(INCOMING_CALL_NOTIFICATION_ID);
401 }
402
403 public void cancelOngoingCallNotification() {
404 cancel(ONGOING_CALL_NOTIFICATION_ID);
405 }
406
407 private void pushNow(final Message message) {
408 mXmppConnectionService.updateUnreadCountBadge();
409 if (!notify(message)) {
410 Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing notification because turned off");
411 return;
412 }
413 final boolean isScreenOn = mXmppConnectionService.isInteractive();
414 if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
415 Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing notification because conversation is open");
416 return;
417 }
418 synchronized (notifications) {
419 pushToStack(message);
420 final Conversational conversation = message.getConversation();
421 final Account account = conversation.getAccount();
422 final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
423 && !account.inGracePeriod()
424 && !this.inMiniGracePeriod(account);
425 updateNotification(doNotify, Collections.singletonList(conversation.getUuid()));
426 }
427 }
428
429 public void clear() {
430 synchronized (notifications) {
431 for (ArrayList<Message> messages : notifications.values()) {
432 markAsReadIfHasDirectReply(messages);
433 }
434 notifications.clear();
435 updateNotification(false);
436 }
437 }
438
439 public void clear(final Conversation conversation) {
440 synchronized (this.mBacklogMessageCounter) {
441 this.mBacklogMessageCounter.remove(conversation);
442 }
443 synchronized (notifications) {
444 markAsReadIfHasDirectReply(conversation);
445 if (notifications.remove(conversation.getUuid()) != null) {
446 cancel(conversation.getUuid(), NOTIFICATION_ID);
447 updateNotification(false, null, true);
448 }
449 }
450 }
451
452 private void markAsReadIfHasDirectReply(final Conversation conversation) {
453 markAsReadIfHasDirectReply(notifications.get(conversation.getUuid()));
454 }
455
456 private void markAsReadIfHasDirectReply(final ArrayList<Message> messages) {
457 if (messages != null && messages.size() > 0) {
458 Message last = messages.get(messages.size() - 1);
459 if (last.getStatus() != Message.STATUS_RECEIVED) {
460 if (mXmppConnectionService.markRead((Conversation) last.getConversation(), false)) {
461 mXmppConnectionService.updateConversationUi();
462 }
463 }
464 }
465 }
466
467 private void setNotificationColor(final Builder mBuilder) {
468 mBuilder.setColor(ContextCompat.getColor(mXmppConnectionService, R.color.green600));
469 }
470
471 public void updateNotification() {
472 synchronized (notifications) {
473 updateNotification(false);
474 }
475 }
476
477 private void updateNotification(final boolean notify) {
478 updateNotification(notify, null, false);
479 }
480
481 private void updateNotification(final boolean notify, final List<String> conversations) {
482 updateNotification(notify, conversations, false);
483 }
484
485 private void updateNotification(final boolean notify, final List<String> conversations, final boolean summaryOnly) {
486 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
487
488 final boolean quiteHours = isQuietHours();
489
490 final boolean notifyOnlyOneChild = notify && conversations != null && conversations.size() == 1; //if this check is changed to > 0 catchup messages will create one notification per conversation
491
492
493 if (notifications.size() == 0) {
494 cancel(NOTIFICATION_ID);
495 } else {
496 if (notify) {
497 this.markLastNotification();
498 }
499 final Builder mBuilder;
500 if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
501 mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify, quiteHours);
502 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
503 notify(NOTIFICATION_ID, mBuilder.build());
504 } else {
505 mBuilder = buildMultipleConversation(notify, quiteHours);
506 if (notifyOnlyOneChild) {
507 mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
508 }
509 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
510 if (!summaryOnly) {
511 for (Map.Entry<String, ArrayList<Message>> entry : notifications.entrySet()) {
512 String uuid = entry.getKey();
513 final boolean notifyThis = notifyOnlyOneChild ? conversations.contains(uuid) : notify;
514 Builder singleBuilder = buildSingleConversations(entry.getValue(), notifyThis, quiteHours);
515 if (!notifyOnlyOneChild) {
516 singleBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
517 }
518 modifyForSoundVibrationAndLight(singleBuilder, notifyThis, quiteHours, preferences);
519 singleBuilder.setGroup(CONVERSATIONS_GROUP);
520 setNotificationColor(singleBuilder);
521 notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
522 }
523 }
524 notify(NOTIFICATION_ID, mBuilder.build());
525 }
526 }
527 }
528
529 private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) {
530 final Resources resources = mXmppConnectionService.getResources();
531 final String ringtone = preferences.getString("notification_ringtone", resources.getString(R.string.notification_ringtone));
532 final boolean vibrate = preferences.getBoolean("vibrate_on_notification", resources.getBoolean(R.bool.vibrate_on_notification));
533 final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
534 final boolean headsup = preferences.getBoolean("notification_headsup", resources.getBoolean(R.bool.headsup_notifications));
535 if (notify && !quietHours) {
536 if (vibrate) {
537 final int dat = 70;
538 final long[] pattern = {0, 3 * dat, dat, dat};
539 mBuilder.setVibrate(pattern);
540 } else {
541 mBuilder.setVibrate(new long[]{0});
542 }
543 Uri uri = Uri.parse(ringtone);
544 try {
545 mBuilder.setSound(fixRingtoneUri(uri));
546 } catch (SecurityException e) {
547 Log.d(Config.LOGTAG, "unable to use custom notification sound " + uri.toString());
548 }
549 } else {
550 mBuilder.setLocalOnly(true);
551 }
552 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
553 mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
554 }
555 mBuilder.setPriority(notify ? (headsup ? NotificationCompat.PRIORITY_HIGH : NotificationCompat.PRIORITY_DEFAULT) : NotificationCompat.PRIORITY_LOW);
556 setNotificationColor(mBuilder);
557 mBuilder.setDefaults(0);
558 if (led) {
559 mBuilder.setLights(LED_COLOR, 2000, 3000);
560 }
561 }
562
563 private Uri fixRingtoneUri(Uri uri) {
564 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && "file".equals(uri.getScheme())) {
565 return FileBackend.getUriForFile(mXmppConnectionService, new File(uri.getPath()));
566 } else {
567 return uri;
568 }
569 }
570
571 private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
572 final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
573 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
574 style.setBigContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
575 final StringBuilder names = new StringBuilder();
576 Conversation conversation = null;
577 for (final ArrayList<Message> messages : notifications.values()) {
578 if (messages.size() > 0) {
579 conversation = (Conversation) messages.get(0).getConversation();
580 final String name = conversation.getName().toString();
581 SpannableString styledString;
582 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
583 int count = messages.size();
584 styledString = new SpannableString(name + ": " + mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
585 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
586 style.addLine(styledString);
587 } else {
588 styledString = new SpannableString(name + ": " + UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first);
589 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
590 style.addLine(styledString);
591 }
592 names.append(name);
593 names.append(", ");
594 }
595 }
596 if (names.length() >= 2) {
597 names.delete(names.length() - 2, names.length());
598 }
599 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
600 mBuilder.setTicker(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
601 mBuilder.setContentText(names.toString());
602 mBuilder.setStyle(style);
603 if (conversation != null) {
604 mBuilder.setContentIntent(createContentIntent(conversation));
605 }
606 mBuilder.setGroupSummary(true);
607 mBuilder.setGroup(CONVERSATIONS_GROUP);
608 mBuilder.setDeleteIntent(createDeleteIntent(null));
609 mBuilder.setSmallIcon(R.drawable.ic_notification);
610 return mBuilder;
611 }
612
613 private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify, final boolean quietHours) {
614 final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
615 if (messages.size() >= 1) {
616 final Conversation conversation = (Conversation) messages.get(0).getConversation();
617 mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
618 .get(conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
619 mBuilder.setContentTitle(conversation.getName());
620 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
621 int count = messages.size();
622 mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
623 } else {
624 Message message;
625 //TODO starting with Android 9 we might want to put images in MessageStyle
626 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && (message = getImage(messages)) != null) {
627 modifyForImage(mBuilder, message, messages);
628 } else {
629 modifyForTextOnly(mBuilder, messages);
630 }
631 RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
632 PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
633 NotificationCompat.Action markReadAction = new NotificationCompat.Action.Builder(
634 R.drawable.ic_drafts_white_24dp,
635 mXmppConnectionService.getString(R.string.mark_as_read),
636 markAsReadPendingIntent)
637 .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
638 .setShowsUserInterface(false)
639 .build();
640 String replyLabel = mXmppConnectionService.getString(R.string.reply);
641 NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
642 R.drawable.ic_send_text_offline,
643 replyLabel,
644 createReplyIntent(conversation, false))
645 .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
646 .setShowsUserInterface(false)
647 .addRemoteInput(remoteInput).build();
648 NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
649 replyLabel,
650 createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build();
651 mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
652 int addedActionsCount = 1;
653 mBuilder.addAction(markReadAction);
654 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
655 mBuilder.addAction(replyAction);
656 ++addedActionsCount;
657 }
658
659 if (displaySnoozeAction(messages)) {
660 String label = mXmppConnectionService.getString(R.string.snooze);
661 PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
662 NotificationCompat.Action snoozeAction = new NotificationCompat.Action.Builder(
663 R.drawable.ic_notifications_paused_white_24dp,
664 label,
665 pendingSnoozeIntent).build();
666 mBuilder.addAction(snoozeAction);
667 ++addedActionsCount;
668 }
669 if (addedActionsCount < 3) {
670 final Message firstLocationMessage = getFirstLocationMessage(messages);
671 if (firstLocationMessage != null) {
672 final PendingIntent pendingShowLocationIntent = createShowLocationIntent(firstLocationMessage);
673 if (pendingShowLocationIntent != null) {
674 final String label = mXmppConnectionService.getResources().getString(R.string.show_location);
675 NotificationCompat.Action locationAction = new NotificationCompat.Action.Builder(
676 R.drawable.ic_room_white_24dp,
677 label,
678 pendingShowLocationIntent).build();
679 mBuilder.addAction(locationAction);
680 ++addedActionsCount;
681 }
682 }
683 }
684 if (addedActionsCount < 3) {
685 Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
686 if (firstDownloadableMessage != null) {
687 String label = mXmppConnectionService.getResources().getString(R.string.download_x_file, UIHelper.getFileDescriptionString(mXmppConnectionService, firstDownloadableMessage));
688 PendingIntent pendingDownloadIntent = createDownloadIntent(firstDownloadableMessage);
689 NotificationCompat.Action downloadAction = new NotificationCompat.Action.Builder(
690 R.drawable.ic_file_download_white_24dp,
691 label,
692 pendingDownloadIntent).build();
693 mBuilder.addAction(downloadAction);
694 ++addedActionsCount;
695 }
696 }
697 }
698 if (conversation.getMode() == Conversation.MODE_SINGLE) {
699 Contact contact = conversation.getContact();
700 Uri systemAccount = contact.getSystemAccount();
701 if (systemAccount != null) {
702 mBuilder.addPerson(systemAccount.toString());
703 }
704 }
705 mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
706 mBuilder.setSmallIcon(R.drawable.ic_notification);
707 mBuilder.setDeleteIntent(createDeleteIntent(conversation));
708 mBuilder.setContentIntent(createContentIntent(conversation));
709 }
710 return mBuilder;
711 }
712
713 private void modifyForImage(final Builder builder, final Message message, final ArrayList<Message> messages) {
714 try {
715 final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(message, getPixel(288), false);
716 final ArrayList<Message> tmp = new ArrayList<>();
717 for (final Message msg : messages) {
718 if (msg.getType() == Message.TYPE_TEXT
719 && msg.getTransferable() == null) {
720 tmp.add(msg);
721 }
722 }
723 final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
724 bigPictureStyle.bigPicture(bitmap);
725 if (tmp.size() > 0) {
726 CharSequence text = getMergedBodies(tmp);
727 bigPictureStyle.setSummaryText(text);
728 builder.setContentText(text);
729 builder.setTicker(text);
730 } else {
731 final String description = UIHelper.getFileDescriptionString(mXmppConnectionService, message);
732 builder.setContentText(description);
733 builder.setTicker(description);
734 }
735 builder.setStyle(bigPictureStyle);
736 } catch (final IOException e) {
737 modifyForTextOnly(builder, messages);
738 }
739 }
740
741 private Person getPerson(Message message) {
742 final Contact contact = message.getContact();
743 final Person.Builder builder = new Person.Builder();
744 if (contact != null) {
745 builder.setName(contact.getDisplayName());
746 final Uri uri = contact.getSystemAccount();
747 if (uri != null) {
748 builder.setUri(uri.toString());
749 }
750 } else {
751 builder.setName(UIHelper.getMessageDisplayName(message));
752 }
753 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
754 builder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(message, AvatarService.getSystemUiAvatarSize(mXmppConnectionService), false)));
755 }
756 return builder.build();
757 }
758
759 private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages) {
760 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
761 final Conversation conversation = (Conversation) messages.get(0).getConversation();
762 final Person.Builder meBuilder = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me));
763 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
764 meBuilder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation.getAccount(), AvatarService.getSystemUiAvatarSize(mXmppConnectionService))));
765 }
766 final Person me = meBuilder.build();
767 NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me);
768 final boolean multiple = conversation.getMode() == Conversation.MODE_MULTI;
769 if (multiple) {
770 messagingStyle.setConversationTitle(conversation.getName());
771 }
772 for (Message message : messages) {
773 final Person sender = message.getStatus() == Message.STATUS_RECEIVED ? getPerson(message) : null;
774 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isImageMessage(message)) {
775 final Uri dataUri = FileBackend.getMediaUri(mXmppConnectionService, mXmppConnectionService.getFileBackend().getFile(message));
776 NotificationCompat.MessagingStyle.Message imageMessage = new NotificationCompat.MessagingStyle.Message(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
777 if (dataUri != null) {
778 imageMessage.setData(message.getMimeType(), dataUri);
779 }
780 messagingStyle.addMessage(imageMessage);
781 } else {
782 messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
783 }
784 }
785 messagingStyle.setGroupConversation(multiple);
786 builder.setStyle(messagingStyle);
787 } else {
788 if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
789 builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
790 final CharSequence preview = UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size() - 1)).first;
791 builder.setContentText(preview);
792 builder.setTicker(preview);
793 builder.setNumber(messages.size());
794 } else {
795 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
796 SpannableString styledString;
797 for (Message message : messages) {
798 final String name = UIHelper.getMessageDisplayName(message);
799 styledString = new SpannableString(name + ": " + message.getBody());
800 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
801 style.addLine(styledString);
802 }
803 builder.setStyle(style);
804 int count = messages.size();
805 if (count == 1) {
806 final String name = UIHelper.getMessageDisplayName(messages.get(0));
807 styledString = new SpannableString(name + ": " + messages.get(0).getBody());
808 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
809 builder.setContentText(styledString);
810 builder.setTicker(styledString);
811 } else {
812 final String text = mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count);
813 builder.setContentText(text);
814 builder.setTicker(text);
815 }
816 }
817 }
818 }
819
820 private Message getImage(final Iterable<Message> messages) {
821 Message image = null;
822 for (final Message message : messages) {
823 if (message.getStatus() != Message.STATUS_RECEIVED) {
824 return null;
825 }
826 if (isImageMessage(message)) {
827 image = message;
828 }
829 }
830 return image;
831 }
832
833 private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
834 for (final Message message : messages) {
835 if (message.getTransferable() != null || (message.getType() == Message.TYPE_TEXT && message.treatAsDownloadable())) {
836 return message;
837 }
838 }
839 return null;
840 }
841
842 private Message getFirstLocationMessage(final Iterable<Message> messages) {
843 for (final Message message : messages) {
844 if (message.isGeoUri()) {
845 return message;
846 }
847 }
848 return null;
849 }
850
851 private CharSequence getMergedBodies(final ArrayList<Message> messages) {
852 final StringBuilder text = new StringBuilder();
853 for (Message message : messages) {
854 if (text.length() != 0) {
855 text.append("\n");
856 }
857 text.append(UIHelper.getMessagePreview(mXmppConnectionService, message).first);
858 }
859 return text.toString();
860 }
861
862 private PendingIntent createShowLocationIntent(final Message message) {
863 Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(mXmppConnectionService, message);
864 for (Intent intent : intents) {
865 if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
866 return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT);
867 }
868 }
869 return null;
870 }
871
872 private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
873 final Intent viewConversationIntent = new Intent(mXmppConnectionService, ConversationsActivity.class);
874 viewConversationIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
875 viewConversationIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversationUuid);
876 if (downloadMessageUuid != null) {
877 viewConversationIntent.putExtra(ConversationsActivity.EXTRA_DOWNLOAD_UUID, downloadMessageUuid);
878 return PendingIntent.getActivity(mXmppConnectionService,
879 generateRequestCode(conversationUuid, 8),
880 viewConversationIntent,
881 PendingIntent.FLAG_UPDATE_CURRENT);
882 } else {
883 return PendingIntent.getActivity(mXmppConnectionService,
884 generateRequestCode(conversationUuid, 10),
885 viewConversationIntent,
886 PendingIntent.FLAG_UPDATE_CURRENT);
887 }
888 }
889
890 private int generateRequestCode(String uuid, int actionId) {
891 return (actionId * NOTIFICATION_ID_MULTIPLIER) + (uuid.hashCode() % NOTIFICATION_ID_MULTIPLIER);
892 }
893
894 private int generateRequestCode(Conversational conversation, int actionId) {
895 return generateRequestCode(conversation.getUuid(), actionId);
896 }
897
898 private PendingIntent createDownloadIntent(final Message message) {
899 return createContentIntent(message.getConversationUuid(), message.getUuid());
900 }
901
902 private PendingIntent createContentIntent(final Conversational conversation) {
903 return createContentIntent(conversation.getUuid(), null);
904 }
905
906 private PendingIntent createDeleteIntent(Conversation conversation) {
907 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
908 intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
909 if (conversation != null) {
910 intent.putExtra("uuid", conversation.getUuid());
911 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 20), intent, 0);
912 }
913 return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
914 }
915
916 private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) {
917 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
918 intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
919 intent.putExtra("uuid", conversation.getUuid());
920 intent.putExtra("dismiss_notification", dismissAfterReply);
921 final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14);
922 return PendingIntent.getService(mXmppConnectionService, id, intent, 0);
923 }
924
925 private PendingIntent createReadPendingIntent(Conversation conversation) {
926 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
927 intent.setAction(XmppConnectionService.ACTION_MARK_AS_READ);
928 intent.putExtra("uuid", conversation.getUuid());
929 intent.setPackage(mXmppConnectionService.getPackageName());
930 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 16), intent, PendingIntent.FLAG_UPDATE_CURRENT);
931 }
932
933 private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
934 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
935 intent.setAction(action);
936 intent.setPackage(mXmppConnectionService.getPackageName());
937 intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId);
938 return PendingIntent.getService(mXmppConnectionService, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
939 }
940
941 private PendingIntent createSnoozeIntent(Conversation conversation) {
942 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
943 intent.setAction(XmppConnectionService.ACTION_SNOOZE);
944 intent.putExtra("uuid", conversation.getUuid());
945 intent.setPackage(mXmppConnectionService.getPackageName());
946 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 22), intent, PendingIntent.FLAG_UPDATE_CURRENT);
947 }
948
949 private PendingIntent createTryAgainIntent() {
950 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
951 intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
952 return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
953 }
954
955 private PendingIntent createDismissErrorIntent() {
956 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
957 intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS);
958 return PendingIntent.getService(mXmppConnectionService, 69, intent, 0);
959 }
960
961 private boolean wasHighlightedOrPrivate(final Message message) {
962 if (message.getConversation() instanceof Conversation) {
963 Conversation conversation = (Conversation) message.getConversation();
964 final String nick = conversation.getMucOptions().getActualNick();
965 final Pattern highlight = generateNickHighlightPattern(nick);
966 if (message.getBody() == null || nick == null) {
967 return false;
968 }
969 final Matcher m = highlight.matcher(message.getBody());
970 return (m.find() || message.isPrivateMessage());
971 } else {
972 return false;
973 }
974 }
975
976 public void setOpenConversation(final Conversation conversation) {
977 this.mOpenConversation = conversation;
978 }
979
980 public void setIsInForeground(final boolean foreground) {
981 this.mIsInForeground = foreground;
982 }
983
984 private int getPixel(final int dp) {
985 final DisplayMetrics metrics = mXmppConnectionService.getResources()
986 .getDisplayMetrics();
987 return ((int) (dp * metrics.density));
988 }
989
990 private void markLastNotification() {
991 this.mLastNotification = SystemClock.elapsedRealtime();
992 }
993
994 private boolean inMiniGracePeriod(final Account account) {
995 final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD
996 : Config.MINI_GRACE_PERIOD * 2;
997 return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
998 }
999
1000 Notification createForegroundNotification() {
1001 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1002 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
1003 final List<Account> accounts = mXmppConnectionService.getAccounts();
1004 int enabled = 0;
1005 int connected = 0;
1006 if (accounts != null) {
1007 for (Account account : accounts) {
1008 if (account.isOnlineAndConnected()) {
1009 connected++;
1010 enabled++;
1011 } else if (account.isEnabled()) {
1012 enabled++;
1013 }
1014 }
1015 }
1016 mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
1017 final PendingIntent openIntent = createOpenConversationsIntent();
1018 if (openIntent != null) {
1019 mBuilder.setContentIntent(openIntent);
1020 }
1021 mBuilder.setWhen(0);
1022 mBuilder.setPriority(Notification.PRIORITY_MIN);
1023 mBuilder.setSmallIcon(connected > 0 ? R.drawable.ic_link_white_24dp : R.drawable.ic_link_off_white_24dp);
1024
1025 if (Compatibility.runsTwentySix()) {
1026 mBuilder.setChannelId("foreground");
1027 }
1028
1029
1030 return mBuilder.build();
1031 }
1032
1033 private PendingIntent createOpenConversationsIntent() {
1034 try {
1035 return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationsActivity.class), 0);
1036 } catch (RuntimeException e) {
1037 return null;
1038 }
1039 }
1040
1041 void updateErrorNotification() {
1042 if (Config.SUPPRESS_ERROR_NOTIFICATION) {
1043 cancel(ERROR_NOTIFICATION_ID);
1044 return;
1045 }
1046 final boolean showAllErrors = QuickConversationsService.isConversations();
1047 final List<Account> errors = new ArrayList<>();
1048 for (final Account account : mXmppConnectionService.getAccounts()) {
1049 if (account.hasErrorStatus() && account.showErrorNotification() && (showAllErrors || account.getLastErrorStatus() == Account.State.UNAUTHORIZED)) {
1050 errors.add(account);
1051 }
1052 }
1053 if (mXmppConnectionService.foregroundNotificationNeedsUpdatingWhenErrorStateChanges()) {
1054 notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
1055 }
1056 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1057 if (errors.size() == 0) {
1058 cancel(ERROR_NOTIFICATION_ID);
1059 return;
1060 } else if (errors.size() == 1) {
1061 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account));
1062 mBuilder.setContentText(errors.get(0).getJid().asBareJid().toString());
1063 } else {
1064 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
1065 mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
1066 }
1067 mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
1068 mXmppConnectionService.getString(R.string.try_again),
1069 createTryAgainIntent());
1070 mBuilder.setDeleteIntent(createDismissErrorIntent());
1071 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1072 mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
1073 mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
1074 } else {
1075 mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
1076 }
1077 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
1078 mBuilder.setLocalOnly(true);
1079 }
1080 mBuilder.setPriority(Notification.PRIORITY_LOW);
1081 final Intent intent;
1082 if (AccountUtils.MANAGE_ACCOUNT_ACTIVITY != null) {
1083 intent = new Intent(mXmppConnectionService, AccountUtils.MANAGE_ACCOUNT_ACTIVITY);
1084 } else {
1085 intent = new Intent(mXmppConnectionService, EditAccountActivity.class);
1086 intent.putExtra("jid", errors.get(0).getJid().asBareJid().toEscapedString());
1087 intent.putExtra(EditAccountActivity.EXTRA_OPENED_FROM_NOTIFICATION, true);
1088 }
1089 mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, 145, intent, PendingIntent.FLAG_UPDATE_CURRENT));
1090 if (Compatibility.runsTwentySix()) {
1091 mBuilder.setChannelId("error");
1092 }
1093 notify(ERROR_NOTIFICATION_ID, mBuilder.build());
1094 }
1095
1096 void updateFileAddingNotification(int current, Message message) {
1097 Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1098 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
1099 mBuilder.setProgress(100, current, false);
1100 mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
1101 mBuilder.setContentIntent(createContentIntent(message.getConversation()));
1102 mBuilder.setOngoing(true);
1103 if (Compatibility.runsTwentySix()) {
1104 mBuilder.setChannelId("compression");
1105 }
1106 Notification notification = mBuilder.build();
1107 notify(FOREGROUND_NOTIFICATION_ID, notification);
1108 }
1109
1110 void dismissForcedForegroundNotification() {
1111 cancel(FOREGROUND_NOTIFICATION_ID);
1112 }
1113
1114 private void notify(String tag, int id, Notification notification) {
1115 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1116 try {
1117 notificationManager.notify(tag, id, notification);
1118 } catch (RuntimeException e) {
1119 Log.d(Config.LOGTAG, "unable to make notification", e);
1120 }
1121 }
1122
1123 public void notify(int id, Notification notification) {
1124 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1125 try {
1126 notificationManager.notify(id, notification);
1127 } catch (RuntimeException e) {
1128 Log.d(Config.LOGTAG, "unable to make notification", e);
1129 }
1130 }
1131
1132 private void cancel(int id) {
1133 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1134 try {
1135 notificationManager.cancel(id);
1136 } catch (RuntimeException e) {
1137 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1138 }
1139 }
1140
1141 private void cancel(String tag, int id) {
1142 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1143 try {
1144 notificationManager.cancel(tag, id);
1145 } catch (RuntimeException e) {
1146 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1147 }
1148 }
1149}