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