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	protected boolean isOptimizingBattery() {
 382		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 383			PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
 384			return !pm.isIgnoringBatteryOptimizations(getPackageName());
 385		} else {
 386			return false;
 387		}
 388	}
 389
 390	protected boolean usingEnterKey() {
 391		return getPreferences().getBoolean("display_enter_key", false);
 392	}
 393
 394	protected SharedPreferences getPreferences() {
 395		return PreferenceManager
 396			.getDefaultSharedPreferences(getApplicationContext());
 397	}
 398
 399	public boolean useSubjectToIdentifyConference() {
 400		return mUseSubject;
 401	}
 402
 403	public void switchToConversation(Conversation conversation) {
 404		switchToConversation(conversation, null, false);
 405	}
 406
 407	public void switchToConversation(Conversation conversation, String text,
 408			boolean newTask) {
 409		switchToConversation(conversation,text,null,false,newTask);
 410	}
 411
 412	public void highlightInMuc(Conversation conversation, String nick) {
 413		switchToConversation(conversation, null, nick, false, false);
 414	}
 415
 416	public void privateMsgInMuc(Conversation conversation, String nick) {
 417		switchToConversation(conversation, null, nick, true, false);
 418	}
 419
 420	private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) {
 421		Intent viewConversationIntent = new Intent(this,
 422				ConversationActivity.class);
 423		viewConversationIntent.setAction(Intent.ACTION_VIEW);
 424		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
 425				conversation.getUuid());
 426		if (text != null) {
 427			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
 428		}
 429		if (nick != null) {
 430			viewConversationIntent.putExtra(ConversationActivity.NICK, nick);
 431			viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm);
 432		}
 433		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
 434		if (newTask) {
 435			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
 436					| Intent.FLAG_ACTIVITY_NEW_TASK
 437					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
 438		} else {
 439			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
 440					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
 441		}
 442		startActivity(viewConversationIntent);
 443		finish();
 444	}
 445
 446	public void switchToContactDetails(Contact contact) {
 447		switchToContactDetails(contact, null);
 448	}
 449
 450	public void switchToContactDetails(Contact contact, String messageFingerprint) {
 451		Intent intent = new Intent(this, ContactDetailsActivity.class);
 452		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
 453		intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString());
 454		intent.putExtra("contact", contact.getJid().toString());
 455		intent.putExtra("fingerprint", messageFingerprint);
 456		startActivity(intent);
 457	}
 458
 459	public void switchToAccount(Account account) {
 460		switchToAccount(account, false);
 461	}
 462
 463	public void switchToAccount(Account account, boolean init) {
 464		Intent intent = new Intent(this, EditAccountActivity.class);
 465		intent.putExtra("jid", account.getJid().toBareJid().toString());
 466		intent.putExtra("init", init);
 467		startActivity(intent);
 468	}
 469
 470	protected void inviteToConversation(Conversation conversation) {
 471		Intent intent = new Intent(getApplicationContext(),
 472				ChooseContactActivity.class);
 473		List<String> contacts = new ArrayList<>();
 474		if (conversation.getMode() == Conversation.MODE_MULTI) {
 475			for (MucOptions.User user : conversation.getMucOptions().getUsers()) {
 476				Jid jid = user.getJid();
 477				if (jid != null) {
 478					contacts.add(jid.toBareJid().toString());
 479				}
 480			}
 481		} else {
 482			contacts.add(conversation.getJid().toBareJid().toString());
 483		}
 484		intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
 485		intent.putExtra("conversation", conversation.getUuid());
 486		intent.putExtra("multiple", true);
 487		intent.putExtra("show_enter_jid", true);
 488		intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
 489		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
 490	}
 491
 492	protected void announcePgp(Account account, final Conversation conversation) {
 493		if (account.getPgpId() == -1) {
 494			choosePgpSignId(account);
 495		} else {
 496			xmppConnectionService.getPgpEngine().generateSignature(account, "", new UiCallback<Account>() {
 497
 498				@Override
 499				public void userInputRequried(PendingIntent pi,
 500											  Account account) {
 501					try {
 502						startIntentSenderForResult(pi.getIntentSender(),
 503								REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
 504					} catch (final SendIntentException ignored) {
 505					}
 506				}
 507
 508				@Override
 509				public void success(Account account) {
 510					xmppConnectionService.databaseBackend.updateAccount(account);
 511					xmppConnectionService.sendPresence(account);
 512					if (conversation != null) {
 513						conversation.setNextEncryption(Message.ENCRYPTION_PGP);
 514						xmppConnectionService.databaseBackend.updateConversation(conversation);
 515					}
 516				}
 517
 518				@Override
 519				public void error(int error, Account account) {
 520					displayErrorDialog(error);
 521				}
 522			});
 523		}
 524	}
 525
 526	@SuppressWarnings("deprecation")
 527	@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 528	protected void setListItemBackgroundOnView(View view) {
 529		int sdk = android.os.Build.VERSION.SDK_INT;
 530		if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
 531			view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
 532		} else {
 533			view.setBackground(getResources().getDrawable(R.drawable.greybackground));
 534		}
 535	}
 536
 537	protected void choosePgpSignId(Account account) {
 538		xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
 539			@Override
 540			public void success(Account account1) {
 541			}
 542
 543			@Override
 544			public void error(int errorCode, Account object) {
 545
 546			}
 547
 548			@Override
 549			public void userInputRequried(PendingIntent pi, Account object) {
 550				try {
 551					startIntentSenderForResult(pi.getIntentSender(),
 552							REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
 553				} catch (final SendIntentException ignored) {
 554				}
 555			}
 556		});
 557	}
 558
 559	protected void displayErrorDialog(final int errorCode) {
 560		runOnUiThread(new Runnable() {
 561
 562			@Override
 563			public void run() {
 564				AlertDialog.Builder builder = new AlertDialog.Builder(
 565						XmppActivity.this);
 566				builder.setIconAttribute(android.R.attr.alertDialogIcon);
 567				builder.setTitle(getString(R.string.error));
 568				builder.setMessage(errorCode);
 569				builder.setNeutralButton(R.string.accept, null);
 570				builder.create().show();
 571			}
 572		});
 573
 574	}
 575
 576	protected void showAddToRosterDialog(final Conversation conversation) {
 577		showAddToRosterDialog(conversation.getContact());
 578	}
 579
 580	protected void showAddToRosterDialog(final Contact contact) {
 581		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 582		builder.setTitle(contact.getJid().toString());
 583		builder.setMessage(getString(R.string.not_in_roster));
 584		builder.setNegativeButton(getString(R.string.cancel), null);
 585		builder.setPositiveButton(getString(R.string.add_contact),
 586				new DialogInterface.OnClickListener() {
 587
 588					@Override
 589					public void onClick(DialogInterface dialog, int which) {
 590						final Jid jid = contact.getJid();
 591						Account account = contact.getAccount();
 592						Contact contact = account.getRoster().getContact(jid);
 593						xmppConnectionService.createContact(contact);
 594					}
 595				});
 596		builder.create().show();
 597	}
 598
 599	private void showAskForPresenceDialog(final Contact contact) {
 600		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 601		builder.setTitle(contact.getJid().toString());
 602		builder.setMessage(R.string.request_presence_updates);
 603		builder.setNegativeButton(R.string.cancel, null);
 604		builder.setPositiveButton(R.string.request_now,
 605				new DialogInterface.OnClickListener() {
 606
 607					@Override
 608					public void onClick(DialogInterface dialog, int which) {
 609						if (xmppConnectionServiceBound) {
 610							xmppConnectionService.sendPresencePacket(contact
 611									.getAccount(), xmppConnectionService
 612									.getPresenceGenerator()
 613									.requestPresenceUpdatesFrom(contact));
 614						}
 615					}
 616				});
 617		builder.create().show();
 618	}
 619
 620	private void warnMutalPresenceSubscription(final Conversation conversation,
 621			final OnPresenceSelected listener) {
 622		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 623		builder.setTitle(conversation.getContact().getJid().toString());
 624		builder.setMessage(R.string.without_mutual_presence_updates);
 625		builder.setNegativeButton(R.string.cancel, null);
 626		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
 627
 628			@Override
 629			public void onClick(DialogInterface dialog, int which) {
 630				conversation.setNextCounterpart(null);
 631				if (listener != null) {
 632					listener.onPresenceSelected();
 633				}
 634			}
 635		});
 636		builder.create().show();
 637	}
 638
 639	protected void quickEdit(String previousValue, OnValueEdited callback) {
 640		quickEdit(previousValue, callback, false);
 641	}
 642
 643	protected void quickPasswordEdit(String previousValue,
 644			OnValueEdited callback) {
 645		quickEdit(previousValue, callback, true);
 646	}
 647
 648	@SuppressLint("InflateParams")
 649	private void quickEdit(final String previousValue,
 650			final OnValueEdited callback, boolean password) {
 651		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 652		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
 653		final EditText editor = (EditText) view.findViewById(R.id.editor);
 654		OnClickListener mClickListener = new OnClickListener() {
 655
 656			@Override
 657			public void onClick(DialogInterface dialog, int which) {
 658				String value = editor.getText().toString();
 659				if (!previousValue.equals(value) && value.trim().length() > 0) {
 660					callback.onValueEdited(value);
 661				}
 662			}
 663		};
 664		if (password) {
 665			editor.setInputType(InputType.TYPE_CLASS_TEXT
 666					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
 667			editor.setHint(R.string.password);
 668			builder.setPositiveButton(R.string.accept, mClickListener);
 669		} else {
 670			builder.setPositiveButton(R.string.edit, mClickListener);
 671		}
 672		editor.requestFocus();
 673		editor.setText(previousValue);
 674		builder.setView(view);
 675		builder.setNegativeButton(R.string.cancel, null);
 676		builder.create().show();
 677	}
 678
 679	protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
 680		final XmppAxolotlSession.Trust trust = account.getAxolotlService()
 681				.getFingerprintTrust(fingerprint);
 682		if (trust == null) {
 683			return false;
 684		}
 685		return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true,
 686				new CompoundButton.OnCheckedChangeListener() {
 687					@Override
 688					public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 689						account.getAxolotlService().setFingerprintTrust(fingerprint,
 690								(isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
 691										XmppAxolotlSession.Trust.UNTRUSTED);
 692					}
 693				},
 694				new View.OnClickListener() {
 695					@Override
 696					public void onClick(View v) {
 697						account.getAxolotlService().setFingerprintTrust(fingerprint,
 698								XmppAxolotlSession.Trust.UNTRUSTED);
 699						v.setEnabled(true);
 700					}
 701				},
 702				onKeyClickedListener
 703
 704		);
 705	}
 706
 707	protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
 708	                                                 final String fingerprint,
 709	                                                 boolean highlight,
 710	                                                 XmppAxolotlSession.Trust trust,
 711	                                                 boolean showTag,
 712	                                                 CompoundButton.OnCheckedChangeListener
 713			                                                 onCheckedChangeListener,
 714	                                                 View.OnClickListener onClickListener,
 715													 View.OnClickListener onKeyClickedListener) {
 716		if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
 717			return false;
 718		}
 719		View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
 720		TextView key = (TextView) view.findViewById(R.id.key);
 721		key.setOnClickListener(onKeyClickedListener);
 722		TextView keyType = (TextView) view.findViewById(R.id.key_type);
 723		keyType.setOnClickListener(onKeyClickedListener);
 724		Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
 725		trustToggle.setVisibility(View.VISIBLE);
 726		trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
 727		trustToggle.setOnClickListener(onClickListener);
 728		final View.OnLongClickListener purge = new View.OnLongClickListener() {
 729			@Override
 730			public boolean onLongClick(View v) {
 731				showPurgeKeyDialog(account, fingerprint);
 732				return true;
 733			}
 734		};
 735		view.setOnLongClickListener(purge);
 736		key.setOnLongClickListener(purge);
 737		keyType.setOnLongClickListener(purge);
 738		boolean x509 = Config.X509_VERIFICATION
 739				&& (trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
 740		switch (trust) {
 741			case UNTRUSTED:
 742			case TRUSTED:
 743			case TRUSTED_X509:
 744				trustToggle.setChecked(trust.trusted(), false);
 745				trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
 746				if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
 747					trustToggle.setOnClickListener(null);
 748				}
 749				key.setTextColor(getPrimaryTextColor());
 750				keyType.setTextColor(getSecondaryTextColor());
 751				break;
 752			case UNDECIDED:
 753				trustToggle.setChecked(false, false);
 754				trustToggle.setEnabled(false);
 755				key.setTextColor(getPrimaryTextColor());
 756				keyType.setTextColor(getSecondaryTextColor());
 757				break;
 758			case INACTIVE_UNTRUSTED:
 759			case INACTIVE_UNDECIDED:
 760				trustToggle.setOnClickListener(null);
 761				trustToggle.setChecked(false, false);
 762				trustToggle.setEnabled(false);
 763				key.setTextColor(getTertiaryTextColor());
 764				keyType.setTextColor(getTertiaryTextColor());
 765				break;
 766			case INACTIVE_TRUSTED:
 767			case INACTIVE_TRUSTED_X509:
 768				trustToggle.setOnClickListener(null);
 769				trustToggle.setChecked(true, false);
 770				trustToggle.setEnabled(false);
 771				key.setTextColor(getTertiaryTextColor());
 772				keyType.setTextColor(getTertiaryTextColor());
 773				break;
 774		}
 775
 776		if (showTag) {
 777			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 778		} else {
 779			keyType.setVisibility(View.GONE);
 780		}
 781		if (highlight) {
 782			keyType.setTextColor(getResources().getColor(R.color.accent));
 783			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
 784		} else {
 785			keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
 786		}
 787
 788		key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
 789		keys.addView(view);
 790		return true;
 791	}
 792
 793	public void showPurgeKeyDialog(final Account account, final String fingerprint) {
 794		Builder builder = new Builder(this);
 795		builder.setTitle(getString(R.string.purge_key));
 796		builder.setIconAttribute(android.R.attr.alertDialogIcon);
 797		builder.setMessage(getString(R.string.purge_key_desc_part1)
 798				+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
 799				+ "\n\n" + getString(R.string.purge_key_desc_part2));
 800		builder.setNegativeButton(getString(R.string.cancel), null);
 801		builder.setPositiveButton(getString(R.string.purge_key),
 802				new DialogInterface.OnClickListener() {
 803					@Override
 804					public void onClick(DialogInterface dialog, int which) {
 805						account.getAxolotlService().purgeKey(fingerprint);
 806						refreshUi();
 807					}
 808				});
 809		builder.create().show();
 810	}
 811
 812	public boolean hasStoragePermission(int requestCode) {
 813		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 814			if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 815				requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
 816				return false;
 817			} else {
 818				return true;
 819			}
 820		} else {
 821			return true;
 822		}
 823	}
 824
 825	public void selectPresence(final Conversation conversation,
 826			final OnPresenceSelected listener) {
 827		final Contact contact = conversation.getContact();
 828		if (conversation.hasValidOtrSession()) {
 829			SessionID id = conversation.getOtrSession().getSessionID();
 830			Jid jid;
 831			try {
 832				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
 833			} catch (InvalidJidException e) {
 834				jid = null;
 835			}
 836			conversation.setNextCounterpart(jid);
 837			listener.onPresenceSelected();
 838		} else 	if (!contact.showInRoster()) {
 839			showAddToRosterDialog(conversation);
 840		} else {
 841			Presences presences = contact.getPresences();
 842			if (presences.size() == 0) {
 843				if (!contact.getOption(Contact.Options.TO)
 844						&& !contact.getOption(Contact.Options.ASKING)
 845						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
 846					showAskForPresenceDialog(contact);
 847				} else if (!contact.getOption(Contact.Options.TO)
 848						|| !contact.getOption(Contact.Options.FROM)) {
 849					warnMutalPresenceSubscription(conversation, listener);
 850				} else {
 851					conversation.setNextCounterpart(null);
 852					listener.onPresenceSelected();
 853				}
 854			} else if (presences.size() == 1) {
 855				String presence = presences.asStringArray()[0];
 856				try {
 857					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
 858				} catch (InvalidJidException e) {
 859					conversation.setNextCounterpart(null);
 860				}
 861				listener.onPresenceSelected();
 862			} else {
 863				final StringBuilder presence = new StringBuilder();
 864				AlertDialog.Builder builder = new AlertDialog.Builder(this);
 865				builder.setTitle(getString(R.string.choose_presence));
 866				final String[] presencesArray = presences.asStringArray();
 867				int preselectedPresence = 0;
 868				for (int i = 0; i < presencesArray.length; ++i) {
 869					if (presencesArray[i].equals(contact.lastseen.presence)) {
 870						preselectedPresence = i;
 871						break;
 872					}
 873				}
 874				presence.append(presencesArray[preselectedPresence]);
 875				builder.setSingleChoiceItems(presencesArray,
 876						preselectedPresence,
 877						new DialogInterface.OnClickListener() {
 878
 879							@Override
 880							public void onClick(DialogInterface dialog,
 881									int which) {
 882								presence.delete(0, presence.length());
 883								presence.append(presencesArray[which]);
 884							}
 885						});
 886				builder.setNegativeButton(R.string.cancel, null);
 887				builder.setPositiveButton(R.string.ok, new OnClickListener() {
 888
 889					@Override
 890					public void onClick(DialogInterface dialog, int which) {
 891						try {
 892							conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
 893						} catch (InvalidJidException e) {
 894							conversation.setNextCounterpart(null);
 895						}
 896						listener.onPresenceSelected();
 897					}
 898				});
 899				builder.create().show();
 900			}
 901		}
 902	}
 903
 904	protected void onActivityResult(int requestCode, int resultCode,
 905			final Intent data) {
 906		super.onActivityResult(requestCode, resultCode, data);
 907		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
 908			mPendingConferenceInvite = ConferenceInvite.parse(data);
 909			if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
 910				mPendingConferenceInvite.execute(this);
 911				mPendingConferenceInvite = null;
 912			}
 913		}
 914	}
 915
 916	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
 917		@Override
 918		public void success(final Conversation conversation) {
 919			switchToConversation(conversation);
 920			runOnUiThread(new Runnable() {
 921				@Override
 922				public void run() {
 923					Toast.makeText(XmppActivity.this,R.string.conference_created,Toast.LENGTH_LONG).show();
 924				}
 925			});
 926		}
 927
 928		@Override
 929		public void error(final int errorCode, Conversation object) {
 930			runOnUiThread(new Runnable() {
 931				@Override
 932				public void run() {
 933					Toast.makeText(XmppActivity.this,errorCode,Toast.LENGTH_LONG).show();
 934				}
 935			});
 936		}
 937
 938		@Override
 939		public void userInputRequried(PendingIntent pi, Conversation object) {
 940
 941		}
 942	};
 943
 944	public int getTertiaryTextColor() {
 945		return this.mTertiaryTextColor;
 946	}
 947
 948	public int getSecondaryTextColor() {
 949		return this.mSecondaryTextColor;
 950	}
 951
 952	public int getPrimaryTextColor() {
 953		return this.mPrimaryTextColor;
 954	}
 955
 956	public int getWarningTextColor() {
 957		return this.mColorRed;
 958	}
 959
 960	public int getOnlineColor() {
 961		return this.mColorGreen;
 962	}
 963
 964	public int getPrimaryBackgroundColor() {
 965		return this.mPrimaryBackgroundColor;
 966	}
 967
 968	public int getSecondaryBackgroundColor() {
 969		return this.mSecondaryBackgroundColor;
 970	}
 971
 972	public int getPixel(int dp) {
 973		DisplayMetrics metrics = getResources().getDisplayMetrics();
 974		return ((int) (dp * metrics.density));
 975	}
 976
 977	public boolean copyTextToClipboard(String text, int labelResId) {
 978		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
 979		String label = getResources().getString(labelResId);
 980		if (mClipBoardManager != null) {
 981			ClipData mClipData = ClipData.newPlainText(label, text);
 982			mClipBoardManager.setPrimaryClip(mClipData);
 983			return true;
 984		}
 985		return false;
 986	}
 987
 988	protected void registerNdefPushMessageCallback() {
 989		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
 990		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
 991			nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
 992				@Override
 993				public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
 994					return new NdefMessage(new NdefRecord[]{
 995							NdefRecord.createUri(getShareableUri()),
 996							NdefRecord.createApplicationRecord("eu.siacs.conversations")
 997					});
 998				}
 999			}, this);
