XmppActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.annotation.SuppressLint;
  4import android.app.Activity;
  5import android.app.AlertDialog;
  6import android.app.AlertDialog.Builder;
  7import android.app.PendingIntent;
  8import android.content.ClipData;
  9import android.content.ClipboardManager;
 10import android.content.ComponentName;
 11import android.content.Context;
 12import android.content.DialogInterface;
 13import android.content.DialogInterface.OnClickListener;
 14import android.content.Intent;
 15import android.content.IntentSender.SendIntentException;
 16import android.content.ServiceConnection;
 17import android.content.SharedPreferences;
 18import android.content.pm.PackageManager;
 19import android.content.pm.ResolveInfo;
 20import android.content.res.Resources;
 21import android.graphics.Bitmap;
 22import android.graphics.Color;
 23import android.graphics.Point;
 24import android.graphics.drawable.BitmapDrawable;
 25import android.graphics.drawable.Drawable;
 26import android.net.Uri;
 27import android.nfc.NdefMessage;
 28import android.nfc.NdefRecord;
 29import android.nfc.NfcAdapter;
 30import android.nfc.NfcEvent;
 31import android.os.AsyncTask;
 32import android.os.Bundle;
 33import android.os.IBinder;
 34import android.preference.PreferenceManager;
 35import android.text.InputType;
 36import android.util.DisplayMetrics;
 37import android.util.Log;
 38import android.view.MenuItem;
 39import android.view.View;
 40import android.view.inputmethod.InputMethodManager;
 41import android.widget.EditText;
 42import android.widget.ImageView;
 43
 44import com.google.zxing.BarcodeFormat;
 45import com.google.zxing.EncodeHintType;
 46import com.google.zxing.WriterException;
 47import com.google.zxing.common.BitMatrix;
 48import com.google.zxing.qrcode.QRCodeWriter;
 49import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 50
 51import net.java.otr4j.session.SessionID;
 52
 53import java.io.FileNotFoundException;
 54import java.lang.ref.WeakReference;
 55import java.util.Hashtable;
 56import java.util.List;
 57import java.util.concurrent.RejectedExecutionException;
 58
 59import eu.siacs.conversations.Config;
 60import eu.siacs.conversations.R;
 61import eu.siacs.conversations.entities.Account;
 62import eu.siacs.conversations.entities.Contact;
 63import eu.siacs.conversations.entities.Conversation;
 64import eu.siacs.conversations.entities.Message;
 65import eu.siacs.conversations.entities.Presences;
 66import eu.siacs.conversations.services.AvatarService;
 67import eu.siacs.conversations.services.XmppConnectionService;
 68import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 69import eu.siacs.conversations.utils.ExceptionHelper;
 70import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 71import eu.siacs.conversations.xmpp.jid.Jid;
 72
 73public abstract class XmppActivity extends Activity {
 74
 75	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
 76	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
 77
 78	public XmppConnectionService xmppConnectionService;
 79	public boolean xmppConnectionServiceBound = false;
 80
 81	protected int mPrimaryTextColor;
 82	protected int mSecondaryTextColor;
 83	protected int mSecondaryBackgroundColor;
 84	protected int mColorRed;
 85	protected int mColorOrange;
 86	protected int mColorGreen;
 87	protected int mPrimaryColor;
 88
 89	protected boolean mUseSubject = true;
 90
 91	private DisplayMetrics metrics;
 92
 93	protected interface OnValueEdited {
 94		public void onValueEdited(String value);
 95	}
 96
 97	public interface OnPresenceSelected {
 98		public void onPresenceSelected();
 99	}
100
101	protected ServiceConnection mConnection = new ServiceConnection() {
102
103		@Override
104		public void onServiceConnected(ComponentName className, IBinder service) {
105			XmppConnectionBinder binder = (XmppConnectionBinder) service;
106			xmppConnectionService = binder.getService();
107			xmppConnectionServiceBound = true;
108			if (!isFinishing() && !isDestroyed()) {
109				onBackendConnected();
110			} else {
111				Log.d(Config.LOGTAG,"omitting call to onBackendConnected()");
112			}
113		}
114
115		@Override
116		public void onServiceDisconnected(ComponentName arg0) {
117			xmppConnectionServiceBound = false;
118		}
119	};
120
121	@Override
122	protected void onStart() {
123		super.onStart();
124		if (!xmppConnectionServiceBound) {
125			connectToBackend();
126		}
127	}
128
129	public void connectToBackend() {
130		Intent intent = new Intent(this, XmppConnectionService.class);
131		intent.setAction("ui");
132		startService(intent);
133		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
134	}
135
136	@Override
137	protected void onStop() {
138		super.onStop();
139		if (xmppConnectionServiceBound) {
140			unbindService(mConnection);
141			xmppConnectionServiceBound = false;
142		}
143	}
144
145	protected void hideKeyboard() {
146		InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
147
148		View focus = getCurrentFocus();
149
150		if (focus != null) {
151
152			inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
153					InputMethodManager.HIDE_NOT_ALWAYS);
154		}
155	}
156
157	public boolean hasPgp() {
158		return xmppConnectionService.getPgpEngine() != null;
159	}
160
161	public void showInstallPgpDialog() {
162		Builder builder = new AlertDialog.Builder(this);
163		builder.setTitle(getString(R.string.openkeychain_required));
164		builder.setIconAttribute(android.R.attr.alertDialogIcon);
165		builder.setMessage(getText(R.string.openkeychain_required_long));
166		builder.setNegativeButton(getString(R.string.cancel), null);
167		builder.setNeutralButton(getString(R.string.restart),
168				new OnClickListener() {
169
170					@Override
171					public void onClick(DialogInterface dialog, int which) {
172						if (xmppConnectionServiceBound) {
173							unbindService(mConnection);
174							xmppConnectionServiceBound = false;
175						}
176						stopService(new Intent(XmppActivity.this,
177								XmppConnectionService.class));
178						finish();
179					}
180				});
181		builder.setPositiveButton(getString(R.string.install),
182				new OnClickListener() {
183
184					@Override
185					public void onClick(DialogInterface dialog, int which) {
186						Uri uri = Uri
187								.parse("market://details?id=org.sufficientlysecure.keychain");
188						Intent marketIntent = new Intent(Intent.ACTION_VIEW,
189								uri);
190						PackageManager manager = getApplicationContext()
191								.getPackageManager();
192						List<ResolveInfo> infos = manager
193								.queryIntentActivities(marketIntent, 0);
194						if (infos.size() > 0) {
195							startActivity(marketIntent);
196						} else {
197							uri = Uri.parse("http://www.openkeychain.org/");
198							Intent browserIntent = new Intent(
199									Intent.ACTION_VIEW, uri);
200							startActivity(browserIntent);
201						}
202						finish();
203					}
204				});
205		builder.create().show();
206	}
207
208	abstract void onBackendConnected();
209
210	public boolean onOptionsItemSelected(MenuItem item) {
211		switch (item.getItemId()) {
212			case R.id.action_settings:
213				startActivity(new Intent(this, SettingsActivity.class));
214				break;
215			case R.id.action_accounts:
216				startActivity(new Intent(this, ManageAccountActivity.class));
217				break;
218			case android.R.id.home:
219				finish();
220				break;
221			case R.id.action_show_qr_code:
222				showQrCode();
223				break;
224		}
225		return super.onOptionsItemSelected(item);
226	}
227
228	@Override
229	protected void onCreate(Bundle savedInstanceState) {
230		super.onCreate(savedInstanceState);
231		metrics = getResources().getDisplayMetrics();
232		ExceptionHelper.init(getApplicationContext());
233		mPrimaryTextColor = getResources().getColor(R.color.primarytext);
234		mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
235		mColorRed = getResources().getColor(R.color.red);
236		mColorOrange = getResources().getColor(R.color.orange);
237		mColorGreen = getResources().getColor(R.color.green);
238		mPrimaryColor = getResources().getColor(R.color.primary);
239		mSecondaryBackgroundColor = getResources().getColor(
240				R.color.secondarybackground);
241		if (getPreferences().getBoolean("use_larger_font", false)) {
242			setTheme(R.style.ConversationsTheme_LargerText);
243		}
244		mUseSubject = getPreferences().getBoolean("use_subject", true);
245	}
246
247	protected SharedPreferences getPreferences() {
248		return PreferenceManager
249				.getDefaultSharedPreferences(getApplicationContext());
250	}
251
252	public boolean useSubjectToIdentifyConference() {
253		return mUseSubject;
254	}
255
256	public void switchToConversation(Conversation conversation) {
257		switchToConversation(conversation, null, false);
258	}
259
260	public void switchToConversation(Conversation conversation, String text,
261									 boolean newTask) {
262		Intent viewConversationIntent = new Intent(this,
263				ConversationActivity.class);
264		viewConversationIntent.setAction(Intent.ACTION_VIEW);
265		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
266				conversation.getUuid());
267		if (text != null) {
268			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
269		}
270		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
271		if (newTask) {
272			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
273					| Intent.FLAG_ACTIVITY_NEW_TASK
274					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
275		} else {
276			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
277					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
278		}
279		startActivity(viewConversationIntent);
280		finish();
281	}
282
283	public void switchToContactDetails(Contact contact) {
284		Intent intent = new Intent(this, ContactDetailsActivity.class);
285		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
286		intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString());
287		intent.putExtra("contact", contact.getJid().toString());
288		startActivity(intent);
289	}
290
291	public void switchToAccount(Account account) {
292		Intent intent = new Intent(this, EditAccountActivity.class);
293		intent.putExtra("jid", account.getJid().toBareJid().toString());
294		startActivity(intent);
295	}
296
297	protected void inviteToConversation(Conversation conversation) {
298		Intent intent = new Intent(getApplicationContext(),
299				ChooseContactActivity.class);
300		intent.putExtra("conversation", conversation.getUuid());
301		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
302	}
303
304	protected void announcePgp(Account account, final Conversation conversation) {
305		xmppConnectionService.getPgpEngine().generateSignature(account,
306				"online", new UiCallback<Account>() {
307
308					@Override
309					public void userInputRequried(PendingIntent pi,
310												  Account account) {
311						try {
312							startIntentSenderForResult(pi.getIntentSender(),
313									REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
314						} catch (final SendIntentException ignored) {
315						}
316					}
317
318					@Override
319					public void success(Account account) {
320						xmppConnectionService.databaseBackend
321								.updateAccount(account);
322						xmppConnectionService.sendPresencePacket(account,
323								xmppConnectionService.getPresenceGenerator()
324										.sendPresence(account));
325						if (conversation != null) {
326							conversation
327									.setNextEncryption(Message.ENCRYPTION_PGP);
328							xmppConnectionService.databaseBackend
329									.updateConversation(conversation);
330						}
331					}
332
333					@Override
334					public void error(int error, Account account) {
335						displayErrorDialog(error);
336					}
337				});
338	}
339
340	protected void displayErrorDialog(final int errorCode) {
341		runOnUiThread(new Runnable() {
342
343			@Override
344			public void run() {
345				AlertDialog.Builder builder = new AlertDialog.Builder(
346						XmppActivity.this);
347				builder.setIconAttribute(android.R.attr.alertDialogIcon);
348				builder.setTitle(getString(R.string.error));
349				builder.setMessage(errorCode);
350				builder.setNeutralButton(R.string.accept, null);
351				builder.create().show();
352			}
353		});
354
355	}
356
357	protected void showAddToRosterDialog(final Conversation conversation) {
358		final Jid jid = conversation.getContactJid();
359		AlertDialog.Builder builder = new AlertDialog.Builder(this);
360		builder.setTitle(jid.toString());
361		builder.setMessage(getString(R.string.not_in_roster));
362		builder.setNegativeButton(getString(R.string.cancel), null);
363		builder.setPositiveButton(getString(R.string.add_contact),
364				new DialogInterface.OnClickListener() {
365
366					@Override
367					public void onClick(DialogInterface dialog, int which) {
368						final Jid jid = conversation.getContactJid();
369						Account account = conversation.getAccount();
370						Contact contact = account.getRoster().getContact(jid);
371						xmppConnectionService.createContact(contact);
372						switchToContactDetails(contact);
373					}
374				});
375		builder.create().show();
376	}
377
378	private void showAskForPresenceDialog(final Contact contact) {
379		AlertDialog.Builder builder = new AlertDialog.Builder(this);
380		builder.setTitle(contact.getJid().toString());
381		builder.setMessage(R.string.request_presence_updates);
382		builder.setNegativeButton(R.string.cancel, null);
383		builder.setPositiveButton(R.string.request_now,
384				new DialogInterface.OnClickListener() {
385
386					@Override
387					public void onClick(DialogInterface dialog, int which) {
388						if (xmppConnectionServiceBound) {
389							xmppConnectionService.sendPresencePacket(contact
390									.getAccount(), xmppConnectionService
391									.getPresenceGenerator()
392									.requestPresenceUpdatesFrom(contact));
393						}
394					}
395				});
396		builder.create().show();
397	}
398
399	private void warnMutalPresenceSubscription(final Conversation conversation,
400											   final OnPresenceSelected listener) {
401		AlertDialog.Builder builder = new AlertDialog.Builder(this);
402		builder.setTitle(conversation.getContact().getJid().toString());
403		builder.setMessage(R.string.without_mutual_presence_updates);
404		builder.setNegativeButton(R.string.cancel, null);
405		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
406
407			@Override
408			public void onClick(DialogInterface dialog, int which) {
409				conversation.setNextCounterpart(null);
410				if (listener != null) {
411					listener.onPresenceSelected();
412				}
413			}
414		});
415		builder.create().show();
416	}
417
418	protected void quickEdit(String previousValue, OnValueEdited callback) {
419		quickEdit(previousValue, callback, false);
420	}
421
422	protected void quickPasswordEdit(String previousValue,
423									 OnValueEdited callback) {
424		quickEdit(previousValue, callback, true);
425	}
426
427	@SuppressLint("InflateParams")
428	private void quickEdit(final String previousValue,
429						   final OnValueEdited callback, boolean password) {
430		AlertDialog.Builder builder = new AlertDialog.Builder(this);
431		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
432		final EditText editor = (EditText) view.findViewById(R.id.editor);
433		OnClickListener mClickListener = new OnClickListener() {
434
435			@Override
436			public void onClick(DialogInterface dialog, int which) {
437				String value = editor.getText().toString();
438				if (!previousValue.equals(value) && value.trim().length() > 0) {
439					callback.onValueEdited(value);
440				}
441			}
442		};
443		if (password) {
444			editor.setInputType(InputType.TYPE_CLASS_TEXT
445					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
446			editor.setHint(R.string.password);
447			builder.setPositiveButton(R.string.accept, mClickListener);
448		} else {
449			builder.setPositiveButton(R.string.edit, mClickListener);
450		}
451		editor.requestFocus();
452		editor.setText(previousValue);
453		builder.setView(view);
454		builder.setNegativeButton(R.string.cancel, null);
455		builder.create().show();
456	}
457
458	public void selectPresence(final Conversation conversation,
459							   final OnPresenceSelected listener) {
460		final Contact contact = conversation.getContact();
461		if (conversation.hasValidOtrSession()) {
462			SessionID id = conversation.getOtrSession().getSessionID();
463			Jid jid;
464			try {
465				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
466			} catch (InvalidJidException e) {
467				jid = null;
468			}
469			conversation.setNextCounterpart(jid);
470			listener.onPresenceSelected();
471		} else 	if (!contact.showInRoster()) {
472			showAddToRosterDialog(conversation);
473		} else {
474			Presences presences = contact.getPresences();
475			if (presences.size() == 0) {
476				if (!contact.getOption(Contact.Options.TO)
477						&& !contact.getOption(Contact.Options.ASKING)
478						&& contact.getAccount().getStatus() == Account.STATUS_ONLINE) {
479					showAskForPresenceDialog(contact);
480				} else if (!contact.getOption(Contact.Options.TO)
481						|| !contact.getOption(Contact.Options.FROM)) {
482					warnMutalPresenceSubscription(conversation, listener);
483				} else {
484					conversation.setNextCounterpart(null);
485					listener.onPresenceSelected();
486				}
487			} else if (presences.size() == 1) {
488				String presence = presences.asStringArray()[0];
489				try {
490					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
491				} catch (InvalidJidException e) {
492					conversation.setNextCounterpart(null);
493				}
494				listener.onPresenceSelected();
495			} else {
496				final StringBuilder presence = new StringBuilder();
497				AlertDialog.Builder builder = new AlertDialog.Builder(this);
498				builder.setTitle(getString(R.string.choose_presence));
499				final String[] presencesArray = presences.asStringArray();
500				int preselectedPresence = 0;
501				for (int i = 0; i < presencesArray.length; ++i) {
502					if (presencesArray[i].equals(contact.lastseen.presence)) {
503						preselectedPresence = i;
504						break;
505					}
506				}
507				presence.append(presencesArray[preselectedPresence]);
508				builder.setSingleChoiceItems(presencesArray,
509						preselectedPresence,
510						new DialogInterface.OnClickListener() {
511
512							@Override
513							public void onClick(DialogInterface dialog,
514												int which) {
515								presence.delete(0, presence.length());
516								presence.append(presencesArray[which]);
517							}
518						});
519				builder.setNegativeButton(R.string.cancel, null);
520				builder.setPositiveButton(R.string.ok, new OnClickListener() {
521
522					@Override
523					public void onClick(DialogInterface dialog, int which) {
524						try {
525							conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
526						} catch (InvalidJidException e) {
527							conversation.setNextCounterpart(null);
528						}
529						listener.onPresenceSelected();
530					}
531				});
532				builder.create().show();
533			}
534		}
535	}
536
537	protected void onActivityResult(int requestCode, int resultCode,
538									final Intent data) {
539		super.onActivityResult(requestCode, resultCode, data);
540		if (requestCode == REQUEST_INVITE_TO_CONVERSATION
541				&& resultCode == RESULT_OK) {
542			String contactJid = data.getStringExtra("contact");
543			String conversationUuid = data.getStringExtra("conversation");
544			Conversation conversation = xmppConnectionService
545					.findConversationByUuid(conversationUuid);
546			if (conversation.getMode() == Conversation.MODE_MULTI) {
547				xmppConnectionService.invite(conversation, contactJid);
548			}
549			Log.d(Config.LOGTAG, "inviting " + contactJid + " to "
550					+ conversation.getName());
551		}
552	}
553
554	public int getSecondaryTextColor() {
555		return this.mSecondaryTextColor;
556	}
557
558	public int getPrimaryTextColor() {
559		return this.mPrimaryTextColor;
560	}
561
562	public int getWarningTextColor() {
563		return this.mColorRed;
564	}
565
566	public int getPrimaryColor() {
567		return this.mPrimaryColor;
568	}
569
570	public int getSecondaryBackgroundColor() {
571		return this.mSecondaryBackgroundColor;
572	}
573
574	public int getPixel(int dp) {
575		DisplayMetrics metrics = getResources().getDisplayMetrics();
576		return ((int) (dp * metrics.density));
577	}
578
579	public boolean copyTextToClipboard(String text, int labelResId) {
580		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
581		String label = getResources().getString(labelResId);
582		if (mClipBoardManager != null) {
583			ClipData mClipData = ClipData.newPlainText(label, text);
584			mClipBoardManager.setPrimaryClip(mClipData);
585			return true;
586		}
587		return false;
588	}
589
590	protected void registerNdefPushMessageCallback() {
591			NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
592			if (nfcAdapter != null && nfcAdapter.isEnabled()) {
593				nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
594					@Override
595					public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
596                        return new NdefMessage(new NdefRecord[]{
597                                NdefRecord.createUri(getShareableUri()),
598                                NdefRecord.createApplicationRecord("eu.siacs.conversations")
599                        });
600					}
601				}, this);
602			}
603	}
604
605	protected void unregisterNdefPushMessageCallback() {
606		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
607		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
608			nfcAdapter.setNdefPushMessageCallback(null,this);
609		}
610	}
611
612	protected String getShareableUri() {
613		return null;
614	}
615
616	@Override
617	public void onResume() {
618		super.onResume();
619		if (this.getShareableUri()!=null) {
620			this.registerNdefPushMessageCallback();
621		}
622	}
623
624	@Override
625	public void onPause() {
626		super.onPause();
627		this.unregisterNdefPushMessageCallback();
628	}
629
630	protected void showQrCode() {
631		String uri = getShareableUri();
632		if (uri!=null) {
633			Point size = new Point();
634			getWindowManager().getDefaultDisplay().getSize(size);
635			final int width = (size.x < size.y ? size.x : size.y);
636			Bitmap bitmap = createQrCodeBitmap(uri, width);
637			ImageView view = new ImageView(this);
638			view.setImageBitmap(bitmap);
639			AlertDialog.Builder builder = new AlertDialog.Builder(this);
640			builder.setView(view);
641			builder.create().show();
642		}
643	}
644
645	protected Bitmap createQrCodeBitmap(String input, int size) {
646		try {
647			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
648			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
649			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
650			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
651			final int width = result.getWidth();
652			final int height = result.getHeight();
653			final int[] pixels = new int[width * height];
654			for (int y = 0; y < height; y++) {
655				final int offset = y * width;
656				for (int x = 0; x < width; x++) {
657					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
658				}
659			}
660			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
661			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
662			return bitmap;
663		} catch (final WriterException e) {
664			return null;
665		}
666	}
667
668	public AvatarService avatarService() {
669		return xmppConnectionService.getAvatarService();
670	}
671
672	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
673		private final WeakReference<ImageView> imageViewReference;
674		private Message message = null;
675
676		public BitmapWorkerTask(ImageView imageView) {
677			imageViewReference = new WeakReference<>(imageView);
678		}
679
680		@Override
681		protected Bitmap doInBackground(Message... params) {
682			message = params[0];
683			try {
684				return xmppConnectionService.getFileBackend().getThumbnail(
685						message, (int) (metrics.density * 288), false);
686			} catch (FileNotFoundException e) {
687				return null;
688			}
689		}
690
691		@Override
692		protected void onPostExecute(Bitmap bitmap) {
693			if (bitmap != null) {
694				final ImageView imageView = imageViewReference.get();
695				if (imageView != null) {
696					imageView.setImageBitmap(bitmap);
697					imageView.setBackgroundColor(0x00000000);
698				}
699			}
700		}
701	}
702
703	public void loadBitmap(Message message, ImageView imageView) {
704		Bitmap bm;
705		try {
706			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
707					(int) (metrics.density * 288), true);
708		} catch (FileNotFoundException e) {
709			bm = null;
710		}
711		if (bm != null) {
712			imageView.setImageBitmap(bm);
713			imageView.setBackgroundColor(0x00000000);
714		} else {
715			if (cancelPotentialWork(message, imageView)) {
716				imageView.setBackgroundColor(0xff333333);
717				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
718				final AsyncDrawable asyncDrawable = new AsyncDrawable(
719						getResources(), null, task);
720				imageView.setImageDrawable(asyncDrawable);
721				try {
722					task.execute(message);
723				} catch (final RejectedExecutionException ignored) {
724                }
725			}
726		}
727	}
728
729	public static boolean cancelPotentialWork(Message message,
730											  ImageView imageView) {
731		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
732
733		if (bitmapWorkerTask != null) {
734			final Message oldMessage = bitmapWorkerTask.message;
735			if (oldMessage == null || message != oldMessage) {
736				bitmapWorkerTask.cancel(true);
737			} else {
738				return false;
739			}
740		}
741		return true;
742	}
743
744	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
745		if (imageView != null) {
746			final Drawable drawable = imageView.getDrawable();
747			if (drawable instanceof AsyncDrawable) {
748				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
749				return asyncDrawable.getBitmapWorkerTask();
750			}
751		}
752		return null;
753	}
754
755	static class AsyncDrawable extends BitmapDrawable {
756		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
757
758		public AsyncDrawable(Resources res, Bitmap bitmap,
759							 BitmapWorkerTask bitmapWorkerTask) {
760			super(res, bitmap);
761			bitmapWorkerTaskReference = new WeakReference<>(
762					bitmapWorkerTask);
763		}
764
765		public BitmapWorkerTask getBitmapWorkerTask() {
766			return bitmapWorkerTaskReference.get();
767		}
768	}
769}