NotificationService.java

  1package eu.siacs.conversations.services;
  2
  3import android.app.Notification;
  4import android.app.NotificationManager;
  5import android.app.PendingIntent;
  6import android.content.Context;
  7import android.content.Intent;
  8import android.content.SharedPreferences;
  9import android.graphics.Bitmap;
 10import android.net.Uri;
 11import android.os.Build;
 12import android.os.SystemClock;
 13import android.support.v4.app.NotificationCompat;
 14import android.support.v4.app.NotificationCompat.BigPictureStyle;
 15import android.support.v4.app.NotificationCompat.Builder;
 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 NotificationManager nm = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
187			nm.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 NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
211				.getSystemService(Context.NOTIFICATION_SERVICE);
212		final SharedPreferences preferences = mXmppConnectionService.getPreferences();
213
214		if (notifications.size() == 0) {
215			notificationManager.cancel(NOTIFICATION_ID);
216		} else {
217			if (notify) {
218				this.markLastNotification();
219			}
220			final Builder mBuilder;
221			if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
222				mBuilder = buildSingleConversations(notifications.values().iterator().next());
223				modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
224				notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
225			} else {
226				mBuilder = buildMultipleConversation();
227				modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
228				notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
229				for(Map.Entry<String,ArrayList<Message>> entry : notifications.entrySet()) {
230					Builder singleBuilder = buildSingleConversations(entry.getValue());
231					singleBuilder.setGroup(CONVERSATIONS_GROUP);
232					modifyForSoundVibrationAndLight(singleBuilder,notify,preferences);
233					notificationManager.notify(entry.getKey(), NOTIFICATION_ID ,singleBuilder.build());
234				}
235			}
236		}
237	}
238
239
240	private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, SharedPreferences preferences) {
241		final String ringtone = preferences.getString("notification_ringtone", null);
242		final boolean vibrate = preferences.getBoolean("vibrate_on_notification", true);
243		final boolean led = preferences.getBoolean("led", true);
244		if (notify && !isQuietHours()) {
245			if (vibrate) {
246				final int dat = 70;
247				final long[] pattern = {0, 3 * dat, dat, dat};
248				mBuilder.setVibrate(pattern);
249			}
250			if (ringtone != null) {
251				mBuilder.setSound(Uri.parse(ringtone));
252			}
253		}
254		if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
255			mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
256		}
257		setNotificationColor(mBuilder);
258		mBuilder.setDefaults(0);
259		if (led) {
260			mBuilder.setLights(0xff00FF00, 2000, 3000);
261		}
262	}
263
264	private Builder buildMultipleConversation() {
265		final Builder mBuilder = new NotificationCompat.Builder(
266				mXmppConnectionService);
267		final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
268		style.setBigContentTitle(notifications.size()
269				+ " "
270				+ mXmppConnectionService
271				.getString(R.string.unread_conversations));
272		final StringBuilder names = new StringBuilder();
273		Conversation conversation = null;
274		for (final ArrayList<Message> messages : notifications.values()) {
275			if (messages.size() > 0) {
276				conversation = messages.get(0).getConversation();
277				final String name = conversation.getName();
278				if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
279					int count = messages.size();
280					style.addLine(Html.fromHtml("<b>"+name+"</b>: "+mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count)));
281				} else {
282					style.addLine(Html.fromHtml("<b>" + name + "</b>: "
283							+ UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first));
284				}
285				names.append(name);
286				names.append(", ");
287			}
288		}
289		if (names.length() >= 2) {
290			names.delete(names.length() - 2, names.length());
291		}
292		mBuilder.setContentTitle(notifications.size()
293				+ " "
294				+ mXmppConnectionService
295				.getString(R.string.unread_conversations));
296		mBuilder.setContentText(names.toString());
297		mBuilder.setStyle(style);
298		if (conversation != null) {
299			mBuilder.setContentIntent(createContentIntent(conversation));
300		}
301		mBuilder.setGroupSummary(true);
302		mBuilder.setGroup(CONVERSATIONS_GROUP);
303		mBuilder.setDeleteIntent(createDeleteIntent(null));
304		mBuilder.setSmallIcon(R.drawable.ic_notification);
305		return mBuilder;
306	}
307
308	private Builder buildSingleConversations(final ArrayList<Message> messages) {
309		final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
310		if (messages.size() >= 1) {
311			final Conversation conversation = messages.get(0).getConversation();
312			mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
313					.get(conversation, getPixel(64)));
314			mBuilder.setContentTitle(conversation.getName());
315			if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
316				int count = messages.size();
317				mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count));
318			} else {
319				Message message;
320				if ((message = getImage(messages)) != null) {
321					modifyForImage(mBuilder, message, messages);
322				} else {
323					modifyForTextOnly(mBuilder, messages);
324				}
325				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
326					RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
327					NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, "Reply", createReplyIntent(conversation)).addRemoteInput(remoteInput).build();
328					mBuilder.addAction(action);
329					if ((message = getFirstDownloadableMessage(messages)) != null) {
330						mBuilder.addAction(
331								Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
332										R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download,
333								mXmppConnectionService.getResources().getString(R.string.download_x_file,
334										UIHelper.getFileDescriptionString(mXmppConnectionService, message)),
335								createDownloadIntent(message)
336						);
337					}
338				}
339				if ((message = getFirstLocationMessage(messages)) != null) {
340					mBuilder.addAction(R.drawable.ic_room_white_24dp,
341							mXmppConnectionService.getString(R.string.show_location),
342							createShowLocationIntent(message));
343				}
344			}
345			if (conversation.getMode() == Conversation.MODE_SINGLE) {
346				Contact contact = conversation.getContact();
347				Uri systemAccount = contact.getSystemAccount();
348				if (systemAccount != null) {
349					mBuilder.addPerson(systemAccount.toString());
350				}
351			}
352			mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
353			mBuilder.setSmallIcon(R.drawable.ic_notification);
354			mBuilder.setDeleteIntent(createDeleteIntent(conversation));
355			mBuilder.setContentIntent(createContentIntent(conversation));
356		}
357		return mBuilder;
358	}
359
360	private void modifyForImage(final Builder builder, final Message message,
361								final ArrayList<Message> messages) {
362		try {
363			final Bitmap bitmap = mXmppConnectionService.getFileBackend()
364					.getThumbnail(message, getPixel(288), false);
365			final ArrayList<Message> tmp = new ArrayList<>();
366			for (final Message msg : messages) {
367				if (msg.getType() == Message.TYPE_TEXT
368						&& msg.getTransferable() == null) {
369					tmp.add(msg);
370				}
371			}
372			final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
373			bigPictureStyle.bigPicture(bitmap);
374			if (tmp.size() > 0) {
375				CharSequence text = getMergedBodies(tmp);
376				bigPictureStyle.setSummaryText(text);
377				builder.setContentText(text);
378			} else {
379				builder.setContentText(mXmppConnectionService.getString(
380						R.string.received_x_file,
381						UIHelper.getFileDescriptionString(mXmppConnectionService, message)));
382			}
383			builder.setStyle(bigPictureStyle);
384		} catch (final FileNotFoundException e) {
385			modifyForTextOnly(builder, messages);
386		}
387	}
388
389	private void modifyForTextOnly(final Builder builder, final ArrayList<Message> messages) {
390		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
391			NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(mXmppConnectionService.getString(R.string.me));
392			Conversation conversation = messages.get(0).getConversation();
393			if (conversation.getMode() == Conversation.MODE_MULTI) {
394				messagingStyle.setConversationTitle(conversation.getName());
395			}
396			for (Message message : messages) {
397				String sender = message.getStatus() == Message.STATUS_RECEIVED ? UIHelper.getMessageDisplayName(message) : null;
398				messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService,message).first, message.getTimeSent(), sender);
399			}
400			builder.setStyle(messagingStyle);
401		} else {
402			builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
403			builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first);
404		}
405	}
406
407	private Message getImage(final Iterable<Message> messages) {
408		Message image = null;
409		for (final Message message : messages) {
410			if (message.getStatus() != Message.STATUS_RECEIVED) {
411				return null;
412			}
413			if (message.getType() != Message.TYPE_TEXT
414					&& message.getTransferable() == null
415					&& message.getEncryption() != Message.ENCRYPTION_PGP
416					&& message.getFileParams().height > 0) {
417				image = message;
418			}
419		}
420		return image;
421	}
422
423	private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
424		for (final Message message : messages) {
425			if (message.getTransferable() != null
426					&& (message.getType() == Message.TYPE_FILE
427							|| message.getType() == Message.TYPE_IMAGE
428							|| message.treatAsDownloadable() != Message.Decision.NEVER)) {
429				return message;
430			}
431		}
432		return null;
433	}
434
435	private Message getFirstLocationMessage(final Iterable<Message> messages) {
436		for (final Message message : messages) {
437			if (GeoHelper.isGeoUri(message.getBody())) {
438				return message;
439			}
440		}
441		return null;
442	}
443
444	private CharSequence getMergedBodies(final ArrayList<Message> messages) {
445		final StringBuilder text = new StringBuilder();
446		for (int i = 0; i < messages.size(); ++i) {
447			text.append(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(i)).first);
448			if (i != messages.size() - 1) {
449				text.append("\n");
450			}
451		}
452		return text.toString();
453	}
454
455	private PendingIntent createShowLocationIntent(final Message message) {
456		Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message);
457		for (Intent intent : intents) {
458			if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
459				return PendingIntent.getActivity(mXmppConnectionService, 18, intent, PendingIntent.FLAG_UPDATE_CURRENT);
460			}
461		}
462		return createOpenConversationsIntent();
463	}
464
465	private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) {
466		final Intent viewConversationIntent = new Intent(mXmppConnectionService,ConversationActivity.class);
467		viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION);
468		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, conversationUuid);
469		if (downloadMessageUuid != null) {
470			viewConversationIntent.putExtra(ConversationActivity.EXTRA_DOWNLOAD_UUID, downloadMessageUuid);
471			return PendingIntent.getActivity(mXmppConnectionService,
472					conversationUuid.hashCode() % 389782,
473					viewConversationIntent,
474					PendingIntent.FLAG_UPDATE_CURRENT);
475		} else {
476			return PendingIntent.getActivity(mXmppConnectionService,
477					conversationUuid.hashCode() % 936236,
478					viewConversationIntent,
479					PendingIntent.FLAG_UPDATE_CURRENT);
480		}
481	}
482
483	private PendingIntent createDownloadIntent(final Message message) {
484		return createContentIntent(message.getConversationUuid(), message.getUuid());
485	}
486
487	private PendingIntent createContentIntent(final Conversation conversation) {
488		return createContentIntent(conversation.getUuid(), null);
489	}
490
491	private PendingIntent createDeleteIntent(Conversation conversation) {
492		final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
493		intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION);
494		if (conversation != null) {
495			intent.putExtra("uuid", conversation.getUuid());
496			return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 247527, intent, 0);
497		}
498		return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
499	}
500
501	private PendingIntent createReplyIntent(Conversation conversation) {
502		final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
503		intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
504		intent.putExtra("uuid",conversation.getUuid());
505		return PendingIntent.getService(mXmppConnectionService, conversation.getUuid().hashCode() % 402361, intent, 0);
506	}
507
508	private PendingIntent createDisableForeground() {
509		final Intent intent = new Intent(mXmppConnectionService,
510				XmppConnectionService.class);
511		intent.setAction(XmppConnectionService.ACTION_DISABLE_FOREGROUND);
512		return PendingIntent.getService(mXmppConnectionService, 34, intent, 0);
513	}
514
515	private PendingIntent createTryAgainIntent() {
516		final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
517		intent.setAction(XmppConnectionService.ACTION_TRY_AGAIN);
518		return PendingIntent.getService(mXmppConnectionService, 45, intent, 0);
519	}
520
521	private PendingIntent createDisableAccountIntent(final Account account) {
522		final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
523		intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT);
524		intent.putExtra("account", account.getJid().toBareJid().toString());
525		return PendingIntent.getService(mXmppConnectionService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
526	}
527
528	private boolean wasHighlightedOrPrivate(final Message message) {
529		final String nick = message.getConversation().getMucOptions().getActualNick();
530		final Pattern highlight = generateNickHighlightPattern(nick);
531		if (message.getBody() == null || nick == null) {
532			return false;
533		}
534		final Matcher m = highlight.matcher(message.getBody());
535		return (m.find() || message.getType() == Message.TYPE_PRIVATE);
536	}
537
538	private static Pattern generateNickHighlightPattern(final String nick) {
539		// We expect a word boundary, i.e. space or start of string, followed by
540		// the
541		// nick (matched in case-insensitive manner), followed by optional
542		// punctuation (for example "bob: i disagree" or "how are you alice?"),
543		// followed by another word boundary.
544		return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
545				Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
546	}
547
548	public void setOpenConversation(final Conversation conversation) {
549		this.mOpenConversation = conversation;
550	}
551
552	public void setIsInForeground(final boolean foreground) {
553		this.mIsInForeground = foreground;
554	}
555
556	private int getPixel(final int dp) {
557		final DisplayMetrics metrics = mXmppConnectionService.getResources()
558				.getDisplayMetrics();
559		return ((int) (dp * metrics.density));
560	}
561
562	private void markLastNotification() {
563		this.mLastNotification = SystemClock.elapsedRealtime();
564	}
565
566	private boolean inMiniGracePeriod(final Account account) {
567		final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD
568				: Config.MINI_GRACE_PERIOD * 2;
569		return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
570	}
571
572	public Notification createForegroundNotification() {
573		final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
574
575		mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service));
576		if (Config.SHOW_CONNECTED_ACCOUNTS) {
577			List<Account> accounts = mXmppConnectionService.getAccounts();
578			int enabled = 0;
579			int connected = 0;
580			for (Account account : accounts) {
581				if (account.isOnlineAndConnected()) {
582					connected++;
583					enabled++;
584				} else if (!account.isOptionSet(Account.OPTION_DISABLED)) {
585					enabled++;
586				}
587			}
588			mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
589		} else {
590			mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
591		}
592		mBuilder.setContentIntent(createOpenConversationsIntent());
593		mBuilder.setWhen(0);
594		mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN);
595		mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
596		if (Config.SHOW_DISABLE_FOREGROUND) {
597			final int cancelIcon;
598			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
599				mBuilder.setCategory(Notification.CATEGORY_SERVICE);
600				cancelIcon = R.drawable.ic_cancel_white_24dp;
601			} else {
602				cancelIcon = R.drawable.ic_action_cancel;
603			}
604			mBuilder.addAction(cancelIcon,
605					mXmppConnectionService.getString(R.string.disable_foreground_service),
606					createDisableForeground());
607		}
608		return mBuilder.build();
609	}
610
611	private PendingIntent createOpenConversationsIntent() {
612		return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationActivity.class), 0);
613	}
614
615	public void updateErrorNotification() {
616		final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
617		final List<Account> errors = new ArrayList<>();
618		for (final Account account : mXmppConnectionService.getAccounts()) {
619			if (account.hasErrorStatus()) {
620				errors.add(account);
621			}
622		}
623		if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) {
624			notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
625		}
626		final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
627		if (errors.size() == 0) {
628			notificationManager.cancel(ERROR_NOTIFICATION_ID);
629			return;
630		} else if (errors.size() == 1) {
631			mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account));
632			mBuilder.setContentText(errors.get(0).getJid().toBareJid().toString());
633		} else {
634			mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
635			mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
636		}
637		mBuilder.addAction(R.drawable.ic_autorenew_white_24dp,
638				mXmppConnectionService.getString(R.string.try_again),
639				createTryAgainIntent());
640		if (errors.size() == 1) {
641			mBuilder.addAction(R.drawable.ic_block_white_24dp,
642					mXmppConnectionService.getString(R.string.disable_account),
643					createDisableAccountIntent(errors.get(0)));
644		}
645		mBuilder.setOngoing(true);
646		//mBuilder.setLights(0xffffffff, 2000, 4000);
647		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
648			mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
649		} else {
650			mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
651		}
652		final TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService);
653		stackBuilder.addParentStack(ConversationActivity.class);
654
655		final Intent manageAccountsIntent = new Intent(mXmppConnectionService, ManageAccountActivity.class);
656		stackBuilder.addNextIntent(manageAccountsIntent);
657
658		final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
659
660		mBuilder.setContentIntent(resultPendingIntent);
661		notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build());
662	}
663}