1000		}
1001	}
1002
1003	protected boolean neverCompressPictures() {
1004		return getPreferences().getString("picture_compression", "auto").equals("never");
1005	}
1006
1007	protected boolean manuallyChangePresence() {
1008		return getPreferences().getBoolean("manually_change_presence", false);
1009	}
1010
1011	protected void unregisterNdefPushMessageCallback() {
1012		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1013		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1014			nfcAdapter.setNdefPushMessageCallback(null,this);
1015		}
1016	}
1017
1018	protected String getShareableUri() {
1019		return null;
1020	}
1021
1022	@Override
1023	public void onResume() {
1024		super.onResume();
1025		if (this.getShareableUri()!=null) {
1026			this.registerNdefPushMessageCallback();
1027		}
1028	}
1029
1030	protected int findTheme() {
1031		if (getPreferences().getBoolean("use_larger_font", false)) {
1032			return R.style.ConversationsTheme_LargerText;
1033		} else {
1034			return R.style.ConversationsTheme;
1035		}
1036	}
1037
1038	@Override
1039	public void onPause() {
1040		super.onPause();
1041		this.unregisterNdefPushMessageCallback();
1042	}
1043
1044	protected void showQrCode() {
1045		String uri = getShareableUri();
1046		if (uri!=null) {
1047			Point size = new Point();
1048			getWindowManager().getDefaultDisplay().getSize(size);
1049			final int width = (size.x < size.y ? size.x : size.y);
1050			Bitmap bitmap = createQrCodeBitmap(uri, width);
1051			ImageView view = new ImageView(this);
1052			view.setImageBitmap(bitmap);
1053			AlertDialog.Builder builder = new AlertDialog.Builder(this);
1054			builder.setView(view);
1055			builder.create().show();
1056		}
1057	}
1058
1059	protected Bitmap createQrCodeBitmap(String input, int size) {
1060		Log.d(Config.LOGTAG,"qr code requested size: "+size);
1061		try {
1062			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1063			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1064			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1065			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1066			final int width = result.getWidth();
1067			final int height = result.getHeight();
1068			final int[] pixels = new int[width * height];
1069			for (int y = 0; y < height; y++) {
1070				final int offset = y * width;
1071				for (int x = 0; x < width; x++) {
1072					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1073				}
1074			}
1075			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1076			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1077			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1078			return bitmap;
1079		} catch (final WriterException e) {
1080			return null;
1081		}
1082	}
1083
1084	protected Account extractAccount(Intent intent) {
1085		String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1086		try {
1087			return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1088		} catch (InvalidJidException e) {
1089			return null;
1090		}
1091	}
1092
1093	public static class ConferenceInvite {
1094		private String uuid;
1095		private List<Jid> jids = new ArrayList<>();
1096
1097		public static ConferenceInvite parse(Intent data) {
1098			ConferenceInvite invite = new ConferenceInvite();
1099			invite.uuid = data.getStringExtra("conversation");
1100			if (invite.uuid == null) {
1101				return null;
1102			}
1103			try {
1104				if (data.getBooleanExtra("multiple", false)) {
1105					String[] toAdd = data.getStringArrayExtra("contacts");
1106					for (String item : toAdd) {
1107						invite.jids.add(Jid.fromString(item));
1108					}
1109				} else {
1110					invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1111				}
1112			} catch (final InvalidJidException ignored) {
1113				return null;
1114			}
1115			return invite;
1116		}
1117
1118		public void execute(XmppActivity activity) {
1119			XmppConnectionService service = activity.xmppConnectionService;
1120			Conversation conversation = service.findConversationByUuid(this.uuid);
1121			if (conversation == null) {
1122				return;
1123			}
1124			if (conversation.getMode() == Conversation.MODE_MULTI) {
1125				for (Jid jid : jids) {
1126					service.invite(conversation, jid);
1127				}
1128			} else {
1129				jids.add(conversation.getJid().toBareJid());
1130				service.createAdhocConference(conversation.getAccount(), jids, activity.adhocCallback);
1131			}
1132		}
1133	}
1134
1135	public AvatarService avatarService() {
1136		return xmppConnectionService.getAvatarService();
1137	}
1138
1139	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1140		private final WeakReference<ImageView> imageViewReference;
1141		private Message message = null;
1142
1143		public BitmapWorkerTask(ImageView imageView) {
1144			imageViewReference = new WeakReference<>(imageView);
1145		}
1146
1147		@Override
1148		protected Bitmap doInBackground(Message... params) {
1149			if (isCancelled()) {
1150				return null;
1151			}
1152			message = params[0];
1153			try {
1154				return xmppConnectionService.getFileBackend().getThumbnail(
1155						message, (int) (metrics.density * 288), false);
1156			} catch (FileNotFoundException e) {
1157				return null;
1158			}
1159		}
1160
1161		@Override
1162		protected void onPostExecute(Bitmap bitmap) {
1163			if (bitmap != null && !isCancelled()) {
1164				final ImageView imageView = imageViewReference.get();
1165				if (imageView != null) {
1166					imageView.setImageBitmap(bitmap);
1167					imageView.setBackgroundColor(0x00000000);
1168				}
1169			}
1170		}
1171	}
1172
1173	public void loadBitmap(Message message, ImageView imageView) {
1174		Bitmap bm;
1175		try {
1176			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1177					(int) (metrics.density * 288), true);
1178		} catch (FileNotFoundException e) {
1179			bm = null;
1180		}
1181		if (bm != null) {
1182			cancelPotentialWork(message, imageView);
1183			imageView.setImageBitmap(bm);
1184			imageView.setBackgroundColor(0x00000000);
1185		} else {
1186			if (cancelPotentialWork(message, imageView)) {
1187				imageView.setBackgroundColor(0xff333333);
1188				imageView.setImageDrawable(null);
1189				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1190				final AsyncDrawable asyncDrawable = new AsyncDrawable(
1191						getResources(), null, task);
1192				imageView.setImageDrawable(asyncDrawable);
1193				try {
1194					task.execute(message);
1195				} catch (final RejectedExecutionException ignored) {
1196					ignored.printStackTrace();
1197				}
1198			}
1199		}
1200	}
1201
1202	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1203		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1204
1205		if (bitmapWorkerTask != null) {
1206			final Message oldMessage = bitmapWorkerTask.message;
1207			if (oldMessage == null || message != oldMessage) {
1208				bitmapWorkerTask.cancel(true);
1209			} else {
1210				return false;
1211			}
1212		}
1213		return true;
1214	}
1215
1216	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1217		if (imageView != null) {
1218			final Drawable drawable = imageView.getDrawable();
1219			if (drawable instanceof AsyncDrawable) {
1220				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1221				return asyncDrawable.getBitmapWorkerTask();
1222			}
1223		}
1224		return null;
1225	}
1226
1227	static class AsyncDrawable extends BitmapDrawable {
1228		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1229
1230		public AsyncDrawable(Resources res, Bitmap bitmap,
1231				BitmapWorkerTask bitmapWorkerTask) {
1232			super(res, bitmap);
1233			bitmapWorkerTaskReference = new WeakReference<>(
1234					bitmapWorkerTask);
1235		}
1236
1237		public BitmapWorkerTask getBitmapWorkerTask() {
1238			return bitmapWorkerTaskReference.get();
1239		}
1240	}
1241}