AvatarService.java

  1package eu.siacs.conversations.services;
  2
  3import android.graphics.Bitmap;
  4import android.graphics.Canvas;
  5import android.graphics.Paint;
  6import android.graphics.PorterDuff;
  7import android.graphics.PorterDuffXfermode;
  8import android.graphics.Rect;
  9import android.graphics.Typeface;
 10import android.net.Uri;
 11import android.util.DisplayMetrics;
 12import android.util.Log;
 13
 14import java.util.ArrayList;
 15import java.util.List;
 16import java.util.Locale;
 17
 18import eu.siacs.conversations.Config;
 19import eu.siacs.conversations.entities.Account;
 20import eu.siacs.conversations.entities.Bookmark;
 21import eu.siacs.conversations.entities.Contact;
 22import eu.siacs.conversations.entities.Conversation;
 23import eu.siacs.conversations.entities.ListItem;
 24import eu.siacs.conversations.entities.Message;
 25import eu.siacs.conversations.entities.MucOptions;
 26import eu.siacs.conversations.utils.UIHelper;
 27import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
 28import eu.siacs.conversations.xmpp.XmppConnection;
 29
 30public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 31
 32	private static final int FG_COLOR = 0xFFFAFAFA;
 33	private static final int TRANSPARENT = 0x00000000;
 34	private static final int PLACEHOLDER_COLOR = 0xFF202020;
 35
 36	private static final String PREFIX_CONTACT = "contact";
 37	private static final String PREFIX_CONVERSATION = "conversation";
 38	private static final String PREFIX_ACCOUNT = "account";
 39	private static final String PREFIX_GENERIC = "generic";
 40
 41	final private ArrayList<Integer> sizes = new ArrayList<>();
 42
 43	protected XmppConnectionService mXmppConnectionService = null;
 44
 45	public AvatarService(XmppConnectionService service) {
 46		this.mXmppConnectionService = service;
 47	}
 48
 49	private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
 50		if (contact.isSelf()) {
 51			return get(contact.getAccount(),size,cachedOnly);
 52		}
 53		final String KEY = key(contact, size);
 54		Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
 55		if (avatar != null || cachedOnly) {
 56			return avatar;
 57		}
 58		if (contact.getProfilePhoto() != null) {
 59			avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
 60		}
 61		if (avatar == null && contact.getAvatar() != null) {
 62			avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
 63		}
 64		if (avatar == null) {
 65            avatar = get(contact.getDisplayName(), size, cachedOnly);
 66		}
 67		this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
 68		return avatar;
 69	}
 70
 71	public Bitmap getRoundedShortcut(final Contact contact) {
 72		DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics();
 73		int size = Math.round(metrics.density * 48);
 74		Bitmap bitmap = get(contact,size);
 75		Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
 76		Canvas canvas = new Canvas(output);
 77
 78		final Paint paint = new Paint();
 79		final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
 80
 81		paint.setAntiAlias(true);
 82		canvas.drawARGB(0, 0, 0, 0);
 83		canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
 84		paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
 85		canvas.drawBitmap(bitmap, rect, rect, paint);
 86		return output;
 87	}
 88
 89	public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) {
 90		Contact c = user.getContact();
 91		if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null || user.getAvatar() == null)) {
 92			return get(c, size, cachedOnly);
 93		} else {
 94			return getImpl(user, size, cachedOnly);
 95		}
 96	}
 97
 98	private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) {
 99		final String KEY = key(user, size);
100		Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
101		if (avatar != null || cachedOnly) {
102			return avatar;
103		}
104		if (user.getAvatar() != null) {
105			avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size);
106		}
107		if (avatar == null) {
108			Contact contact = user.getContact();
109			if (contact != null) {
110				avatar = get(contact, size, cachedOnly);
111			} else {
112				avatar = get(user.getName(), size, cachedOnly);
113			}
114		}
115		this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
116		return avatar;
117	}
118
119	public void clear(Contact contact) {
120		synchronized (this.sizes) {
121			for (Integer size : sizes) {
122				this.mXmppConnectionService.getBitmapCache().remove(
123						key(contact, size));
124			}
125		}
126		for(Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) {
127			clear(conversation);
128		}
129	}
130
131	private String key(Contact contact, int size) {
132		synchronized (this.sizes) {
133			if (!this.sizes.contains(size)) {
134				this.sizes.add(size);
135			}
136		}
137		return PREFIX_CONTACT + "_" + contact.getAccount().getJid().toBareJid() + "_"
138				+ contact.getJid() + "_" + String.valueOf(size);
139	}
140
141	private String key(MucOptions.User user, int size) {
142		synchronized (this.sizes) {
143			if (!this.sizes.contains(size)) {
144				this.sizes.add(size);
145			}
146		}
147		return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_"
148				+ user.getFullJid() + "_" + String.valueOf(size);
149	}
150
151	public Bitmap get(ListItem item, int size) {
152		return get(item,size,false);
153	}
154
155	public Bitmap get(ListItem item, int size, boolean cachedOnly) {
156		if (item instanceof Contact) {
157			return get((Contact) item, size,cachedOnly);
158		} else if (item instanceof Bookmark) {
159			Bookmark bookmark = (Bookmark) item;
160			if (bookmark.getConversation() != null) {
161				return get(bookmark.getConversation(), size, cachedOnly);
162			} else {
163				return get(bookmark.getDisplayName(), size, cachedOnly);
164			}
165		} else {
166			return get(item.getDisplayName(), size, cachedOnly);
167		}
168	}
169
170	public Bitmap get(Conversation conversation, int size) {
171		return get(conversation,size,false);
172	}
173
174	public Bitmap get(Conversation conversation, int size, boolean cachedOnly) {
175		if (conversation.getMode() == Conversation.MODE_SINGLE) {
176			return get(conversation.getContact(), size, cachedOnly);
177		} else {
178			return get(conversation.getMucOptions(), size, cachedOnly);
179		}
180	}
181
182	public void clear(Conversation conversation) {
183		if (conversation.getMode() == Conversation.MODE_SINGLE) {
184			clear(conversation.getContact());
185		} else {
186			clear(conversation.getMucOptions());
187		}
188	}
189
190	private Bitmap get(MucOptions mucOptions, int size,  boolean cachedOnly) {
191		final String KEY = key(mucOptions, size);
192		Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
193		if (bitmap != null || cachedOnly) {
194			return bitmap;
195		}
196		final List<MucOptions.User> users = mucOptions.getUsers(5);
197		int count = users.size();
198		bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
199		Canvas canvas = new Canvas(bitmap);
200		bitmap.eraseColor(TRANSPARENT);
201
202		if (count == 0) {
203			String name = mucOptions.getConversation().getName();
204			drawTile(canvas, name, 0, 0, size, size);
205		} else if (count == 1) {
206			drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
207			drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 0, size, size);
208		} else if (count == 2) {
209			drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
210			drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size);
211		} else if (count == 3) {
212			drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
213			drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1);
214			drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size,
215					size);
216		} else if (count == 4) {
217			drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
218			drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
219			drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
220			drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size,
221					size);
222		} else {
223			drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
224			drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
225			drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
226			drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1,
227					size, size);
228		}
229		this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
230		return bitmap;
231	}
232
233	public void clear(MucOptions options) {
234		synchronized (this.sizes) {
235			for (Integer size : sizes) {
236				this.mXmppConnectionService.getBitmapCache().remove(key(options, size));
237			}
238		}
239	}
240
241	private String key(MucOptions options, int size) {
242		synchronized (this.sizes) {
243			if (!this.sizes.contains(size)) {
244				this.sizes.add(size);
245			}
246		}
247		return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid()
248				+ "_" + String.valueOf(size);
249	}
250
251	public Bitmap get(Account account, int size) {
252		return get(account, size, false);
253	}
254
255	public Bitmap get(Account account, int size, boolean cachedOnly) {
256		final String KEY = key(account, size);
257		Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY);
258		if (avatar != null || cachedOnly) {
259			return avatar;
260		}
261		avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size);
262		if (avatar == null) {
263			avatar = get(account.getJid().toBareJid().toString(), size,false);
264		}
265		mXmppConnectionService.getBitmapCache().put(KEY, avatar);
266		return avatar;
267	}
268
269	public Bitmap get(Message message, int size, boolean cachedOnly) {
270		final Conversation conversation = message.getConversation();
271		if (message.getStatus() == Message.STATUS_RECEIVED) {
272			Contact c = message.getContact();
273			if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) {
274				return get(c, size, cachedOnly);
275			} else if (message.getConversation().getMode() == Conversation.MODE_MULTI){
276				MucOptions.User user = conversation.getMucOptions().findUserByFullJid(message.getCounterpart());
277				if (user != null) {
278					return getImpl(user,size,cachedOnly);
279				}
280			}
281			return get(UIHelper.getMessageDisplayName(message), size, cachedOnly);
282		} else  {
283			return get(conversation.getAccount(), size, cachedOnly);
284		}
285	}
286
287	public void clear(Account account) {
288		synchronized (this.sizes) {
289			for (Integer size : sizes) {
290				this.mXmppConnectionService.getBitmapCache().remove(key(account, size));
291			}
292		}
293	}
294
295	public void clear(MucOptions.User user) {
296		synchronized (this.sizes) {
297			for (Integer size : sizes) {
298				this.mXmppConnectionService.getBitmapCache().remove(key(user, size));
299			}
300		}
301	}
302
303	private String key(Account account, int size) {
304		synchronized (this.sizes) {
305			if (!this.sizes.contains(size)) {
306				this.sizes.add(size);
307			}
308		}
309		return PREFIX_ACCOUNT + "_" + account.getUuid() + "_"
310				+ String.valueOf(size);
311	}
312
313	public Bitmap get(String name, int size) {
314		return get(name,size,false);
315	}
316
317	public Bitmap get(final String name, final int size, boolean cachedOnly) {
318		final String KEY = key(name, size);
319		Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
320		if (bitmap != null || cachedOnly) {
321			return bitmap;
322		}
323		bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
324		Canvas canvas = new Canvas(bitmap);
325		final String trimmedName = name == null ? "" : name.trim();
326		drawTile(canvas, trimmedName, 0, 0, size, size);
327		mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
328		return bitmap;
329	}
330
331	private String key(String name, int size) {
332		synchronized (this.sizes) {
333			if (!this.sizes.contains(size)) {
334				this.sizes.add(size);
335			}
336		}
337		return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size);
338	}
339
340	private boolean drawTile(Canvas canvas, String letter, int tileColor,
341						  int left, int top, int right, int bottom) {
342		letter = letter.toUpperCase(Locale.getDefault());
343		Paint tilePaint = new Paint(), textPaint = new Paint();
344		tilePaint.setColor(tileColor);
345		textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
346		textPaint.setColor(FG_COLOR);
347		textPaint.setTypeface(Typeface.create("sans-serif-light",
348				Typeface.NORMAL));
349		textPaint.setTextSize((float) ((right - left) * 0.8));
350		Rect rect = new Rect();
351
352		canvas.drawRect(new Rect(left, top, right, bottom), tilePaint);
353		textPaint.getTextBounds(letter, 0, 1, rect);
354		float width = textPaint.measureText(letter);
355		canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom)
356				/ 2 + rect.height() / 2, textPaint);
357		return true;
358	}
359
360	private boolean drawTile(Canvas canvas, MucOptions.User user, int left,
361						  int top, int right, int bottom) {
362		Contact contact = user.getContact();
363		if (contact != null) {
364			Uri uri = null;
365			if (contact.getProfilePhoto() != null) {
366				uri = Uri.parse(contact.getProfilePhoto());
367			} else if (contact.getAvatar() != null) {
368				uri = mXmppConnectionService.getFileBackend().getAvatarUri(
369						contact.getAvatar());
370			}
371			if (drawTile(canvas, uri, left, top, right, bottom)) {
372				return true;
373			}
374		} else if (user.getAvatar() != null) {
375			Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
376			if (drawTile(canvas, uri, left, top, right, bottom)) {
377				return true;
378			}
379		}
380		String name = contact != null ? contact.getDisplayName() : user.getName();
381		drawTile(canvas, name, left, top, right, bottom);
382		return true;
383	}
384
385	private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) {
386		String avatar = account.getAvatar();
387		if (avatar != null) {
388			Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar);
389			if (uri != null) {
390				if (drawTile(canvas, uri, left, top, right, bottom)) {
391					return true;
392				}
393			}
394		}
395		return drawTile(canvas, account.getJid().toBareJid().toString(), left, top, right, bottom);
396	}
397
398	private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) {
399		if (name != null) {
400			final String letter = getFirstLetter(name);
401			final int color = UIHelper.getColorForName(name);
402			drawTile(canvas, letter, color, left, top, right, bottom);
403			return true;
404		}
405		return false;
406	}
407
408	private static String getFirstLetter(String name) {
409		for(Character c : name.toCharArray()) {
410			if (Character.isLetterOrDigit(c)) {
411				return c.toString();
412			}
413		}
414		return "X";
415	}
416
417	private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) {
418		if (uri != null) {
419			Bitmap bitmap = mXmppConnectionService.getFileBackend()
420					.cropCenter(uri, bottom - top, right - left);
421			if (bitmap != null) {
422				drawTile(canvas, bitmap, left, top, right, bottom);
423				return true;
424			}
425		}
426		return false;
427	}
428
429	private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) {
430		Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom);
431		canvas.drawBitmap(bm, null, dst, null);
432		return true;
433	}
434
435	@Override
436	public void onAdvancedStreamFeaturesAvailable(Account account) {
437		XmppConnection.Features features = account.getXmppConnection().getFeatures();
438		if (features.pep() && !features.pepPersistent()) {
439			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": has pep but is not persistent");
440			if (account.getAvatar() != null) {
441				mXmppConnectionService.republishAvatarIfNeeded(account);
442			}
443		}
444	}
445}