make presence selector work with empty resources (bare jid)

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/XmppActivity.java          | 1848 
src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java |   11 
2 files changed, 932 insertions(+), 927 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -83,931 +83,927 @@ import eu.siacs.conversations.xmpp.Jid;
 
 public abstract class XmppActivity extends ActionBarActivity {
 
-	public static final String EXTRA_ACCOUNT = "account";
-	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
-	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
-	protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103;
-	protected static final int REQUEST_BATTERY_OP = 0x49ff;
-	public XmppConnectionService xmppConnectionService;
-	public boolean xmppConnectionServiceBound = false;
-
-	protected static final String FRAGMENT_TAG_DIALOG = "dialog";
-
-	private boolean isCameraFeatureAvailable = false;
-
-	protected int mTheme;
-	protected boolean mUsingEnterKey = false;
-	protected boolean mUseTor = false;
-	protected Toast mToast;
-	public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
-	protected ConferenceInvite mPendingConferenceInvite = null;
-	protected ServiceConnection mConnection = new ServiceConnection() {
-
-		@Override
-		public void onServiceConnected(ComponentName className, IBinder service) {
-			XmppConnectionBinder binder = (XmppConnectionBinder) service;
-			xmppConnectionService = binder.getService();
-			xmppConnectionServiceBound = true;
-			registerListeners();
-			onBackendConnected();
-		}
-
-		@Override
-		public void onServiceDisconnected(ComponentName arg0) {
-			xmppConnectionServiceBound = false;
-		}
-	};
-	private DisplayMetrics metrics;
-	private long mLastUiRefresh = 0;
-	private Handler mRefreshUiHandler = new Handler();
-	private Runnable mRefreshUiRunnable = () -> {
-		mLastUiRefresh = SystemClock.elapsedRealtime();
-		refreshUiReal();
-	};
-	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
-		@Override
-		public void success(final Conversation conversation) {
-			runOnUiThread(() -> {
-				switchToConversation(conversation);
-				hideToast();
-			});
-		}
-
-		@Override
-		public void error(final int errorCode, Conversation object) {
-			runOnUiThread(() -> replaceToast(getString(errorCode)));
-		}
-
-		@Override
-		public void userInputRequired(PendingIntent pi, Conversation object) {
-
-		}
-	};
-	public boolean mSkipBackgroundBinding = false;
-
-	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
-		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
-		if (bitmapWorkerTask != null) {
-			final Message oldMessage = bitmapWorkerTask.message;
-			if (oldMessage == null || message != oldMessage) {
-				bitmapWorkerTask.cancel(true);
-			} else {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
-		if (imageView != null) {
-			final Drawable drawable = imageView.getDrawable();
-			if (drawable instanceof AsyncDrawable) {
-				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
-				return asyncDrawable.getBitmapWorkerTask();
-			}
-		}
-		return null;
-	}
-
-	protected void hideToast() {
-		if (mToast != null) {
-			mToast.cancel();
-		}
-	}
-
-	protected void replaceToast(String msg) {
-		replaceToast(msg, true);
-	}
-
-	protected void replaceToast(String msg, boolean showlong) {
-		hideToast();
-		mToast = Toast.makeText(this, msg, showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
-		mToast.show();
-	}
-
-	protected final void refreshUi() {
-		final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
-		if (diff > Config.REFRESH_UI_INTERVAL) {
-			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
-			runOnUiThread(mRefreshUiRunnable);
-		} else {
-			final long next = Config.REFRESH_UI_INTERVAL - diff;
-			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
-			mRefreshUiHandler.postDelayed(mRefreshUiRunnable, next);
-		}
-	}
-
-	abstract protected void refreshUiReal();
-
-	@Override
-	protected void onStart() {
-		super.onStart();
-		if (!xmppConnectionServiceBound) {
-			if (this.mSkipBackgroundBinding) {
-				Log.d(Config.LOGTAG,"skipping background binding");
-			} else {
-				connectToBackend();
-			}
-		} else {
-			this.registerListeners();
-			this.onBackendConnected();
-		}
-		this.mUsingEnterKey = usingEnterKey();
-		this.mUseTor = useTor();
-	}
-
-	public void connectToBackend() {
-		Intent intent = new Intent(this, XmppConnectionService.class);
-		intent.setAction("ui");
-		try {
-			startService(intent);
-		} catch (IllegalStateException e) {
-			Log.w(Config.LOGTAG,"unable to start service from "+getClass().getSimpleName());
-		}
-		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-	}
-
-	@Override
-	protected void onStop() {
-		super.onStop();
-		if (xmppConnectionServiceBound) {
-			this.unregisterListeners();
-			unbindService(mConnection);
-			xmppConnectionServiceBound = false;
-		}
-	}
-
-
-	public boolean hasPgp() {
-		return xmppConnectionService.getPgpEngine() != null;
-	}
-
-	public void showInstallPgpDialog() {
-		Builder builder = new AlertDialog.Builder(this);
-		builder.setTitle(getString(R.string.openkeychain_required));
-		builder.setIconAttribute(android.R.attr.alertDialogIcon);
-		builder.setMessage(getText(R.string.openkeychain_required_long));
-		builder.setNegativeButton(getString(R.string.cancel), null);
-		builder.setNeutralButton(getString(R.string.restart),
-				(dialog, which) -> {
-					if (xmppConnectionServiceBound) {
-						unbindService(mConnection);
-						xmppConnectionServiceBound = false;
-					}
-					stopService(new Intent(XmppActivity.this,
-							XmppConnectionService.class));
-					finish();
-				});
-		builder.setPositiveButton(getString(R.string.install),
-				(dialog, which) -> {
-					Uri uri = Uri
-							.parse("market://details?id=org.sufficientlysecure.keychain");
-					Intent marketIntent = new Intent(Intent.ACTION_VIEW,
-							uri);
-					PackageManager manager = getApplicationContext()
-							.getPackageManager();
-					List<ResolveInfo> infos = manager
-							.queryIntentActivities(marketIntent, 0);
-					if (infos.size() > 0) {
-						startActivity(marketIntent);
-					} else {
-						uri = Uri.parse("http://www.openkeychain.org/");
-						Intent browserIntent = new Intent(
-								Intent.ACTION_VIEW, uri);
-						startActivity(browserIntent);
-					}
-					finish();
-				});
-		builder.create().show();
-	}
-
-	abstract void onBackendConnected();
-
-	protected void registerListeners() {
-		if (this instanceof XmppConnectionService.OnConversationUpdate) {
-			this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnAccountUpdate) {
-			this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnCaptchaRequested) {
-			this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
-		}
-		if (this instanceof XmppConnectionService.OnRosterUpdate) {
-			this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
-			this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
-		}
-		if (this instanceof OnUpdateBlocklist) {
-			this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
-		}
-		if (this instanceof XmppConnectionService.OnShowErrorToast) {
-			this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
-		}
-		if (this instanceof OnKeyStatusUpdated) {
-			this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
-		}
-		if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
-			this.xmppConnectionService.setOnRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
-		}
-	}
-
-	protected void unregisterListeners() {
-		if (this instanceof XmppConnectionService.OnConversationUpdate) {
-			this.xmppConnectionService.removeOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnAccountUpdate) {
-			this.xmppConnectionService.removeOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnCaptchaRequested) {
-			this.xmppConnectionService.removeOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
-		}
-		if (this instanceof XmppConnectionService.OnRosterUpdate) {
-			this.xmppConnectionService.removeOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
-		}
-		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
-			this.xmppConnectionService.removeOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
-		}
-		if (this instanceof OnUpdateBlocklist) {
-			this.xmppConnectionService.removeOnUpdateBlocklistListener((OnUpdateBlocklist) this);
-		}
-		if (this instanceof XmppConnectionService.OnShowErrorToast) {
-			this.xmppConnectionService.removeOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
-		}
-		if (this instanceof OnKeyStatusUpdated) {
-			this.xmppConnectionService.removeOnNewKeysAvailableListener((OnKeyStatusUpdated) this);
-		}
-		if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
-			this.xmppConnectionService.removeRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
-		}
-	}
-
-	@Override
-	public boolean onOptionsItemSelected(final MenuItem item) {
-		switch (item.getItemId()) {
-			case R.id.action_settings:
-				startActivity(new Intent(this, SettingsActivity.class));
-				break;
-			case R.id.action_accounts:
-				AccountUtils.launchManageAccounts(this);
-				break;
-			case R.id.action_account:
-				AccountUtils.launchManageAccount(this);
-				break;
-			case android.R.id.home:
-				finish();
-				break;
-			case R.id.action_show_qr_code:
-				showQrCode();
-				break;
-		}
-		return super.onOptionsItemSelected(item);
-	}
-
-	public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
-		final Contact contact = conversation.getContact();
-		if (!contact.showInRoster()) {
-			showAddToRosterDialog(conversation.getContact());
-		} else {
-			final Presences presences = contact.getPresences();
-			if (presences.size() == 0) {
-				if (!contact.getOption(Contact.Options.TO)
-						&& !contact.getOption(Contact.Options.ASKING)
-						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
-					showAskForPresenceDialog(contact);
-				} else if (!contact.getOption(Contact.Options.TO)
-						|| !contact.getOption(Contact.Options.FROM)) {
-					PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener);
-				} else {
-					conversation.setNextCounterpart(null);
-					listener.onPresenceSelected();
-				}
-			} else if (presences.size() == 1) {
-				String presence = presences.toResourceArray()[0];
-				try {
-					conversation.setNextCounterpart(Jid.of(contact.getJid().getLocal(), contact.getJid().getDomain(), presence));
-				} catch (IllegalArgumentException e) {
-					conversation.setNextCounterpart(null);
-				}
-				listener.onPresenceSelected();
-			} else {
-				PresenceSelector.showPresenceSelectionDialog(this, conversation, listener);
-			}
-		}
-	}
-
-	@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
-	@Override
-	protected void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		metrics = getResources().getDisplayMetrics();
-		ExceptionHelper.init(getApplicationContext());
-		new EmojiService(this).init();
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-			this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
-		} else {
-			this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
-		}
-		this.mTheme = findTheme();
-		setTheme(this.mTheme);
-	}
-
-	protected boolean isCameraFeatureAvailable() {
-		return this.isCameraFeatureAvailable;
-	}
-
-	public boolean isDarkTheme() {
-		return ThemeHelper.isDark(mTheme);
-	}
-
-	public int getThemeResource(int r_attr_name, int r_drawable_def) {
-		int[] attrs = {r_attr_name};
-		TypedArray ta = this.getTheme().obtainStyledAttributes(attrs);
-
-		int res = ta.getResourceId(0, r_drawable_def);
-		ta.recycle();
-
-		return res;
-	}
-
-	protected boolean isOptimizingBattery() {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-			final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
-			return pm != null
-					&& !pm.isIgnoringBatteryOptimizations(getPackageName());
-		} else {
-			return false;
-		}
-	}
-
-	protected boolean isAffectedByDataSaver() {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-			final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-			return cm != null
-					&& cm.isActiveNetworkMetered()
-					&& cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-		} else {
-			return false;
-		}
-	}
-
-	private boolean usingEnterKey() {
-		return getBooleanPreference("display_enter_key", R.bool.display_enter_key);
-	}
-
-	private boolean useTor() {
-		return QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor);
-	}
-
-	protected SharedPreferences getPreferences() {
-		return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-	}
-
-	protected boolean getBooleanPreference(String name, @BoolRes int res) {
-		return getPreferences().getBoolean(name, getResources().getBoolean(res));
-	}
-
-	public void switchToConversation(Conversation conversation) {
-		switchToConversation(conversation, null);
-	}
-
-	public void switchToConversationAndQuote(Conversation conversation, String text) {
-		switchToConversation(conversation, text, true, null, false, false);
-	}
-
-	public void switchToConversation(Conversation conversation, String text) {
-		switchToConversation(conversation, text, false, null, false, false);
-	}
-
-	public void switchToConversationDoNotAppend(Conversation conversation, String text) {
-		switchToConversation(conversation, text, false, null, false, true);
-	}
-
-	public void highlightInMuc(Conversation conversation, String nick) {
-		switchToConversation(conversation, null, false, nick, false, false);
-	}
-
-	public void privateMsgInMuc(Conversation conversation, String nick) {
-		switchToConversation(conversation, null, false, nick, true, false);
-	}
-
-	private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) {
-		Intent intent = new Intent(this, ConversationsActivity.class);
-		intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
-		intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
-		if (text != null) {
-			intent.putExtra(Intent.EXTRA_TEXT, text);
-			if (asQuote) {
-				intent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true);
-			}
-		}
-		if (nick != null) {
-			intent.putExtra(ConversationsActivity.EXTRA_NICK, nick);
-			intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm);
-		}
-		if (doNotAppend) {
-			intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true);
-		}
-		intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-		startActivity(intent);
-		finish();
-	}
-
-	public void switchToContactDetails(Contact contact) {
-		switchToContactDetails(contact, null);
-	}
-
-	public void switchToContactDetails(Contact contact, String messageFingerprint) {
-		Intent intent = new Intent(this, ContactDetailsActivity.class);
-		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
-		intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString());
-		intent.putExtra("contact", contact.getJid().toEscapedString());
-		intent.putExtra("fingerprint", messageFingerprint);
-		startActivity(intent);
-	}
-
-	public void switchToAccount(Account account, String fingerprint) {
-		switchToAccount(account, false, fingerprint);
-	}
-
-	public void switchToAccount(Account account) {
-		switchToAccount(account, false, null);
-	}
-
-	public void switchToAccount(Account account, boolean init, String fingerprint) {
-		Intent intent = new Intent(this, EditAccountActivity.class);
-		intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
-		intent.putExtra("init", init);
-		if (init) {
-			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
-		}
-		if (fingerprint != null) {
-			intent.putExtra("fingerprint", fingerprint);
-		}
-		startActivity(intent);
-		if (init) {
-			overridePendingTransition(0, 0);
-		}
-	}
-
-	protected void delegateUriPermissionsToService(Uri uri) {
-		Intent intent = new Intent(this, XmppConnectionService.class);
-		intent.setAction(Intent.ACTION_SEND);
-		intent.setData(uri);
-		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-		try {
-			startService(intent);
-		} catch (Exception e) {
-			Log.e(Config.LOGTAG,"unable to delegate uri permission",e);
-		}
-	}
-
-	protected void inviteToConversation(Conversation conversation) {
-		startActivityForResult(ChooseContactActivity.create(this,conversation), REQUEST_INVITE_TO_CONVERSATION);
-	}
-
-	protected void announcePgp(final Account account, final Conversation conversation, Intent intent, final Runnable onSuccess) {
-		if (account.getPgpId() == 0) {
-			choosePgpSignId(account);
-		} else {
-			String status = null;
-			if (manuallyChangePresence()) {
-				status = account.getPresenceStatusMessage();
-			}
-			if (status == null) {
-				status = "";
-			}
-			xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback<String>() {
-
-				@Override
-				public void userInputRequired(PendingIntent pi, String signature) {
-					try {
-						startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
-					} catch (final SendIntentException ignored) {
-					}
-				}
-
-				@Override
-				public void success(String signature) {
-					account.setPgpSignature(signature);
-					xmppConnectionService.databaseBackend.updateAccount(account);
-					xmppConnectionService.sendPresence(account);
-					if (conversation != null) {
-						conversation.setNextEncryption(Message.ENCRYPTION_PGP);
-						xmppConnectionService.updateConversation(conversation);
-						refreshUi();
-					}
-					if (onSuccess != null) {
-						runOnUiThread(onSuccess);
-					}
-				}
-
-				@Override
-				public void error(int error, String signature) {
-					if (error == 0) {
-						account.setPgpSignId(0);
-						account.unsetPgpSignature();
-						xmppConnectionService.databaseBackend.updateAccount(account);
-						choosePgpSignId(account);
-					} else {
-						displayErrorDialog(error);
-					}
-				}
-			});
-		}
-	}
-
-	@SuppressWarnings("deprecation")
-	@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
-	protected void setListItemBackgroundOnView(View view) {
-		int sdk = android.os.Build.VERSION.SDK_INT;
-		if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-			view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
-		} else {
-			view.setBackground(getResources().getDrawable(R.drawable.greybackground));
-		}
-	}
-
-	protected void choosePgpSignId(Account account) {
-		xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
-			@Override
-			public void success(Account account1) {
-			}
-
-			@Override
-			public void error(int errorCode, Account object) {
-
-			}
-
-			@Override
-			public void userInputRequired(PendingIntent pi, Account object) {
-				try {
-					startIntentSenderForResult(pi.getIntentSender(),
-							REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
-				} catch (final SendIntentException ignored) {
-				}
-			}
-		});
-	}
-
-	protected void displayErrorDialog(final int errorCode) {
-		runOnUiThread(() -> {
-			Builder builder = new Builder(XmppActivity.this);
-			builder.setIconAttribute(android.R.attr.alertDialogIcon);
-			builder.setTitle(getString(R.string.error));
-			builder.setMessage(errorCode);
-			builder.setNeutralButton(R.string.accept, null);
-			builder.create().show();
-		});
-
-	}
-
-	protected void showAddToRosterDialog(final Contact contact) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(this);
-		builder.setTitle(contact.getJid().toString());
-		builder.setMessage(getString(R.string.not_in_roster));
-		builder.setNegativeButton(getString(R.string.cancel), null);
-		builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> xmppConnectionService.createContact(contact,true));
-		builder.create().show();
-	}
-
-	private void showAskForPresenceDialog(final Contact contact) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(this);
-		builder.setTitle(contact.getJid().toString());
-		builder.setMessage(R.string.request_presence_updates);
-		builder.setNegativeButton(R.string.cancel, null);
-		builder.setPositiveButton(R.string.request_now,
-				(dialog, which) -> {
-					if (xmppConnectionServiceBound) {
-						xmppConnectionService.sendPresencePacket(contact
-								.getAccount(), xmppConnectionService
-								.getPresenceGenerator()
-								.requestPresenceUpdatesFrom(contact));
-					}
-				});
-		builder.create().show();
-	}
-
-	protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback) {
-		quickEdit(previousValue, callback, hint, false, false);
-	}
-
-	protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) {
-		quickEdit(previousValue, callback, hint, false, permitEmpty);
-	}
-
-	protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
-		quickEdit(previousValue, callback, R.string.password, true, false);
-	}
-
-	@SuppressLint("InflateParams")
-	private void quickEdit(final String previousValue,
-	                       final OnValueEdited callback,
-	                       final @StringRes int hint,
-	                       boolean password,
-	                       boolean permitEmpty) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(this);
-		DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.dialog_quickedit, null, false);
-		if (password) {
-			binding.inputEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-		}
-		builder.setPositiveButton(R.string.accept, null);
-		if (hint != 0) {
-			binding.inputLayout.setHint(getString(hint));
-		}
-		binding.inputEditText.requestFocus();
-		if (previousValue != null) {
-			binding.inputEditText.getText().append(previousValue);
-		}
-		builder.setView(binding.getRoot());
-		builder.setNegativeButton(R.string.cancel, null);
-		final AlertDialog dialog = builder.create();
-		dialog.setOnShowListener(d -> SoftKeyboardUtils.showKeyboard(binding.inputEditText));
-		dialog.show();
-		View.OnClickListener clickListener = v -> {
-			String value = binding.inputEditText.getText().toString();
-			if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) {
-				String error = callback.onValueEdited(value);
-				if (error != null) {
-					binding.inputLayout.setError(error);
-					return;
-				}
-			}
-			SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
-			dialog.dismiss();
-		};
-		dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(clickListener);
-		dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((v -> {
-			SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
-			dialog.dismiss();
-		}));
-		dialog.setCanceledOnTouchOutside(false);
-		dialog.setOnDismissListener(dialog1 -> {
-			SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+    public static final String EXTRA_ACCOUNT = "account";
+    protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
+    protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
+    protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103;
+    protected static final int REQUEST_BATTERY_OP = 0x49ff;
+    public XmppConnectionService xmppConnectionService;
+    public boolean xmppConnectionServiceBound = false;
+
+    protected static final String FRAGMENT_TAG_DIALOG = "dialog";
+
+    private boolean isCameraFeatureAvailable = false;
+
+    protected int mTheme;
+    protected boolean mUsingEnterKey = false;
+    protected boolean mUseTor = false;
+    protected Toast mToast;
+    public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
+    protected ConferenceInvite mPendingConferenceInvite = null;
+    protected ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            XmppConnectionBinder binder = (XmppConnectionBinder) service;
+            xmppConnectionService = binder.getService();
+            xmppConnectionServiceBound = true;
+            registerListeners();
+            onBackendConnected();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            xmppConnectionServiceBound = false;
+        }
+    };
+    private DisplayMetrics metrics;
+    private long mLastUiRefresh = 0;
+    private Handler mRefreshUiHandler = new Handler();
+    private Runnable mRefreshUiRunnable = () -> {
+        mLastUiRefresh = SystemClock.elapsedRealtime();
+        refreshUiReal();
+    };
+    private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
+        @Override
+        public void success(final Conversation conversation) {
+            runOnUiThread(() -> {
+                switchToConversation(conversation);
+                hideToast();
+            });
+        }
+
+        @Override
+        public void error(final int errorCode, Conversation object) {
+            runOnUiThread(() -> replaceToast(getString(errorCode)));
+        }
+
+        @Override
+        public void userInputRequired(PendingIntent pi, Conversation object) {
+
+        }
+    };
+    public boolean mSkipBackgroundBinding = false;
+
+    public static boolean cancelPotentialWork(Message message, ImageView imageView) {
+        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+        if (bitmapWorkerTask != null) {
+            final Message oldMessage = bitmapWorkerTask.message;
+            if (oldMessage == null || message != oldMessage) {
+                bitmapWorkerTask.cancel(true);
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+        if (imageView != null) {
+            final Drawable drawable = imageView.getDrawable();
+            if (drawable instanceof AsyncDrawable) {
+                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+                return asyncDrawable.getBitmapWorkerTask();
+            }
+        }
+        return null;
+    }
+
+    protected void hideToast() {
+        if (mToast != null) {
+            mToast.cancel();
+        }
+    }
+
+    protected void replaceToast(String msg) {
+        replaceToast(msg, true);
+    }
+
+    protected void replaceToast(String msg, boolean showlong) {
+        hideToast();
+        mToast = Toast.makeText(this, msg, showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
+        mToast.show();
+    }
+
+    protected final void refreshUi() {
+        final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
+        if (diff > Config.REFRESH_UI_INTERVAL) {
+            mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+            runOnUiThread(mRefreshUiRunnable);
+        } else {
+            final long next = Config.REFRESH_UI_INTERVAL - diff;
+            mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+            mRefreshUiHandler.postDelayed(mRefreshUiRunnable, next);
+        }
+    }
+
+    abstract protected void refreshUiReal();
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        if (!xmppConnectionServiceBound) {
+            if (this.mSkipBackgroundBinding) {
+                Log.d(Config.LOGTAG, "skipping background binding");
+            } else {
+                connectToBackend();
+            }
+        } else {
+            this.registerListeners();
+            this.onBackendConnected();
+        }
+        this.mUsingEnterKey = usingEnterKey();
+        this.mUseTor = useTor();
+    }
+
+    public void connectToBackend() {
+        Intent intent = new Intent(this, XmppConnectionService.class);
+        intent.setAction("ui");
+        try {
+            startService(intent);
+        } catch (IllegalStateException e) {
+            Log.w(Config.LOGTAG, "unable to start service from " + getClass().getSimpleName());
+        }
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (xmppConnectionServiceBound) {
+            this.unregisterListeners();
+            unbindService(mConnection);
+            xmppConnectionServiceBound = false;
+        }
+    }
+
+
+    public boolean hasPgp() {
+        return xmppConnectionService.getPgpEngine() != null;
+    }
+
+    public void showInstallPgpDialog() {
+        Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(getString(R.string.openkeychain_required));
+        builder.setIconAttribute(android.R.attr.alertDialogIcon);
+        builder.setMessage(getText(R.string.openkeychain_required_long));
+        builder.setNegativeButton(getString(R.string.cancel), null);
+        builder.setNeutralButton(getString(R.string.restart),
+                (dialog, which) -> {
+                    if (xmppConnectionServiceBound) {
+                        unbindService(mConnection);
+                        xmppConnectionServiceBound = false;
+                    }
+                    stopService(new Intent(XmppActivity.this,
+                            XmppConnectionService.class));
+                    finish();
+                });
+        builder.setPositiveButton(getString(R.string.install),
+                (dialog, which) -> {
+                    Uri uri = Uri
+                            .parse("market://details?id=org.sufficientlysecure.keychain");
+                    Intent marketIntent = new Intent(Intent.ACTION_VIEW,
+                            uri);
+                    PackageManager manager = getApplicationContext()
+                            .getPackageManager();
+                    List<ResolveInfo> infos = manager
+                            .queryIntentActivities(marketIntent, 0);
+                    if (infos.size() > 0) {
+                        startActivity(marketIntent);
+                    } else {
+                        uri = Uri.parse("http://www.openkeychain.org/");
+                        Intent browserIntent = new Intent(
+                                Intent.ACTION_VIEW, uri);
+                        startActivity(browserIntent);
+                    }
+                    finish();
+                });
+        builder.create().show();
+    }
+
+    abstract void onBackendConnected();
+
+    protected void registerListeners() {
+        if (this instanceof XmppConnectionService.OnConversationUpdate) {
+            this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnAccountUpdate) {
+            this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+            this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+        }
+        if (this instanceof XmppConnectionService.OnRosterUpdate) {
+            this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
+            this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
+        }
+        if (this instanceof OnUpdateBlocklist) {
+            this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
+        }
+        if (this instanceof XmppConnectionService.OnShowErrorToast) {
+            this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+        }
+        if (this instanceof OnKeyStatusUpdated) {
+            this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
+        }
+        if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
+            this.xmppConnectionService.setOnRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
+        }
+    }
+
+    protected void unregisterListeners() {
+        if (this instanceof XmppConnectionService.OnConversationUpdate) {
+            this.xmppConnectionService.removeOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnAccountUpdate) {
+            this.xmppConnectionService.removeOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+            this.xmppConnectionService.removeOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+        }
+        if (this instanceof XmppConnectionService.OnRosterUpdate) {
+            this.xmppConnectionService.removeOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
+        }
+        if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
+            this.xmppConnectionService.removeOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
+        }
+        if (this instanceof OnUpdateBlocklist) {
+            this.xmppConnectionService.removeOnUpdateBlocklistListener((OnUpdateBlocklist) this);
+        }
+        if (this instanceof XmppConnectionService.OnShowErrorToast) {
+            this.xmppConnectionService.removeOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+        }
+        if (this instanceof OnKeyStatusUpdated) {
+            this.xmppConnectionService.removeOnNewKeysAvailableListener((OnKeyStatusUpdated) this);
+        }
+        if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) {
+            this.xmppConnectionService.removeRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this);
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_settings:
+                startActivity(new Intent(this, SettingsActivity.class));
+                break;
+            case R.id.action_accounts:
+                AccountUtils.launchManageAccounts(this);
+                break;
+            case R.id.action_account:
+                AccountUtils.launchManageAccount(this);
+                break;
+            case android.R.id.home:
+                finish();
+                break;
+            case R.id.action_show_qr_code:
+                showQrCode();
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
+        final Contact contact = conversation.getContact();
+        if (!contact.showInRoster()) {
+            showAddToRosterDialog(conversation.getContact());
+        } else {
+            final Presences presences = contact.getPresences();
+            if (presences.size() == 0) {
+                if (!contact.getOption(Contact.Options.TO)
+                        && !contact.getOption(Contact.Options.ASKING)
+                        && contact.getAccount().getStatus() == Account.State.ONLINE) {
+                    showAskForPresenceDialog(contact);
+                } else if (!contact.getOption(Contact.Options.TO)
+                        || !contact.getOption(Contact.Options.FROM)) {
+                    PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener);
+                } else {
+                    conversation.setNextCounterpart(null);
+                    listener.onPresenceSelected();
+                }
+            } else if (presences.size() == 1) {
+                final String presence = presences.toResourceArray()[0];
+                conversation.setNextCounterpart(PresenceSelector.getNextCounterpart(contact, presence));
+                listener.onPresenceSelected();
+            } else {
+                PresenceSelector.showPresenceSelectionDialog(this, conversation, listener);
+            }
+        }
+    }
+
+    @SuppressLint("UnsupportedChromeOsCameraSystemFeature")
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        metrics = getResources().getDisplayMetrics();
+        ExceptionHelper.init(getApplicationContext());
+        new EmojiService(this).init();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+        } else {
+            this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        }
+        this.mTheme = findTheme();
+        setTheme(this.mTheme);
+    }
+
+    protected boolean isCameraFeatureAvailable() {
+        return this.isCameraFeatureAvailable;
+    }
+
+    public boolean isDarkTheme() {
+        return ThemeHelper.isDark(mTheme);
+    }
+
+    public int getThemeResource(int r_attr_name, int r_drawable_def) {
+        int[] attrs = {r_attr_name};
+        TypedArray ta = this.getTheme().obtainStyledAttributes(attrs);
+
+        int res = ta.getResourceId(0, r_drawable_def);
+        ta.recycle();
+
+        return res;
+    }
+
+    protected boolean isOptimizingBattery() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+            return pm != null
+                    && !pm.isIgnoringBatteryOptimizations(getPackageName());
+        } else {
+            return false;
+        }
+    }
+
+    protected boolean isAffectedByDataSaver() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+            return cm != null
+                    && cm.isActiveNetworkMetered()
+                    && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean usingEnterKey() {
+        return getBooleanPreference("display_enter_key", R.bool.display_enter_key);
+    }
+
+    private boolean useTor() {
+        return QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor);
+    }
+
+    protected SharedPreferences getPreferences() {
+        return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+    }
+
+    protected boolean getBooleanPreference(String name, @BoolRes int res) {
+        return getPreferences().getBoolean(name, getResources().getBoolean(res));
+    }
+
+    public void switchToConversation(Conversation conversation) {
+        switchToConversation(conversation, null);
+    }
+
+    public void switchToConversationAndQuote(Conversation conversation, String text) {
+        switchToConversation(conversation, text, true, null, false, false);
+    }
+
+    public void switchToConversation(Conversation conversation, String text) {
+        switchToConversation(conversation, text, false, null, false, false);
+    }
+
+    public void switchToConversationDoNotAppend(Conversation conversation, String text) {
+        switchToConversation(conversation, text, false, null, false, true);
+    }
+
+    public void highlightInMuc(Conversation conversation, String nick) {
+        switchToConversation(conversation, null, false, nick, false, false);
+    }
+
+    public void privateMsgInMuc(Conversation conversation, String nick) {
+        switchToConversation(conversation, null, false, nick, true, false);
+    }
+
+    private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) {
+        Intent intent = new Intent(this, ConversationsActivity.class);
+        intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
+        intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
+        if (text != null) {
+            intent.putExtra(Intent.EXTRA_TEXT, text);
+            if (asQuote) {
+                intent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true);
+            }
+        }
+        if (nick != null) {
+            intent.putExtra(ConversationsActivity.EXTRA_NICK, nick);
+            intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm);
+        }
+        if (doNotAppend) {
+            intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true);
+        }
+        intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+        finish();
+    }
+
+    public void switchToContactDetails(Contact contact) {
+        switchToContactDetails(contact, null);
+    }
+
+    public void switchToContactDetails(Contact contact, String messageFingerprint) {
+        Intent intent = new Intent(this, ContactDetailsActivity.class);
+        intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
+        intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString());
+        intent.putExtra("contact", contact.getJid().toEscapedString());
+        intent.putExtra("fingerprint", messageFingerprint);
+        startActivity(intent);
+    }
+
+    public void switchToAccount(Account account, String fingerprint) {
+        switchToAccount(account, false, fingerprint);
+    }
+
+    public void switchToAccount(Account account) {
+        switchToAccount(account, false, null);
+    }
+
+    public void switchToAccount(Account account, boolean init, String fingerprint) {
+        Intent intent = new Intent(this, EditAccountActivity.class);
+        intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
+        intent.putExtra("init", init);
+        if (init) {
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        }
+        if (fingerprint != null) {
+            intent.putExtra("fingerprint", fingerprint);
+        }
+        startActivity(intent);
+        if (init) {
+            overridePendingTransition(0, 0);
+        }
+    }
+
+    protected void delegateUriPermissionsToService(Uri uri) {
+        Intent intent = new Intent(this, XmppConnectionService.class);
+        intent.setAction(Intent.ACTION_SEND);
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        try {
+            startService(intent);
+        } catch (Exception e) {
+            Log.e(Config.LOGTAG, "unable to delegate uri permission", e);
+        }
+    }
+
+    protected void inviteToConversation(Conversation conversation) {
+        startActivityForResult(ChooseContactActivity.create(this, conversation), REQUEST_INVITE_TO_CONVERSATION);
+    }
+
+    protected void announcePgp(final Account account, final Conversation conversation, Intent intent, final Runnable onSuccess) {
+        if (account.getPgpId() == 0) {
+            choosePgpSignId(account);
+        } else {
+            String status = null;
+            if (manuallyChangePresence()) {
+                status = account.getPresenceStatusMessage();
+            }
+            if (status == null) {
+                status = "";
+            }
+            xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback<String>() {
+
+                @Override
+                public void userInputRequired(PendingIntent pi, String signature) {
+                    try {
+                        startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
+                    } catch (final SendIntentException ignored) {
+                    }
+                }
+
+                @Override
+                public void success(String signature) {
+                    account.setPgpSignature(signature);
+                    xmppConnectionService.databaseBackend.updateAccount(account);
+                    xmppConnectionService.sendPresence(account);
+                    if (conversation != null) {
+                        conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+                        xmppConnectionService.updateConversation(conversation);
+                        refreshUi();
+                    }
+                    if (onSuccess != null) {
+                        runOnUiThread(onSuccess);
+                    }
+                }
+
+                @Override
+                public void error(int error, String signature) {
+                    if (error == 0) {
+                        account.setPgpSignId(0);
+                        account.unsetPgpSignature();
+                        xmppConnectionService.databaseBackend.updateAccount(account);
+                        choosePgpSignId(account);
+                    } else {
+                        displayErrorDialog(error);
+                    }
+                }
+            });
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    protected void setListItemBackgroundOnView(View view) {
+        int sdk = android.os.Build.VERSION.SDK_INT;
+        if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+            view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
+        } else {
+            view.setBackground(getResources().getDrawable(R.drawable.greybackground));
+        }
+    }
+
+    protected void choosePgpSignId(Account account) {
+        xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
+            @Override
+            public void success(Account account1) {
+            }
+
+            @Override
+            public void error(int errorCode, Account object) {
+
+            }
+
+            @Override
+            public void userInputRequired(PendingIntent pi, Account object) {
+                try {
+                    startIntentSenderForResult(pi.getIntentSender(),
+                            REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
+                } catch (final SendIntentException ignored) {
+                }
+            }
         });
