From fda9e7b51cf243658ff4fbff35ea56a630b757dc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 13 Jun 2020 09:59:39 +0200 Subject: [PATCH] make presence selector work with empty resources (bare jid) --- .../siacs/conversations/ui/XmppActivity.java | 1788 ++++++++--------- .../ui/util/PresenceSelector.java | 11 +- 2 files changed, 902 insertions(+), 897 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 93196a8b0..fcdccb9c4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/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 adhocCallback = new UiCallback() { - @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 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() { - - @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() { - @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 adhocCallback = new UiCallback() { + @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 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() { + + @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() { + @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 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 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 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(); + } - protected boolean manuallyChangePresence() { - return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); - } + 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 String getShareableUri() { - return getShareableUri(false); - } + protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback) { + quickEdit(previousValue, callback, hint, false, false); + } - protected String getShareableUri(boolean http) { - return null; - } + protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) { + quickEdit(previousValue, callback, hint, false, permitEmpty); + } - 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 quickPasswordEdit(String previousValue, OnValueEdited callback) { + quickEdit(previousValue, callback, R.string.password, true, false); + } - 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(); - } - } + @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); + }); + } - @Override - public void onResume() { - super.onResume(); - } + 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 int findTheme() { - return ThemeHelper.find(this); - } + 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; + } + } + } - @Override - public void onPause() { - super.onPause(); - } + 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; + } - @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 boolean manuallyChangePresence() { + return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); + } - protected void showQrCode() { - showQrCode(getShareableUri()); - } + protected String getShareableUri() { + return getShareableUri(false); + } - 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 String getShareableUri(boolean http) { + return null; + } - 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; - } - } + 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(); + } + } - public AvatarService avatarService() { - return xmppConnectionService.getAvatarService(); - } + 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(); + } + } - 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(); - } - } - } - } + @Override + public void onResume() { + super.onResume(); + } - protected interface OnValueEdited { - String onValueEdited(String value); - } + protected int findTheme() { + return ThemeHelper.find(this); + } - public static class ConferenceInvite { - private String uuid; - private List jids = new ArrayList<>(); + @Override + public void onPause() { + super.onPause(); + } - 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; - } + @Override + public boolean onMenuOpened(int id, Menu menu) { + if (id == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null) { + MenuDoubleTabUtil.recordMenuOpen(); + } + return super.onMenuOpened(id, menu); + } - 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); - } - } - } + protected void showQrCode() { + showQrCode(getShareableUri()); + } - static class BitmapWorkerTask extends AsyncTask { - private final WeakReference imageViewReference; - private Message message = null; + 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(); + } - private BitmapWorkerTask(ImageView imageView) { - this.imageViewReference = new WeakReference<>(imageView); - } + 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; + } + } - @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; - } - } + public AvatarService avatarService() { + return xmppConnectionService.getAvatarService(); + } - @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); - } - } - } - } + 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(); + } + } + } + } - private static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; + protected interface OnValueEdited { + String onValueEdited(String value); + } - private AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } + public static class ConferenceInvite { + private String uuid; + private List jids = new ArrayList<>(); - private BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + 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 static XmppActivity find(@NonNull WeakReference viewWeakReference) { - final View view = viewWeakReference.get(); - return view == null ? null : find(view); - } + 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); + } + } + } - 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; - } + static class BitmapWorkerTask extends AsyncTask { + private final WeakReference 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 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 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; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java index 14e82b406..870fdc268 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java +++ b/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());