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