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