NotificationService.java

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