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(true));
 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		if (status.isActive()) {
 839			key.setTextColor(getPrimaryTextColor());
 840			keyType.setTextColor(getSecondaryTextColor());
 841			trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
 842			if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED) {
 843				trustToggle.setOnClickListener(onClickListener);
 844				trustToggle.setEnabled(false);
 845			} else {
 846				trustToggle.setOnClickListener(null);
 847				trustToggle.setChecked(status.isTrusted(), false);
 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			trustToggle.setChecked(status.isTrusted(), false);
 862			toast = new View.OnClickListener() {
 863				@Override
 864				public void onClick(View v) {
 865					replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
 866				}
 867			};
 868			trustToggle.setOnClickListener(toast);
 869		}
 870		view.setOnClickListener(toast);
 871		key.setOnClickListener(toast);
 872		keyType.setOnClickListener(toast);
 873		if (showTag) {
 874			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 875		} else {
 876			keyType.setVisibility(View.GONE);
 877		}
 878		if (highlight) {
 879			keyType.setTextColor(getResources().getColor(R.color.accent));
 880			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
 881		} else {
 882			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 883		}
 884
 885		key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
 886
 887		keys.addView(view);
 888		return true;
 889	}
 890
 891	public void showPurgeKeyDialog(final Account account, final String fingerprint) {
 892		Builder builder = new Builder(this);
 893		builder.setTitle(getString(R.string.purge_key));
 894		builder.setIconAttribute(android.R.attr.alertDialogIcon);
 895		builder.setMessage(getString(R.string.purge_key_desc_part1)
 896				+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
 897				+ "\n\n" + getString(R.string.purge_key_desc_part2));
 898		builder.setNegativeButton(getString(R.string.cancel), null);
 899		builder.setPositiveButton(getString(R.string.purge_key),
 900				new DialogInterface.OnClickListener() {
 901					@Override
 902					public void onClick(DialogInterface dialog, int which) {
 903						account.getAxolotlService().purgeKey(fingerprint);
 904						refreshUi();
 905					}
 906				});
 907		builder.create().show();
 908	}
 909
 910	public boolean hasStoragePermission(int requestCode) {
 911		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 912			if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 913				requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
 914				return false;
 915			} else {
 916				return true;
 917			}
 918		} else {
 919			return true;
 920		}
 921	}
 922
 923	public void selectPresence(final Conversation conversation,
 924			final OnPresenceSelected listener) {
 925		final Contact contact = conversation.getContact();
 926		if (conversation.hasValidOtrSession()) {
 927			SessionID id = conversation.getOtrSession().getSessionID();
 928			Jid jid;
 929			try {
 930				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
 931			} catch (InvalidJidException e) {
 932				jid = null;
 933			}
 934			conversation.setNextCounterpart(jid);
 935			listener.onPresenceSelected();
 936		} else 	if (!contact.showInRoster()) {
 937			showAddToRosterDialog(conversation);
 938		} else {
 939			final Presences presences = contact.getPresences();
 940			if (presences.size() == 0) {
 941				if (!contact.getOption(Contact.Options.TO)
 942						&& !contact.getOption(Contact.Options.ASKING)
 943						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
 944					showAskForPresenceDialog(contact);
 945				} else if (!contact.getOption(Contact.Options.TO)
 946						|| !contact.getOption(Contact.Options.FROM)) {
 947					warnMutalPresenceSubscription(conversation, listener);
 948				} else {
 949					conversation.setNextCounterpart(null);
 950					listener.onPresenceSelected();
 951				}
 952			} else if (presences.size() == 1) {
 953				String presence = presences.toResourceArray()[0];
 954				try {
 955					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
 956				} catch (InvalidJidException e) {
 957					conversation.setNextCounterpart(null);
 958				}
 959				listener.onPresenceSelected();
 960			} else {
 961				showPresenceSelectionDialog(presences,conversation,listener);
 962			}
 963		}
 964	}
 965
 966	private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
 967		final Contact contact = conversation.getContact();
 968		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 969		builder.setTitle(getString(R.string.choose_presence));
 970		final String[] resourceArray = presences.toResourceArray();
 971		Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
 972		final Map<String,String> resourceTypeMap = typeAndName.first;
 973		final Map<String,String> resourceNameMap = typeAndName.second;
 974		final String[] readableIdentities = new String[resourceArray.length];
 975		final AtomicInteger selectedResource = new AtomicInteger(0);
 976		for (int i = 0; i < resourceArray.length; ++i) {
 977			String resource = resourceArray[i];
 978			if (resource.equals(contact.getLastResource())) {
 979				selectedResource.set(i);
 980			}
 981			String type = resourceTypeMap.get(resource);
 982			String name = resourceNameMap.get(resource);
 983			if (type != null) {
 984				if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
 985					readableIdentities[i] = UIHelper.tranlasteType(this,type);
 986				} else if (name != null) {
 987					if (Collections.frequency(resourceNameMap.values(), name) == 1
 988							|| CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
 989						readableIdentities[i] = UIHelper.tranlasteType(this,type) + "  (" + name+")";
 990					} else {
 991						readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
 992					}
 993				} else {
 994					readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
 995				}
 996			} else {
 997				readableIdentities[i] = resource;
 998			}
 999		}
