NotificationService.java

  1package eu.siacs.conversations.services;
  2
  3import java.io.FileNotFoundException;
  4import java.util.ArrayList;
  5import java.util.LinkedHashMap;
  6import java.util.regex.Matcher;
  7import java.util.regex.Pattern;
  8
  9import android.app.Notification;
 10import android.app.NotificationManager;
 11import android.app.PendingIntent;
 12import android.content.Context;
 13import android.content.Intent;
 14import android.content.SharedPreferences;
 15import android.graphics.Bitmap;
 16import android.net.Uri;
 17import android.os.PowerManager;
 18import android.os.SystemClock;
 19import android.support.v4.app.NotificationCompat;
 20import android.support.v4.app.TaskStackBuilder;
 21import android.text.Html;
 22import android.util.DisplayMetrics;
 23
 24import eu.siacs.conversations.Config;
 25import eu.siacs.conversations.R;
 26import eu.siacs.conversations.entities.Account;
 27import eu.siacs.conversations.entities.Conversation;
 28import eu.siacs.conversations.entities.Downloadable;
 29import eu.siacs.conversations.entities.Message;
 30import eu.siacs.conversations.ui.ConversationActivity;
 31
 32public class NotificationService {
 33
 34	private XmppConnectionService mXmppConnectionService;
 35
 36	private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
 37
 38	public static int NOTIFICATION_ID = 0x2342;
 39	private Conversation mOpenConversation;
 40	private boolean mIsInForeground;
 41	private long mLastNotification;
 42
 43	public NotificationService(XmppConnectionService service) {
 44		this.mXmppConnectionService = service;
 45	}
 46
 47	public void push(Message message) {
 48		PowerManager pm = (PowerManager) mXmppConnectionService
 49				.getSystemService(Context.POWER_SERVICE);
 50		boolean isScreenOn = pm.isScreenOn();
 51
 52		if (this.mIsInForeground && isScreenOn
 53				&& this.mOpenConversation == message.getConversation()) {
 54			return;
 55		}
 56		synchronized (notifications) {
 57			String conversationUuid = message.getConversationUuid();
 58			if (notifications.containsKey(conversationUuid)) {
 59				notifications.get(conversationUuid).add(message);
 60			} else {
 61				ArrayList<Message> mList = new ArrayList<Message>();
 62				mList.add(message);
 63				notifications.put(conversationUuid, mList);
 64			}
 65			Account account = message.getConversation().getAccount();
 66			updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
 67					&& !account.inGracePeriod()
 68					&& !this.inMiniGracePeriod(account));
 69		}
 70
 71	}
 72
 73	public void clear() {
 74		synchronized (notifications) {
 75			notifications.clear();
 76			updateNotification(false);
 77		}
 78	}
 79
 80	public void clear(Conversation conversation) {
 81		synchronized (notifications) {
 82			notifications.remove(conversation.getUuid());
 83			updateNotification(false);
 84		}
 85	}
 86
 87	private void updateNotification(boolean notify) {
 88		NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
 89				.getSystemService(Context.NOTIFICATION_SERVICE);
 90		SharedPreferences preferences = mXmppConnectionService.getPreferences();
 91
 92		String ringtone = preferences.getString("notification_ringtone", null);
 93		boolean vibrate = preferences.getBoolean("vibrate_on_notification",
 94				true);
 95
 96		if (notifications.size() == 0) {
 97			notificationManager.cancel(NOTIFICATION_ID);
 98		} else {
 99			if (notify) {
100				this.markLastNotification();
101			}
102			NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
103					mXmppConnectionService);
104			mBuilder.setSmallIcon(R.drawable.ic_notification);
105			if (notifications.size() == 1) {
106				ArrayList<Message> messages = notifications.values().iterator()
107						.next();
108				if (messages.size() >= 1) {
109					Conversation conversation = messages.get(0)
110							.getConversation();
111					mBuilder.setLargeIcon(mXmppConnectionService
112							.getAvatarService().get(conversation, getPixel(64)));
113					mBuilder.setContentTitle(conversation.getName());
114					if (messages.size() == 1 && messages.get(0).getType() == Message.TYPE_IMAGE) {
115						try {
116							Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(messages.get(0),getPixel(288),false);
117							mBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap));
118							mBuilder.setContentText(mXmppConnectionService.getString(R.string.image_file));
119						} catch (FileNotFoundException e) {
120							// TODO Auto-generated catch block
121							e.printStackTrace();
122						}
123						
124					} else {
125						StringBuilder text = new StringBuilder();
126						for (int i = 0; i < messages.size(); ++i) {
127							text.append(getReadableBody(messages.get(i)));
128							if (i != messages.size() - 1) {
129								text.append("\n");
130							}
131						}
132						mBuilder.setStyle(new NotificationCompat.BigTextStyle()
133								.bigText(text.toString()));
134						mBuilder.setContentText(getReadableBody(messages.get(0)));
135						if (notify) {
136							mBuilder.setTicker(getReadableBody(messages
137									.get(messages.size() - 1)));
138						}
139					}
140					mBuilder.setContentIntent(createContentIntent(conversation
141							.getUuid()));
142				} else {
143					notificationManager.cancel(NOTIFICATION_ID);
144					return;
145				}
146			} else {
147				NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
148				style.setBigContentTitle(notifications.size()
149						+ " "
150						+ mXmppConnectionService
151								.getString(R.string.unread_conversations));
152				StringBuilder names = new StringBuilder();
153				Conversation conversation = null;
154				for (ArrayList<Message> messages : notifications.values()) {
155					if (messages.size() > 0) {
156						conversation = messages.get(0).getConversation();
157						String name = conversation.getName();
158						style.addLine(Html.fromHtml("<b>" + name + "</b> "
159								+ getReadableBody(messages.get(0))));
160						names.append(name);
161						names.append(", ");
162					}
163				}
164				if (names.length() >= 2) {
165					names.delete(names.length() - 2, names.length());
166				}
167				mBuilder.setContentTitle(notifications.size()
168						+ " "
169						+ mXmppConnectionService
170								.getString(R.string.unread_conversations));
171				mBuilder.setContentText(names.toString());
172				mBuilder.setStyle(style);
173				if (conversation != null) {
174					mBuilder.setContentIntent(createContentIntent(conversation
175							.getUuid()));
176				}
177			}
178			if (notify) {
179				if (vibrate) {
180					int dat = 70;
181					long[] pattern = { 0, 3 * dat, dat, dat };
182					mBuilder.setVibrate(pattern);
183				}
184				if (ringtone != null) {
185					mBuilder.setSound(Uri.parse(ringtone));
186				}
187			}
188			mBuilder.setDeleteIntent(createDeleteIntent());
189			mBuilder.setLights(0xffffffff, 2000, 4000);
190			Notification notification = mBuilder.build();
191			notificationManager.notify(NOTIFICATION_ID, notification);
192		}
193	}
194
195	private String getReadableBody(Message message) {
196		if (message.getDownloadable() != null
197				&& (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message
198						.getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) {
199			return mXmppConnectionService.getText(
200					R.string.image_offered_for_download).toString();
201		} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
202			return mXmppConnectionService.getText(
203					R.string.encrypted_message_received).toString();
204		} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
205			return mXmppConnectionService.getText(R.string.decryption_failed)
206					.toString();
207		} else if (message.getType() == Message.TYPE_IMAGE) {
208			return mXmppConnectionService.getText(R.string.image_file)
209					.toString();
210		} else {
211			return message.getBody().trim();
212		}
213	}
214
215	private PendingIntent createContentIntent(String conversationUuid) {
216		TaskStackBuilder stackBuilder = TaskStackBuilder
217				.create(mXmppConnectionService);
218		stackBuilder.addParentStack(ConversationActivity.class);
219
220		Intent viewConversationIntent = new Intent(mXmppConnectionService,
221				ConversationActivity.class);
222		viewConversationIntent.setAction(Intent.ACTION_VIEW);
223		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
224				conversationUuid);
225		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
226
227		stackBuilder.addNextIntent(viewConversationIntent);
228
229		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
230				PendingIntent.FLAG_UPDATE_CURRENT);
231		return resultPendingIntent;
232	}
233
234	private PendingIntent createDeleteIntent() {
235		Intent intent = new Intent(mXmppConnectionService,
236				XmppConnectionService.class);
237		intent.setAction("clear_notification");
238		return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
239	}
240
241	public static boolean wasHighlightedOrPrivate(Message message) {
242		String nick = message.getConversation().getMucOptions().getActualNick();
243		Pattern highlight = generateNickHighlightPattern(nick);
244		if (message.getBody() == null || nick == null) {
245			return false;
246		}
247		Matcher m = highlight.matcher(message.getBody());
248		return (m.find() || message.getType() == Message.TYPE_PRIVATE);
249	}
250
251	private static Pattern generateNickHighlightPattern(String nick) {
252		// We expect a word boundary, i.e. space or start of string, followed by
253		// the
254		// nick (matched in case-insensitive manner), followed by optional
255		// punctuation (for example "bob: i disagree" or "how are you alice?"),
256		// followed by another word boundary.
257		return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
258				Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
259	}
260
261	public void setOpenConversation(Conversation conversation) {
262		this.mOpenConversation = conversation;
263	}
264
265	public void setIsInForeground(boolean foreground) {
266		this.mIsInForeground = foreground;
267	}
268
269	private int getPixel(int dp) {
270		DisplayMetrics metrics = mXmppConnectionService.getResources()
271				.getDisplayMetrics();
272		return ((int) (dp * metrics.density));
273	}
274
275	private void markLastNotification() {
276		this.mLastNotification = SystemClock.elapsedRealtime();
277	}
278
279	private boolean inMiniGracePeriod(Account account) {
280		int miniGrace = account.getStatus() == Account.STATUS_ONLINE ? Config.MINI_GRACE_PERIOD
281				: Config.MINI_GRACE_PERIOD * 2;
282		return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
283	}
284}