1package eu.siacs.conversations.services;
2
3import static eu.siacs.conversations.utils.Compatibility.s;
4
5import android.app.Notification;
6import android.app.NotificationChannel;
7import android.app.NotificationChannelGroup;
8import android.app.NotificationManager;
9import android.app.PendingIntent;
10import android.content.Context;
11import android.content.Intent;
12import android.content.SharedPreferences;
13import android.content.res.Resources;
14import android.graphics.Bitmap;
15import android.graphics.Typeface;
16import android.media.AudioAttributes;
17import android.media.Ringtone;
18import android.media.RingtoneManager;
19import android.net.Uri;
20import android.os.Build;
21import android.os.SystemClock;
22import android.os.Vibrator;
23import android.preference.PreferenceManager;
24import android.text.SpannableString;
25import android.text.style.StyleSpan;
26import android.util.DisplayMetrics;
27import android.util.Log;
28
29import androidx.annotation.RequiresApi;
30import androidx.core.app.NotificationCompat;
31import androidx.core.app.NotificationCompat.BigPictureStyle;
32import androidx.core.app.NotificationCompat.Builder;
33import androidx.core.app.NotificationManagerCompat;
34import androidx.core.app.Person;
35import androidx.core.app.RemoteInput;
36import androidx.core.content.ContextCompat;
37import androidx.core.graphics.drawable.IconCompat;
38
39import com.google.common.base.Strings;
40import com.google.common.collect.Iterables;
41
42import java.io.File;
43import java.io.IOException;
44import java.util.ArrayList;
45import java.util.Calendar;
46import java.util.Collections;
47import java.util.HashMap;
48import java.util.Iterator;
49import java.util.LinkedHashMap;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53import java.util.concurrent.Executors;
54import java.util.concurrent.ScheduledExecutorService;
55import java.util.concurrent.ScheduledFuture;
56import java.util.concurrent.TimeUnit;
57import java.util.concurrent.atomic.AtomicInteger;
58import java.util.regex.Matcher;
59import java.util.regex.Pattern;
60
61import eu.siacs.conversations.Config;
62import eu.siacs.conversations.R;
63import eu.siacs.conversations.entities.Account;
64import eu.siacs.conversations.entities.Contact;
65import eu.siacs.conversations.entities.Conversation;
66import eu.siacs.conversations.entities.Conversational;
67import eu.siacs.conversations.entities.Message;
68import eu.siacs.conversations.persistance.FileBackend;
69import eu.siacs.conversations.ui.ConversationsActivity;
70import eu.siacs.conversations.ui.EditAccountActivity;
71import eu.siacs.conversations.ui.RtpSessionActivity;
72import eu.siacs.conversations.ui.TimePreference;
73import eu.siacs.conversations.utils.AccountUtils;
74import eu.siacs.conversations.utils.Compatibility;
75import eu.siacs.conversations.utils.GeoHelper;
76import eu.siacs.conversations.utils.TorServiceUtils;
77import eu.siacs.conversations.utils.UIHelper;
78import eu.siacs.conversations.xmpp.XmppConnection;
79import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
80import eu.siacs.conversations.xmpp.jingle.Media;
81
82public class NotificationService {
83
84 private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
85 Executors.newSingleThreadScheduledExecutor();
86
87 public static final Object CATCHUP_LOCK = new Object();
88
89 private static final int LED_COLOR = 0xff00ff00;
90
91 private static final long[] CALL_PATTERN = {0, 500, 300, 600};
92
93 private static final String MESSAGES_GROUP = "eu.siacs.conversations.messages";
94 private static final String MISSED_CALLS_GROUP = "eu.siacs.conversations.missed_calls";
95 private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
96 static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
97 private static final int NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 2;
98 private static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6;
99 private static final int INCOMING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
100 public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
101 public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
102 private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 13;
103 private final XmppConnectionService mXmppConnectionService;
104 private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
105 private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
106 private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>();
107 private Conversation mOpenConversation;
108 private boolean mIsInForeground;
109 private long mLastNotification;
110
111 private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL = "incoming_calls_channel";
112 private Ringtone currentlyPlayingRingtone = null;
113 private ScheduledFuture<?> vibrationFuture;
114
115 NotificationService(final XmppConnectionService service) {
116 this.mXmppConnectionService = service;
117 }
118
119 private static boolean displaySnoozeAction(List<Message> messages) {
120 int numberOfMessagesWithoutReply = 0;
121 for (Message message : messages) {
122 if (message.getStatus() == Message.STATUS_RECEIVED) {
123 ++numberOfMessagesWithoutReply;
124 } else {
125 return false;
126 }
127 }
128 return numberOfMessagesWithoutReply >= 3;
129 }
130
131 public static Pattern generateNickHighlightPattern(final String nick) {
132 return Pattern.compile("(?<=(^|\\s))" + Pattern.quote(nick) + "(?=\\s|$|\\p{Punct})");
133 }
134
135 private static boolean isImageMessage(Message message) {
136 return message.getType() != Message.TYPE_TEXT
137 && message.getTransferable() == null
138 && !message.isDeleted()
139 && message.getEncryption() != Message.ENCRYPTION_PGP
140 && message.getFileParams().height > 0;
141 }
142
143 @RequiresApi(api = Build.VERSION_CODES.O)
144 void initializeChannels() {
145 final Context c = mXmppConnectionService;
146 final NotificationManager notificationManager =
147 c.getSystemService(NotificationManager.class);
148 if (notificationManager == null) {
149 return;
150 }
151
152 notificationManager.deleteNotificationChannel("export");
153 notificationManager.deleteNotificationChannel("incoming_calls");
154
155 notificationManager.createNotificationChannelGroup(
156 new NotificationChannelGroup(
157 "status", c.getString(R.string.notification_group_status_information)));
158 notificationManager.createNotificationChannelGroup(
159 new NotificationChannelGroup(
160 "chats", c.getString(R.string.notification_group_messages)));
161 notificationManager.createNotificationChannelGroup(
162 new NotificationChannelGroup(
163 "calls", c.getString(R.string.notification_group_calls)));
164 final NotificationChannel foregroundServiceChannel =
165 new NotificationChannel(
166 "foreground",
167 c.getString(R.string.foreground_service_channel_name),
168 NotificationManager.IMPORTANCE_MIN);
169 foregroundServiceChannel.setDescription(
170 c.getString(
171 R.string.foreground_service_channel_description,
172 c.getString(R.string.app_name)));
173 foregroundServiceChannel.setShowBadge(false);
174 foregroundServiceChannel.setGroup("status");
175 notificationManager.createNotificationChannel(foregroundServiceChannel);
176 final NotificationChannel errorChannel =
177 new NotificationChannel(
178 "error",
179 c.getString(R.string.error_channel_name),
180 NotificationManager.IMPORTANCE_LOW);
181 errorChannel.setDescription(c.getString(R.string.error_channel_description));
182 errorChannel.setShowBadge(false);
183 errorChannel.setGroup("status");
184 notificationManager.createNotificationChannel(errorChannel);
185
186 final NotificationChannel videoCompressionChannel =
187 new NotificationChannel(
188 "compression",
189 c.getString(R.string.video_compression_channel_name),
190 NotificationManager.IMPORTANCE_LOW);
191 videoCompressionChannel.setShowBadge(false);
192 videoCompressionChannel.setGroup("status");
193 notificationManager.createNotificationChannel(videoCompressionChannel);
194
195 final NotificationChannel exportChannel =
196 new NotificationChannel(
197 "backup",
198 c.getString(R.string.backup_channel_name),
199 NotificationManager.IMPORTANCE_LOW);
200 exportChannel.setShowBadge(false);
201 exportChannel.setGroup("status");
202 notificationManager.createNotificationChannel(exportChannel);
203
204 final NotificationChannel incomingCallsChannel =
205 new NotificationChannel(
206 INCOMING_CALLS_NOTIFICATION_CHANNEL,
207 c.getString(R.string.incoming_calls_channel_name),
208 NotificationManager.IMPORTANCE_HIGH);
209 incomingCallsChannel.setSound(null, null);
210 incomingCallsChannel.setShowBadge(false);
211 incomingCallsChannel.setLightColor(LED_COLOR);
212 incomingCallsChannel.enableLights(true);
213 incomingCallsChannel.setGroup("calls");
214 incomingCallsChannel.setBypassDnd(true);
215 incomingCallsChannel.enableVibration(false);
216 notificationManager.createNotificationChannel(incomingCallsChannel);
217
218 final NotificationChannel ongoingCallsChannel =
219 new NotificationChannel(
220 "ongoing_calls",
221 c.getString(R.string.ongoing_calls_channel_name),
222 NotificationManager.IMPORTANCE_LOW);
223 ongoingCallsChannel.setShowBadge(false);
224 ongoingCallsChannel.setGroup("calls");
225 notificationManager.createNotificationChannel(ongoingCallsChannel);
226
227 final NotificationChannel missedCallsChannel = new NotificationChannel("missed_calls",
228 c.getString(R.string.missed_calls_channel_name),
229 NotificationManager.IMPORTANCE_HIGH);
230 missedCallsChannel.setShowBadge(true);
231 missedCallsChannel.setSound(null, null);
232 missedCallsChannel.setLightColor(LED_COLOR);
233 missedCallsChannel.enableLights(true);
234 missedCallsChannel.setGroup("calls");
235 notificationManager.createNotificationChannel(missedCallsChannel);
236
237 final NotificationChannel messagesChannel =
238 new NotificationChannel(
239 "messages",
240 c.getString(R.string.messages_channel_name),
241 NotificationManager.IMPORTANCE_HIGH);
242 messagesChannel.setShowBadge(true);
243 messagesChannel.setSound(
244 RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
245 new AudioAttributes.Builder()
246 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
247 .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
248 .build());
249 messagesChannel.setLightColor(LED_COLOR);
250 final int dat = 70;
251 final long[] pattern = {0, 3 * dat, dat, dat};
252 messagesChannel.setVibrationPattern(pattern);
253 messagesChannel.enableVibration(true);
254 messagesChannel.enableLights(true);
255 messagesChannel.setGroup("chats");
256 notificationManager.createNotificationChannel(messagesChannel);
257 final NotificationChannel silentMessagesChannel =
258 new NotificationChannel(
259 "silent_messages",
260 c.getString(R.string.silent_messages_channel_name),
261 NotificationManager.IMPORTANCE_LOW);
262 silentMessagesChannel.setDescription(
263 c.getString(R.string.silent_messages_channel_description));
264 silentMessagesChannel.setShowBadge(true);
265 silentMessagesChannel.setLightColor(LED_COLOR);
266 silentMessagesChannel.enableLights(true);
267 silentMessagesChannel.setGroup("chats");
268 notificationManager.createNotificationChannel(silentMessagesChannel);
269
270 final NotificationChannel quietHoursChannel =
271 new NotificationChannel(
272 "quiet_hours",
273 c.getString(R.string.title_pref_quiet_hours),
274 NotificationManager.IMPORTANCE_LOW);
275 quietHoursChannel.setShowBadge(true);
276 quietHoursChannel.setLightColor(LED_COLOR);
277 quietHoursChannel.enableLights(true);
278 quietHoursChannel.setGroup("chats");
279 quietHoursChannel.enableVibration(false);
280 quietHoursChannel.setSound(null, null);
281
282 notificationManager.createNotificationChannel(quietHoursChannel);
283
284 final NotificationChannel deliveryFailedChannel =
285 new NotificationChannel(
286 "delivery_failed",
287 c.getString(R.string.delivery_failed_channel_name),
288 NotificationManager.IMPORTANCE_DEFAULT);
289 deliveryFailedChannel.setShowBadge(false);
290 deliveryFailedChannel.setSound(
291 RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
292 new AudioAttributes.Builder()
293 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
294 .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
295 .build());
296 deliveryFailedChannel.setGroup("chats");
297 notificationManager.createNotificationChannel(deliveryFailedChannel);
298 }
299
300 private boolean notifyMessage(final Message message) {
301 final Conversation conversation = (Conversation) message.getConversation();
302 return message.getStatus() == Message.STATUS_RECEIVED
303 && !conversation.isMuted()
304 && (conversation.alwaysNotify() || wasHighlightedOrPrivate(message))
305 && (!conversation.isWithStranger() || notificationsFromStrangers())
306 && message.getType() != Message.TYPE_RTP_SESSION;
307 }
308
309 private boolean notifyMissedCall(final Message message) {
310 return message.getType() == Message.TYPE_RTP_SESSION
311 && message.getStatus() == Message.STATUS_RECEIVED;
312 }
313
314 public boolean notificationsFromStrangers() {
315 return mXmppConnectionService.getBooleanPreference(
316 "notifications_from_strangers", R.bool.notifications_from_strangers);
317 }
318
319 private boolean isQuietHours() {
320 if (!mXmppConnectionService.getBooleanPreference(
321 "enable_quiet_hours", R.bool.enable_quiet_hours)) {
322 return false;
323 }
324 final SharedPreferences preferences =
325 PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
326 final long startTime =
327 TimePreference.minutesToTimestamp(
328 preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE));
329 final long endTime =
330 TimePreference.minutesToTimestamp(
331 preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE));
332 final long nowTime = Calendar.getInstance().getTimeInMillis();
333
334 if (endTime < startTime) {
335 return nowTime > startTime || nowTime < endTime;
336 } else {
337 return nowTime > startTime && nowTime < endTime;
338 }
339 }
340
341 public void pushFromBacklog(final Message message) {
342 if (notifyMessage(message)) {
343 synchronized (notifications) {
344 getBacklogMessageCounter((Conversation) message.getConversation())
345 .incrementAndGet();
346 pushToStack(message);
347 }
348 } else if (notifyMissedCall(message)) {
349 synchronized (mMissedCalls) {
350 pushMissedCall(message);
351 }
352 }
353 }
354
355 private AtomicInteger getBacklogMessageCounter(Conversation conversation) {
356 synchronized (mBacklogMessageCounter) {
357 if (!mBacklogMessageCounter.containsKey(conversation)) {
358 mBacklogMessageCounter.put(conversation, new AtomicInteger(0));
359 }
360 return mBacklogMessageCounter.get(conversation);
361 }
362 }
363
364 void pushFromDirectReply(final Message message) {
365 synchronized (notifications) {
366 pushToStack(message);
367 updateNotification(false);
368 }
369 }
370
371 public void finishBacklog(boolean notify, Account account) {
372 synchronized (notifications) {
373 mXmppConnectionService.updateUnreadCountBadge();
374 if (account == null || !notify) {
375 updateNotification(notify);
376 } else {
377 final int count;
378 final List<String> conversations;
379 synchronized (this.mBacklogMessageCounter) {
380 conversations = getBacklogConversations(account);
381 count = getBacklogMessageCount(account);
382 }
383 updateNotification(count > 0, conversations);
384 }
385 }
386 synchronized (mMissedCalls) {
387 updateMissedCallNotifications(mMissedCalls.keySet());
388 }
389 }
390
391 private List<String> getBacklogConversations(Account account) {
392 final List<String> conversations = new ArrayList<>();
393 for (Map.Entry<Conversation, AtomicInteger> entry : mBacklogMessageCounter.entrySet()) {
394 if (entry.getKey().getAccount() == account) {
395 conversations.add(entry.getKey().getUuid());
396 }
397 }
398 return conversations;
399 }
400
401 private int getBacklogMessageCount(Account account) {
402 int count = 0;
403 for (Iterator<Map.Entry<Conversation, AtomicInteger>> it =
404 mBacklogMessageCounter.entrySet().iterator();
405 it.hasNext(); ) {
406 Map.Entry<Conversation, AtomicInteger> entry = it.next();
407 if (entry.getKey().getAccount() == account) {
408 count += entry.getValue().get();
409 it.remove();
410 }
411 }
412 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": backlog message count=" + count);
413 return count;
414 }
415
416 void finishBacklog(boolean notify) {
417 finishBacklog(notify, null);
418 }
419
420 private void pushToStack(final Message message) {
421 final String conversationUuid = message.getConversationUuid();
422 if (notifications.containsKey(conversationUuid)) {
423 notifications.get(conversationUuid).add(message);
424 } else {
425 final ArrayList<Message> mList = new ArrayList<>();
426 mList.add(message);
427 notifications.put(conversationUuid, mList);
428 }
429 }
430
431 public void push(final Message message) {
432 synchronized (CATCHUP_LOCK) {
433 final XmppConnection connection =
434 message.getConversation().getAccount().getXmppConnection();
435 if (connection != null && connection.isWaitingForSmCatchup()) {
436 connection.incrementSmCatchupMessageCounter();
437 pushFromBacklog(message);
438 } else {
439 pushNow(message);
440 }
441 }
442 }
443
444 public void pushFailedDelivery(final Message message) {
445 final Conversation conversation = (Conversation) message.getConversation();
446 final boolean isScreenLocked = !mXmppConnectionService.isScreenLocked();
447 if (this.mIsInForeground
448 && isScreenLocked
449 && this.mOpenConversation == message.getConversation()) {
450 Log.d(
451 Config.LOGTAG,
452 message.getConversation().getAccount().getJid().asBareJid()
453 + ": suppressing failed delivery notification because conversation is open");
454 return;
455 }
456 final PendingIntent pendingIntent = createContentIntent(conversation);
457 final int notificationId =
458 generateRequestCode(conversation, 0) + DELIVERY_FAILED_NOTIFICATION_ID;
459 final int failedDeliveries = conversation.countFailedDeliveries();
460 final Notification notification =
461 new Builder(mXmppConnectionService, "delivery_failed")
462 .setContentTitle(conversation.getName())
463 .setAutoCancel(true)
464 .setSmallIcon(R.drawable.ic_error_white_24dp)
465 .setContentText(
466 mXmppConnectionService
467 .getResources()
468 .getQuantityText(
469 R.plurals.some_messages_could_not_be_delivered,
470 failedDeliveries))
471 .setGroup("delivery_failed")
472 .setContentIntent(pendingIntent)
473 .build();
474 final Notification summaryNotification =
475 new Builder(mXmppConnectionService, "delivery_failed")
476 .setContentTitle(
477 mXmppConnectionService.getString(R.string.failed_deliveries))
478 .setContentText(
479 mXmppConnectionService
480 .getResources()
481 .getQuantityText(
482 R.plurals.some_messages_could_not_be_delivered,
483 1024))
484 .setSmallIcon(R.drawable.ic_error_white_24dp)
485 .setGroup("delivery_failed")
486 .setGroupSummary(true)
487 .setAutoCancel(true)
488 .build();
489 notify(notificationId, notification);
490 notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification);
491 }
492
493 public synchronized void startRinging(
494 final AbstractJingleConnection.Id id, final Set<Media> media) {
495 showIncomingCallNotification(id, media);
496 final NotificationManager notificationManager =
497 (NotificationManager)
498 mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
499 final int currentInterruptionFilter;
500 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && notificationManager != null) {
501 currentInterruptionFilter = notificationManager.getCurrentInterruptionFilter();
502 } else {
503 currentInterruptionFilter = 1; // INTERRUPTION_FILTER_ALL
504 }
505 if (currentInterruptionFilter != 1) {
506 Log.d(
507 Config.LOGTAG,
508 "do not ring or vibrate because interruption filter has been set to "
509 + currentInterruptionFilter);
510 return;
511 }
512 final ScheduledFuture<?> currentVibrationFuture = this.vibrationFuture;
513 this.vibrationFuture =
514 SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(
515 new VibrationRunnable(), 0, 3, TimeUnit.SECONDS);
516 if (currentVibrationFuture != null) {
517 currentVibrationFuture.cancel(true);
518 }
519 final SharedPreferences preferences =
520 PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
521 final Resources resources = mXmppConnectionService.getResources();
522 final String ringtonePreference =
523 preferences.getString(
524 "call_ringtone", resources.getString(R.string.incoming_call_ringtone));
525 if (Strings.isNullOrEmpty(ringtonePreference)) {
526 Log.d(Config.LOGTAG, "ringtone has been set to none");
527 return;
528 }
529 final Uri uri = Uri.parse(ringtonePreference);
530 this.currentlyPlayingRingtone = RingtoneManager.getRingtone(mXmppConnectionService, uri);
531 if (this.currentlyPlayingRingtone == null) {
532 Log.d(Config.LOGTAG, "unable to find ringtone for uri " + uri);
533 return;
534 }
535 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
536 this.currentlyPlayingRingtone.setLooping(true);
537 }
538 this.currentlyPlayingRingtone.play();
539 }
540
541 private void showIncomingCallNotification(
542 final AbstractJingleConnection.Id id, final Set<Media> media) {
543 final Intent fullScreenIntent =
544 new Intent(mXmppConnectionService, RtpSessionActivity.class);
545 fullScreenIntent.putExtra(
546 RtpSessionActivity.EXTRA_ACCOUNT,
547 id.account.getJid().asBareJid().toEscapedString());
548 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
549 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
550 fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
551 fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
552 final NotificationCompat.Builder builder =
553 new NotificationCompat.Builder(
554 mXmppConnectionService, INCOMING_CALLS_NOTIFICATION_CHANNEL);
555 if (media.contains(Media.VIDEO)) {
556 builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
557 builder.setContentTitle(
558 mXmppConnectionService.getString(R.string.rtp_state_incoming_video_call));
559 } else {
560 builder.setSmallIcon(R.drawable.ic_call_white_24dp);
561 builder.setContentTitle(
562 mXmppConnectionService.getString(R.string.rtp_state_incoming_call));
563 }
564 final Contact contact = id.getContact();
565 builder.setLargeIcon(
566 mXmppConnectionService
567 .getAvatarService()
568 .get(contact, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
569 final Uri systemAccount = contact.getSystemAccount();
570 if (systemAccount != null) {
571 builder.addPerson(systemAccount.toString());
572 }
573 builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
574 builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
575 builder.setPriority(NotificationCompat.PRIORITY_HIGH);
576 builder.setCategory(NotificationCompat.CATEGORY_CALL);
577 PendingIntent pendingIntent = createPendingRtpSession(id, Intent.ACTION_VIEW, 101);
578 builder.setFullScreenIntent(pendingIntent, true);
579 builder.setContentIntent(pendingIntent); // old androids need this?
580 builder.setOngoing(true);
581 builder.addAction(
582 new NotificationCompat.Action.Builder(
583 R.drawable.ic_call_end_white_48dp,
584 mXmppConnectionService.getString(R.string.dismiss_call),
585 createCallAction(
586 id.sessionId,
587 XmppConnectionService.ACTION_DISMISS_CALL,
588 102))
589 .build());
590 builder.addAction(
591 new NotificationCompat.Action.Builder(
592 R.drawable.ic_call_white_24dp,
593 mXmppConnectionService.getString(R.string.answer_call),
594 createPendingRtpSession(
595 id, RtpSessionActivity.ACTION_ACCEPT_CALL, 103))
596 .build());
597 modifyIncomingCall(builder);
598 final Notification notification = builder.build();
599 notification.flags = notification.flags | Notification.FLAG_INSISTENT;
600 notify(INCOMING_CALL_NOTIFICATION_ID, notification);
601 }
602
603 public Notification getOngoingCallNotification(
604 final XmppConnectionService.OngoingCall ongoingCall) {
605 final AbstractJingleConnection.Id id = ongoingCall.id;
606 final NotificationCompat.Builder builder =
607 new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls");
608 if (ongoingCall.media.contains(Media.VIDEO)) {
609 builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
610 if (ongoingCall.reconnecting) {
611 builder.setContentTitle(
612 mXmppConnectionService.getString(R.string.reconnecting_video_call));
613 } else {
614 builder.setContentTitle(
615 mXmppConnectionService.getString(R.string.ongoing_video_call));
616 }
617 } else {
618 builder.setSmallIcon(R.drawable.ic_call_white_24dp);
619 if (ongoingCall.reconnecting) {
620 builder.setContentTitle(
621 mXmppConnectionService.getString(R.string.reconnecting_call));
622 } else {
623 builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
624 }
625 }
626 builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
627 builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
628 builder.setPriority(NotificationCompat.PRIORITY_HIGH);
629 builder.setCategory(NotificationCompat.CATEGORY_CALL);
630 builder.setContentIntent(createPendingRtpSession(id, Intent.ACTION_VIEW, 101));
631 builder.setOngoing(true);
632 builder.addAction(
633 new NotificationCompat.Action.Builder(
634 R.drawable.ic_call_end_white_48dp,
635 mXmppConnectionService.getString(R.string.hang_up),
636 createCallAction(
637 id.sessionId, XmppConnectionService.ACTION_END_CALL, 104))
638 .build());
639 return builder.build();
640 }
641
642 private PendingIntent createPendingRtpSession(
643 final AbstractJingleConnection.Id id, final String action, final int requestCode) {
644 final Intent fullScreenIntent =
645 new Intent(mXmppConnectionService, RtpSessionActivity.class);
646 fullScreenIntent.setAction(action);
647 fullScreenIntent.putExtra(
648 RtpSessionActivity.EXTRA_ACCOUNT,
649 id.account.getJid().asBareJid().toEscapedString());
650 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
651 fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
652 return PendingIntent.getActivity(
653 mXmppConnectionService,
654 requestCode,
655 fullScreenIntent,
656 s()
657 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
658 : PendingIntent.FLAG_UPDATE_CURRENT);
659 }
660
661 public void cancelIncomingCallNotification() {
662 stopSoundAndVibration();
663 cancel(INCOMING_CALL_NOTIFICATION_ID);
664 }
665
666 public boolean stopSoundAndVibration() {
667 int stopped = 0;
668 if (this.currentlyPlayingRingtone != null) {
669 if (this.currentlyPlayingRingtone.isPlaying()) {
670 Log.d(Config.LOGTAG, "stop playing ring tone");
671 ++stopped;
672 }
673 this.currentlyPlayingRingtone.stop();
674 }
675 if (this.vibrationFuture != null && !this.vibrationFuture.isCancelled()) {
676 Log.d(Config.LOGTAG, "stop vibration");
677 this.vibrationFuture.cancel(true);
678 ++stopped;
679 }
680 return stopped > 0;
681 }
682
683 public static void cancelIncomingCallNotification(final Context context) {
684 final NotificationManagerCompat notificationManager =
685 NotificationManagerCompat.from(context);
686 try {
687 notificationManager.cancel(INCOMING_CALL_NOTIFICATION_ID);
688 } catch (RuntimeException e) {
689 Log.d(Config.LOGTAG, "unable to cancel incoming call notification after crash", e);
690 }
691 }
692
693 private void pushNow(final Message message) {
694 mXmppConnectionService.updateUnreadCountBadge();
695 if (!notifyMessage(message)) {
696 Log.d(
697 Config.LOGTAG,
698 message.getConversation().getAccount().getJid().asBareJid()
699 + ": suppressing notification because turned off");
700 return;
701 }
702 final boolean isScreenLocked = mXmppConnectionService.isScreenLocked();
703 if (this.mIsInForeground
704 && !isScreenLocked
705 && this.mOpenConversation == message.getConversation()) {
706 Log.d(
707 Config.LOGTAG,
708 message.getConversation().getAccount().getJid().asBareJid()
709 + ": suppressing notification because conversation is open");
710 return;
711 }
712 synchronized (notifications) {
713 pushToStack(message);
714 final Conversational conversation = message.getConversation();
715 final Account account = conversation.getAccount();
716 final boolean doNotify =
717 (!(this.mIsInForeground && this.mOpenConversation == null) || isScreenLocked)
718 && !account.inGracePeriod()
719 && !this.inMiniGracePeriod(account);
720 updateNotification(doNotify, Collections.singletonList(conversation.getUuid()));
721 }
722 }
723
724 private void pushMissedCall(final Message message) {
725 final Conversational conversation = message.getConversation();
726 final MissedCallsInfo info = mMissedCalls.get(conversation);
727 if (info == null) {
728 mMissedCalls.put(conversation, new MissedCallsInfo(message.getTimeSent()));
729 } else {
730 info.newMissedCall(message.getTimeSent());
731 }
732 }
733
734 public void pushMissedCallNow(final Message message) {
735 synchronized (mMissedCalls) {
736 pushMissedCall(message);
737 updateMissedCallNotifications(Collections.singleton(message.getConversation()));
738 }
739 }
740
741 public void clear(final Conversation conversation) {
742 clearMessages(conversation);
743 clearMissedCalls(conversation);
744 }
745
746 public void clearMessages() {
747 synchronized (notifications) {
748 for (ArrayList<Message> messages : notifications.values()) {
749 markAsReadIfHasDirectReply(messages);
750 }
751 notifications.clear();
752 updateNotification(false);
753 }
754 }
755
756 public void clearMessages(final Conversation conversation) {
757 synchronized (this.mBacklogMessageCounter) {
758 this.mBacklogMessageCounter.remove(conversation);
759 }
760 synchronized (notifications) {
761 markAsReadIfHasDirectReply(conversation);
762 if (notifications.remove(conversation.getUuid()) != null) {
763 cancel(conversation.getUuid(), NOTIFICATION_ID);
764 updateNotification(false, null, true);
765 }
766 }
767 }
768
769 public void clearMissedCalls() {
770 synchronized (mMissedCalls) {
771 for (final Conversational conversation : mMissedCalls.keySet()) {
772 cancel(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID);
773 }
774 mMissedCalls.clear();
775 updateMissedCallNotifications(null);
776 }
777 }
778
779 public void clearMissedCalls(final Conversation conversation) {
780 synchronized (mMissedCalls) {
781 if (mMissedCalls.remove(conversation) != null) {
782 cancel(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID);
783 updateMissedCallNotifications(null);
784 }
785 }
786 }
787
788 private void markAsReadIfHasDirectReply(final Conversation conversation) {
789 markAsReadIfHasDirectReply(notifications.get(conversation.getUuid()));
790 }
791
792 private void markAsReadIfHasDirectReply(final ArrayList<Message> messages) {
793 if (messages != null && messages.size() > 0) {
794 Message last = messages.get(messages.size() - 1);
795 if (last.getStatus() != Message.STATUS_RECEIVED) {
796 if (mXmppConnectionService.markRead((Conversation) last.getConversation(), false)) {
797 mXmppConnectionService.updateConversationUi();
798 }
799 }
800 }
801 }
802
803 private void setNotificationColor(final Builder mBuilder) {
804 mBuilder.setColor(ContextCompat.getColor(mXmppConnectionService, R.color.green600));
805 }
806
807 public void updateNotification() {
808 synchronized (notifications) {
809 updateNotification(false);
810 }
811 }
812
813 private void updateNotification(final boolean notify) {
814 updateNotification(notify, null, false);
815 }
816
817 private void updateNotification(final boolean notify, final List<String> conversations) {
818 updateNotification(notify, conversations, false);
819 }
820
821 private void updateNotification(
822 final boolean notify, final List<String> conversations, final boolean summaryOnly) {
823 final SharedPreferences preferences =
824 PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
825
826 final boolean quiteHours = isQuietHours();
827
828 final boolean notifyOnlyOneChild =
829 notify
830 && conversations != null
831 && conversations.size()
832 == 1; // if this check is changed to > 0 catchup messages will
833 // create one notification per conversation
834
835 if (notifications.size() == 0) {
836 cancel(NOTIFICATION_ID);
837 } else {
838 if (notify) {
839 this.markLastNotification();
840 }
841 final Builder mBuilder;
842 if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
843 mBuilder =
844 buildSingleConversations(
845 notifications.values().iterator().next(), notify, quiteHours);
846 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
847 notify(NOTIFICATION_ID, mBuilder.build());
848 } else {
849 mBuilder = buildMultipleConversation(notify, quiteHours);
850 if (notifyOnlyOneChild) {
851 mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
852 }
853 modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
854 if (!summaryOnly) {
855 for (Map.Entry<String, ArrayList<Message>> entry : notifications.entrySet()) {
856 String uuid = entry.getKey();
857 final boolean notifyThis =
858 notifyOnlyOneChild ? conversations.contains(uuid) : notify;
859 Builder singleBuilder =
860 buildSingleConversations(entry.getValue(), notifyThis, quiteHours);
861 if (!notifyOnlyOneChild) {
862 singleBuilder.setGroupAlertBehavior(
863 NotificationCompat.GROUP_ALERT_SUMMARY);
864 }
865 modifyForSoundVibrationAndLight(
866 singleBuilder, notifyThis, quiteHours, preferences);
867 singleBuilder.setGroup(MESSAGES_GROUP);
868 setNotificationColor(singleBuilder);
869 notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
870 }
871 }
872 notify(NOTIFICATION_ID, mBuilder.build());
873 }
874 }
875 }
876
877 private void updateMissedCallNotifications(final Set<Conversational> update) {
878 if (mMissedCalls.isEmpty()) {
879 cancel(MISSED_CALL_NOTIFICATION_ID);
880 return;
881 }
882 if (mMissedCalls.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
883 final Conversational conversation = mMissedCalls.keySet().iterator().next();
884 final MissedCallsInfo info = mMissedCalls.values().iterator().next();
885 final Notification notification = missedCall(conversation, info);
886 notify(MISSED_CALL_NOTIFICATION_ID, notification);
887 } else {
888 final Notification summary = missedCallsSummary();
889 notify(MISSED_CALL_NOTIFICATION_ID, summary);
890 if (update != null) {
891 for (final Conversational conversation : update) {
892 final MissedCallsInfo info = mMissedCalls.get(conversation);
893 if (info != null) {
894 final Notification notification = missedCall(conversation, info);
895 notify(conversation.getUuid(), MISSED_CALL_NOTIFICATION_ID, notification);
896 }
897 }
898 }
899 }
900 }
901
902 private void modifyForSoundVibrationAndLight(
903 Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) {
904 final Resources resources = mXmppConnectionService.getResources();
905 final String ringtone =
906 preferences.getString(
907 "notification_ringtone",
908 resources.getString(R.string.notification_ringtone));
909 final boolean vibrate =
910 preferences.getBoolean(
911 "vibrate_on_notification",
912 resources.getBoolean(R.bool.vibrate_on_notification));
913 final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
914 final boolean headsup =
915 preferences.getBoolean(
916 "notification_headsup", resources.getBoolean(R.bool.headsup_notifications));
917 if (notify && !quietHours) {
918 if (vibrate) {
919 final int dat = 70;
920 final long[] pattern = {0, 3 * dat, dat, dat};
921 mBuilder.setVibrate(pattern);
922 } else {
923 mBuilder.setVibrate(new long[] {0});
924 }
925 Uri uri = Uri.parse(ringtone);
926 try {
927 mBuilder.setSound(fixRingtoneUri(uri));
928 } catch (SecurityException e) {
929 Log.d(Config.LOGTAG, "unable to use custom notification sound " + uri.toString());
930 }
931 } else {
932 mBuilder.setLocalOnly(true);
933 }
934 mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
935 mBuilder.setPriority(
936 notify
937 ? (headsup
938 ? NotificationCompat.PRIORITY_HIGH
939 : NotificationCompat.PRIORITY_DEFAULT)
940 : NotificationCompat.PRIORITY_LOW);
941 setNotificationColor(mBuilder);
942 mBuilder.setDefaults(0);
943 if (led) {
944 mBuilder.setLights(LED_COLOR, 2000, 3000);
945 }
946 }
947
948 private void modifyIncomingCall(final Builder mBuilder) {
949 mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
950 setNotificationColor(mBuilder);
951 mBuilder.setLights(LED_COLOR, 2000, 3000);
952 }
953
954 private Uri fixRingtoneUri(Uri uri) {
955 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && "file".equals(uri.getScheme())) {
956 return FileBackend.getUriForFile(mXmppConnectionService, new File(uri.getPath()));
957 } else {
958 return uri;
959 }
960 }
961
962 private Notification missedCallsSummary() {
963 final Builder publicBuilder = buildMissedCallsSummary(true);
964 final Builder builder = buildMissedCallsSummary(false);
965 builder.setPublicVersion(publicBuilder.build());
966 return builder.build();
967 }
968
969 private Builder buildMissedCallsSummary(boolean publicVersion) {
970 final Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "missed_calls");
971 int totalCalls = 0;
972 final StringBuilder names = new StringBuilder();
973 long lastTime = 0;
974 for (Map.Entry<Conversational, MissedCallsInfo> entry : mMissedCalls.entrySet()) {
975 final Conversational conversation = entry.getKey();
976 final MissedCallsInfo missedCallsInfo = entry.getValue();
977 names.append(conversation.getContact().getDisplayName());
978 names.append(", ");
979 totalCalls += missedCallsInfo.getNumberOfCalls();
980 lastTime = Math.max(lastTime, missedCallsInfo.getLastTime());
981 }
982 if (names.length() >= 2) {
983 names.delete(names.length() - 2, names.length());
984 }
985 final String title = (totalCalls == 1) ? mXmppConnectionService.getString(R.string.missed_call) :
986 (mMissedCalls.size() == 1) ? mXmppConnectionService.getString(R.string.n_missed_calls, totalCalls) :
987 mXmppConnectionService.getString(R.string.n_missed_calls_from_m_contacts, totalCalls, mMissedCalls.size());
988 builder.setContentTitle(title);
989 builder.setTicker(title);
990 if (!publicVersion) {
991 builder.setContentText(names.toString());
992 }
993 builder.setSmallIcon(R.drawable.ic_missed_call_notification);
994 builder.setGroupSummary(true);
995 builder.setGroup(MISSED_CALLS_GROUP);
996 builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
997 builder.setCategory(NotificationCompat.CATEGORY_CALL);
998 builder.setWhen(lastTime);
999 if (!mMissedCalls.isEmpty()) {
1000 final Conversational firstConversation = mMissedCalls.keySet().iterator().next();
1001 builder.setContentIntent(createContentIntent(firstConversation));
1002 }
1003 builder.setDeleteIntent(createMissedCallsDeleteIntent(null));
1004 modifyMissedCall(builder);
1005 return builder;
1006 }
1007
1008 private Notification missedCall(final Conversational conversation, final MissedCallsInfo info) {
1009 final Builder publicBuilder = buildMissedCall(conversation, info, true);
1010 final Builder builder = buildMissedCall(conversation, info, false);
1011 builder.setPublicVersion(publicBuilder.build());
1012 return builder.build();
1013 }
1014
1015 private Builder buildMissedCall(final Conversational conversation, final MissedCallsInfo info, boolean publicVersion) {
1016 final Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "missed_calls");
1017 final String title = (info.getNumberOfCalls() == 1) ? mXmppConnectionService.getString(R.string.missed_call) :
1018 mXmppConnectionService.getString(R.string.n_missed_calls, info.getNumberOfCalls());
1019 builder.setContentTitle(title);
1020 final String name = conversation.getContact().getDisplayName();
1021 if (publicVersion) {
1022 builder.setTicker(title);
1023 } else {
1024 if (info.getNumberOfCalls() == 1) {
1025 builder.setTicker(mXmppConnectionService.getString(R.string.missed_call_from_x, name));
1026 } else {
1027 builder.setTicker(mXmppConnectionService.getString(R.string.n_missed_calls_from_x, info.getNumberOfCalls(), name));
1028 }
1029 builder.setContentText(name);
1030 }
1031 builder.setSmallIcon(R.drawable.ic_missed_call_notification);
1032 builder.setGroup(MISSED_CALLS_GROUP);
1033 builder.setCategory(NotificationCompat.CATEGORY_CALL);
1034 builder.setWhen(info.getLastTime());
1035 builder.setContentIntent(createContentIntent(conversation));
1036 builder.setDeleteIntent(createMissedCallsDeleteIntent(conversation));
1037 if (!publicVersion && conversation instanceof Conversation) {
1038 builder.setLargeIcon(mXmppConnectionService.getAvatarService()
1039 .get((Conversation) conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
1040 }
1041 modifyMissedCall(builder);
1042 return builder;
1043 }
1044
1045 private void modifyMissedCall(final Builder builder) {
1046 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
1047 final Resources resources = mXmppConnectionService.getResources();
1048 final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
1049 if (led) {
1050 builder.setLights(LED_COLOR, 2000, 3000);
1051 }
1052 builder.setPriority(NotificationCompat.PRIORITY_HIGH);
1053 builder.setSound(null);
1054 setNotificationColor(builder);
1055 }
1056
1057 private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
1058 final Builder mBuilder =
1059 new NotificationCompat.Builder(
1060 mXmppConnectionService,
1061 quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
1062 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
1063 style.setBigContentTitle(
1064 mXmppConnectionService
1065 .getResources()
1066 .getQuantityString(
1067 R.plurals.x_unread_conversations,
1068 notifications.size(),
1069 notifications.size()));
1070 final StringBuilder names = new StringBuilder();
1071 Conversation conversation = null;
1072 for (final ArrayList<Message> messages : notifications.values()) {
1073 if (messages.size() > 0) {
1074 conversation = (Conversation) messages.get(0).getConversation();
1075 final String name = conversation.getName().toString();
1076 SpannableString styledString;
1077 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
1078 int count = messages.size();
1079 styledString =
1080 new SpannableString(
1081 name
1082 + ": "
1083 + mXmppConnectionService
1084 .getResources()
1085 .getQuantityString(
1086 R.plurals.x_messages, count, count));
1087 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
1088 style.addLine(styledString);
1089 } else {
1090 styledString =
1091 new SpannableString(
1092 name
1093 + ": "
1094 + UIHelper.getMessagePreview(
1095 mXmppConnectionService, messages.get(0))
1096 .first);
1097 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
1098 style.addLine(styledString);
1099 }
1100 names.append(name);
1101 names.append(", ");
1102 }
1103 }
1104 if (names.length() >= 2) {
1105 names.delete(names.length() - 2, names.length());
1106 }
1107 final String contentTitle =
1108 mXmppConnectionService
1109 .getResources()
1110 .getQuantityString(
1111 R.plurals.x_unread_conversations,
1112 notifications.size(),
1113 notifications.size());
1114 mBuilder.setContentTitle(contentTitle);
1115 mBuilder.setTicker(contentTitle);
1116 mBuilder.setContentText(names.toString());
1117 mBuilder.setStyle(style);
1118 if (conversation != null) {
1119 mBuilder.setContentIntent(createContentIntent(conversation));
1120 }
1121 mBuilder.setGroupSummary(true);
1122 mBuilder.setGroup(MESSAGES_GROUP);
1123 mBuilder.setDeleteIntent(createDeleteIntent(null));
1124 mBuilder.setSmallIcon(R.drawable.ic_notification);
1125 return mBuilder;
1126 }
1127
1128 private Builder buildSingleConversations(
1129 final ArrayList<Message> messages, final boolean notify, final boolean quietHours) {
1130 final Builder mBuilder =
1131 new NotificationCompat.Builder(
1132 mXmppConnectionService,
1133 quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
1134 if (messages.size() >= 1) {
1135 final Conversation conversation = (Conversation) messages.get(0).getConversation();
1136 mBuilder.setLargeIcon(
1137 mXmppConnectionService
1138 .getAvatarService()
1139 .get(
1140 conversation,
1141 AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
1142 mBuilder.setContentTitle(conversation.getName());
1143 if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
1144 int count = messages.size();
1145 mBuilder.setContentText(
1146 mXmppConnectionService
1147 .getResources()
1148 .getQuantityString(R.plurals.x_messages, count, count));
1149 } else {
1150 Message message;
1151 // TODO starting with Android 9 we might want to put images in MessageStyle
1152 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
1153 && (message = getImage(messages)) != null) {
1154 modifyForImage(mBuilder, message, messages);
1155 } else {
1156 modifyForTextOnly(mBuilder, messages);
1157 }
1158 RemoteInput remoteInput =
1159 new RemoteInput.Builder("text_reply")
1160 .setLabel(
1161 UIHelper.getMessageHint(
1162 mXmppConnectionService, conversation))
1163 .build();
1164 PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
1165 NotificationCompat.Action markReadAction =
1166 new NotificationCompat.Action.Builder(
1167 R.drawable.ic_drafts_white_24dp,
1168 mXmppConnectionService.getString(R.string.mark_as_read),
1169 markAsReadPendingIntent)
1170 .setSemanticAction(
1171 NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
1172 .setShowsUserInterface(false)
1173 .build();
1174 final String replyLabel = mXmppConnectionService.getString(R.string.reply);
1175 final String lastMessageUuid = Iterables.getLast(messages).getUuid();
1176 final NotificationCompat.Action replyAction =
1177 new NotificationCompat.Action.Builder(
1178 R.drawable.ic_send_text_offline,
1179 replyLabel,
1180 createReplyIntent(conversation, lastMessageUuid, false))
1181 .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
1182 .setShowsUserInterface(false)
1183 .addRemoteInput(remoteInput)
1184 .build();
1185 final NotificationCompat.Action wearReplyAction =
1186 new NotificationCompat.Action.Builder(
1187 R.drawable.ic_wear_reply,
1188 replyLabel,
1189 createReplyIntent(conversation, lastMessageUuid, true))
1190 .addRemoteInput(remoteInput)
1191 .build();
1192 mBuilder.extend(
1193 new NotificationCompat.WearableExtender().addAction(wearReplyAction));
1194 int addedActionsCount = 1;
1195 mBuilder.addAction(markReadAction);
1196 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
1197 mBuilder.addAction(replyAction);
1198 ++addedActionsCount;
1199 }
1200
1201 if (displaySnoozeAction(messages)) {
1202 String label = mXmppConnectionService.getString(R.string.snooze);
1203 PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
1204 NotificationCompat.Action snoozeAction =
1205 new NotificationCompat.Action.Builder(
1206 R.drawable.ic_notifications_paused_white_24dp,
1207 label,
1208 pendingSnoozeIntent)
1209 .build();
1210 mBuilder.addAction(snoozeAction);
1211 ++addedActionsCount;
1212 }
1213 if (addedActionsCount < 3) {
1214 final Message firstLocationMessage = getFirstLocationMessage(messages);
1215 if (firstLocationMessage != null) {
1216 final PendingIntent pendingShowLocationIntent =
1217 createShowLocationIntent(firstLocationMessage);
1218 if (pendingShowLocationIntent != null) {
1219 final String label =
1220 mXmppConnectionService
1221 .getResources()
1222 .getString(R.string.show_location);
1223 NotificationCompat.Action locationAction =
1224 new NotificationCompat.Action.Builder(
1225 R.drawable.ic_room_white_24dp,
1226 label,
1227 pendingShowLocationIntent)
1228 .build();
1229 mBuilder.addAction(locationAction);
1230 ++addedActionsCount;
1231 }
1232 }
1233 }
1234 if (addedActionsCount < 3) {
1235 Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
1236 if (firstDownloadableMessage != null) {
1237 String label =
1238 mXmppConnectionService
1239 .getResources()
1240 .getString(
1241 R.string.download_x_file,
1242 UIHelper.getFileDescriptionString(
1243 mXmppConnectionService,
1244 firstDownloadableMessage));
1245 PendingIntent pendingDownloadIntent =
1246 createDownloadIntent(firstDownloadableMessage);
1247 NotificationCompat.Action downloadAction =
1248 new NotificationCompat.Action.Builder(
1249 R.drawable.ic_file_download_white_24dp,
1250 label,
1251 pendingDownloadIntent)
1252 .build();
1253 mBuilder.addAction(downloadAction);
1254 ++addedActionsCount;
1255 }
1256 }
1257 }
1258 if (conversation.getMode() == Conversation.MODE_SINGLE) {
1259 Contact contact = conversation.getContact();
1260 Uri systemAccount = contact.getSystemAccount();
1261 if (systemAccount != null) {
1262 mBuilder.addPerson(systemAccount.toString());
1263 }
1264 }
1265 mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
1266 mBuilder.setSmallIcon(R.drawable.ic_notification);
1267 mBuilder.setDeleteIntent(createDeleteIntent(conversation));
1268 mBuilder.setContentIntent(createContentIntent(conversation));
1269 }
1270 return mBuilder;
1271 }
1272
1273 private void modifyForImage(
1274 final Builder builder, final Message message, final ArrayList<Message> messages) {
1275 try {
1276 final Bitmap bitmap =
1277 mXmppConnectionService
1278 .getFileBackend()
1279 .getThumbnail(message, getPixel(288), false);
1280 final ArrayList<Message> tmp = new ArrayList<>();
1281 for (final Message msg : messages) {
1282 if (msg.getType() == Message.TYPE_TEXT && msg.getTransferable() == null) {
1283 tmp.add(msg);
1284 }
1285 }
1286 final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
1287 bigPictureStyle.bigPicture(bitmap);
1288 if (tmp.size() > 0) {
1289 CharSequence text = getMergedBodies(tmp);
1290 bigPictureStyle.setSummaryText(text);
1291 builder.setContentText(text);
1292 builder.setTicker(text);
1293 } else {
1294 final String description =
1295 UIHelper.getFileDescriptionString(mXmppConnectionService, message);
1296 builder.setContentText(description);
1297 builder.setTicker(description);
1298 }
1299 builder.setStyle(bigPictureStyle);
1300 } catch (final IOException e) {
1301 modifyForTextOnly(builder, messages);
1302 }
1303 }
1304
1305 private Person getPerson(Message message) {
1306 final Contact contact = message.getContact();
1307 final Person.Builder builder = new Person.Builder();
1308 if (contact != null) {
1309 builder.setName(contact.getDisplayName());
1310 final Uri uri = contact.getSystemAccount();
1311 if (uri != null) {
1312 builder.setUri(uri.toString());
1313 }
1314 } else {
1315 builder.setName(UIHelper.getMessageDisplayName(message));
1316 }
1317 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1318 builder.setIcon(
1319 IconCompat.createWithBitmap(
1320 mXmppConnectionService
1321 .getAvatarService()
1322 .get(
1323 message,
1324 AvatarService.getSystemUiAvatarSize(
1325 mXmppConnectionService),
1326 false)));
1327 }
1328 return builder.build();
1329 }
1330
1331 private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages) {
1332 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
1333 final Conversation conversation = (Conversation) messages.get(0).getConversation();
1334 final Person.Builder meBuilder =
1335 new Person.Builder().setName(mXmppConnectionService.getString(R.string.me));
1336 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1337 meBuilder.setIcon(
1338 IconCompat.createWithBitmap(
1339 mXmppConnectionService
1340 .getAvatarService()
1341 .get(
1342 conversation.getAccount(),
1343 AvatarService.getSystemUiAvatarSize(
1344 mXmppConnectionService))));
1345 }
1346 final Person me = meBuilder.build();
1347 NotificationCompat.MessagingStyle messagingStyle =
1348 new NotificationCompat.MessagingStyle(me);
1349 final boolean multiple = conversation.getMode() == Conversation.MODE_MULTI;
1350 if (multiple) {
1351 messagingStyle.setConversationTitle(conversation.getName());
1352 }
1353 for (Message message : messages) {
1354 final Person sender =
1355 message.getStatus() == Message.STATUS_RECEIVED ? getPerson(message) : null;
1356 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isImageMessage(message)) {
1357 final Uri dataUri =
1358 FileBackend.getMediaUri(
1359 mXmppConnectionService,
1360 mXmppConnectionService.getFileBackend().getFile(message));
1361 NotificationCompat.MessagingStyle.Message imageMessage =
1362 new NotificationCompat.MessagingStyle.Message(
1363 UIHelper.getMessagePreview(mXmppConnectionService, message)
1364 .first,
1365 message.getTimeSent(),
1366 sender);
1367 if (dataUri != null) {
1368 imageMessage.setData(message.getMimeType(), dataUri);
1369 }
1370 messagingStyle.addMessage(imageMessage);
1371 } else {
1372 messagingStyle.addMessage(
1373 UIHelper.getMessagePreview(mXmppConnectionService, message).first,
1374 message.getTimeSent(),
1375 sender);
1376 }
1377 }
1378 messagingStyle.setGroupConversation(multiple);
1379 builder.setStyle(messagingStyle);
1380 } else {
1381 if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
1382 builder.setStyle(
1383 new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
1384 final CharSequence preview =
1385 UIHelper.getMessagePreview(
1386 mXmppConnectionService, messages.get(messages.size() - 1))
1387 .first;
1388 builder.setContentText(preview);
1389 builder.setTicker(preview);
1390 builder.setNumber(messages.size());
1391 } else {
1392 final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
1393 SpannableString styledString;
1394 for (Message message : messages) {
1395 final String name = UIHelper.getMessageDisplayName(message);
1396 styledString = new SpannableString(name + ": " + message.getBody());
1397 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
1398 style.addLine(styledString);
1399 }
1400 builder.setStyle(style);
1401 int count = messages.size();
1402 if (count == 1) {
1403 final String name = UIHelper.getMessageDisplayName(messages.get(0));
1404 styledString = new SpannableString(name + ": " + messages.get(0).getBody());
1405 styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
1406 builder.setContentText(styledString);
1407 builder.setTicker(styledString);
1408 } else {
1409 final String text =
1410 mXmppConnectionService
1411 .getResources()
1412 .getQuantityString(R.plurals.x_messages, count, count);
1413 builder.setContentText(text);
1414 builder.setTicker(text);
1415 }
1416 }
1417 }
1418 }
1419
1420 private Message getImage(final Iterable<Message> messages) {
1421 Message image = null;
1422 for (final Message message : messages) {
1423 if (message.getStatus() != Message.STATUS_RECEIVED) {
1424 return null;
1425 }
1426 if (isImageMessage(message)) {
1427 image = message;
1428 }
1429 }
1430 return image;
1431 }
1432
1433 private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
1434 for (final Message message : messages) {
1435 if (message.getTransferable() != null
1436 || (message.getType() == Message.TYPE_TEXT && message.treatAsDownloadable())) {
1437 return message;
1438 }
1439 }
1440 return null;
1441 }
1442
1443 private Message getFirstLocationMessage(final Iterable<Message> messages) {
1444 for (final Message message : messages) {
1445 if (message.isGeoUri()) {
1446 return message;
1447 }
1448 }
1449 return null;
1450 }
1451
1452 private CharSequence getMergedBodies(final ArrayList<Message> messages) {
1453 final StringBuilder text = new StringBuilder();
1454 for (Message message : messages) {
1455 if (text.length() != 0) {
1456 text.append("\n");
1457 }
1458 text.append(UIHelper.getMessagePreview(mXmppConnectionService, message).first);
1459 }
1460 return text.toString();
1461 }
1462
1463 private PendingIntent createShowLocationIntent(final Message message) {
1464 Iterable<Intent> intents =
1465 GeoHelper.createGeoIntentsFromMessage(mXmppConnectionService, message);
1466 for (final Intent intent : intents) {
1467 if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
1468 return PendingIntent.getActivity(
1469 mXmppConnectionService,
1470 generateRequestCode(message.getConversation(), 18),
1471 intent,
1472 s()
1473 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1474 : PendingIntent.FLAG_UPDATE_CURRENT);
1475 }
1476 }
1477 return null;
1478 }
1479
1480 private PendingIntent createContentIntent(
1481 final String conversationUuid, final String downloadMessageUuid) {
1482 final Intent viewConversationIntent =
1483 new Intent(mXmppConnectionService, ConversationsActivity.class);
1484 viewConversationIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
1485 viewConversationIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversationUuid);
1486 if (downloadMessageUuid != null) {
1487 viewConversationIntent.putExtra(
1488 ConversationsActivity.EXTRA_DOWNLOAD_UUID, downloadMessageUuid);
1489 return PendingIntent.getActivity(
1490 mXmppConnectionService,
1491 generateRequestCode(conversationUuid, 8),
1492 viewConversationIntent,
1493 s()
1494 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1495 : PendingIntent.FLAG_UPDATE_CURRENT);
1496 } else {
1497 return PendingIntent.getActivity(
1498 mXmppConnectionService,
1499 generateRequestCode(conversationUuid, 10),
1500 viewConversationIntent,
1501 s()
1502 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1503 : PendingIntent.FLAG_UPDATE_CURRENT);
1504 }
1505 }
1506
1507 private int generateRequestCode(String uuid, int actionId) {
1508 return (actionId * NOTIFICATION_ID_MULTIPLIER)
1509 + (uuid.hashCode() % NOTIFICATION_ID_MULTIPLIER);
1510 }
1511
1512 private int generateRequestCode(Conversational conversation, int actionId) {
1513 return generateRequestCode(conversation.getUuid(), actionId);
1514 }
1515
1516 private PendingIntent createDownloadIntent(final Message message) {
1517 return createContentIntent(message.getConversationUuid(), message.getUuid());
1518 }
1519
1520 private PendingIntent createContentIntent(final Conversational conversation) {
1521 return createContentIntent(conversation.getUuid(), null);
1522 }
1523
1524 private PendingIntent createDeleteIntent(Conversation conversation) {
1525 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1526 intent.setAction(XmppConnectionService.ACTION_CLEAR_MESSAGE_NOTIFICATION);
1527 if (conversation != null) {
1528 intent.putExtra("uuid", conversation.getUuid());
1529 return PendingIntent.getService(
1530 mXmppConnectionService,
1531 generateRequestCode(conversation, 20),
1532 intent,
1533 s()
1534 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1535 : PendingIntent.FLAG_UPDATE_CURRENT);
1536 }
1537 return PendingIntent.getService(
1538 mXmppConnectionService,
1539 0,
1540 intent,
1541 s()
1542 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1543 : PendingIntent.FLAG_UPDATE_CURRENT);
1544 }
1545
1546 private PendingIntent createMissedCallsDeleteIntent(final Conversational conversation) {
1547 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1548 intent.setAction(XmppConnectionService.ACTION_CLEAR_MISSED_CALL_NOTIFICATION);
1549 if (conversation != null) {
1550 intent.putExtra("uuid", conversation.getUuid());
1551 return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 21), intent, 0);
1552 }
1553 return PendingIntent.getService(mXmppConnectionService, 1, intent, 0);
1554 }
1555
1556 private PendingIntent createReplyIntent(
1557 final Conversation conversation,
1558 final String lastMessageUuid,
1559 final boolean dismissAfterReply) {
1560 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1561 intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
1562 intent.putExtra("uuid", conversation.getUuid());
1563 intent.putExtra("dismiss_notification", dismissAfterReply);
1564 intent.putExtra("last_message_uuid", lastMessageUuid);
1565 final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14);
1566 return PendingIntent.getService(
1567 mXmppConnectionService,
1568 id,
1569 intent,
1570 s()
1571 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1572 : PendingIntent.FLAG_UPDATE_CURRENT);
1573 }
1574
1575 private PendingIntent createReadPendingIntent(Conversation conversation) {
1576 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1577 intent.setAction(XmppConnectionService.ACTION_MARK_AS_READ);
1578 intent.putExtra("uuid", conversation.getUuid());
1579 intent.setPackage(mXmppConnectionService.getPackageName());
1580 return PendingIntent.getService(
1581 mXmppConnectionService,
1582 generateRequestCode(conversation, 16),
1583 intent,
1584 s()
1585 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1586 : PendingIntent.FLAG_UPDATE_CURRENT);
1587 }
1588
1589 private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
1590 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1591 intent.setAction(action);
1592 intent.setPackage(mXmppConnectionService.getPackageName());
1593 intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, sessionId);
1594 return PendingIntent.getService(
1595 mXmppConnectionService,
1596 requestCode,
1597 intent,
1598 s()
1599 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1600 : PendingIntent.FLAG_UPDATE_CURRENT);
1601 }
1602
1603 private PendingIntent createSnoozeIntent(Conversation conversation) {
1604 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1605 intent.setAction(XmppConnectionService.ACTION_SNOOZE);
1606 intent.putExtra("uuid", conversation.getUuid());
1607 intent.setPackage(mXmppConnectionService.getPackageName());
1608 return PendingIntent.getService(
1609 mXmppConnectionService,
1610 generateRequestCode(conversation, 22),
1611 intent,
1612 s()
1613 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1614 : PendingIntent.FLAG_UPDATE_CURRENT);
1615 }
1616
1617 private PendingIntent createTryAgainIntent() {
1618 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1619 intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
1620 return PendingIntent.getService(
1621 mXmppConnectionService,
1622 45,
1623 intent,
1624 s()
1625 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1626 : PendingIntent.FLAG_UPDATE_CURRENT);
1627 }
1628
1629 private PendingIntent createDismissErrorIntent() {
1630 final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
1631 intent.setAction(XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS);
1632 return PendingIntent.getService(
1633 mXmppConnectionService,
1634 69,
1635 intent,
1636 s()
1637 ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1638 : PendingIntent.FLAG_UPDATE_CURRENT);
1639 }
1640
1641 private boolean wasHighlightedOrPrivate(final Message message) {
1642 if (message.getConversation() instanceof Conversation) {
1643 Conversation conversation = (Conversation) message.getConversation();
1644 final String nick = conversation.getMucOptions().getActualNick();
1645 final Pattern highlight = generateNickHighlightPattern(nick);
1646 if (message.getBody() == null || nick == null) {
1647 return false;
1648 }
1649 final Matcher m = highlight.matcher(message.getBody());
1650 return (m.find() || message.isPrivateMessage());
1651 } else {
1652 return false;
1653 }
1654 }
1655
1656 public void setOpenConversation(final Conversation conversation) {
1657 this.mOpenConversation = conversation;
1658 }
1659
1660 public void setIsInForeground(final boolean foreground) {
1661 this.mIsInForeground = foreground;
1662 }
1663
1664 private int getPixel(final int dp) {
1665 final DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics();
1666 return ((int) (dp * metrics.density));
1667 }
1668
1669 private void markLastNotification() {
1670 this.mLastNotification = SystemClock.elapsedRealtime();
1671 }
1672
1673 private boolean inMiniGracePeriod(final Account account) {
1674 final int miniGrace =
1675 account.getStatus() == Account.State.ONLINE
1676 ? Config.MINI_GRACE_PERIOD
1677 : Config.MINI_GRACE_PERIOD * 2;
1678 return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
1679 }
1680
1681 Notification createForegroundNotification() {
1682 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1683 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
1684 final List<Account> accounts = mXmppConnectionService.getAccounts();
1685 int enabled = 0;
1686 int connected = 0;
1687 if (accounts != null) {
1688 for (Account account : accounts) {
1689 if (account.isOnlineAndConnected()) {
1690 connected++;
1691 enabled++;
1692 } else if (account.isEnabled()) {
1693 enabled++;
1694 }
1695 }
1696 }
1697 mBuilder.setContentText(
1698 mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
1699 final PendingIntent openIntent = createOpenConversationsIntent();
1700 if (openIntent != null) {
1701 mBuilder.setContentIntent(openIntent);
1702 }
1703 mBuilder.setWhen(0)
1704 .setPriority(Notification.PRIORITY_MIN)
1705 .setSmallIcon(
1706 connected > 0
1707 ? R.drawable.ic_link_white_24dp
1708 : R.drawable.ic_link_off_white_24dp)
1709 .setLocalOnly(true);
1710
1711 if (Compatibility.runsTwentySix()) {
1712 mBuilder.setChannelId("foreground");
1713 }
1714
1715 return mBuilder.build();
1716 }
1717
1718 private PendingIntent createOpenConversationsIntent() {
1719 try {
1720 return PendingIntent.getActivity(
1721 mXmppConnectionService,
1722 0,
1723 new Intent(mXmppConnectionService, ConversationsActivity.class),
1724 s()
1725 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1726 : PendingIntent.FLAG_UPDATE_CURRENT);
1727 } catch (RuntimeException e) {
1728 return null;
1729 }
1730 }
1731
1732 void updateErrorNotification() {
1733 if (Config.SUPPRESS_ERROR_NOTIFICATION) {
1734 cancel(ERROR_NOTIFICATION_ID);
1735 return;
1736 }
1737 final boolean showAllErrors = QuickConversationsService.isConversations();
1738 final List<Account> errors = new ArrayList<>();
1739 boolean torNotAvailable = false;
1740 for (final Account account : mXmppConnectionService.getAccounts()) {
1741 if (account.hasErrorStatus()
1742 && account.showErrorNotification()
1743 && (showAllErrors
1744 || account.getLastErrorStatus() == Account.State.UNAUTHORIZED)) {
1745 errors.add(account);
1746 torNotAvailable |= account.getStatus() == Account.State.TOR_NOT_AVAILABLE;
1747 }
1748 }
1749 if (mXmppConnectionService.foregroundNotificationNeedsUpdatingWhenErrorStateChanges()) {
1750 notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
1751 }
1752 final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1753 if (errors.size() == 0) {
1754 cancel(ERROR_NOTIFICATION_ID);
1755 return;
1756 } else if (errors.size() == 1) {
1757 mBuilder.setContentTitle(
1758 mXmppConnectionService.getString(R.string.problem_connecting_to_account));
1759 mBuilder.setContentText(errors.get(0).getJid().asBareJid().toEscapedString());
1760 } else {
1761 mBuilder.setContentTitle(
1762 mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
1763 mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
1764 }
1765 mBuilder.addAction(
1766 R.drawable.ic_autorenew_white_24dp,
1767 mXmppConnectionService.getString(R.string.try_again),
1768 createTryAgainIntent());
1769 if (torNotAvailable) {
1770 if (TorServiceUtils.isOrbotInstalled(mXmppConnectionService)) {
1771 mBuilder.addAction(
1772 R.drawable.ic_play_circle_filled_white_48dp,
1773 mXmppConnectionService.getString(R.string.start_orbot),
1774 PendingIntent.getActivity(
1775 mXmppConnectionService,
1776 147,
1777 TorServiceUtils.LAUNCH_INTENT,
1778 s()
1779 ? PendingIntent.FLAG_IMMUTABLE
1780 | PendingIntent.FLAG_UPDATE_CURRENT
1781 : PendingIntent.FLAG_UPDATE_CURRENT));
1782 } else {
1783 mBuilder.addAction(
1784 R.drawable.ic_file_download_white_24dp,
1785 mXmppConnectionService.getString(R.string.install_orbot),
1786 PendingIntent.getActivity(
1787 mXmppConnectionService,
1788 146,
1789 TorServiceUtils.INSTALL_INTENT,
1790 s()
1791 ? PendingIntent.FLAG_IMMUTABLE
1792 | PendingIntent.FLAG_UPDATE_CURRENT
1793 : PendingIntent.FLAG_UPDATE_CURRENT));
1794 }
1795 }
1796 mBuilder.setDeleteIntent(createDismissErrorIntent());
1797 mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
1798 mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
1799 mBuilder.setLocalOnly(true);
1800 mBuilder.setPriority(Notification.PRIORITY_LOW);
1801 final Intent intent;
1802 if (AccountUtils.MANAGE_ACCOUNT_ACTIVITY != null) {
1803 intent = new Intent(mXmppConnectionService, AccountUtils.MANAGE_ACCOUNT_ACTIVITY);
1804 } else {
1805 intent = new Intent(mXmppConnectionService, EditAccountActivity.class);
1806 intent.putExtra("jid", errors.get(0).getJid().asBareJid().toEscapedString());
1807 intent.putExtra(EditAccountActivity.EXTRA_OPENED_FROM_NOTIFICATION, true);
1808 }
1809 mBuilder.setContentIntent(
1810 PendingIntent.getActivity(
1811 mXmppConnectionService,
1812 145,
1813 intent,
1814 s()
1815 ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
1816 : PendingIntent.FLAG_UPDATE_CURRENT));
1817 if (Compatibility.runsTwentySix()) {
1818 mBuilder.setChannelId("error");
1819 }
1820 notify(ERROR_NOTIFICATION_ID, mBuilder.build());
1821 }
1822
1823 void updateFileAddingNotification(int current, Message message) {
1824 Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
1825 mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
1826 mBuilder.setProgress(100, current, false);
1827 mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
1828 mBuilder.setContentIntent(createContentIntent(message.getConversation()));
1829 mBuilder.setOngoing(true);
1830 if (Compatibility.runsTwentySix()) {
1831 mBuilder.setChannelId("compression");
1832 }
1833 Notification notification = mBuilder.build();
1834 notify(FOREGROUND_NOTIFICATION_ID, notification);
1835 }
1836
1837 private void notify(String tag, int id, Notification notification) {
1838 final NotificationManagerCompat notificationManager =
1839 NotificationManagerCompat.from(mXmppConnectionService);
1840 try {
1841 notificationManager.notify(tag, id, notification);
1842 } catch (RuntimeException e) {
1843 Log.d(Config.LOGTAG, "unable to make notification", e);
1844 }
1845 }
1846
1847 public void notify(int id, Notification notification) {
1848 final NotificationManagerCompat notificationManager =
1849 NotificationManagerCompat.from(mXmppConnectionService);
1850 try {
1851 notificationManager.notify(id, notification);
1852 } catch (RuntimeException e) {
1853 Log.d(Config.LOGTAG, "unable to make notification", e);
1854 }
1855 }
1856
1857 public void cancel(int id) {
1858 final NotificationManagerCompat notificationManager =
1859 NotificationManagerCompat.from(mXmppConnectionService);
1860 try {
1861 notificationManager.cancel(id);
1862 } catch (RuntimeException e) {
1863 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1864 }
1865 }
1866
1867 private void cancel(String tag, int id) {
1868 final NotificationManagerCompat notificationManager =
1869 NotificationManagerCompat.from(mXmppConnectionService);
1870 try {
1871 notificationManager.cancel(tag, id);
1872 } catch (RuntimeException e) {
1873 Log.d(Config.LOGTAG, "unable to cancel notification", e);
1874 }
1875 }
1876
1877 private static class MissedCallsInfo {
1878 private int numberOfCalls;
1879 private long lastTime;
1880
1881 MissedCallsInfo(final long time) {
1882 numberOfCalls = 1;
1883 lastTime = time;
1884 }
1885
1886 public void newMissedCall(final long time) {
1887 ++numberOfCalls;
1888 lastTime = time;
1889 }
1890
1891 public int getNumberOfCalls() {
1892 return numberOfCalls;
1893 }
1894
1895 public long getLastTime() {
1896 return lastTime;
1897 }
1898 }
1899 private class VibrationRunnable implements Runnable {
1900
1901 @Override
1902 public void run() {
1903 final Vibrator vibrator =
1904 (Vibrator) mXmppConnectionService.getSystemService(Context.VIBRATOR_SERVICE);
1905 vibrator.vibrate(CALL_PATTERN, -1);
1906 }
1907 }
1908}