1000		builder.setSingleChoiceItems(readableIdentities,
1001				selectedResource.get(),
1002				new DialogInterface.OnClickListener() {
1003
1004					@Override
1005					public void onClick(DialogInterface dialog, int which) {
1006						selectedResource.set(which);
1007					}
1008				});
1009		builder.setNegativeButton(R.string.cancel, null);
1010		builder.setPositiveButton(R.string.ok, new OnClickListener() {
1011
1012			@Override
1013			public void onClick(DialogInterface dialog, int which) {
1014				try {
1015					Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
1016					conversation.setNextCounterpart(next);
1017				} catch (InvalidJidException e) {
1018					conversation.setNextCounterpart(null);
1019				}
1020				listener.onPresenceSelected();
1021			}
1022		});
1023		builder.create().show();
1024	}
1025
1026	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1027		super.onActivityResult(requestCode, resultCode, data);
1028		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1029			mPendingConferenceInvite = ConferenceInvite.parse(data);
1030			if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1031				if (mPendingConferenceInvite.execute(this)) {
1032					mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1033					mToast.show();
1034				}
1035				mPendingConferenceInvite = null;
1036			}
1037		}
1038	}
1039
1040
1041	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1042		@Override
1043		public void success(final Conversation conversation) {
1044			runOnUiThread(new Runnable() {
1045				@Override
1046				public void run() {
1047					switchToConversation(conversation);
1048					hideToast();
1049				}
1050			});
1051		}
1052
1053		@Override
1054		public void error(final int errorCode, Conversation object) {
1055			runOnUiThread(new Runnable() {
1056				@Override
1057				public void run() {
1058					replaceToast(getString(errorCode));
1059				}
1060			});
1061		}
1062
1063		@Override
1064		public void userInputRequried(PendingIntent pi, Conversation object) {
1065
1066		}
1067	};
1068
1069	public int getTertiaryTextColor() {
1070		return this.mTertiaryTextColor;
1071	}
1072
1073	public int getSecondaryTextColor() {
1074		return this.mSecondaryTextColor;
1075	}
1076
1077	public int getPrimaryTextColor() {
1078		return this.mPrimaryTextColor;
1079	}
1080
1081	public int getWarningTextColor() {
1082		return this.mColorRed;
1083	}
1084
1085	public int getOnlineColor() {
1086		return this.mColorGreen;
1087	}
1088
1089	public int getPrimaryBackgroundColor() {
1090		return this.mPrimaryBackgroundColor;
1091	}
1092
1093	public int getSecondaryBackgroundColor() {
1094		return this.mSecondaryBackgroundColor;
1095	}
1096
1097	public int getPixel(int dp) {
1098		DisplayMetrics metrics = getResources().getDisplayMetrics();
1099		return ((int) (dp * metrics.density));
1100	}
1101
1102	public boolean copyTextToClipboard(String text, int labelResId) {
1103		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1104		String label = getResources().getString(labelResId);
1105		if (mClipBoardManager != null) {
1106			ClipData mClipData = ClipData.newPlainText(label, text);
1107			mClipBoardManager.setPrimaryClip(mClipData);
1108			return true;
1109		}
1110		return false;
1111	}
1112
1113	protected void registerNdefPushMessageCallback() {
1114		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1115		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1116			nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1117				@Override
1118				public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1119					return new NdefMessage(new NdefRecord[]{
1120							NdefRecord.createUri(getShareableUri()),
1121							NdefRecord.createApplicationRecord("eu.siacs.conversations")
1122					});
1123				}
1124			}, this);
1125		}
1126	}
1127
1128	protected boolean neverCompressPictures() {
1129		return getPreferences().getString("picture_compression", "auto").equals("never");
1130	}
1131
1132	protected boolean manuallyChangePresence() {
1133		return getPreferences().getBoolean("manually_change_presence", false);
1134	}
1135
1136	protected void unregisterNdefPushMessageCallback() {
1137		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1138		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1139			nfcAdapter.setNdefPushMessageCallback(null,this);
1140		}
1141	}
1142
1143	protected String getShareableUri() {
1144		return null;
1145	}
1146
1147	protected void shareUri() {
1148		String uri = getShareableUri();
1149		if (uri == null || uri.isEmpty()) {
1150			return;
1151		}
1152		Intent shareIntent = new Intent();
1153		shareIntent.setAction(Intent.ACTION_SEND);
1154		shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1155		shareIntent.setType("text/plain");
1156		try {
1157			startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1158		} catch (ActivityNotFoundException e) {
1159			Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1160		}
1161	}
1162
1163	@Override
1164	public void onResume() {
1165		super.onResume();
1166		if (this.getShareableUri()!=null) {
1167			this.registerNdefPushMessageCallback();
1168		}
1169	}
1170
1171	protected int findTheme() {
1172		Boolean dark   = getPreferences().getString("theme", "light").equals("dark");
1173		Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1174
1175		if(dark) {
1176			if(larger)
1177				return R.style.ConversationsTheme_Dark_LargerText;
1178			else
1179				return R.style.ConversationsTheme_Dark;
1180		} else {
1181			if (larger)
1182				return R.style.ConversationsTheme_LargerText;
1183			else
1184				return R.style.ConversationsTheme;
1185		}
1186	}
1187
1188	@Override
1189	public void onPause() {
1190		super.onPause();
1191		this.unregisterNdefPushMessageCallback();
1192	}
1193
1194	protected void showQrCode() {
1195		String uri = getShareableUri();
1196		if (uri!=null) {
1197			Point size = new Point();
1198			getWindowManager().getDefaultDisplay().getSize(size);
1199			final int width = (size.x < size.y ? size.x : size.y);
1200			Bitmap bitmap = createQrCodeBitmap(uri, width);
1201			ImageView view = new ImageView(this);
1202			view.setBackgroundColor(Color.WHITE);
1203			view.setImageBitmap(bitmap);
1204			AlertDialog.Builder builder = new AlertDialog.Builder(this);
1205			builder.setView(view);
1206			builder.create().show();
1207		}
1208	}
1209
1210	protected Bitmap createQrCodeBitmap(String input, int size) {
1211		Log.d(Config.LOGTAG,"qr code requested size: "+size);
1212		try {
1213			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1214			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1215			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1216			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1217			final int width = result.getWidth();
1218			final int height = result.getHeight();
1219			final int[] pixels = new int[width * height];
1220			for (int y = 0; y < height; y++) {
1221				final int offset = y * width;
1222				for (int x = 0; x < width; x++) {
1223					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1224				}
1225			}
1226			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1227			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1228			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1229			return bitmap;
1230		} catch (final WriterException e) {
1231			return null;
1232		}
1233	}
1234
1235	protected Account extractAccount(Intent intent) {
1236		String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1237		try {
1238			return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1239		} catch (InvalidJidException e) {
1240			return null;
1241		}
1242	}
1243
1244	public static class ConferenceInvite {
1245		private String uuid;
1246		private List<Jid> jids = new ArrayList<>();
1247
1248		public static ConferenceInvite parse(Intent data) {
1249			ConferenceInvite invite = new ConferenceInvite();
1250			invite.uuid = data.getStringExtra("conversation");
1251			if (invite.uuid == null) {
1252				return null;
1253			}
1254			try {
1255				if (data.getBooleanExtra("multiple", false)) {
1256					String[] toAdd = data.getStringArrayExtra("contacts");
1257					for (String item : toAdd) {
1258						invite.jids.add(Jid.fromString(item));
1259					}
1260				} else {
1261					invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1262				}
1263			} catch (final InvalidJidException ignored) {
1264				return null;
1265			}
1266			return invite;
1267		}
1268
1269		public boolean execute(XmppActivity activity) {
1270			XmppConnectionService service = activity.xmppConnectionService;
1271			Conversation conversation = service.findConversationByUuid(this.uuid);
1272			if (conversation == null) {
1273				return false;
1274			}
1275			if (conversation.getMode() == Conversation.MODE_MULTI) {
1276				for (Jid jid : jids) {
1277					service.invite(conversation, jid);
1278				}
1279				return false;
1280			} else {
1281				jids.add(conversation.getJid().toBareJid());
1282				service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1283				return true;
1284			}
1285		}
1286	}
1287
1288	public AvatarService avatarService() {
1289		return xmppConnectionService.getAvatarService();
1290	}
1291
1292	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1293		private final WeakReference<ImageView> imageViewReference;
1294		private Message message = null;
1295
1296		public BitmapWorkerTask(ImageView imageView) {
1297			imageViewReference = new WeakReference<>(imageView);
1298		}
1299
1300		@Override
1301		protected Bitmap doInBackground(Message... params) {
1302			if (isCancelled()) {
1303				return null;
1304			}
1305			message = params[0];
1306			try {
1307				return xmppConnectionService.getFileBackend().getThumbnail(
1308						message, (int) (metrics.density * 288), false);
1309			} catch (FileNotFoundException e) {
1310				return null;
1311			}
1312		}
1313
1314		@Override
1315		protected void onPostExecute(Bitmap bitmap) {
1316			if (bitmap != null && !isCancelled()) {
1317				final ImageView imageView = imageViewReference.get();
1318				if (imageView != null) {
1319					imageView.setImageBitmap(bitmap);
1320					imageView.setBackgroundColor(0x00000000);
1321				}
1322			}
1323		}
1324	}
1325
1326	public void loadBitmap(Message message, ImageView imageView) {
1327		Bitmap bm;
1328		try {
1329			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1330					(int) (metrics.density * 288), true);
1331		} catch (FileNotFoundException e) {
1332			bm = null;
1333		}
1334		if (bm != null) {
1335			cancelPotentialWork(message, imageView);
1336			imageView.setImageBitmap(bm);
1337			imageView.setBackgroundColor(0x00000000);
1338		} else {
1339			if (cancelPotentialWork(message, imageView)) {
1340				imageView.setBackgroundColor(0xff333333);
1341				imageView.setImageDrawable(null);
1342				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1343				final AsyncDrawable asyncDrawable = new AsyncDrawable(
1344						getResources(), null, task);
1345				imageView.setImageDrawable(asyncDrawable);
1346				try {
1347					task.execute(message);
1348				} catch (final RejectedExecutionException ignored) {
1349					ignored.printStackTrace();
1350				}
1351			}
1352		}
1353	}
1354
1355	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1356		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1357
1358		if (bitmapWorkerTask != null) {
1359			final Message oldMessage = bitmapWorkerTask.message;
1360			if (oldMessage == null || message != oldMessage) {
1361				bitmapWorkerTask.cancel(true);
1362			} else {
1363				return false;
1364			}
1365		}
1366		return true;
1367	}
1368
1369	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1370		if (imageView != null) {
1371			final Drawable drawable = imageView.getDrawable();
1372			if (drawable instanceof AsyncDrawable) {
1373				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1374				return asyncDrawable.getBitmapWorkerTask();
1375			}
1376		}
1377		return null;
1378	}
1379
1380	static class AsyncDrawable extends BitmapDrawable {
1381		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1382
1383		public AsyncDrawable(Resources res, Bitmap bitmap,
1384				BitmapWorkerTask bitmapWorkerTask) {
1385			super(res, bitmap);
1386			bitmapWorkerTaskReference = new WeakReference<>(
1387					bitmapWorkerTask);
1388		}
1389
1390		public BitmapWorkerTask getBitmapWorkerTask() {
1391			return bitmapWorkerTaskReference.get();
1392		}
1393	}
1394}