-	}
-
-	protected boolean hasStoragePermission(int requestCode) {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-			if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
-				requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
-				return false;
-			} else {
-				return true;
-			}
-		} else {
-			return true;
-		}
-	}
-
-	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
-		super.onActivityResult(requestCode, resultCode, data);
-		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
-			mPendingConferenceInvite = ConferenceInvite.parse(data);
-			if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
-				if (mPendingConferenceInvite.execute(this)) {
-					mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
-					mToast.show();
-				}
-				mPendingConferenceInvite = null;
-			}
-		}
-	}
-
-	public boolean copyTextToClipboard(String text, int labelResId) {
-		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
-		String label = getResources().getString(labelResId);
-		if (mClipBoardManager != null) {
-			ClipData mClipData = ClipData.newPlainText(label, text);
-			mClipBoardManager.setPrimaryClip(mClipData);
-			return true;
-		}
-		return false;
-	}
-
-	protected boolean manuallyChangePresence() {
-		return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
-	}
-
-	protected String getShareableUri() {
-		return getShareableUri(false);
-	}
-
-	protected String getShareableUri(boolean http) {
-		return null;
-	}
-
-	protected void shareLink(boolean http) {
-		String uri = getShareableUri(http);
-		if (uri == null || uri.isEmpty()) {
-			return;
-		}
-		Intent intent = new Intent(Intent.ACTION_SEND);
-		intent.setType("text/plain");
-		intent.putExtra(Intent.EXTRA_TEXT, getShareableUri(http));
-		try {
-			startActivity(Intent.createChooser(intent, getText(R.string.share_uri_with)));
-		} catch (ActivityNotFoundException e) {
-			Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
-		}
-	}
-
-	protected void launchOpenKeyChain(long keyId) {
-		PgpEngine pgp = XmppActivity.this.xmppConnectionService.getPgpEngine();
-		try {
-			startIntentSenderForResult(
-					pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0,
-					0, 0);
-		} catch (Throwable e) {
-			Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show();
-		}
-	}
-
-	@Override
-	public void onResume() {
-		super.onResume();
-	}
-
-	protected int findTheme() {
-		return ThemeHelper.find(this);
-	}
-
-	@Override
-	public void onPause() {
-		super.onPause();
-	}
-
-	@Override
-	public boolean onMenuOpened(int id, Menu menu) {
-		if(id == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null) {
-			MenuDoubleTabUtil.recordMenuOpen();
-		}
-		return super.onMenuOpened(id, menu);
-	}
-
-	protected void showQrCode() {
-		showQrCode(getShareableUri());
-	}
-
-	protected void showQrCode(final String uri) {
-		if (uri == null || uri.isEmpty()) {
-			return;
-		}
-		Point size = new Point();
-		getWindowManager().getDefaultDisplay().getSize(size);
-		final int width = (size.x < size.y ? size.x : size.y);
-		Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width);
-		ImageView view = new ImageView(this);
-		view.setBackgroundColor(Color.WHITE);
-		view.setImageBitmap(bitmap);
-		AlertDialog.Builder builder = new AlertDialog.Builder(this);
-		builder.setView(view);
-		builder.create().show();
-	}
-
-	protected Account extractAccount(Intent intent) {
-		final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
-		try {
-			return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null;
-		} catch (IllegalArgumentException e) {
-			return null;
-		}
-	}
-
-	public AvatarService avatarService() {
-		return xmppConnectionService.getAvatarService();
-	}
-
-	public void loadBitmap(Message message, ImageView imageView) {
-		Bitmap bm;
-		try {
-			bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true);
-		} catch (IOException e) {
-			bm = null;
-		}
-		if (bm != null) {
-			cancelPotentialWork(message, imageView);
-			imageView.setImageBitmap(bm);
-			imageView.setBackgroundColor(0x00000000);
-		} else {
-			if (cancelPotentialWork(message, imageView)) {
-				imageView.setBackgroundColor(0xff333333);
-				imageView.setImageDrawable(null);
-				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
-				final AsyncDrawable asyncDrawable = new AsyncDrawable(
-						getResources(), null, task);
-				imageView.setImageDrawable(asyncDrawable);
-				try {
-					task.execute(message);
-				} catch (final RejectedExecutionException ignored) {
-					ignored.printStackTrace();
-				}
-			}
-		}
-	}
-
-	protected interface OnValueEdited {
-		String onValueEdited(String value);
-	}
-
-	public static class ConferenceInvite {
-		private String uuid;
-		private List<Jid> jids = new ArrayList<>();
-
-		public static ConferenceInvite parse(Intent data) {
-			ConferenceInvite invite = new ConferenceInvite();
-			invite.uuid = data.getStringExtra(ChooseContactActivity.EXTRA_CONVERSATION);
-			if (invite.uuid == null) {
-				return null;
-			}
-			invite.jids.addAll(ChooseContactActivity.extractJabberIds(data));
-			return invite;
-		}
-
-		public boolean execute(XmppActivity activity) {
-			XmppConnectionService service = activity.xmppConnectionService;
-			Conversation conversation = service.findConversationByUuid(this.uuid);
-			if (conversation == null) {
-				return false;
-			}
-			if (conversation.getMode() == Conversation.MODE_MULTI) {
-				for (Jid jid : jids) {
-					service.invite(conversation, jid);
-				}
-				return false;
-			} else {
-				jids.add(conversation.getJid().asBareJid());
-				return service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
-			}
-		}
-	}
-
-	static class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
-		private final WeakReference<ImageView> imageViewReference;
-		private Message message = null;
-
-		private BitmapWorkerTask(ImageView imageView) {
-			this.imageViewReference = new WeakReference<>(imageView);
-		}
-
-		@Override
-		protected Bitmap doInBackground(Message... params) {
-			if (isCancelled()) {
-				return null;
-			}
-			message = params[0];
-			try {
-				final XmppActivity activity = find(imageViewReference);
-				if (activity != null && activity.xmppConnectionService != null) {
-					return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false);
-				} else {
-					return null;
-				}
-			} catch (IOException e) {
-				return null;
-			}
-		}
-
-		@Override
-		protected void onPostExecute(final Bitmap bitmap) {
-			if (!isCancelled()) {
-				final ImageView imageView = imageViewReference.get();
-				if (imageView != null) {
-					imageView.setImageBitmap(bitmap);
-					imageView.setBackgroundColor(bitmap == null ? 0xff333333 : 0x00000000);
-				}
-			}
-		}
-	}
-
-	private static class AsyncDrawable extends BitmapDrawable {
-		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
-		private AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
-			super(res, bitmap);
-			bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
-		}
-
-		private BitmapWorkerTask getBitmapWorkerTask() {
-			return bitmapWorkerTaskReference.get();
-		}
-	}
-
-	public static XmppActivity find(@NonNull  WeakReference<ImageView> viewWeakReference) {
-		final View view = viewWeakReference.get();
-		return view == null ? null : find(view);
-	}
-
-	public static XmppActivity find(@NonNull final View view) {
-		Context context = view.getContext();
-		while (context instanceof ContextWrapper) {
-			if (context instanceof XmppActivity) {
-				return (XmppActivity) context;
-			}
-			context = ((ContextWrapper)context).getBaseContext();
-		}
-		return null;
-	}
+    }
+
+    protected void displayErrorDialog(final int errorCode) {
+        runOnUiThread(() -> {
+            Builder builder = new Builder(XmppActivity.this);
+            builder.setIconAttribute(android.R.attr.alertDialogIcon);
+            builder.setTitle(getString(R.string.error));
+            builder.setMessage(errorCode);
+            builder.setNeutralButton(R.string.accept, null);
+            builder.create().show();
+        });
+
+    }
+
+    protected void showAddToRosterDialog(final Contact contact) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(contact.getJid().toString());
+        builder.setMessage(getString(R.string.not_in_roster));
+        builder.setNegativeButton(getString(R.string.cancel), null);
+        builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> xmppConnectionService.createContact(contact, true));
+        builder.create().show();
+    }
+
+    private void showAskForPresenceDialog(final Contact contact) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(contact.getJid().toString());
+        builder.setMessage(R.string.request_presence_updates);
+        builder.setNegativeButton(R.string.cancel, null);
+        builder.setPositiveButton(R.string.request_now,
+                (dialog, which) -> {
+                    if (xmppConnectionServiceBound) {
+                        xmppConnectionService.sendPresencePacket(contact
+                                .getAccount(), xmppConnectionService
+                                .getPresenceGenerator()
+                                .requestPresenceUpdatesFrom(contact));
+                    }
+                });
+        builder.create().show();
+    }
+
+    protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback) {
+        quickEdit(previousValue, callback, hint, false, false);
+    }
+
+    protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) {
+        quickEdit(previousValue, callback, hint, false, permitEmpty);
+    }
+
+    protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
+        quickEdit(previousValue, callback, R.string.password, true, false);
+    }
+
+    @SuppressLint("InflateParams")
+    private void quickEdit(final String previousValue,
+                           final OnValueEdited callback,
+                           final @StringRes int hint,
+                           boolean password,
+                           boolean permitEmpty) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_quickedit, null, false);
+        if (password) {
+            binding.inputEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        }
+        builder.setPositiveButton(R.string.accept, null);
+        if (hint != 0) {
+            binding.inputLayout.setHint(getString(hint));
+        }
+        binding.inputEditText.requestFocus();
+        if (previousValue != null) {
+            binding.inputEditText.getText().append(previousValue);
+        }
+        builder.setView(binding.getRoot());
+        builder.setNegativeButton(R.string.cancel, null);
+        final AlertDialog dialog = builder.create();
+        dialog.setOnShowListener(d -> SoftKeyboardUtils.showKeyboard(binding.inputEditText));
+        dialog.show();
+        View.OnClickListener clickListener = v -> {
+            String value = binding.inputEditText.getText().toString();
+            if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) {
+                String error = callback.onValueEdited(value);
+                if (error != null) {
+                    binding.inputLayout.setError(error);
+                    return;
+                }
+            }
+            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+            dialog.dismiss();
+        };
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(clickListener);
+        dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((v -> {
+            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+            dialog.dismiss();
+        }));
+        dialog.setCanceledOnTouchOutside(false);
+        dialog.setOnDismissListener(dialog1 -> {
+            SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText);
+        });
+    }
+
+    protected boolean hasStoragePermission(int requestCode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
+                return false;
+            } else {
+                return true;
+            }
+        } else {
+            return true;
+        }
+    }
+
+    protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
+            mPendingConferenceInvite = ConferenceInvite.parse(data);
+            if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
+                if (mPendingConferenceInvite.execute(this)) {
+                    mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
+                    mToast.show();
+                }
+                mPendingConferenceInvite = null;
+            }
+        }
+    }
+
+    public boolean copyTextToClipboard(String text, int labelResId) {
+        ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+        String label = getResources().getString(labelResId);
+        if (mClipBoardManager != null) {
+            ClipData mClipData = ClipData.newPlainText(label, text);
+            mClipBoardManager.setPrimaryClip(mClipData);
+            return true;
+        }
+        return false;
+    }
+
+    protected boolean manuallyChangePresence() {
+        return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
+    }
+
+    protected String getShareableUri() {
+        return getShareableUri(false);
+    }
+
+    protected String getShareableUri(boolean http) {
+        return null;
+    }
+
+    protected void shareLink(boolean http) {
+        String uri = getShareableUri(http);
+        if (uri == null || uri.isEmpty()) {
+            return;
+        }
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("text/plain");
+        intent.putExtra(Intent.EXTRA_TEXT, getShareableUri(http));
+        try {
+            startActivity(Intent.createChooser(intent, getText(R.string.share_uri_with)));
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    protected void launchOpenKeyChain(long keyId) {
+        PgpEngine pgp = XmppActivity.this.xmppConnectionService.getPgpEngine();
+        try {
+            startIntentSenderForResult(
+                    pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0,
+                    0, 0);
+        } catch (Throwable e) {
+            Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    protected int findTheme() {
+        return ThemeHelper.find(this);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public boolean onMenuOpened(int id, Menu menu) {
+        if (id == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null) {
+            MenuDoubleTabUtil.recordMenuOpen();
+        }
+        return super.onMenuOpened(id, menu);
+    }
+
+    protected void showQrCode() {
+        showQrCode(getShareableUri());
+    }
+
+    protected void showQrCode(final String uri) {
+        if (uri == null || uri.isEmpty()) {
+            return;
+        }
+        Point size = new Point();
+        getWindowManager().getDefaultDisplay().getSize(size);
+        final int width = (size.x < size.y ? size.x : size.y);
+        Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width);
+        ImageView view = new ImageView(this);
+        view.setBackgroundColor(Color.WHITE);
+        view.setImageBitmap(bitmap);
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setView(view);
+        builder.create().show();
+    }
+
+    protected Account extractAccount(Intent intent) {
+        final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
+        try {
+            return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null;
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    public AvatarService avatarService() {
+        return xmppConnectionService.getAvatarService();
+    }
+
+    public void loadBitmap(Message message, ImageView imageView) {
+        Bitmap bm;
+        try {
+            bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true);
+        } catch (IOException e) {
+            bm = null;
+        }
+        if (bm != null) {
+            cancelPotentialWork(message, imageView);
+            imageView.setImageBitmap(bm);
+            imageView.setBackgroundColor(0x00000000);
+        } else {
+            if (cancelPotentialWork(message, imageView)) {
+                imageView.setBackgroundColor(0xff333333);
+                imageView.setImageDrawable(null);
+                final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+                final AsyncDrawable asyncDrawable = new AsyncDrawable(
+                        getResources(), null, task);
+                imageView.setImageDrawable(asyncDrawable);
+                try {
+                    task.execute(message);
+                } catch (final RejectedExecutionException ignored) {
+                    ignored.printStackTrace();
+                }
+            }
+        }
+    }
+
+    protected interface OnValueEdited {
+        String onValueEdited(String value);
+    }
+
+    public static class ConferenceInvite {
+        private String uuid;
+        private List<Jid> jids = new ArrayList<>();
+
+        public static ConferenceInvite parse(Intent data) {
+            ConferenceInvite invite = new ConferenceInvite();
+            invite.uuid = data.getStringExtra(ChooseContactActivity.EXTRA_CONVERSATION);
+            if (invite.uuid == null) {
+                return null;
+            }
+            invite.jids.addAll(ChooseContactActivity.extractJabberIds(data));
+            return invite;
+        }
+
+        public boolean execute(XmppActivity activity) {
+            XmppConnectionService service = activity.xmppConnectionService;
+            Conversation conversation = service.findConversationByUuid(this.uuid);
+            if (conversation == null) {
+                return false;
+            }
+            if (conversation.getMode() == Conversation.MODE_MULTI) {
+                for (Jid jid : jids) {
+                    service.invite(conversation, jid);
+                }
+                return false;
+            } else {
+                jids.add(conversation.getJid().asBareJid());
+                return service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
+            }
+        }
+    }
+
+    static class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
+        private final WeakReference<ImageView> imageViewReference;
+        private Message message = null;
+
+        private BitmapWorkerTask(ImageView imageView) {
+            this.imageViewReference = new WeakReference<>(imageView);
+        }
+
+        @Override
+        protected Bitmap doInBackground(Message... params) {
+            if (isCancelled()) {
+                return null;
+            }
+            message = params[0];
+            try {
+                final XmppActivity activity = find(imageViewReference);
+                if (activity != null && activity.xmppConnectionService != null) {
+                    return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false);
+                } else {
+                    return null;
+                }
+            } catch (IOException e) {
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(final Bitmap bitmap) {
+            if (!isCancelled()) {
+                final ImageView imageView = imageViewReference.get();
+                if (imageView != null) {
+                    imageView.setImageBitmap(bitmap);
+                    imageView.setBackgroundColor(bitmap == null ? 0xff333333 : 0x00000000);
+                }
+            }
+        }
+    }
+
+    private static class AsyncDrawable extends BitmapDrawable {
+        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+        private AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+            super(res, bitmap);
+            bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+        }
+
+        private BitmapWorkerTask getBitmapWorkerTask() {
+            return bitmapWorkerTaskReference.get();
+        }
+    }
+
+    public static XmppActivity find(@NonNull WeakReference<ImageView> viewWeakReference) {
+        final View view = viewWeakReference.get();
+        return view == null ? null : find(view);
+    }
+
+    public static XmppActivity find(@NonNull final View view) {
+        Context context = view.getContext();
+        while (context instanceof ContextWrapper) {
+            if (context instanceof XmppActivity) {
+                return (XmppActivity) context;
+            }
+            context = ((ContextWrapper) context).getBaseContext();
+        }
+        return null;
+    }
 }

src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java 🔗

@@ -106,12 +106,21 @@ public class PresenceSelector {
         builder.setPositiveButton(
                 R.string.ok,
                 (dialog, which) -> onFullJidSelected.onFullJidSelected(
-                        Jid.of(contact.getJid().getLocal(), contact.getJid().getDomain(), resourceArray[selectedResource.get()])
+                        getNextCounterpart(contact, resourceArray[selectedResource.get()])
                 )
         );
         builder.create().show();
     }
 
+
+    public static Jid getNextCounterpart(final Contact contact, final String resource) {
+        if (resource.isEmpty()) {
+            return contact.getJid().asBareJid();
+        } else {
+            return contact.getJid().withResource(resource);
+        }
+    }
+
     public static void warnMutualPresenceSubscription(Activity activity, final Conversation conversation, final OnPresenceSelected listener) {
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         builder.setTitle(conversation.getContact().getJid().toString());