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