XmppActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import android.Manifest;
   4import android.annotation.SuppressLint;
   5import android.annotation.TargetApi;
   6import android.app.ActionBar;
   7import android.app.Activity;
   8import android.app.AlertDialog;
   9import android.app.AlertDialog.Builder;
  10import android.app.PendingIntent;
  11import android.content.ActivityNotFoundException;
  12import android.content.ClipData;
  13import android.content.ClipboardManager;
  14import android.content.ComponentName;
  15import android.content.Context;
  16import android.content.DialogInterface;
  17import android.content.DialogInterface.OnClickListener;
  18import android.content.Intent;
  19import android.content.IntentSender.SendIntentException;
  20import android.content.ServiceConnection;
  21import android.content.SharedPreferences;
  22import android.content.pm.PackageManager;
  23import android.content.pm.ResolveInfo;
  24import android.content.res.Resources;
  25import android.content.res.TypedArray;
  26import android.graphics.Bitmap;
  27import android.graphics.Color;
  28import android.graphics.Point;
  29import android.graphics.drawable.BitmapDrawable;
  30import android.graphics.drawable.Drawable;
  31import android.net.ConnectivityManager;
  32import android.net.Uri;
  33import android.nfc.NdefMessage;
  34import android.nfc.NdefRecord;
  35import android.nfc.NfcAdapter;
  36import android.nfc.NfcEvent;
  37import android.os.AsyncTask;
  38import android.os.Build;
  39import android.os.Bundle;
  40import android.os.Handler;
  41import android.os.IBinder;
  42import android.os.PowerManager;
  43import android.os.SystemClock;
  44import android.preference.PreferenceManager;
  45import android.text.InputType;
  46import android.util.DisplayMetrics;
  47import android.util.Log;
  48import android.util.Pair;
  49import android.view.MenuItem;
  50import android.view.View;
  51import android.view.inputmethod.InputMethodManager;
  52import android.widget.CompoundButton;
  53import android.widget.EditText;
  54import android.widget.ImageView;
  55import android.widget.LinearLayout;
  56import android.widget.TextView;
  57import android.widget.Toast;
  58
  59import com.google.zxing.BarcodeFormat;
  60import com.google.zxing.EncodeHintType;
  61import com.google.zxing.WriterException;
  62import com.google.zxing.common.BitMatrix;
  63import com.google.zxing.qrcode.QRCodeWriter;
  64import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
  65
  66import net.java.otr4j.session.SessionID;
  67
  68import java.io.FileNotFoundException;
  69import java.lang.ref.WeakReference;
  70import java.util.ArrayList;
  71import java.util.Collections;
  72import java.util.HashMap;
  73import java.util.Hashtable;
  74import java.util.List;
  75import java.util.Map;
  76import java.util.concurrent.RejectedExecutionException;
  77import java.util.concurrent.atomic.AtomicInteger;
  78
  79import eu.siacs.conversations.Config;
  80import eu.siacs.conversations.R;
  81import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
  82import eu.siacs.conversations.entities.Account;
  83import eu.siacs.conversations.entities.Contact;
  84import eu.siacs.conversations.entities.Conversation;
  85import eu.siacs.conversations.entities.Message;
  86import eu.siacs.conversations.entities.MucOptions;
  87import eu.siacs.conversations.entities.Presence;
  88import eu.siacs.conversations.entities.Presences;
  89import eu.siacs.conversations.entities.ServiceDiscoveryResult;
  90import eu.siacs.conversations.services.AvatarService;
  91import eu.siacs.conversations.services.XmppConnectionService;
  92import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
  93import eu.siacs.conversations.ui.widget.Switch;
  94import eu.siacs.conversations.utils.CryptoHelper;
  95import eu.siacs.conversations.utils.ExceptionHelper;
  96import eu.siacs.conversations.utils.UIHelper;
  97import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
  98import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
  99import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 100import eu.siacs.conversations.xmpp.jid.Jid;
 101
 102public abstract class XmppActivity extends Activity {
 103
 104	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
 105	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
 106	protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103;
 107	protected static final int REQUEST_BATTERY_OP = 0x13849ff;
 108
 109	public static final String EXTRA_ACCOUNT = "account";
 110
 111	public XmppConnectionService xmppConnectionService;
 112	public boolean xmppConnectionServiceBound = false;
 113	protected boolean registeredListeners = false;
 114
 115	protected int mPrimaryTextColor;
 116	protected int mSecondaryTextColor;
 117	protected int mTertiaryTextColor;
 118	protected int mPrimaryBackgroundColor;
 119	protected int mSecondaryBackgroundColor;
 120	protected int mColorRed;
 121	protected int mColorOrange;
 122	protected int mColorGreen;
 123	protected int mPrimaryColor;
 124
 125	protected boolean mUseSubject = true;
 126
 127	private DisplayMetrics metrics;
 128	protected int mTheme;
 129	protected boolean mUsingEnterKey = false;
 130
 131	protected Toast mToast;
 132
 133	protected void hideToast() {
 134		if (mToast != null) {
 135			mToast.cancel();
 136		}
 137	}
 138
 139	protected void replaceToast(String msg) {
 140		replaceToast(msg, true);
 141	}
 142
 143	protected void replaceToast(String msg, boolean showlong) {
 144		hideToast();
 145		mToast = Toast.makeText(this, msg ,showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
 146		mToast.show();
 147	}
 148
 149	protected Runnable onOpenPGPKeyPublished = new Runnable() {
 150		@Override
 151		public void run() {
 152			Toast.makeText(XmppActivity.this,R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
 153		}
 154	};
 155
 156	private long mLastUiRefresh = 0;
 157	private Handler mRefreshUiHandler = new Handler();
 158	private Runnable mRefreshUiRunnable = new Runnable() {
 159		@Override
 160		public void run() {
 161			mLastUiRefresh = SystemClock.elapsedRealtime();
 162			refreshUiReal();
 163		}
 164	};
 165
 166	protected ConferenceInvite mPendingConferenceInvite = null;
 167
 168
 169	protected final void refreshUi() {
 170		final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
 171		if (diff > Config.REFRESH_UI_INTERVAL) {
 172			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
 173			runOnUiThread(mRefreshUiRunnable);
 174		} else {
 175			final long next = Config.REFRESH_UI_INTERVAL - diff;
 176			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
 177			mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
 178		}
 179	}
 180
 181	abstract protected void refreshUiReal();
 182
 183	protected interface OnValueEdited {
 184		public void onValueEdited(String value);
 185	}
 186
 187	public interface OnPresenceSelected {
 188		public void onPresenceSelected();
 189	}
 190
 191	protected ServiceConnection mConnection = new ServiceConnection() {
 192
 193		@Override
 194		public void onServiceConnected(ComponentName className, IBinder service) {
 195			XmppConnectionBinder binder = (XmppConnectionBinder) service;
 196			xmppConnectionService = binder.getService();
 197			xmppConnectionServiceBound = true;
 198			if (!registeredListeners && shouldRegisterListeners()) {
 199				registerListeners();
 200				registeredListeners = true;
 201			}
 202			onBackendConnected();
 203		}
 204
 205		@Override
 206		public void onServiceDisconnected(ComponentName arg0) {
 207			xmppConnectionServiceBound = false;
 208		}
 209	};
 210
 211	@Override
 212	protected void onStart() {
 213		super.onStart();
 214		if (!xmppConnectionServiceBound) {
 215			connectToBackend();
 216		} else {
 217			if (!registeredListeners) {
 218				this.registerListeners();
 219				this.registeredListeners = true;
 220			}
 221			this.onBackendConnected();
 222		}
 223	}
 224
 225	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
 226	protected boolean shouldRegisterListeners() {
 227		if  (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
 228			return !isDestroyed() && !isFinishing();
 229		} else {
 230			return !isFinishing();
 231		}
 232	}
 233
 234	public void connectToBackend() {
 235		Intent intent = new Intent(this, XmppConnectionService.class);
 236		intent.setAction("ui");
 237		startService(intent);
 238		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
 239	}
 240
 241	@Override
 242	protected void onStop() {
 243		super.onStop();
 244		if (xmppConnectionServiceBound) {
 245			if (registeredListeners) {
 246				this.unregisterListeners();
 247				this.registeredListeners = false;
 248			}
 249			unbindService(mConnection);
 250			xmppConnectionServiceBound = false;
 251		}
 252	}
 253
 254	protected void hideKeyboard() {
 255		InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 256
 257		View focus = getCurrentFocus();
 258
 259		if (focus != null) {
 260
 261			inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
 262					InputMethodManager.HIDE_NOT_ALWAYS);
 263		}
 264	}
 265
 266	public boolean hasPgp() {
 267		return xmppConnectionService.getPgpEngine() != null;
 268	}
 269
 270	public void showInstallPgpDialog() {
 271		Builder builder = new AlertDialog.Builder(this);
 272		builder.setTitle(getString(R.string.openkeychain_required));
 273		builder.setIconAttribute(android.R.attr.alertDialogIcon);
 274		builder.setMessage(getText(R.string.openkeychain_required_long));
 275		builder.setNegativeButton(getString(R.string.cancel), null);
 276		builder.setNeutralButton(getString(R.string.restart),
 277				new OnClickListener() {
 278
 279					@Override
 280					public void onClick(DialogInterface dialog, int which) {
 281						if (xmppConnectionServiceBound) {
 282							unbindService(mConnection);
 283							xmppConnectionServiceBound = false;
 284						}
 285						stopService(new Intent(XmppActivity.this,
 286									XmppConnectionService.class));
 287						finish();
 288					}
 289				});
 290		builder.setPositiveButton(getString(R.string.install),
 291				new OnClickListener() {
 292
 293					@Override
 294					public void onClick(DialogInterface dialog, int which) {
 295						Uri uri = Uri
 296							.parse("market://details?id=org.sufficientlysecure.keychain");
 297						Intent marketIntent = new Intent(Intent.ACTION_VIEW,
 298								uri);
 299						PackageManager manager = getApplicationContext()
 300							.getPackageManager();
 301						List<ResolveInfo> infos = manager
 302							.queryIntentActivities(marketIntent, 0);
 303						if (infos.size() > 0) {
 304							startActivity(marketIntent);
 305						} else {
 306							uri = Uri.parse("http://www.openkeychain.org/");
 307							Intent browserIntent = new Intent(
 308									Intent.ACTION_VIEW, uri);
 309							startActivity(browserIntent);
 310						}
 311						finish();
 312					}
 313				});
 314		builder.create().show();
 315	}
 316
 317	abstract void onBackendConnected();
 318
 319	protected void registerListeners() {
 320		if (this instanceof XmppConnectionService.OnConversationUpdate) {
 321			this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
 322		}
 323		if (this instanceof XmppConnectionService.OnAccountUpdate) {
 324			this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
 325		}
 326		if (this instanceof XmppConnectionService.OnCaptchaRequested) {
 327			this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
 328		}
 329		if (this instanceof XmppConnectionService.OnRosterUpdate) {
 330			this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
 331		}
 332		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
 333			this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
 334		}
 335		if (this instanceof OnUpdateBlocklist) {
 336			this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
 337		}
 338		if (this instanceof XmppConnectionService.OnShowErrorToast) {
 339			this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
 340		}
 341		if (this instanceof OnKeyStatusUpdated) {
 342			this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
 343		}
 344	}
 345
 346	protected void unregisterListeners() {
 347		if (this instanceof XmppConnectionService.OnConversationUpdate) {
 348			this.xmppConnectionService.removeOnConversationListChangedListener();
 349		}
 350		if (this instanceof XmppConnectionService.OnAccountUpdate) {
 351			this.xmppConnectionService.removeOnAccountListChangedListener();
 352		}
 353		if (this instanceof XmppConnectionService.OnCaptchaRequested) {
 354			this.xmppConnectionService.removeOnCaptchaRequestedListener();
 355		}
 356		if (this instanceof XmppConnectionService.OnRosterUpdate) {
 357			this.xmppConnectionService.removeOnRosterUpdateListener();
 358		}
 359		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
 360			this.xmppConnectionService.removeOnMucRosterUpdateListener();
 361		}
 362		if (this instanceof OnUpdateBlocklist) {
 363			this.xmppConnectionService.removeOnUpdateBlocklistListener();
 364		}
 365		if (this instanceof XmppConnectionService.OnShowErrorToast) {
 366			this.xmppConnectionService.removeOnShowErrorToastListener();
 367		}
 368		if (this instanceof OnKeyStatusUpdated) {
 369			this.xmppConnectionService.removeOnNewKeysAvailableListener();
 370		}
 371	}
 372
 373	@Override
 374	public boolean onOptionsItemSelected(final MenuItem item) {
 375		switch (item.getItemId()) {
 376			case R.id.action_settings:
 377				startActivity(new Intent(this, SettingsActivity.class));
 378				break;
 379			case R.id.action_accounts:
 380				startActivity(new Intent(this, ManageAccountActivity.class));
 381				break;
 382			case android.R.id.home:
 383				finish();
 384				break;
 385			case R.id.action_show_qr_code:
 386				showQrCode();
 387				break;
 388		}
 389		return super.onOptionsItemSelected(item);
 390	}
 391
 392	@Override
 393	protected void onCreate(Bundle savedInstanceState) {
 394		super.onCreate(savedInstanceState);
 395		metrics = getResources().getDisplayMetrics();
 396		ExceptionHelper.init(getApplicationContext());
 397
 398		mPrimaryTextColor = getResources().getColor(R.color.black87);
 399		mSecondaryTextColor = getResources().getColor(R.color.black54);
 400		mTertiaryTextColor = getResources().getColor(R.color.black12);
 401		mColorRed = getResources().getColor(R.color.red800);
 402		mColorOrange = getResources().getColor(R.color.orange500);
 403		mColorGreen = getResources().getColor(R.color.green500);
 404		mPrimaryColor = getResources().getColor(R.color.primary500);
 405		mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
 406		mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
 407
 408		if(isDarkTheme()) {
 409			mPrimaryTextColor = getResources().getColor(R.color.white);
 410			mSecondaryTextColor = getResources().getColor(R.color.white70);
 411			mTertiaryTextColor = getResources().getColor(R.color.white12);
 412			mPrimaryBackgroundColor = getResources().getColor(R.color.grey800);
 413			mSecondaryBackgroundColor = getResources().getColor(R.color.grey900);
 414		}
 415
 416		this.mTheme = findTheme();
 417		setTheme(this.mTheme);
 418
 419		this.mUsingEnterKey = usingEnterKey();
 420		mUseSubject = getPreferences().getBoolean("use_subject", true);
 421		final ActionBar ab = getActionBar();
 422		if (ab!=null) {
 423			ab.setDisplayHomeAsUpEnabled(true);
 424		}
 425	}
 426
 427	public boolean isDarkTheme() {
 428		return getPreferences().getString("theme", "light").equals("dark");
 429	}
 430
 431	public int getThemeResource(int r_attr_name, int r_drawable_def) {
 432		int[] attrs = {	r_attr_name };
 433		TypedArray ta = this.getTheme().obtainStyledAttributes(attrs);
 434
 435		int res = ta.getResourceId(0, r_drawable_def);
 436		ta.recycle();
 437
 438		return res;
 439	}
 440
 441	protected boolean isOptimizingBattery() {
 442		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 443			PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
 444			return !pm.isIgnoringBatteryOptimizations(getPackageName());
 445		} else {
 446			return false;
 447		}
 448	}
 449
 450	protected boolean isAffectedByDataSaver() {
 451		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 452			ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
 453			return cm.isActiveNetworkMetered()
 454					&& cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 455		} else {
 456			return false;
 457		}
 458	}
 459
 460	protected boolean usingEnterKey() {
 461		return getPreferences().getBoolean("display_enter_key", false);
 462	}
 463
 464	protected SharedPreferences getPreferences() {
 465		return PreferenceManager
 466			.getDefaultSharedPreferences(getApplicationContext());
 467	}
 468
 469	public boolean useSubjectToIdentifyConference() {
 470		return mUseSubject;
 471	}
 472
 473	public void switchToConversation(Conversation conversation) {
 474		switchToConversation(conversation, null, false);
 475	}
 476
 477	public void switchToConversation(Conversation conversation, String text,
 478			boolean newTask) {
 479		switchToConversation(conversation,text,null,false,newTask);
 480	}
 481
 482	public void highlightInMuc(Conversation conversation, String nick) {
 483		switchToConversation(conversation, null, nick, false, false);
 484	}
 485
 486	public void privateMsgInMuc(Conversation conversation, String nick) {
 487		switchToConversation(conversation, null, nick, true, false);
 488	}
 489
 490	private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) {
 491		Intent viewConversationIntent = new Intent(this,
 492				ConversationActivity.class);
 493		viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION);
 494		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
 495				conversation.getUuid());
 496		if (text != null) {
 497			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
 498		}
 499		if (nick != null) {
 500			viewConversationIntent.putExtra(ConversationActivity.NICK, nick);
 501			viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm);
 502		}
 503		if (newTask) {
 504			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
 505					| Intent.FLAG_ACTIVITY_NEW_TASK
 506					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
 507		} else {
 508			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
 509					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
 510		}
 511		startActivity(viewConversationIntent);
 512		finish();
 513	}
 514
 515	public void switchToContactDetails(Contact contact) {
 516		switchToContactDetails(contact, null);
 517	}
 518
 519	public void switchToContactDetails(Contact contact, String messageFingerprint) {
 520		Intent intent = new Intent(this, ContactDetailsActivity.class);
 521		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
 522		intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString());
 523		intent.putExtra("contact", contact.getJid().toString());
 524		intent.putExtra("fingerprint", messageFingerprint);
 525		startActivity(intent);
 526	}
 527
 528	public void switchToAccount(Account account) {
 529		switchToAccount(account, false);
 530	}
 531
 532	public void switchToAccount(Account account, boolean init) {
 533		Intent intent = new Intent(this, EditAccountActivity.class);
 534		intent.putExtra("jid", account.getJid().toBareJid().toString());
 535		intent.putExtra("init", init);
 536		startActivity(intent);
 537	}
 538
 539	protected void inviteToConversation(Conversation conversation) {
 540		Intent intent = new Intent(getApplicationContext(),
 541				ChooseContactActivity.class);
 542		List<String> contacts = new ArrayList<>();
 543		if (conversation.getMode() == Conversation.MODE_MULTI) {
 544			for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) {
 545				Jid jid = user.getRealJid();
 546				if (jid != null) {
 547					contacts.add(jid.toBareJid().toString());
 548				}
 549			}
 550		} else {
 551			contacts.add(conversation.getJid().toBareJid().toString());
 552		}
 553		intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
 554		intent.putExtra("conversation", conversation.getUuid());
 555		intent.putExtra("multiple", true);
 556		intent.putExtra("show_enter_jid", true);
 557		intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
 558		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
 559	}
 560
 561	protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) {
 562		if (account.getPgpId() == 0) {
 563			choosePgpSignId(account);
 564		} else {
 565			String status = null;
 566			if (manuallyChangePresence()) {
 567				status = account.getPresenceStatusMessage();
 568			}
 569			if (status == null) {
 570				status = "";
 571			}
 572			xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback<Account>() {
 573
 574				@Override
 575				public void userInputRequried(PendingIntent pi, Account account) {
 576					try {
 577						startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
 578					} catch (final SendIntentException ignored) {
 579					}
 580				}
 581
 582				@Override
 583				public void success(Account account) {
 584					xmppConnectionService.databaseBackend.updateAccount(account);
 585					xmppConnectionService.sendPresence(account);
 586					if (conversation != null) {
 587						conversation.setNextEncryption(Message.ENCRYPTION_PGP);
 588						xmppConnectionService.updateConversation(conversation);
 589						refreshUi();
 590					}
 591					if (onSuccess != null) {
 592						runOnUiThread(onSuccess);
 593					}
 594				}
 595
 596				@Override
 597				public void error(int error, Account account) {
 598					if (error == 0 && account != null) {
 599						account.setPgpSignId(0);
 600						account.unsetPgpSignature();
 601						xmppConnectionService.databaseBackend.updateAccount(account);
 602						choosePgpSignId(account);
 603					} else {
 604						displayErrorDialog(error);
 605					}
 606				}
 607			});
 608		}
 609	}
 610
 611	protected  boolean noAccountUsesPgp() {
 612		if (!hasPgp()) {
 613			return true;
 614		}
 615		for(Account account : xmppConnectionService.getAccounts()) {
 616			if (account.getPgpId() != 0) {
 617				return false;
 618			}
 619		}
 620		return true;
 621	}
 622
 623	@SuppressWarnings("deprecation")
 624	@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 625	protected void setListItemBackgroundOnView(View view) {
 626		int sdk = android.os.Build.VERSION.SDK_INT;
 627		if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
 628			view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
 629		} else {
 630			view.setBackground(getResources().getDrawable(R.drawable.greybackground));
 631		}
 632	}
 633
 634	protected void choosePgpSignId(Account account) {
 635		xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
 636			@Override
 637			public void success(Account account1) {
 638			}
 639
 640			@Override
 641			public void error(int errorCode, Account object) {
 642
 643			}
 644
 645			@Override
 646			public void userInputRequried(PendingIntent pi, Account object) {
 647				try {
 648					startIntentSenderForResult(pi.getIntentSender(),
 649							REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
 650				} catch (final SendIntentException ignored) {
 651				}
 652			}
 653		});
 654	}
 655
 656	protected void displayErrorDialog(final int errorCode) {
 657		runOnUiThread(new Runnable() {
 658
 659			@Override
 660			public void run() {
 661				AlertDialog.Builder builder = new AlertDialog.Builder(
 662						XmppActivity.this);
 663				builder.setIconAttribute(android.R.attr.alertDialogIcon);
 664				builder.setTitle(getString(R.string.error));
 665				builder.setMessage(errorCode);
 666				builder.setNeutralButton(R.string.accept, null);
 667				builder.create().show();
 668			}
 669		});
 670
 671	}
 672
 673	protected void showAddToRosterDialog(final Conversation conversation) {
 674		showAddToRosterDialog(conversation.getContact());
 675	}
 676
 677	protected void showAddToRosterDialog(final Contact contact) {
 678		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 679		builder.setTitle(contact.getJid().toString());
 680		builder.setMessage(getString(R.string.not_in_roster));
 681		builder.setNegativeButton(getString(R.string.cancel), null);
 682		builder.setPositiveButton(getString(R.string.add_contact),
 683				new DialogInterface.OnClickListener() {
 684
 685					@Override
 686					public void onClick(DialogInterface dialog, int which) {
 687						final Jid jid = contact.getJid();
 688						Account account = contact.getAccount();
 689						Contact contact = account.getRoster().getContact(jid);
 690						xmppConnectionService.createContact(contact);
 691					}
 692				});
 693		builder.create().show();
 694	}
 695
 696	private void showAskForPresenceDialog(final Contact contact) {
 697		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 698		builder.setTitle(contact.getJid().toString());
 699		builder.setMessage(R.string.request_presence_updates);
 700		builder.setNegativeButton(R.string.cancel, null);
 701		builder.setPositiveButton(R.string.request_now,
 702				new DialogInterface.OnClickListener() {
 703
 704					@Override
 705					public void onClick(DialogInterface dialog, int which) {
 706						if (xmppConnectionServiceBound) {
 707							xmppConnectionService.sendPresencePacket(contact
 708									.getAccount(), xmppConnectionService
 709									.getPresenceGenerator()
 710									.requestPresenceUpdatesFrom(contact));
 711						}
 712					}
 713				});
 714		builder.create().show();
 715	}
 716
 717	private void warnMutalPresenceSubscription(final Conversation conversation,
 718			final OnPresenceSelected listener) {
 719		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 720		builder.setTitle(conversation.getContact().getJid().toString());
 721		builder.setMessage(R.string.without_mutual_presence_updates);
 722		builder.setNegativeButton(R.string.cancel, null);
 723		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
 724
 725			@Override
 726			public void onClick(DialogInterface dialog, int which) {
 727				conversation.setNextCounterpart(null);
 728				if (listener != null) {
 729					listener.onPresenceSelected();
 730				}
 731			}
 732		});
 733		builder.create().show();
 734	}
 735
 736	protected void quickEdit(String previousValue, int hint, OnValueEdited callback) {
 737		quickEdit(previousValue, callback, hint, false);
 738	}
 739
 740	protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
 741		quickEdit(previousValue, callback, R.string.password, true);
 742	}
 743
 744	@SuppressLint("InflateParams")
 745	private void quickEdit(final String previousValue,
 746						   final OnValueEdited callback,
 747						   final int hint,
 748						   boolean password) {
 749		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 750		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
 751		final EditText editor = (EditText) view.findViewById(R.id.editor);
 752		OnClickListener mClickListener = new OnClickListener() {
 753
 754			@Override
 755			public void onClick(DialogInterface dialog, int which) {
 756				String value = editor.getText().toString();
 757				if (!value.equals(previousValue) && value.trim().length() > 0) {
 758					callback.onValueEdited(value);
 759				}
 760			}
 761		};
 762		if (password) {
 763			editor.setInputType(InputType.TYPE_CLASS_TEXT
 764					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
 765			builder.setPositiveButton(R.string.accept, mClickListener);
 766		} else {
 767			builder.setPositiveButton(R.string.edit, mClickListener);
 768		}
 769		if (hint != 0) {
 770			editor.setHint(hint);
 771		}
 772		editor.requestFocus();
 773		editor.setText("");
 774		if (previousValue != null) {
 775			editor.getText().append(previousValue);
 776		}
 777		builder.setView(view);
 778		builder.setNegativeButton(R.string.cancel, null);
 779		builder.create().show();
 780	}
 781
 782	protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
 783		final XmppAxolotlSession.Trust trust = account.getAxolotlService()
 784				.getFingerprintTrust(fingerprint);
 785		if (trust == null) {
 786			return false;
 787		}
 788		return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true,
 789				new CompoundButton.OnCheckedChangeListener() {
 790					@Override
 791					public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 792						account.getAxolotlService().setFingerprintTrust(fingerprint,
 793								(isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
 794										XmppAxolotlSession.Trust.UNTRUSTED);
 795					}
 796				},
 797				new View.OnClickListener() {
 798					@Override
 799					public void onClick(View v) {
 800						account.getAxolotlService().setFingerprintTrust(fingerprint,
 801								XmppAxolotlSession.Trust.UNTRUSTED);
 802						v.setEnabled(true);
 803					}
 804				},
 805				onKeyClickedListener
 806
 807		);
 808	}
 809
 810	protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
 811	                                                 final String fingerprint,
 812	                                                 boolean highlight,
 813	                                                 XmppAxolotlSession.Trust trust,
 814	                                                 boolean showTag,
 815	                                                 CompoundButton.OnCheckedChangeListener
 816			                                                 onCheckedChangeListener,
 817	                                                 View.OnClickListener onClickListener,
 818													 View.OnClickListener onKeyClickedListener) {
 819		if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
 820			return false;
 821		}
 822		View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
 823		TextView key = (TextView) view.findViewById(R.id.key);
 824		key.setOnClickListener(onKeyClickedListener);
 825		TextView keyType = (TextView) view.findViewById(R.id.key_type);
 826		keyType.setOnClickListener(onKeyClickedListener);
 827		Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
 828		trustToggle.setVisibility(View.VISIBLE);
 829		trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
 830		trustToggle.setOnClickListener(onClickListener);
 831		final View.OnLongClickListener purge = new View.OnLongClickListener() {
 832			@Override
 833			public boolean onLongClick(View v) {
 834				showPurgeKeyDialog(account, fingerprint);
 835				return true;
 836			}
 837		};
 838		boolean active = true;
 839		view.setOnLongClickListener(purge);
 840		key.setOnLongClickListener(purge);
 841		keyType.setOnLongClickListener(purge);
 842		boolean x509 = Config.X509_VERIFICATION
 843				&& (trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
 844		switch (trust) {
 845			case UNTRUSTED:
 846			case TRUSTED:
 847			case TRUSTED_X509:
 848				trustToggle.setChecked(trust.trusted(), false);
 849				trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
 850				if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
 851					trustToggle.setOnClickListener(null);
 852				}
 853				key.setTextColor(getPrimaryTextColor());
 854				keyType.setTextColor(getSecondaryTextColor());
 855				break;
 856			case UNDECIDED:
 857				trustToggle.setChecked(false, false);
 858				trustToggle.setEnabled(false);
 859				key.setTextColor(getPrimaryTextColor());
 860				keyType.setTextColor(getSecondaryTextColor());
 861				break;
 862			case INACTIVE_UNTRUSTED:
 863			case INACTIVE_UNDECIDED:
 864				trustToggle.setOnClickListener(null);
 865				trustToggle.setChecked(false, false);
 866				trustToggle.setEnabled(false);
 867				key.setTextColor(getTertiaryTextColor());
 868				keyType.setTextColor(getTertiaryTextColor());
 869				active = false;
 870				break;
 871			case INACTIVE_TRUSTED:
 872			case INACTIVE_TRUSTED_X509:
 873				trustToggle.setOnClickListener(null);
 874				trustToggle.setChecked(true, false);
 875				trustToggle.setEnabled(false);
 876				key.setTextColor(getTertiaryTextColor());
 877				keyType.setTextColor(getTertiaryTextColor());
 878				active = false;
 879				break;
 880		}
 881
 882		if (showTag) {
 883			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 884		} else {
 885			keyType.setVisibility(View.GONE);
 886		}
 887		if (highlight) {
 888			keyType.setTextColor(getResources().getColor(R.color.accent));
 889			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
 890		} else {
 891			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 892		}
 893
 894		key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
 895
 896		final View.OnClickListener toast;
 897		if (!active) {
 898			toast = new View.OnClickListener() {
 899				@Override
 900				public void onClick(View v) {
 901					replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
 902				}
 903			};
 904			trustToggle.setOnClickListener(toast);
 905		} else {
 906			toast = new View.OnClickListener() {
 907				@Override
 908				public void onClick(View v) {
 909					hideToast();
 910				}
 911			};
 912		}
 913		view.setOnClickListener(toast);
 914		key.setOnClickListener(toast);
 915		keyType.setOnClickListener(toast);
 916
 917		keys.addView(view);
 918		return true;
 919	}
 920
 921	public void showPurgeKeyDialog(final Account account, final String fingerprint) {
 922		Builder builder = new Builder(this);
 923		builder.setTitle(getString(R.string.purge_key));
 924		builder.setIconAttribute(android.R.attr.alertDialogIcon);
 925		builder.setMessage(getString(R.string.purge_key_desc_part1)
 926				+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
 927				+ "\n\n" + getString(R.string.purge_key_desc_part2));
 928		builder.setNegativeButton(getString(R.string.cancel), null);
 929		builder.setPositiveButton(getString(R.string.purge_key),
 930				new DialogInterface.OnClickListener() {
 931					@Override
 932					public void onClick(DialogInterface dialog, int which) {
 933						account.getAxolotlService().purgeKey(fingerprint);
 934						refreshUi();
 935					}
 936				});
 937		builder.create().show();
 938	}
 939
 940	public boolean hasStoragePermission(int requestCode) {
 941		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 942			if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 943				requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
 944				return false;
 945			} else {
 946				return true;
 947			}
 948		} else {
 949			return true;
 950		}
 951	}
 952
 953	public void selectPresence(final Conversation conversation,
 954			final OnPresenceSelected listener) {
 955		final Contact contact = conversation.getContact();
 956		if (conversation.hasValidOtrSession()) {
 957			SessionID id = conversation.getOtrSession().getSessionID();
 958			Jid jid;
 959			try {
 960				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
 961			} catch (InvalidJidException e) {
 962				jid = null;
 963			}
 964			conversation.setNextCounterpart(jid);
 965			listener.onPresenceSelected();
 966		} else 	if (!contact.showInRoster()) {
 967			showAddToRosterDialog(conversation);
 968		} else {
 969			final Presences presences = contact.getPresences();
 970			if (presences.size() == 0) {
 971				if (!contact.getOption(Contact.Options.TO)
 972						&& !contact.getOption(Contact.Options.ASKING)
 973						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
 974					showAskForPresenceDialog(contact);
 975				} else if (!contact.getOption(Contact.Options.TO)
 976						|| !contact.getOption(Contact.Options.FROM)) {
 977					warnMutalPresenceSubscription(conversation, listener);
 978				} else {
 979					conversation.setNextCounterpart(null);
 980					listener.onPresenceSelected();
 981				}
 982			} else if (presences.size() == 1) {
 983				String presence = presences.toResourceArray()[0];
 984				try {
 985					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
 986				} catch (InvalidJidException e) {
 987					conversation.setNextCounterpart(null);
 988				}
 989				listener.onPresenceSelected();
 990			} else {
 991				showPresenceSelectionDialog(presences,conversation,listener);
 992			}
 993		}
 994	}
 995
 996	private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
 997		final Contact contact = conversation.getContact();
 998		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 999		builder.setTitle(getString(R.string.choose_presence));
