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