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.databaseBackend.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					displayErrorDialog(error);
 588				}
 589			});
 590		}
 591	}
 592
 593	protected  boolean noAccountUsesPgp() {
 594		if (!hasPgp()) {
 595			return true;
 596		}
 597		for(Account account : xmppConnectionService.getAccounts()) {
 598			if (account.getPgpId() != 0) {
 599				return false;
 600			}
 601		}
 602		return true;
 603	}
 604
 605	@SuppressWarnings("deprecation")
 606	@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 607	protected void setListItemBackgroundOnView(View view) {
 608		int sdk = android.os.Build.VERSION.SDK_INT;
 609		if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
 610			view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
 611		} else {
 612			view.setBackground(getResources().getDrawable(R.drawable.greybackground));
 613		}
 614	}
 615
 616	protected void choosePgpSignId(Account account) {
 617		xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
 618			@Override
 619			public void success(Account account1) {
 620			}
 621
 622			@Override
 623			public void error(int errorCode, Account object) {
 624
 625			}
 626
 627			@Override
 628			public void userInputRequried(PendingIntent pi, Account object) {
 629				try {
 630					startIntentSenderForResult(pi.getIntentSender(),
 631							REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
 632				} catch (final SendIntentException ignored) {
 633				}
 634			}
 635		});
 636	}
 637
 638	protected void displayErrorDialog(final int errorCode) {
 639		runOnUiThread(new Runnable() {
 640
 641			@Override
 642			public void run() {
 643				AlertDialog.Builder builder = new AlertDialog.Builder(
 644						XmppActivity.this);
 645				builder.setIconAttribute(android.R.attr.alertDialogIcon);
 646				builder.setTitle(getString(R.string.error));
 647				builder.setMessage(errorCode);
 648				builder.setNeutralButton(R.string.accept, null);
 649				builder.create().show();
 650			}
 651		});
 652
 653	}
 654
 655	protected void showAddToRosterDialog(final Conversation conversation) {
 656		showAddToRosterDialog(conversation.getContact());
 657	}
 658
 659	protected void showAddToRosterDialog(final Contact contact) {
 660		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 661		builder.setTitle(contact.getJid().toString());
 662		builder.setMessage(getString(R.string.not_in_roster));
 663		builder.setNegativeButton(getString(R.string.cancel), null);
 664		builder.setPositiveButton(getString(R.string.add_contact),
 665				new DialogInterface.OnClickListener() {
 666
 667					@Override
 668					public void onClick(DialogInterface dialog, int which) {
 669						final Jid jid = contact.getJid();
 670						Account account = contact.getAccount();
 671						Contact contact = account.getRoster().getContact(jid);
 672						xmppConnectionService.createContact(contact);
 673					}
 674				});
 675		builder.create().show();
 676	}
 677
 678	private void showAskForPresenceDialog(final Contact contact) {
 679		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 680		builder.setTitle(contact.getJid().toString());
 681		builder.setMessage(R.string.request_presence_updates);
 682		builder.setNegativeButton(R.string.cancel, null);
 683		builder.setPositiveButton(R.string.request_now,
 684				new DialogInterface.OnClickListener() {
 685
 686					@Override
 687					public void onClick(DialogInterface dialog, int which) {
 688						if (xmppConnectionServiceBound) {
 689							xmppConnectionService.sendPresencePacket(contact
 690									.getAccount(), xmppConnectionService
 691									.getPresenceGenerator()
 692									.requestPresenceUpdatesFrom(contact));
 693						}
 694					}
 695				});
 696		builder.create().show();
 697	}
 698
 699	private void warnMutalPresenceSubscription(final Conversation conversation,
 700			final OnPresenceSelected listener) {
 701		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 702		builder.setTitle(conversation.getContact().getJid().toString());
 703		builder.setMessage(R.string.without_mutual_presence_updates);
 704		builder.setNegativeButton(R.string.cancel, null);
 705		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
 706
 707			@Override
 708			public void onClick(DialogInterface dialog, int which) {
 709				conversation.setNextCounterpart(null);
 710				if (listener != null) {
 711					listener.onPresenceSelected();
 712				}
 713			}
 714		});
 715		builder.create().show();
 716	}
 717
 718	protected void quickEdit(String previousValue, int hint, OnValueEdited callback) {
 719		quickEdit(previousValue, callback, hint, false);
 720	}
 721
 722	protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
 723		quickEdit(previousValue, callback, R.string.password, true);
 724	}
 725
 726	@SuppressLint("InflateParams")
 727	private void quickEdit(final String previousValue,
 728						   final OnValueEdited callback,
 729						   final int hint,
 730						   boolean password) {
 731		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 732		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
 733		final EditText editor = (EditText) view.findViewById(R.id.editor);
 734		OnClickListener mClickListener = new OnClickListener() {
 735
 736			@Override
 737			public void onClick(DialogInterface dialog, int which) {
 738				String value = editor.getText().toString();
 739				if (!value.equals(previousValue) && value.trim().length() > 0) {
 740					callback.onValueEdited(value);
 741				}
 742			}
 743		};
 744		if (password) {
 745			editor.setInputType(InputType.TYPE_CLASS_TEXT
 746					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
 747			builder.setPositiveButton(R.string.accept, mClickListener);
 748		} else {
 749			builder.setPositiveButton(R.string.edit, mClickListener);
 750		}
 751		if (hint != 0) {
 752			editor.setHint(hint);
 753		}
 754		editor.requestFocus();
 755		editor.setText("");
 756		if (previousValue != null) {
 757			editor.getText().append(previousValue);
 758		}
 759		builder.setView(view);
 760		builder.setNegativeButton(R.string.cancel, null);
 761		builder.create().show();
 762	}
 763
 764	protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
 765		final XmppAxolotlSession.Trust trust = account.getAxolotlService()
 766				.getFingerprintTrust(fingerprint);
 767		if (trust == null) {
 768			return false;
 769		}
 770		return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true,
 771				new CompoundButton.OnCheckedChangeListener() {
 772					@Override
 773					public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 774						account.getAxolotlService().setFingerprintTrust(fingerprint,
 775								(isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
 776										XmppAxolotlSession.Trust.UNTRUSTED);
 777					}
 778				},
 779				new View.OnClickListener() {
 780					@Override
 781					public void onClick(View v) {
 782						account.getAxolotlService().setFingerprintTrust(fingerprint,
 783								XmppAxolotlSession.Trust.UNTRUSTED);
 784						v.setEnabled(true);
 785					}
 786				},
 787				onKeyClickedListener
 788
 789		);
 790	}
 791
 792	protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
 793	                                                 final String fingerprint,
 794	                                                 boolean highlight,
 795	                                                 XmppAxolotlSession.Trust trust,
 796	                                                 boolean showTag,
 797	                                                 CompoundButton.OnCheckedChangeListener
 798			                                                 onCheckedChangeListener,
 799	                                                 View.OnClickListener onClickListener,
 800													 View.OnClickListener onKeyClickedListener) {
 801		if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
 802			return false;
 803		}
 804		View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
 805		TextView key = (TextView) view.findViewById(R.id.key);
 806		key.setOnClickListener(onKeyClickedListener);
 807		TextView keyType = (TextView) view.findViewById(R.id.key_type);
 808		keyType.setOnClickListener(onKeyClickedListener);
 809		Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
 810		trustToggle.setVisibility(View.VISIBLE);
 811		trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
 812		trustToggle.setOnClickListener(onClickListener);
 813		final View.OnLongClickListener purge = new View.OnLongClickListener() {
 814			@Override
 815			public boolean onLongClick(View v) {
 816				showPurgeKeyDialog(account, fingerprint);
 817				return true;
 818			}
 819		};
 820		boolean active = true;
 821		view.setOnLongClickListener(purge);
 822		key.setOnLongClickListener(purge);
 823		keyType.setOnLongClickListener(purge);
 824		boolean x509 = Config.X509_VERIFICATION
 825				&& (trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
 826		switch (trust) {
 827			case UNTRUSTED:
 828			case TRUSTED:
 829			case TRUSTED_X509:
 830				trustToggle.setChecked(trust.trusted(), false);
 831				trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
 832				if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
 833					trustToggle.setOnClickListener(null);
 834				}
 835				key.setTextColor(getPrimaryTextColor());
 836				keyType.setTextColor(getSecondaryTextColor());
 837				break;
 838			case UNDECIDED:
 839				trustToggle.setChecked(false, false);
 840				trustToggle.setEnabled(false);
 841				key.setTextColor(getPrimaryTextColor());
 842				keyType.setTextColor(getSecondaryTextColor());
 843				break;
 844			case INACTIVE_UNTRUSTED:
 845			case INACTIVE_UNDECIDED:
 846				trustToggle.setOnClickListener(null);
 847				trustToggle.setChecked(false, false);
 848				trustToggle.setEnabled(false);
 849				key.setTextColor(getTertiaryTextColor());
 850				keyType.setTextColor(getTertiaryTextColor());
 851				active = false;
 852				break;
 853			case INACTIVE_TRUSTED:
 854			case INACTIVE_TRUSTED_X509:
 855				trustToggle.setOnClickListener(null);
 856				trustToggle.setChecked(true, false);
 857				trustToggle.setEnabled(false);
 858				key.setTextColor(getTertiaryTextColor());
 859				keyType.setTextColor(getTertiaryTextColor());
 860				active = false;
 861				break;
 862		}
 863
 864		if (showTag) {
 865			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 866		} else {
 867			keyType.setVisibility(View.GONE);
 868		}
 869		if (highlight) {
 870			keyType.setTextColor(getResources().getColor(R.color.accent));
 871			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
 872		} else {
 873			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 874		}
 875
 876		key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
 877
 878		final View.OnClickListener toast;
 879		if (!active) {
 880			toast = new View.OnClickListener() {
 881				@Override
 882				public void onClick(View v) {
 883					replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
 884				}
 885			};
 886			trustToggle.setOnClickListener(toast);
 887		} else {
 888			toast = new View.OnClickListener() {
 889				@Override
 890				public void onClick(View v) {
 891					hideToast();
 892				}
 893			};
 894		}
 895		view.setOnClickListener(toast);
 896		key.setOnClickListener(toast);
 897		keyType.setOnClickListener(toast);
 898
 899		keys.addView(view);
 900		return true;
 901	}
 902
 903	public void showPurgeKeyDialog(final Account account, final String fingerprint) {
 904		Builder builder = new Builder(this);
 905		builder.setTitle(getString(R.string.purge_key));
 906		builder.setIconAttribute(android.R.attr.alertDialogIcon);
 907		builder.setMessage(getString(R.string.purge_key_desc_part1)
 908				+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
 909				+ "\n\n" + getString(R.string.purge_key_desc_part2));
 910		builder.setNegativeButton(getString(R.string.cancel), null);
 911		builder.setPositiveButton(getString(R.string.purge_key),
 912				new DialogInterface.OnClickListener() {
 913					@Override
 914					public void onClick(DialogInterface dialog, int which) {
 915						account.getAxolotlService().purgeKey(fingerprint);
 916						refreshUi();
 917					}
 918				});
 919		builder.create().show();
 920	}
 921
 922	public boolean hasStoragePermission(int requestCode) {
 923		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 924			if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 925				requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
 926				return false;
 927			} else {
 928				return true;
 929			}
 930		} else {
 931			return true;
 932		}
 933	}
 934
 935	public void selectPresence(final Conversation conversation,
 936			final OnPresenceSelected listener) {
 937		final Contact contact = conversation.getContact();
 938		if (conversation.hasValidOtrSession()) {
 939			SessionID id = conversation.getOtrSession().getSessionID();
 940			Jid jid;
 941			try {
 942				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
 943			} catch (InvalidJidException e) {
 944				jid = null;
 945			}
 946			conversation.setNextCounterpart(jid);
 947			listener.onPresenceSelected();
 948		} else 	if (!contact.showInRoster()) {
 949			showAddToRosterDialog(conversation);
 950		} else {
 951			final Presences presences = contact.getPresences();
 952			if (presences.size() == 0) {
 953				if (!contact.getOption(Contact.Options.TO)
 954						&& !contact.getOption(Contact.Options.ASKING)
 955						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
 956					showAskForPresenceDialog(contact);
 957				} else if (!contact.getOption(Contact.Options.TO)
 958						|| !contact.getOption(Contact.Options.FROM)) {
 959					warnMutalPresenceSubscription(conversation, listener);
 960				} else {
 961					conversation.setNextCounterpart(null);
 962					listener.onPresenceSelected();
 963				}
 964			} else if (presences.size() == 1) {
 965				String presence = presences.toResourceArray()[0];
 966				try {
 967					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
 968				} catch (InvalidJidException e) {
 969					conversation.setNextCounterpart(null);
 970				}
 971				listener.onPresenceSelected();
 972			} else {
 973				showPresenceSelectionDialog(presences,conversation,listener);
 974			}
 975		}
 976	}
 977
 978	private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
 979		final Contact contact = conversation.getContact();
 980		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 981		builder.setTitle(getString(R.string.choose_presence));
 982		final String[] resourceArray = presences.toResourceArray();
 983		Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
 984		final Map<String,String> resourceTypeMap = typeAndName.first;
 985		final Map<String,String> resourceNameMap = typeAndName.second;
 986		final String[] readableIdentities = new String[resourceArray.length];
 987		final AtomicInteger selectedResource = new AtomicInteger(0);
 988		for (int i = 0; i < resourceArray.length; ++i) {
 989			String resource = resourceArray[i];
 990			if (resource.equals(contact.getLastResource())) {
 991				selectedResource.set(i);
 992			}
 993			String type = resourceTypeMap.get(resource);
 994			String name = resourceNameMap.get(resource);
 995			if (type != null) {
 996				if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
 997					readableIdentities[i] = UIHelper.tranlasteType(this,type);
 998				} else if (name != null) {
 999					if (Collections.frequency(resourceNameMap.values(), name) == 1
1000							|| CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
1001						readableIdentities[i] = UIHelper.tranlasteType(this,type) + "  (" + name+")";
1002					} else {
1003						readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
1004					}
1005				} else {
1006					readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
1007				}
1008			} else {
1009				readableIdentities[i] = resource;
1010			}
1011		}
1012		builder.setSingleChoiceItems(readableIdentities,
1013				selectedResource.get(),
1014				new DialogInterface.OnClickListener() {
1015
1016					@Override
1017					public void onClick(DialogInterface dialog, int which) {
1018						selectedResource.set(which);
1019					}
1020				});
1021		builder.setNegativeButton(R.string.cancel, null);
1022		builder.setPositiveButton(R.string.ok, new OnClickListener() {
1023
1024			@Override
1025			public void onClick(DialogInterface dialog, int which) {
1026				try {
1027					Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
1028					conversation.setNextCounterpart(next);
1029				} catch (InvalidJidException e) {
1030					conversation.setNextCounterpart(null);
1031				}
1032				listener.onPresenceSelected();
1033			}
1034		});
1035		builder.create().show();
1036	}
1037
1038	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1039		super.onActivityResult(requestCode, resultCode, data);
1040		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1041			mPendingConferenceInvite = ConferenceInvite.parse(data);
1042			if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1043				if (mPendingConferenceInvite.execute(this)) {
1044					mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1045					mToast.show();
1046				}
1047				mPendingConferenceInvite = null;
1048			}
1049		}
1050	}
1051
1052
1053	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1054		@Override
1055		public void success(final Conversation conversation) {
1056			runOnUiThread(new Runnable() {
1057				@Override
1058				public void run() {
1059					switchToConversation(conversation);
1060					hideToast();
1061				}
1062			});
1063		}
1064
1065		@Override
1066		public void error(final int errorCode, Conversation object) {
1067			runOnUiThread(new Runnable() {
1068				@Override
1069				public void run() {
1070					replaceToast(getString(errorCode));
1071				}
1072			});
1073		}
1074
1075		@Override
1076		public void userInputRequried(PendingIntent pi, Conversation object) {
1077
1078		}
1079	};
1080
1081	public int getTertiaryTextColor() {
1082		return this.mTertiaryTextColor;
1083	}
1084
1085	public int getSecondaryTextColor() {
1086		return this.mSecondaryTextColor;
1087	}
1088
1089	public int getPrimaryTextColor() {
1090		return this.mPrimaryTextColor;
1091	}
1092
1093	public int getWarningTextColor() {
1094		return this.mColorRed;
1095	}
1096
1097	public int getOnlineColor() {
1098		return this.mColorGreen;
1099	}
1100
1101	public int getPrimaryBackgroundColor() {
1102		return this.mPrimaryBackgroundColor;
1103	}
1104
1105	public int getSecondaryBackgroundColor() {
1106		return this.mSecondaryBackgroundColor;
1107	}
1108
1109	public int getPixel(int dp) {
1110		DisplayMetrics metrics = getResources().getDisplayMetrics();
1111		return ((int) (dp * metrics.density));
1112	}
1113
1114	public boolean copyTextToClipboard(String text, int labelResId) {
1115		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1116		String label = getResources().getString(labelResId);
1117		if (mClipBoardManager != null) {
1118			ClipData mClipData = ClipData.newPlainText(label, text);
1119			mClipBoardManager.setPrimaryClip(mClipData);
1120			return true;
1121		}
1122		return false;
1123	}
1124
1125	protected void registerNdefPushMessageCallback() {
1126		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1127		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1128			nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1129				@Override
1130				public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1131					return new NdefMessage(new NdefRecord[]{
1132							NdefRecord.createUri(getShareableUri()),
1133							NdefRecord.createApplicationRecord("eu.siacs.conversations")
1134					});
1135				}
1136			}, this);
1137		}
1138	}
1139
1140	protected boolean neverCompressPictures() {
1141		return getPreferences().getString("picture_compression", "auto").equals("never");
1142	}
1143
1144	protected boolean manuallyChangePresence() {
1145		return getPreferences().getBoolean("manually_change_presence", false);
1146	}
1147
1148	protected void unregisterNdefPushMessageCallback() {
1149		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1150		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1151			nfcAdapter.setNdefPushMessageCallback(null,this);
1152		}
1153	}
1154
1155	protected String getShareableUri() {
1156		return null;
1157	}
1158
1159	protected void shareUri() {
1160		String uri = getShareableUri();
1161		if (uri == null || uri.isEmpty()) {
1162			return;
1163		}
1164		Intent shareIntent = new Intent();
1165		shareIntent.setAction(Intent.ACTION_SEND);
1166		shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1167		shareIntent.setType("text/plain");
1168		try {
1169			startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1170		} catch (ActivityNotFoundException e) {
1171			Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1172		}
1173	}
1174
1175	@Override
1176	public void onResume() {
1177		super.onResume();
1178		if (this.getShareableUri()!=null) {
1179			this.registerNdefPushMessageCallback();
1180		}
1181	}
1182
1183	protected int findTheme() {
1184		Boolean dark   = getPreferences().getString("theme", "light").equals("dark");
1185		Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1186
1187		if(dark) {
1188			if(larger)
1189				return R.style.ConversationsTheme_Dark_LargerText;
1190			else
1191				return R.style.ConversationsTheme_Dark;
1192		} else {
1193			if (larger)
1194				return R.style.ConversationsTheme_LargerText;
1195			else
1196				return R.style.ConversationsTheme;
1197		}
1198	}
1199
1200	@Override
1201	public void onPause() {
1202		super.onPause();
1203		this.unregisterNdefPushMessageCallback();
1204	}
1205
1206	protected void showQrCode() {
1207		String uri = getShareableUri();
1208		if (uri!=null) {
1209			Point size = new Point();
1210			getWindowManager().getDefaultDisplay().getSize(size);
1211			final int width = (size.x < size.y ? size.x : size.y);
1212			Bitmap bitmap = createQrCodeBitmap(uri, width);
1213			ImageView view = new ImageView(this);
1214			view.setBackgroundColor(Color.WHITE);
1215			view.setImageBitmap(bitmap);
1216			AlertDialog.Builder builder = new AlertDialog.Builder(this);
1217			builder.setView(view);
1218			builder.create().show();
1219		}
1220	}
1221
1222	protected Bitmap createQrCodeBitmap(String input, int size) {
1223		Log.d(Config.LOGTAG,"qr code requested size: "+size);
1224		try {
1225			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1226			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1227			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1228			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1229			final int width = result.getWidth();
1230			final int height = result.getHeight();
1231			final int[] pixels = new int[width * height];
1232			for (int y = 0; y < height; y++) {
1233				final int offset = y * width;
1234				for (int x = 0; x < width; x++) {
1235					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1236				}
1237			}
1238			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1239			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1240			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1241			return bitmap;
1242		} catch (final WriterException e) {
1243			return null;
1244		}
1245	}
1246
1247	protected Account extractAccount(Intent intent) {
1248		String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1249		try {
1250			return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1251		} catch (InvalidJidException e) {
1252			return null;
1253		}
1254	}
1255
1256	public static class ConferenceInvite {
1257		private String uuid;
1258		private List<Jid> jids = new ArrayList<>();
1259
1260		public static ConferenceInvite parse(Intent data) {
1261			ConferenceInvite invite = new ConferenceInvite();
1262			invite.uuid = data.getStringExtra("conversation");
1263			if (invite.uuid == null) {
1264				return null;
1265			}
1266			try {
1267				if (data.getBooleanExtra("multiple", false)) {
1268					String[] toAdd = data.getStringArrayExtra("contacts");
1269					for (String item : toAdd) {
1270						invite.jids.add(Jid.fromString(item));
1271					}
1272				} else {
1273					invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1274				}
1275			} catch (final InvalidJidException ignored) {
1276				return null;
1277			}
1278			return invite;
1279		}
1280
1281		public boolean execute(XmppActivity activity) {
1282			XmppConnectionService service = activity.xmppConnectionService;
1283			Conversation conversation = service.findConversationByUuid(this.uuid);
1284			if (conversation == null) {
1285				return false;
1286			}
1287			if (conversation.getMode() == Conversation.MODE_MULTI) {
1288				for (Jid jid : jids) {
1289					service.invite(conversation, jid);
1290				}
1291				return false;
1292			} else {
1293				jids.add(conversation.getJid().toBareJid());
1294				service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1295				return true;
1296			}
1297		}
1298	}
1299
1300	public AvatarService avatarService() {
1301		return xmppConnectionService.getAvatarService();
1302	}
1303
1304	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1305		private final WeakReference<ImageView> imageViewReference;
1306		private Message message = null;
1307
1308		public BitmapWorkerTask(ImageView imageView) {
1309			imageViewReference = new WeakReference<>(imageView);
1310		}
1311
1312		@Override
1313		protected Bitmap doInBackground(Message... params) {
1314			if (isCancelled()) {
1315				return null;
1316			}
1317			message = params[0];
1318			try {
1319				return xmppConnectionService.getFileBackend().getThumbnail(
1320						message, (int) (metrics.density * 288), false);
1321			} catch (FileNotFoundException e) {
1322				return null;
1323			}
1324		}
1325
1326		@Override
1327		protected void onPostExecute(Bitmap bitmap) {
1328			if (bitmap != null && !isCancelled()) {
1329				final ImageView imageView = imageViewReference.get();
1330				if (imageView != null) {
1331					imageView.setImageBitmap(bitmap);
1332					imageView.setBackgroundColor(0x00000000);
1333				}
1334			}
1335		}
1336	}
1337
1338	public void loadBitmap(Message message, ImageView imageView) {
1339		Bitmap bm;
1340		try {
1341			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1342					(int) (metrics.density * 288), true);
1343		} catch (FileNotFoundException e) {
1344			bm = null;
1345		}
1346		if (bm != null) {
1347			cancelPotentialWork(message, imageView);
1348			imageView.setImageBitmap(bm);
1349			imageView.setBackgroundColor(0x00000000);
1350		} else {
1351			if (cancelPotentialWork(message, imageView)) {
1352				imageView.setBackgroundColor(0xff333333);
1353				imageView.setImageDrawable(null);
1354				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1355				final AsyncDrawable asyncDrawable = new AsyncDrawable(
1356						getResources(), null, task);
1357				imageView.setImageDrawable(asyncDrawable);
1358				try {
1359					task.execute(message);
1360				} catch (final RejectedExecutionException ignored) {
1361					ignored.printStackTrace();
1362				}
1363			}
1364		}
1365	}
1366
1367	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1368		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1369
1370		if (bitmapWorkerTask != null) {
1371			final Message oldMessage = bitmapWorkerTask.message;
1372			if (oldMessage == null || message != oldMessage) {
1373				bitmapWorkerTask.cancel(true);
1374			} else {
1375				return false;
1376			}
1377		}
1378		return true;
1379	}
1380
1381	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1382		if (imageView != null) {
1383			final Drawable drawable = imageView.getDrawable();
1384			if (drawable instanceof AsyncDrawable) {
1385				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1386				return asyncDrawable.getBitmapWorkerTask();
1387			}
1388		}
1389		return null;
1390	}
1391
1392	static class AsyncDrawable extends BitmapDrawable {
1393		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1394
1395		public AsyncDrawable(Resources res, Bitmap bitmap,
1396				BitmapWorkerTask bitmapWorkerTask) {
1397			super(res, bitmap);
1398			bitmapWorkerTaskReference = new WeakReference<>(
1399					bitmapWorkerTask);
1400		}
1401
1402		public BitmapWorkerTask getBitmapWorkerTask() {
1403			return bitmapWorkerTaskReference.get();
1404		}
1405	}
1406}