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