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 public 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, notification);
366 }
367
368 public Notification getOngoingCallNotification(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 return builder.build();
384 }
385
386 private PendingIntent createPendingRtpSession(final AbstractJingleConnection.Id id, final String action, final int requestCode) {
387 final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
388 fullScreenIntent.setAction(action);
389 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
390 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
391 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
392 //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
393 //fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
394 return PendingIntent.getActivity(mXmppConnectionService, requestCode, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
395 }
396
397 public void cancelIncomingCallNotification() {
398 cancel(INCOMING_CALL_NOTIFICATION_ID);
399 }
400
401 public void cancelOngoingCallNotification() {
402 cancel(ONGOING_CALL_NOTIFICATION_ID);
403 }
404
405 private void pushNow(final Message message) {
406 mXmppConnectionService.updateUnreadCountBadge();
407 if (!notify(message)) {
408 Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing notification because turned off");
409 return;
410 }
411 final boolean isScreenOn = mXmppConnectionService.isInteractive();
412 if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
413 Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing notification because conversation is open");
414 return;
415 }
416 synchronized (notifications) {
417 pushToStack(message);
418 final Conversational conversation = message.getConversation();
419 final Account account = conversation.getAccount();
420 final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
421 && !account.inGracePeriod()
422 && !this.inMiniGracePeriod(account);
423 updateNotification(doNotify, Collections.singletonList(conversation.getUuid()));
424 }
425 }
426
427 public void clear() {
428 synchronized (notifications) {
429 for (ArrayList<Message> messages : notifications.values()) {
430 markAsReadIfHasDirectReply(messages);
431 }
432 notifications.clear();
433 updateNotification(false);
434 }
435 }
436
437 public void clear(final Conversation conversation) {
438 synchronized (this.mBacklogMessageCounter) {
439 this.mBacklogMessageCounter.remove(conversation);
440 }
441 synchronized (notifications) {
442 markAsReadIfHasDirectReply(conversation);
443 if (notifications.remove(conversation.getUuid()) != null) {
444 cancel(conversation.getUuid(), NOTIFICATION_ID);
445 updateNotification(false, null, true);
446 }
447 }
448 }
449
450 private void markAsReadIfHasDirectReply(final Conversation conversation) {
451 markAsReadIfHasDirectReply(notifications.get(conversation.getUuid()));
452 }
453
454 private void markAsReadIfHasDirectReply(final ArrayList<Message> messages) {
455 if (messages != null && messages.size() > 0) {
456 Message last = messages.get(messages.size() - 1);
457 if (last.getStatus() != Message.STATUS_RECEIVED) {
458 if (mXmppConnectionService.markRead((Conversation) last.getConversation(), false)) {
459 mXmppConnectionService.updateConversationUi();
460 }
461 }
462 }
463 }
464
465 private void setNotificationColor(final Builder mBuilder) {
466 mBuilder.setColor(ContextCompat.getColor(mXmppConnectionService, R.color.green600));
467 }
468
469 public void updateNotification() {
470 synchronized (notifications) {
471 updateNotification(false);
472 }
473 }
474
475 private void updateNotification(final boolean notify) {
476 updateNotification(notify, null, false);
477 }
478
479 private void updateNotification(final boolean notify, final List<String> conversations) {
480 updateNotification(notify, conversations, false);
481 }
482
483 private void updateNotification(final boolean notify, final List<String> conversations, final boolean summaryOnly) {
484 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
485
486 final boolean quiteHours = isQuietHours();
487
488 final boolean notifyOnlyOneChild = notify && conversations != null && conversations.size() == 1; //if this check is changed to > 0 catchup messages will create one notification per conversation
489
490
491 if (notifications.size() == 0) {
492 cancel(NOTIFICATION_ID);
493 } else {
494 if (notify) {
495 this.markLastNotification();
496 }
497 final Builder mBuilder;
498 if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
499 mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify, quiteHours);
500 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
501 notify(NOTIFICATION_ID, mBuilder.build());
502 } else {
503 mBuilder = buildMultipleConversation(notify, quiteHours);
504 if (notifyOnlyOneChild) {
505 mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
506 }
507 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
508 if (!summaryOnly) {
509 for (Map.Entry<String, ArrayList<Message>> entry : notifications.entrySet()) {
510 String uuid = entry.getKey();
511 final boolean notifyThis = notifyOnlyOneChild ? conversations.contains(uuid) : notify;
512 Builder singleBuilder = buildSingleConversations(entry.getValue(), notifyThis, quiteHours);
513 if (!notifyOnlyOneChild) {
514 singleBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
515 }
516 modifyForSoundVibrationAndLight(singleBuilder, notifyThis, quiteHours, preferences);
517 singleBuilder.setGroup(CONVERSATIONS_GROUP);
518 setNotificationColor(singleBuilder);
519 notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
520 }
521 }
522 notify(NOTIFICATION_ID, mBuilder.build());
523 }
524 }
525 }
526
527 private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) {
528 final Resources resources = mXmppConnectionService.getResources();
529 final String ringtone = preferences.getString("notification_ringtone", resources.getString(R.string.notification_ringtone));
530 final boolean vibrate = preferences.getBoolean("vibrate_on_notification", resources.getBoolean(R.bool.vibrate_on_notification));
531 final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
532 final boolean headsup = preferences.getBoolean("notification_headsup", resources.getBoolean(R.bool.headsup_notifications));
533 if (notify && !quietHours) {
534 if (vibrate) {
535 final int dat = 70;
536 final long[] pattern = {0, 3 * dat, dat, dat};
537 mBuilder.setVibrate(pattern);
538 } else {
539 mBuilder.setVibrate(new long[]{0});
540 }
541 Uri uri = Uri.parse(ringtone);
542 try {
543 mBuilder.setSound(fixRingtoneUri(uri));
544 } catch (SecurityException e) {
545 Log.d(Config.LOGTAG, "unable to use custom notification sound " + uri.toString());
546 }
547 } else {
548 mBuilder.setLocalOnly(true);
549 }
550 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
551 mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
552 }
553 mBuilder.setPriority(notify ? (headsup ? NotificationCompat.PRIORITY_HIGH : NotificationCompat.PRIORITY_DEFAULT) : NotificationCompat.PRIORITY_LOW);
554 setNotificationColor(mBuilder);
555 mBuilder.setDefaults(0);
556 if (led) {
557 mBuilder.setLights(LED_COLOR, 2000, 3000);
558 }
559 }
560
561 private Uri fixRingtoneUri(Uri uri) {
562 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && "file".equals(uri.getScheme())) {
563 return FileBackend.getUriForFile(mXmppConnectionService, new File(uri.getPath()));
564 } else {
565 return uri;
566 }
567 }
568
569 private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
570 final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
571 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
572 style.setBigContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
573 final StringBuilder names = new StringBuilder();
574 Conversation conversation = null;
575 for (final ArrayList<Message> messages : notifications.values()) {
576 if (messages.size() > 0) {
577 conversation = (Conversation) messages.get(0).getConversation();
578 final String name = conversation.getName().toString();
579 SpannableString styledString;
580 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
581 int count = messages.size();
582 styledString = new SpannableString(name + ": " + mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
583 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
584 style.addLine(styledString);
585 } else {
586 styledString = new SpannableString(name + ": " + UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first);
587 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
588 style.addLine(styledString);
589 }
590 names.append(name);
591 names.append(", ");
592 }
593 }
594 if (names.length() >= 2) {
595 names.delete(names.length() - 2, names.length());
596 }
597 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
598 mBuilder.setTicker(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
599 mBuilder.setContentText(names.toString());
600 mBuilder.setStyle(style);
601 if (conversation != null) {
602 mBuilder.setContentIntent(createContentIntent(conversation));
603 }
604 mBuilder.setGroupSummary(true);
605 mBuilder.setGroup(CONVERSATIONS_GROUP);
606 mBuilder.setDeleteIntent(createDeleteIntent(null));
607 mBuilder.setSmallIcon(R.drawable.ic_notification);
608 return mBuilder;
609 }
610
611 private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify, final boolean quietHours) {
612 final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
613 if (messages.size() >= 1) {
614 final Conversation conversation = (Conversation) messages.get(0).getConversation();
615 mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
616 .get(conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
617 mBuilder.setContentTitle(conversation.getName());
618 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
619 int count = messages.size();
620 mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
621 } else {
622 Message message;
623 //TODO starting with Android 9 we might want to put images in MessageStyle
624 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && (message = getImage(messages)) != null) {
625 modifyForImage(mBuilder, message, messages);
626 } else {
627 modifyForTextOnly(mBuilder, messages);
628 }
629 RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
630 PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
631 NotificationCompat.Action markReadAction = new NotificationCompat.Action.Builder(
632 R.drawable.ic_drafts_white_24dp,
633 mXmppConnectionService.getString(R.string.mark_as_read),
634 markAsReadPendingIntent)
635 .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
636 .setShowsUserInterface(false)
637 .build();
638 String replyLabel = mXmppConnectionService.getString(R.string.reply);
639 NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
640 R.drawable.ic_send_text_offline,
641 replyLabel,
642 createReplyIntent(conversation, false))
643 .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
644 .setShowsUserInterface(false)
645 .addRemoteInput(remoteInput).build();
646 NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
647 replyLabel,
648 createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build();
649 mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
650 int addedActionsCount = 1;
651 mBuilder.addAction(markReadAction);
652 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
653 mBuilder.addAction(replyAction);
654 ++addedActionsCount;
655 }
656
657 if (displaySnoozeAction(messages)) {
658 String label = mXmppConnectionService.getString(R.string.snooze);
659 PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
660 NotificationCompat.Action snoozeAction = new NotificationCompat.Action.Builder(
661 R.drawable.ic_notifications_paused_white_24dp,
662 label,
663 pendingSnoozeIntent).build();
664 mBuilder.addAction(snoozeAction);
665 ++addedActionsCount;
666 }
667 if (addedActionsCount < 3) {
668 final Message firstLocationMessage = getFirstLocationMessage(messages);
669 if (firstLocationMessage != null) {
670 final PendingIntent pendingShowLocationIntent = createShowLocationIntent(firstLocationMessage);
671 if (pendingShowLocationIntent != null) {
672 final String label = mXmppConnectionService.getResources().getString(R.string.show_location);
673 NotificationCompat.Action locationAction = new NotificationCompat.Action.Builder(
674 R.drawable.ic_room_white_24dp,
675 label,
676 pendingShowLocationIntent).build();
677 mBuilder.addAction(locationAction);
678 ++addedActionsCount;
679 }
680 }
681 }
682 if (addedActionsCount < 3) {
683 Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
684 if (firstDownloadableMessage != null) {
685 String label = mXmppConnectionService.getResources().getString(R.string.download_x_file, UIHelper.getFileDescriptionString(mXmppConnectionService, firstDownloadableMessage));
686 PendingIntent pendingDownloadIntent = createDownloadIntent(firstDownloadableMessage);
687 NotificationCompat.Action downloadAction = new NotificationCompat.Action.Builder(
688 R.drawable.ic_file_download_white_24dp,
689 label,
690 pendingDownloadIntent).build();
691 mBuilder.addAction(downloadAction);
692 ++addedActionsCount;
693 }
694 }
695 }
696 if (conversation.getMode() == Conversation.MODE_SINGLE) {
697 Contact contact = conversation.getContact();
698 Uri systemAccount = contact.getSystemAccount();
699 if (systemAccount != null) {
700 mBuilder.addPerson(systemAccount.toString());
701 }
702 }
703 mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
704 mBuilder.setSmallIcon(R.drawable.ic_notification);
705 mBuilder.setDeleteIntent(createDeleteIntent(conversation));
706 mBuilder.setContentIntent(createContentIntent(conversation));
707 }
708 return mBuilder;
709 }
710
711 private void modifyForImage(final Builder builder, final Message message, final ArrayList<Message> messages) {
712 try {
713 final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(message, getPixel(288), false);
714 final ArrayList<Message> tmp = new ArrayList<>();
715 for (final Message msg : messages) {
716 if (msg.getType() == Message.TYPE_TEXT
717 && msg.getTransferable() == null) {
718 tmp.add(msg);
719 }
720 }
721 final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
722 bigPictureStyle.bigPicture(bitmap);
723 if (tmp.size() > 0) {
724 CharSequence text = getMergedBodies(tmp);
725 bigPictureStyle.setSummaryText(text);
726 builder.setContentText(text);
727 builder.setTicker(text);
728 } else {
729 final String description = UIHelper.getFileDescriptionString(mXmppConnectionService, message);
730 builder.setContentText(description);
731 builder.setTicker(description);
732 }
733 builder.setStyle(bigPictureStyle);
734 } catch (final IOException e) {
735 modifyForTextOnly(builder, messages);
736 }
737 }
738
739 private Person getPerson(Message message) {
740 final Contact contact = message.getContact();
741 final Person.Builder builder = new Person.Builder();
742 if (contact != null) {
743 builder.setName(contact.getDisplayName());
744 final Uri uri = contact.getSystemAccount();
745 if (uri != null) {
746 builder.setUri(uri.toString());
747 }
748 } else {
749 builder.setName(UIHelper.getMessageDisplayName(message));
750 }
751 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
752 builder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(message, AvatarService.getSystemUiAvatarSize(mXmppConnectionService), false)));
753 }
754 return builder.build();
755 }
756
757 private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages) {
758 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
759 final Conversation conversation = (Conversation) messages.get(0).getConversation();
760 final Person.Builder meBuilder = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me));
761 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
762 meBuilder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation.getAccount(), AvatarService.getSystemUiAvatarSize(mXmppConnectionService))));
763 }
764 final Person me = meBuilder.build();
765 NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me);
766 final boolean multiple = conversation.getMode() == Conversation.MODE_MULTI;
767 if (multiple) {
768 messagingStyle.setConversationTitle(conversation.getName());
769 }
770 for (Message message : messages) {
771 final Person sender = message.getStatus() == Message.STATUS_RECEIVED ? getPerson(message) : null;
772 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isImageMessage(message)) {
773 final Uri dataUri = FileBackend.getMediaUri(mXmppConnectionService, mXmppConnectionService.getFileBackend().getFile(message));
774 NotificationCompat.MessagingStyle.Message imageMessage = new NotificationCompat.MessagingStyle.Message(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
775 if (dataUri != null) {
776 imageMessage.setData(message.getMimeType(), dataUri);
777 }
778 messagingStyle.addMessage(imageMessage);
779 } else {
780 messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
781 }
782 }
783 messagingStyle.setGroupConversation(multiple);
784 builder.setStyle(messagingStyle);
785 } else {
786 if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
787 builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
788 final CharSequence preview = UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size() - 1)).first;
789 builder.setContentText(preview);
790 builder.setTicker(preview);
791 builder.setNumber(messages.size());
792 } else {
793 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
794 SpannableString styledString;
795 for (Message message : messages) {
796 final String name = UIHelper.getMessageDisplayName(message);
797 styledString = new SpannableString(name + ": " + message.getBody());
798 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
799 style.addLine(styledString);
800 }
801 builder.setStyle(style);
802 int count = messages.size();
803 if (count == 1) {
804 final String name = UIHelper.getMessageDisplayName(messages.get(0));
805 styledString = new SpannableString(name + ": " + messages.get(0).getBody());
806 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
807 builder.setContentText(styledString);
808 builder.setTicker(styledString);
809 } else {
810 final String text = mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count);
811 builder.setContentText(text);
812 builder.setTicker(text);
813 }
814 }
815 }
816 }
817
818 private Message getImage(final Iterable<Message> messages) {
819 Message image = null;
820 for (final Message message : messages) {
821 if (message.getStatus() != Message.STATUS_RECEIVED) {
822 return null;
823 }
824 if (isImageMessage(message)) {
825 image = message;
826 }
827 }
828 return image;
829 }
830
831 private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
832 for (final Message message : messages) {
833 if (message.getTransferable() != null || (message.getType() == Message.TYPE_TEXT && message.treatAsDownloadable())) {
834 return message;
835 }
836 }
837 return null;
838 }
839
840 private Message getFirstLocationMessage(final Iterable<Message> messages) {
841 for (final Message message : messages) {
842 if (message.isGeoUri()) {
843 return message;
844 }
845 }
846 return null;
847 }
848
849 private CharSequence getMergedBodies(final ArrayList<Message> messages) {
850 final StringBuilder text = new StringBuilder();
851 for (Message message : messages) {
852 if (text.length() != 0) {
853 text.append("\n");
854 }
855 text.append(UIHelper.getMessagePreview(mXmppConnectionService, message).first);
856 }
857 return text.toString();
858 }
859
860 private PendingIntent createShowLocationIntent(final Message message) {
861 Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(mXmppConnectionService, message);
862 for (Intent intent : intents) {
863 if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
864 return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT);
865 }
866 }
867 return null;
868 }
869
870 private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
871 final Intent viewConversationIntent = new Intent(mXmppConnectionService, ConversationsActivity.class);
872 viewConversationIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
873 viewConversationIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversationUuid);
874 if (downloadMessageUuid != null) {
875 viewConversationIntent.putExtra(ConversationsActivity.EXTRA_DOWNLOAD_UUID, downloadMessageUuid);
876 return PendingIntent.getActivity(mXmppConnectionService,
877 generateRequestCode(conversationUuid, 8),
878 viewConversationIntent,
879 PendingIntent.FLAG_UPDATE_CURRENT);
880 } else {
881 return PendingIntent.getActivity(mXmppConnectionService,
882 generateRequestCode(conversationUuid, 10),
883 viewConversationIntent,
884 PendingIntent.FLAG_UPDATE_CURRENT);
885 }
886 }
887
888 private int generateRequestCode(String uuid, int actionId) {
889 return (actionId * NOTIFICATION_ID_MULTIPLIER) + (uuid.hashCode() % NOTIFICATION_ID_MULTIPLIER);
890 }
891
892 private int generateRequestCode(Conversational conversation, int actionId) {
893 return generateRequestCode(conversation.getUuid(), actionId);
894 }
895
896 private PendingIntent createDownloadIntent(final Message message) {
897 return createContentIntent(message.getConversationUuid(), message.getUuid());
898 }
899
900 private PendingIntent createContentIntent(final Conversational conversation) {
901 return createContentIntent(conversation.getUuid(), null);
902 }
903
904 private PendingIntent createDeleteIntent(Conversation conversation) {
905 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
906 intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
907 if (conversation != null) {
908 intent.putExtra("uuid", conversation.getUuid());
909 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 20), intent, 0);
910 }
911 return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
912 }
913
914 private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) {
915 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
916 intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
917 intent.putExtra("uuid", conversation.getUuid());
918 intent.putExtra("dismiss_notification", dismissAfterReply);
919 final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14);
920 return PendingIntent.getService(mXmppConnectionService, id, intent, 0);
921 }
922
923 private PendingIntent createReadPendingIntent(Conversation conversation) {
924 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
925 intent.setAction(XmppConnectionService.ACTION_MARK_AS_READ);
926 intent.putExtra("uuid", conversation.getUuid());
927 intent.setPackage(mXmppConnectionService.getPackageName());
928 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 16), intent, PendingIntent.FLAG_UPDATE_CURRENT);
929 }
930
931 private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
932 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
933 intent.setAction(action);
934 intent.setPackage(mXmppConnectionService.getPackageName());
935 intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId);
936 return PendingIntent.getService(mXmppConnectionService, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
937 }
938
939 private PendingIntent createSnoozeIntent(Conversation conversation) {
940 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
941 intent.setAction(XmppConnectionService.ACTION_SNOOZE);
942 intent.putExtra("uuid", conversation.getUuid());
943 intent.setPackage(mXmppConnectionService.getPackageName());
944 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 22), intent, PendingIntent.FLAG_UPDATE_CURRENT);
945 }
946
947 private PendingIntent createTryAgainIntent() {
948 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
949 intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
950 return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
951 }
952
953 private PendingIntent createDismissErrorIntent() {
954 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
955 intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS);
956 return PendingIntent.getService(mXmppConnectionService, 69, intent, 0);
957 }
958
959 private boolean wasHighlightedOrPrivate(final Message message) {
960 if (message.getConversation() instanceof Conversation) {
961 Conversation conversation = (Conversation) message.getConversation();
962 final String nick = conversation.getMucOptions().getActualNick();
963 final Pattern highlight = generateNickHighlightPattern(nick);
964 if (message.getBody() == null || nick == null) {
965 return false;
966 }
967 final Matcher m = highlight.matcher(message.getBody());
968 return (m.find() || message.isPrivateMessage());
969 } else {
970 return false;
971 }
972 }
973
974 public void setOpenConversation(final Conversation conversation) {
975 this.mOpenConversation = conversation;
976 }
977
978 public void setIsInForeground(final boolean foreground) {
979 this.mIsInForeground = foreground;
980 }
981
982 private int getPixel(final int dp) {
983 final DisplayMetrics metrics = mXmppConnectionService.getResources()
984 .getDisplayMetrics();
985 return ((int) (dp * metrics.density));
986 }
987
988 private void markLastNotification() {
989 this.mLastNotification = SystemClock.elapsedRealtime();
990 }
991
992 private boolean inMiniGracePeriod(final Account account) {
993 final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD
994 : Config.MINI_GRACE_PERIOD * 2;
995 return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
996 }
997
998 Notification createForegroundNotification() {
999 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1000 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
1001 final List<Account> accounts = mXmppConnectionService.getAccounts();
1002 int enabled = 0;
1003 int connected = 0;
1004 if (accounts != null) {
1005 for (Account account : accounts) {
1006 if (account.isOnlineAndConnected()) {
1007 connected++;
1008 enabled++;
1009 } else if (account.isEnabled()) {
1010 enabled++;
1011 }
1012 }
1013 }
1014 mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
1015 final PendingIntent openIntent = createOpenConversationsIntent();
1016 if (openIntent != null) {
1017 mBuilder.setContentIntent(openIntent);
1018 }
1019 mBuilder.setWhen(0);
1020 mBuilder.setPriority(Notification.PRIORITY_MIN);
1021 mBuilder.setSmallIcon(connected > 0 ? R.drawable.ic_link_white_24dp : R.drawable.ic_link_off_white_24dp);
1022
1023 if (Compatibility.runsTwentySix()) {
1024 mBuilder.setChannelId("foreground");
1025 }
1026
1027
1028 return mBuilder.build();
1029 }
1030
1031 private PendingIntent createOpenConversationsIntent() {
1032 try {
1033 return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationsActivity.class), 0);
1034 } catch (RuntimeException e) {
1035 return null;
1036 }
1037 }
1038
1039 void updateErrorNotification() {
1040 if (Config.SUPPRESS_ERROR_NOTIFICATION) {
1041 cancel(ERROR_NOTIFICATION_ID);
1042 return;
1043 }
1044 final boolean showAllErrors = QuickConversationsService.isConversations();
1045 final List<Account> errors = new ArrayList<>();
1046 for (final Account account : mXmppConnectionService.getAccounts()) {
1047 if (account.hasErrorStatus() && account.showErrorNotification() && (showAllErrors || account.getLastErrorStatus() == Account.State.UNAUTHORIZED)) {
1048 errors.add(account);
1049 }
1050 }
1051 if (mXmppConnectionService.foregroundNotificationNeedsUpdatingWhenErrorStateChanges()) {
1052 notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
1053 }
1054 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1055 if (errors.size() == 0) {
1056 cancel(ERROR_NOTIFICATION_ID);
1057 return;
1058 } else if (errors.size() == 1) {
1059 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account));
1060 mBuilder.setContentText(errors.get(0).getJid().asBareJid().toString());
1061 } else {
1062 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
1063 mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
1064 }
1065 mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
1066 mXmppConnectionService.getString(R.string.try_again),
1067 createTryAgainIntent());
1068 mBuilder.setDeleteIntent(createDismissErrorIntent());
1069 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1070 mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
1071 mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
1072 } else {
1073 mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
1074 }
1075 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
1076 mBuilder.setLocalOnly(true);
1077 }
1078 mBuilder.setPriority(Notification.PRIORITY_LOW);
1079 final Intent intent;
1080 if (AccountUtils.MANAGE_ACCOUNT_ACTIVITY != null) {
1081 intent = new Intent(mXmppConnectionService, AccountUtils.MANAGE_ACCOUNT_ACTIVITY);
1082 } else {
1083 intent = new Intent(mXmppConnectionService, EditAccountActivity.class);
1084 intent.putExtra("jid", errors.get(0).getJid().asBareJid().toEscapedString());
1085 intent.putExtra(EditAccountActivity.EXTRA_OPENED_FROM_NOTIFICATION, true);
1086 }
1087 mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService, 145, intent, PendingIntent.FLAG_UPDATE_CURRENT));
1088 if (Compatibility.runsTwentySix()) {
1089 mBuilder.setChannelId("error");
1090 }
1091 notify(ERROR_NOTIFICATION_ID, mBuilder.build());
1092 }
1093
1094 void updateFileAddingNotification(int current, Message message) {
1095 Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1096 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
1097 mBuilder.setProgress(100, current, false);
1098 mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
1099 mBuilder.setContentIntent(createContentIntent(message.getConversation()));
1100 mBuilder.setOngoing(true);
1101 if (Compatibility.runsTwentySix()) {
1102 mBuilder.setChannelId("compression");
1103 }
1104 Notification notification = mBuilder.build();
1105 notify(FOREGROUND_NOTIFICATION_ID, notification);
1106 }
1107
1108 private void notify(String tag, int id, Notification notification) {
1109 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1110 try {
1111 notificationManager.notify(tag, id, notification);
1112 } catch (RuntimeException e) {
1113 Log.d(Config.LOGTAG, "unable to make notification", e);
1114 }
1115 }
1116
1117 public void notify(int id, Notification notification) {
1118 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1119 try {
1120 notificationManager.notify(id, notification);
1121 } catch (RuntimeException e) {
1122 Log.d(Config.LOGTAG, "unable to make notification", e);
1123 }
1124 }
1125
1126 public void cancel(int id) {
1127 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1128 try {
1129 notificationManager.cancel(id);
1130 } catch (RuntimeException e) {
1131 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1132 }
1133 }
1134
1135 private void cancel(String tag, int id) {
1136 final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
1137 try {
1138 notificationManager.cancel(tag, id);
1139 } catch (RuntimeException e) {
1140 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1141 }
1142 }
1143}