1000		final String[] resourceArray = presences.toResourceArray();
1001		Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
1002		final Map<String,String> resourceTypeMap = typeAndName.first;
1003		final Map<String,String> resourceNameMap = typeAndName.second;
1004		final String[] readableIdentities = new String[resourceArray.length];
1005		final AtomicInteger selectedResource = new AtomicInteger(0);
1006		for (int i = 0; i < resourceArray.length; ++i) {
1007			String resource = resourceArray[i];
1008			if (resource.equals(contact.getLastResource())) {
1009				selectedResource.set(i);
1010			}
1011			String type = resourceTypeMap.get(resource);
1012			String name = resourceNameMap.get(resource);
1013			if (type != null) {
1014				if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
1015					readableIdentities[i] = UIHelper.tranlasteType(this,type);
1016				} else if (name != null) {
1017					if (Collections.frequency(resourceNameMap.values(), name) == 1
1018							|| CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
1019						readableIdentities[i] = UIHelper.tranlasteType(this,type) + "  (" + name+")";
1020					} else {
1021						readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
1022					}
1023				} else {
1024					readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
1025				}
1026			} else {
1027				readableIdentities[i] = resource;
1028			}
1029		}
1030		builder.setSingleChoiceItems(readableIdentities,
1031				selectedResource.get(),
1032				new DialogInterface.OnClickListener() {
1033
1034					@Override
1035					public void onClick(DialogInterface dialog, int which) {
1036						selectedResource.set(which);
1037					}
1038				});
1039		builder.setNegativeButton(R.string.cancel, null);
1040		builder.setPositiveButton(R.string.ok, new OnClickListener() {
1041
1042			@Override
1043			public void onClick(DialogInterface dialog, int which) {
1044				try {
1045					Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
1046					conversation.setNextCounterpart(next);
1047				} catch (InvalidJidException e) {
1048					conversation.setNextCounterpart(null);
1049				}
1050				listener.onPresenceSelected();
1051			}
1052		});
1053		builder.create().show();
1054	}
1055
1056	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1057		super.onActivityResult(requestCode, resultCode, data);
1058		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1059			mPendingConferenceInvite = ConferenceInvite.parse(data);
1060			if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1061				if (mPendingConferenceInvite.execute(this)) {
1062					mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1063					mToast.show();
1064				}
1065				mPendingConferenceInvite = null;
1066			}
1067		}
1068	}
1069
1070
1071	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1072		@Override
1073		public void success(final Conversation conversation) {
1074			runOnUiThread(new Runnable() {
1075				@Override
1076				public void run() {
1077					switchToConversation(conversation);
1078					hideToast();
1079				}
1080			});
1081		}
1082
1083		@Override
1084		public void error(final int errorCode, Conversation object) {
1085			runOnUiThread(new Runnable() {
1086				@Override
1087				public void run() {
1088					replaceToast(getString(errorCode));
1089				}
1090			});
1091		}
1092
1093		@Override
1094		public void userInputRequried(PendingIntent pi, Conversation object) {
1095
1096		}
1097	};
1098
1099	public int getTertiaryTextColor() {
1100		return this.mTertiaryTextColor;
1101	}
1102
1103	public int getSecondaryTextColor() {
1104		return this.mSecondaryTextColor;
1105	}
1106
1107	public int getPrimaryTextColor() {
1108		return this.mPrimaryTextColor;
1109	}
1110
1111	public int getWarningTextColor() {
1112		return this.mColorRed;
1113	}
1114
1115	public int getOnlineColor() {
1116		return this.mColorGreen;
1117	}
1118
1119	public int getPrimaryBackgroundColor() {
1120		return this.mPrimaryBackgroundColor;
1121	}
1122
1123	public int getSecondaryBackgroundColor() {
1124		return this.mSecondaryBackgroundColor;
1125	}
1126
1127	public int getPixel(int dp) {
1128		DisplayMetrics metrics = getResources().getDisplayMetrics();
1129		return ((int) (dp * metrics.density));
1130	}
1131
1132	public boolean copyTextToClipboard(String text, int labelResId) {
1133		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1134		String label = getResources().getString(labelResId);
1135		if (mClipBoardManager != null) {
1136			ClipData mClipData = ClipData.newPlainText(label, text);
1137			mClipBoardManager.setPrimaryClip(mClipData);
1138			return true;
1139		}
1140		return false;
1141	}
1142
1143	protected void registerNdefPushMessageCallback() {
1144		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1145		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1146			nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1147				@Override
1148				public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1149					return new NdefMessage(new NdefRecord[]{
1150							NdefRecord.createUri(getShareableUri()),
1151							NdefRecord.createApplicationRecord("eu.siacs.conversations")
1152					});
1153				}
1154			}, this);
1155		}
1156	}
1157
1158	protected boolean neverCompressPictures() {
1159		return getPreferences().getString("picture_compression", "auto").equals("never");
1160	}
1161
1162	protected boolean manuallyChangePresence() {
1163		return getPreferences().getBoolean("manually_change_presence", false);
1164	}
1165
1166	protected void unregisterNdefPushMessageCallback() {
1167		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1168		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1169			nfcAdapter.setNdefPushMessageCallback(null,this);
1170		}
1171	}
1172
1173	protected String getShareableUri() {
1174		return null;
1175	}
1176
1177	protected void shareUri() {
1178		String uri = getShareableUri();
1179		if (uri == null || uri.isEmpty()) {
1180			return;
1181		}
1182		Intent shareIntent = new Intent();
1183		shareIntent.setAction(Intent.ACTION_SEND);
1184		shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1185		shareIntent.setType("text/plain");
1186		try {
1187			startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1188		} catch (ActivityNotFoundException e) {
1189			Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1190		}
1191	}
1192
1193	@Override
1194	public void onResume() {
1195		super.onResume();
1196		if (this.getShareableUri()!=null) {
1197			this.registerNdefPushMessageCallback();
1198		}
1199	}
1200
1201	protected int findTheme() {
1202		Boolean dark   = getPreferences().getString("theme", "light").equals("dark");
1203		Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1204
1205		if(dark) {
1206			if(larger)
1207				return R.style.ConversationsTheme_Dark_LargerText;
1208			else
1209				return R.style.ConversationsTheme_Dark;
1210		} else {
1211			if (larger)
1212				return R.style.ConversationsTheme_LargerText;
1213			else
1214				return R.style.ConversationsTheme;
1215		}
1216	}
1217
1218	@Override
1219	public void onPause() {
1220		super.onPause();
1221		this.unregisterNdefPushMessageCallback();
1222	}
1223
1224	protected void showQrCode() {
1225		String uri = getShareableUri();
1226		if (uri!=null) {
1227			Point size = new Point();
1228			getWindowManager().getDefaultDisplay().getSize(size);
1229			final int width = (size.x < size.y ? size.x : size.y);
1230			Bitmap bitmap = createQrCodeBitmap(uri, width);
1231			ImageView view = new ImageView(this);
1232			view.setBackgroundColor(Color.WHITE);
1233			view.setImageBitmap(bitmap);
1234			AlertDialog.Builder builder = new AlertDialog.Builder(this);
1235			builder.setView(view);
1236			builder.create().show();
1237		}
1238	}
1239
1240	protected Bitmap createQrCodeBitmap(String input, int size) {
1241		Log.d(Config.LOGTAG,"qr code requested size: "+size);
1242		try {
1243			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1244			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1245			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1246			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1247			final int width = result.getWidth();
1248			final int height = result.getHeight();
1249			final int[] pixels = new int[width * height];
1250			for (int y = 0; y < height; y++) {
1251				final int offset = y * width;
1252				for (int x = 0; x < width; x++) {
1253					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1254				}
1255			}
1256			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1257			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1258			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1259			return bitmap;
1260		} catch (final WriterException e) {
1261			return null;
1262		}
1263	}
1264
1265	protected Account extractAccount(Intent intent) {
1266		String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1267		try {
1268			return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1269		} catch (InvalidJidException e) {
1270			return null;
1271		}
1272	}
1273
1274	public static class ConferenceInvite {
1275		private String uuid;
1276		private List<Jid> jids = new ArrayList<>();
1277
1278		public static ConferenceInvite parse(Intent data) {
1279			ConferenceInvite invite = new ConferenceInvite();
1280			invite.uuid = data.getStringExtra("conversation");
1281			if (invite.uuid == null) {
1282				return null;
1283			}
1284			try {
1285				if (data.getBooleanExtra("multiple", false)) {
1286					String[] toAdd = data.getStringArrayExtra("contacts");
1287					for (String item : toAdd) {
1288						invite.jids.add(Jid.fromString(item));
1289					}
1290				} else {
1291					invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1292				}
1293			} catch (final InvalidJidException ignored) {
1294				return null;
1295			}
1296			return invite;
1297		}
1298
1299		public boolean execute(XmppActivity activity) {
1300			XmppConnectionService service = activity.xmppConnectionService;
1301			Conversation conversation = service.findConversationByUuid(this.uuid);
1302			if (conversation == null) {
1303				return false;
1304			}
1305			if (conversation.getMode() == Conversation.MODE_MULTI) {
1306				for (Jid jid : jids) {
1307					service.invite(conversation, jid);
1308				}
1309				return false;
1310			} else {
1311				jids.add(conversation.getJid().toBareJid());
1312				service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1313				return true;
1314			}
1315		}
1316	}
1317
1318	public AvatarService avatarService() {
1319		return xmppConnectionService.getAvatarService();
1320	}
1321
1322	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1323		private final WeakReference<ImageView> imageViewReference;
1324		private Message message = null;
1325
1326		public BitmapWorkerTask(ImageView imageView) {
1327			imageViewReference = new WeakReference<>(imageView);
1328		}
1329
1330		@Override
1331		protected Bitmap doInBackground(Message... params) {
1332			if (isCancelled()) {
1333				return null;
1334			}
1335			message = params[0];
1336			try {
1337				return xmppConnectionService.getFileBackend().getThumbnail(
1338						message, (int) (metrics.density * 288), false);
1339			} catch (FileNotFoundException e) {
1340				return null;
1341			}
1342		}
1343
1344		@Override
1345		protected void onPostExecute(Bitmap bitmap) {
1346			if (bitmap != null && !isCancelled()) {
1347				final ImageView imageView = imageViewReference.get();
1348				if (imageView != null) {
1349					imageView.setImageBitmap(bitmap);
1350					imageView.setBackgroundColor(0x00000000);
1351				}
1352			}
1353		}
1354	}
1355
1356	public void loadBitmap(Message message, ImageView imageView) {
1357		Bitmap bm;
1358		try {
1359			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1360					(int) (metrics.density * 288), true);
1361		} catch (FileNotFoundException e) {
1362			bm = null;
1363		}
1364		if (bm != null) {
1365			cancelPotentialWork(message, imageView);
1366			imageView.setImageBitmap(bm);
1367			imageView.setBackgroundColor(0x00000000);
1368		} else {
1369			if (cancelPotentialWork(message, imageView)) {
1370				imageView.setBackgroundColor(0xff333333);
1371				imageView.setImageDrawable(null);
1372				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1373				final AsyncDrawable asyncDrawable = new AsyncDrawable(
1374						getResources(), null, task);
1375				imageView.setImageDrawable(asyncDrawable);
1376				try {
1377					task.execute(message);
1378				} catch (final RejectedExecutionException ignored) {
1379					ignored.printStackTrace();
1380				}
1381			}
1382		}
1383	}
1384
1385	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1386		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1387
1388		if (bitmapWorkerTask != null) {
1389			final Message oldMessage = bitmapWorkerTask.message;
1390			if (oldMessage == null || message != oldMessage) {
1391				bitmapWorkerTask.cancel(true);
1392			} else {
1393				return false;
1394			}
1395		}
1396		return true;
1397	}
1398
1399	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1400		if (imageView != null) {
1401			final Drawable drawable = imageView.getDrawable();
1402			if (drawable instanceof AsyncDrawable) {
1403				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1404				return asyncDrawable.getBitmapWorkerTask();
1405			}
1406		}
1407		return null;
1408	}
1409
1410	static class AsyncDrawable extends BitmapDrawable {
1411		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1412
1413		public AsyncDrawable(Resources res, Bitmap bitmap,
1414				BitmapWorkerTask bitmapWorkerTask) {
1415			super(res, bitmap);
1416			bitmapWorkerTaskReference = new WeakReference<>(
1417					bitmapWorkerTask);
1418		}
1419
1420		public BitmapWorkerTask getBitmapWorkerTask() {
1421			return bitmapWorkerTaskReference.get();
1422		}
1423	}
1424}