UIHelper.java

  1package eu.siacs.conversations.utils;
  2
  3import java.io.FileNotFoundException;
  4import java.text.SimpleDateFormat;
  5import java.util.ArrayList;
  6import java.util.Date;
  7import java.util.List;
  8import java.util.Locale;
  9import java.util.regex.Pattern;
 10import java.util.regex.Matcher;
 11
 12import eu.siacs.conversations.R;
 13import eu.siacs.conversations.entities.Account;
 14import eu.siacs.conversations.entities.Contact;
 15import eu.siacs.conversations.entities.Conversation;
 16import eu.siacs.conversations.entities.Message;
 17import eu.siacs.conversations.entities.MucOptions.User;
 18import eu.siacs.conversations.ui.ConversationActivity;
 19import eu.siacs.conversations.ui.ManageAccountActivity;
 20
 21import android.app.Activity;
 22import android.app.AlertDialog;
 23import android.app.Notification;
 24import android.app.NotificationManager;
 25import android.app.PendingIntent;
 26import android.content.Context;
 27import android.content.DialogInterface;
 28import android.content.DialogInterface.OnClickListener;
 29import android.content.Intent;
 30import android.content.SharedPreferences;
 31import android.graphics.Bitmap;
 32import android.graphics.BitmapFactory;
 33import android.graphics.Canvas;
 34import android.graphics.Paint;
 35import android.graphics.Rect;
 36import android.graphics.Typeface;
 37import android.net.Uri;
 38import android.preference.PreferenceManager;
 39import android.provider.ContactsContract.Contacts;
 40import android.support.v4.app.NotificationCompat;
 41import android.support.v4.app.TaskStackBuilder;
 42import android.text.Html;
 43import android.util.DisplayMetrics;
 44import android.view.LayoutInflater;
 45import android.view.View;
 46import android.widget.LinearLayout;
 47import android.widget.QuickContactBadge;
 48import android.widget.TextView;
 49
 50public class UIHelper {
 51	private static final int BG_COLOR = 0xFF181818;
 52	private static final int FG_COLOR = 0xFFE5E5E5;
 53	private static final int TRANSPARENT = 0x00000000;
 54
 55	public static String readableTimeDifference(Context context, long time) {
 56		if (time == 0) {
 57			return context.getString(R.string.just_now);
 58		}
 59		Date date = new Date(time);
 60		long difference = (System.currentTimeMillis() - time) / 1000;
 61		if (difference < 60) {
 62			return context.getString(R.string.just_now);
 63		} else if (difference < 60 * 10) {
 64			return difference / 60 + " " + context.getString(R.string.minutes_ago);
 65		} else if (difference < 60 * 60 * 24) {
 66			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm",Locale.US);
 67			return sdf.format(date);
 68		} else {
 69			SimpleDateFormat sdf = new SimpleDateFormat("MM/dd",Locale.US);
 70			return sdf.format(date);
 71		}
 72	}
 73
 74	public static int getRealPx(int dp, Context context) {
 75		final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
 76		return ((int) (dp * metrics.density));
 77	}
 78
 79	private static int getNameColor(String name) {
 80		int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713,
 81				0xFFe92727 };
 82		int color = holoColors[Math.abs(name.toLowerCase(Locale.getDefault()).hashCode()) % holoColors.length];
 83		return color;
 84	}
 85
 86	private static Bitmap getUnknownContactPicture(String[] names, int size, int bgColor, int fgColor) {
 87		int tiles = (names.length > 4)? 4 :
 88					(names.length < 1)? 1 :
 89						names.length;
 90		Bitmap bitmap = Bitmap
 91				.createBitmap(size, size, Bitmap.Config.ARGB_8888);
 92		Canvas canvas = new Canvas(bitmap);
 93
 94		String[] letters = new String[tiles];
 95		int[] colors = new int[tiles];
 96		if (names.length < 1) {
 97			letters[0] = "?";
 98			colors[0] = 0xFFe92727;
 99		} else {
100			for(int i = 0; i < tiles; ++i) {
101				letters[i] = (names[i].length() > 0) ?
102						names[i].substring(0, 1).toUpperCase(Locale.US) : " ";
103				colors[i] = getNameColor(names[i]);
104			}
105
106			if (names.length > 4) {
107				letters[3] = "\u2026"; // Unicode ellipsis
108				colors[3] = 0xFF444444;
109			}
110		}
111		Paint textPaint = new Paint(), tilePaint = new Paint();
112		textPaint.setColor(fgColor);
113		textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
114		Rect rect, left, right, topLeft, bottomLeft, topRight, bottomRight;
115		float width;
116
117		switch(tiles) {
118			case 1:
119				bitmap.eraseColor(colors[0]);
120
121				textPaint.setTextSize((float) (size * 0.8));
122				textPaint.setAntiAlias(true);
123				rect = new Rect();
124				textPaint.getTextBounds(letters[0], 0, 1, rect);
125				width = textPaint.measureText(letters[0]);
126				canvas.drawText(letters[0], (size / 2) - (width / 2), (size / 2)
127						+ (rect.height() / 2), textPaint);
128				break;
129
130			case 2:
131				bitmap.eraseColor(bgColor);
132
133				tilePaint.setColor(colors[0]);
134				left = new Rect(0, 0, (size/2)-1, size);
135				canvas.drawRect(left, tilePaint);
136
137				tilePaint.setColor(colors[1]);
138				right = new Rect((size/2)+1, 0, size, size);
139				canvas.drawRect(right, tilePaint);
140
141				textPaint.setTextSize((float) (size * 0.8*0.5));
142				textPaint.setAntiAlias(true);
143				rect = new Rect();
144				textPaint.getTextBounds(letters[0], 0, 1, rect);
145				width = textPaint.measureText(letters[0]);
146				canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2)
147						+ (rect.height() / 2), textPaint);
148				textPaint.getTextBounds(letters[1], 0, 1, rect);
149				width = textPaint.measureText(letters[1]);
150				canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 2)
151						+ (rect.height() / 2), textPaint);
152				break;
153
154			case 3:
155				bitmap.eraseColor(bgColor);
156
157				tilePaint.setColor(colors[0]);
158				left = new Rect(0, 0, (size/2)-1, size);
159				canvas.drawRect(left, tilePaint);
160
161				tilePaint.setColor(colors[1]);
162				topRight = new Rect((size/2)+1, 0, size, (size/2 - 1));
163				canvas.drawRect(topRight, tilePaint);
164
165				tilePaint.setColor(colors[2]);
166				bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size);
167				canvas.drawRect(bottomRight, tilePaint);
168
169				textPaint.setTextSize((float) (size * 0.8*0.5));
170				textPaint.setAntiAlias(true);
171				rect = new Rect();
172
173				textPaint.getTextBounds(letters[0], 0, 1, rect);
174				width = textPaint.measureText(letters[0]);
175				canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2)
176						+ (rect.height() / 2), textPaint);
177
178				textPaint.getTextBounds(letters[1], 0, 1, rect);
179				width = textPaint.measureText(letters[1]);
180				canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 4)
181						+ (rect.height() / 2), textPaint);
182
183				textPaint.getTextBounds(letters[2], 0, 1, rect);
184				width = textPaint.measureText(letters[2]);
185				canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (3* size / 4)
186						+ (rect.height() / 2), textPaint);
187				break;
188
189			case 4:
190				bitmap.eraseColor(bgColor);
191
192				tilePaint.setColor(colors[0]);
193				topLeft = new Rect(0, 0, (size/2)-1, (size/2)-1);
194				canvas.drawRect(topLeft, tilePaint);
195
196				tilePaint.setColor(colors[1]);
197				bottomLeft = new Rect(0, (size/2)+1, (size/2)-1, size);
198				canvas.drawRect(bottomLeft, tilePaint);
199
200				tilePaint.setColor(colors[2]);
201				topRight = new Rect((size/2)+1, 0, size, (size/2 - 1));
202				canvas.drawRect(topRight, tilePaint);
203
204				tilePaint.setColor(colors[3]);
205				bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size);
206				canvas.drawRect(bottomRight, tilePaint);
207
208				textPaint.setTextSize((float) (size * 0.8*0.5));
209				textPaint.setAntiAlias(true);
210				rect = new Rect();
211
212				textPaint.getTextBounds(letters[0], 0, 1, rect);
213				width = textPaint.measureText(letters[0]);
214				canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 4)
215						+ (rect.height() / 2), textPaint);
216
217				textPaint.getTextBounds(letters[1], 0, 1, rect);
218				width = textPaint.measureText(letters[1]);
219				canvas.drawText(letters[1], (size / 4) - (width / 2), (3* size / 4)
220						+ (rect.height() / 2), textPaint);
221
222				textPaint.getTextBounds(letters[2], 0, 1, rect);
223				width = textPaint.measureText(letters[2]);
224				canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (size / 4)
225						+ (rect.height() / 2), textPaint);
226
227				textPaint.getTextBounds(letters[3], 0, 1, rect);
228				width = textPaint.measureText(letters[3]);
229				canvas.drawText(letters[3], (3 * size / 4) - (width / 2), (3* size / 4)
230						+ (rect.height() / 2), textPaint);
231				break;
232		}
233		return bitmap;
234	}
235	
236	private static Bitmap getMucContactPicture(Conversation conversation, int size, int bgColor, int fgColor) {
237		List<User> members = conversation.getMucOptions().getUsers();
238		if (members.size() == 0) {
239			return getUnknownContactPicture(new String[]{conversation.getName(false)}, size, bgColor, fgColor);
240		}
241		String[] names = new String[members.size()+1];
242		names[0] = conversation.getMucOptions().getNick();
243		for(int i = 0; i < members.size(); ++i) {
244			names[i+1] = members.get(i).getName();
245		}
246
247		return getUnknownContactPicture(names, size, bgColor, fgColor);
248	}
249
250	public static Bitmap getContactPicture(Conversation conversation, int dpSize, Context context, boolean notification) {
251		if(conversation.getMode() == Conversation.MODE_SINGLE) {
252				return getContactPicture(conversation.getContact(), dpSize,
253						context, notification);
254		} else{
255			int fgColor = UIHelper.FG_COLOR,
256				bgColor = (notification) ?
257					UIHelper.BG_COLOR : UIHelper.TRANSPARENT;
258
259			return getMucContactPicture(conversation, getRealPx(dpSize, context),
260					bgColor, fgColor);
261		}
262	}
263
264	public static Bitmap getContactPicture(Contact contact, int dpSize, Context context, boolean notification) {
265		String uri = contact.getProfilePhoto();
266		if (uri==null) {
267			return getContactPicture(contact.getDisplayName(), dpSize,
268					context, notification);
269		}
270		try {
271			Bitmap bm = BitmapFactory.decodeStream(context.getContentResolver()
272					.openInputStream(Uri.parse(uri)));
273			return Bitmap.createScaledBitmap(bm, getRealPx(dpSize, context),
274					getRealPx(dpSize, context), false);
275		} catch (FileNotFoundException e) {
276			return getContactPicture(contact.getDisplayName(), dpSize,
277					context, notification);
278		}
279	}
280
281	public static Bitmap getContactPicture(String name, int dpSize, Context context, boolean notification) {
282		int fgColor = UIHelper.FG_COLOR,
283			bgColor = (notification) ?
284				UIHelper.BG_COLOR : UIHelper.TRANSPARENT;
285
286		return getUnknownContactPicture(new String[]{name}, getRealPx(dpSize, context),
287				bgColor, fgColor);
288	}
289
290	public static void showErrorNotification(Context context, List<Account> accounts) {
291		NotificationManager mNotificationManager = (NotificationManager) context
292				.getSystemService(Context.NOTIFICATION_SERVICE);
293		List<Account> accountsWproblems = new ArrayList<Account>();
294		for(Account account : accounts) {
295			if (account.hasErrorStatus()) {
296				accountsWproblems.add(account);
297			}
298		}
299		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
300		if (accountsWproblems.size() == 0) {
301			mNotificationManager.cancel(1111);
302			return;
303		} else if (accountsWproblems.size() == 1) {
304			mBuilder.setContentTitle(context.getString(R.string.problem_connecting_to_account));
305			mBuilder.setContentText(accountsWproblems.get(0).getJid());
306		} else {
307			mBuilder.setContentTitle(context.getString(R.string.problem_connecting_to_accounts));
308			mBuilder.setContentText(context.getString(R.string.touch_to_fix));
309		}
310		mBuilder.setOngoing(true);
311		mBuilder.setLights(0xffffffff, 2000, 4000);
312		mBuilder.setSmallIcon(R.drawable.notification);
313		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
314		stackBuilder.addParentStack(ConversationActivity.class);
315
316		Intent manageAccountsIntent = new Intent(context,
317				ManageAccountActivity.class);
318		stackBuilder.addNextIntent(manageAccountsIntent);
319
320		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
321				0, PendingIntent.FLAG_UPDATE_CURRENT);
322		
323		mBuilder.setContentIntent(resultPendingIntent);
324		Notification notification = mBuilder.build();
325		mNotificationManager.notify(1111, notification);
326	}
327
328	private static Pattern generateNickHighlightPattern(String nick) {
329		// We expect a word boundary, i.e. space or start of string, followed by the
330		// nick (matched in case-insensitive manner), followed by optional
331		// punctuation (for example "bob: i disagree" or "how are you alice?"),
332		// followed by another word boundary.
333		return Pattern.compile("\\b"+nick+"\\p{Punct}?\\b",
334				Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
335	}
336
337	public static void updateNotification(Context context,
338			List<Conversation> conversations, Conversation currentCon, boolean notify) {
339		NotificationManager mNotificationManager = (NotificationManager) context
340				.getSystemService(Context.NOTIFICATION_SERVICE);
341		
342		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
343		boolean useSubject = preferences.getBoolean("use_subject_in_muc", true);
344		boolean showNofifications = preferences.getBoolean("show_notification",true);
345		boolean vibrate = preferences.getBoolean("vibrate_on_notification", true);
346		boolean alwaysNotify = preferences.getBoolean("notify_in_conversation_when_highlighted", false);
347
348		if (!showNofifications) {
349			mNotificationManager.cancel(2342);
350			return;
351		}
352		
353		String targetUuid = "";
354		
355		if ((currentCon != null) &&(currentCon.getMode() == Conversation.MODE_MULTI)&&(!alwaysNotify)) {
356			String nick = currentCon.getMucOptions().getNick();
357			Pattern highlight = generateNickHighlightPattern(nick);
358			Matcher m = highlight.matcher(currentCon.getLatestMessage().getBody());
359			notify = m.find();
360		}
361		
362		List<Conversation> unread = new ArrayList<Conversation>();
363		for (Conversation conversation : conversations) {
364			if (conversation.getMode() == Conversation.MODE_MULTI) {
365				if ((!conversation.isRead())&&((wasHighlighted(conversation)||(alwaysNotify)))) {
366					unread.add(conversation);
367				}
368			} else {
369				if (!conversation.isRead()) {
370					unread.add(conversation);
371				}
372			}
373		}
374		String ringtone = preferences.getString("notification_ringtone", null);
375
376		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
377				context);
378		if (unread.size() == 0) {
379			mNotificationManager.cancel(2342);
380			return;
381		} else if (unread.size() == 1) {
382			Conversation conversation = unread.get(0);
383			targetUuid = conversation.getUuid();
384			mBuilder.setLargeIcon(UIHelper.getContactPicture(conversation, 64,
385							context, true));
386			mBuilder.setContentTitle(conversation.getName(useSubject));
387			if (notify) {
388				mBuilder.setTicker(conversation.getLatestMessage().getReadableBody(context));
389			}
390			StringBuilder bigText = new StringBuilder();
391			List<Message> messages = conversation.getMessages();
392			String firstLine = "";
393			for (int i = messages.size() - 1; i >= 0; --i) {
394				if (!messages.get(i).isRead()) {
395					if (i == messages.size() - 1) {
396						firstLine = messages.get(i).getReadableBody(context);
397						bigText.append(firstLine);
398					} else {
399						firstLine = messages.get(i).getReadableBody(context);
400						bigText.insert(0, firstLine + "\n");
401					}
402				} else {
403					break;
404				}
405			}
406			mBuilder.setContentText(firstLine);
407			mBuilder.setStyle(new NotificationCompat.BigTextStyle()
408					.bigText(bigText.toString()));
409		} else {
410			NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
411			style.setBigContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations));
412			StringBuilder names = new StringBuilder();
413			for (int i = 0; i < unread.size(); ++i) {
414				targetUuid = unread.get(i).getUuid();
415				if (i < unread.size() - 1) {
416					names.append(unread.get(i).getName(useSubject) + ", ");
417				} else {
418					names.append(unread.get(i).getName(useSubject));
419				}
420				style.addLine(Html.fromHtml("<b>" + unread.get(i).getName(useSubject)
421						+ "</b> " + unread.get(i).getLatestMessage().getReadableBody(context)));
422			}
423			mBuilder.setContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations));
424			mBuilder.setContentText(names.toString());
425			mBuilder.setStyle(style);
426		}
427		if ((currentCon!=null)&&(notify)) {
428			targetUuid=currentCon.getUuid();
429		}
430		if (unread.size() != 0) {
431			mBuilder.setSmallIcon(R.drawable.notification);
432			if (notify) {
433				if (vibrate) {
434					int dat = 70;
435					long[] pattern = {0,3*dat,dat,dat};
436					mBuilder.setVibrate(pattern);
437				}
438				mBuilder.setLights(0xffffffff, 2000, 4000);
439				if (ringtone != null) {
440					mBuilder.setSound(Uri.parse(ringtone));
441				}
442			}
443
444			TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
445			stackBuilder.addParentStack(ConversationActivity.class);
446
447			Intent viewConversationIntent = new Intent(context,
448					ConversationActivity.class);
449			viewConversationIntent.setAction(Intent.ACTION_VIEW);
450			viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
451					targetUuid);
452			viewConversationIntent
453					.setType(ConversationActivity.VIEW_CONVERSATION);
454			
455			stackBuilder.addNextIntent(viewConversationIntent);
456
457			PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
458					0, PendingIntent.FLAG_UPDATE_CURRENT);
459			
460			mBuilder.setContentIntent(resultPendingIntent);
461			Notification notification = mBuilder.build();
462			mNotificationManager.notify(2342, notification);
463		}
464	}
465	
466	private static boolean wasHighlighted(Conversation conversation) {
467		List<Message> messages = conversation.getMessages();
468		String nick = conversation.getMucOptions().getNick();
469		Pattern highlight = generateNickHighlightPattern(nick);
470		for(int i = messages.size() - 1; i >= 0; --i) {
471			if (messages.get(i).isRead()) {
472				break;
473			} else {
474				Matcher m = highlight.matcher(messages.get(i).getBody());
475				if (m.find()) {
476					return true;
477				}
478			}
479		}
480		return false;
481	}
482
483	public static void prepareContactBadge(final Activity activity,
484			QuickContactBadge badge, final Contact contact, Context context) {
485		if (contact.getSystemAccount() != null) {
486			String[] systemAccount = contact.getSystemAccount().split("#");
487			long id = Long.parseLong(systemAccount[0]);
488			badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
489		}
490		badge.setImageBitmap(UIHelper.getContactPicture(contact, 72, context, false));
491	}
492
493	public static AlertDialog getVerifyFingerprintDialog(
494			final ConversationActivity activity,
495			final Conversation conversation, final LinearLayout msg) {
496		final Contact contact = conversation.getContact();
497		final Account account = conversation.getAccount();
498
499		AlertDialog.Builder builder = new AlertDialog.Builder(activity);
500		builder.setTitle("Verify fingerprint");
501		LayoutInflater inflater = activity.getLayoutInflater();
502		View view = inflater.inflate(R.layout.dialog_verify_otr, null);
503		TextView jid = (TextView) view.findViewById(R.id.verify_otr_jid);
504		TextView fingerprint = (TextView) view
505				.findViewById(R.id.verify_otr_fingerprint);
506		TextView yourprint = (TextView) view
507				.findViewById(R.id.verify_otr_yourprint);
508
509		jid.setText(contact.getJid());
510		fingerprint.setText(conversation.getOtrFingerprint());
511		yourprint.setText(account.getOtrFingerprint());
512		builder.setNegativeButton("Cancel", null);
513		builder.setPositiveButton("Verify", new OnClickListener() {
514
515			@Override
516			public void onClick(DialogInterface dialog, int which) {
517				contact.addOtrFingerprint(conversation.getOtrFingerprint());
518				msg.setVisibility(View.GONE);
519				//activity.xmppConnectionService.updateContact(contact);
520			}
521		});
522		builder.setView(view);
523		return builder.create();
524	}
525
526	public static Bitmap getSelfContactPicture(Account account, int size, boolean showPhoneSelfContactPicture, Context context) {
527		if (showPhoneSelfContactPicture) {
528			Uri selfiUri = PhoneHelper.getSefliUri(context);
529			if (selfiUri != null) {
530				try {
531					return BitmapFactory.decodeStream(context
532							.getContentResolver().openInputStream(selfiUri));
533				} catch (FileNotFoundException e) {
534					return getContactPicture(account.getJid(), size, context, false);
535				}
536			}
537			return getContactPicture(account.getJid(), size, context, false);
538		} else {
539			return getContactPicture(account.getJid(), size, context, false);
540		}
541	}
542}