From 9350f362b793723db1dda52268f5df65c8dcd30d Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Thu, 22 Mar 2018 23:21:10 +0100 Subject: vastly untested refactor. pushing for backup purposes --- .../de/pixart/messenger/ui/BlockContactDialog.java | 3 +- .../pixart/messenger/ui/ConversationActivity.java | 1023 +------------ .../pixart/messenger/ui/ConversationFragment.java | 1528 ++++++++++++++------ .../de/pixart/messenger/ui/ShareWithActivity.java | 7 +- .../de/pixart/messenger/ui/TrustKeysActivity.java | 2 +- .../java/de/pixart/messenger/ui/XmppActivity.java | 20 +- .../messenger/ui/adapter/MessageAdapter.java | 2 +- .../pixart/messenger/ui/util/ActivityResult.java | 50 + .../pixart/messenger/ui/util/AttachmentTool.java | 61 + .../ui/util/ConversationMenuConfigurator.java | 109 ++ .../pixart/messenger/ui/util/PresenceSelector.java | 136 ++ .../pixart/messenger/ui/util/SendButtonAction.java | 77 + .../pixart/messenger/ui/util/SendButtonTool.java | 174 +++ .../pixart/messenger/utils/SendButtonAction.java | 77 - .../java/de/pixart/messenger/utils/UIHelper.java | 17 - src/main/res/menu/activity_conversations.xml | 22 + src/main/res/menu/conversations.xml | 116 -- src/main/res/menu/fragment_conversation.xml | 116 ++ src/main/res/values/strings.xml | 2 +- 19 files changed, 1843 insertions(+), 1699 deletions(-) create mode 100644 src/main/java/de/pixart/messenger/ui/util/ActivityResult.java create mode 100644 src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java create mode 100644 src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java create mode 100644 src/main/java/de/pixart/messenger/ui/util/PresenceSelector.java create mode 100644 src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java create mode 100644 src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java delete mode 100644 src/main/java/de/pixart/messenger/utils/SendButtonAction.java create mode 100644 src/main/res/menu/activity_conversations.xml delete mode 100644 src/main/res/menu/conversations.xml create mode 100644 src/main/res/menu/fragment_conversation.xml (limited to 'src/main') diff --git a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java index 0775d2cf7..0a27274fd 100644 --- a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java +++ b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java @@ -18,8 +18,7 @@ import de.pixart.messenger.entities.Blockable; import de.pixart.messenger.entities.Conversation; public final class BlockContactDialog { - public static void show(final XmppActivity xmppActivity, - final Blockable blockable) { + public static void show(final XmppActivity xmppActivity, final Blockable blockable) { final AlertDialog.Builder builder = new AlertDialog.Builder(xmppActivity); final boolean isBlocked = blockable.isBlocked(); builder.setNegativeButton(R.string.cancel, null); diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index bf049fe70..2f43461fe 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -4,32 +4,24 @@ import android.annotation.SuppressLint; import android.app.FragmentTransaction; import android.app.PendingIntent; import android.content.ActivityNotFoundException; -import android.content.ClipData; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.provider.MediaStore; import android.provider.Settings; -import android.support.media.ExifInterface; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.util.Log; import android.util.Pair; -import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -38,9 +30,6 @@ import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; @@ -49,28 +38,17 @@ import android.widget.Toast; import net.java.otr4j.session.SessionStatus; -import org.openintents.openpgp.util.OpenPgpApi; - -import java.io.File; -import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import de.pixart.messenger.Config; import de.pixart.messenger.R; -import de.pixart.messenger.crypto.axolotl.AxolotlService; -import de.pixart.messenger.crypto.axolotl.FingerprintStatus; import de.pixart.messenger.entities.Account; -import de.pixart.messenger.entities.Blockable; -import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.Presence; -import de.pixart.messenger.entities.Transferable; -import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.UpdateService; import de.pixart.messenger.services.XmppConnectionService; @@ -79,8 +57,6 @@ import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate; import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate; import de.pixart.messenger.ui.adapter.ConversationAdapter; import de.pixart.messenger.utils.ExceptionHelper; -import de.pixart.messenger.utils.FileUtils; -import de.pixart.messenger.utils.SendButtonAction; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.XmppConnection; @@ -89,7 +65,6 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; -import static de.pixart.messenger.ui.ShowFullscreenMessageActivity.getMimeType; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, View.OnClickListener { @@ -98,41 +73,22 @@ public class ConversationActivity extends XmppActivity public static final String ACTION_DESTROY_MUC = "de.pixart.messenger.DESTROY_MUC"; public static final String CONVERSATION = "conversationUuid"; public static final String EXTRA_DOWNLOAD_UUID = "de.pixart.messenger.download_uuid"; - public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; public static final String TEXT = "text"; public static final String NICK = "nick"; public static final String PRIVATE_MESSAGE = "pm"; - public static final int REQUEST_SEND_MESSAGE = 0x0201; - public static final int REQUEST_DECRYPT_PGP = 0x0202; - public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; - public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; - public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; - public static final int REQUEST_START_DOWNLOAD = 0x0210; - public static final int REQUEST_ADD_EDITOR_CONTENT = 0x0211; - public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; - public static final int ATTACHMENT_CHOICE_TAKE_FROM_CAMERA = 0x0302; - public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; - public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; - public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; - public static final int ATTACHMENT_CHOICE_CHOOSE_VIDEO = 0x0306; - public static final int ATTACHMENT_CHOICE_INVALID = 0x0399; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_IMAGE_URI = "state_pending_image_uri"; private static final String STATE_PENDING_PHOTO_URI = "state_pending_photo_uri"; private static final String STATE_FIRST_VISIBLE = "first_visible"; private static final String STATE_OFFSET_FROM_TOP = "offset_from_top"; - final private List mPendingImageUris = new ArrayList<>(); - final private List mPendingPhotoUris = new ArrayList<>(); - final private List mPendingFileUris = new ArrayList<>(); + private String mOpenConversation = null; private boolean mPanelOpen = true; private AtomicBoolean mShouldPanelBeOpen = new AtomicBoolean(false); private Pair mScrollPosition = null; - private Uri mPendingGeoUri = null; private boolean forbidProcessingPendings = false; - private Message mPendingDownloadableMessage = null; private boolean conversationWasSelectedByKeyboard = false; @@ -148,9 +104,7 @@ public class ConversationActivity extends XmppActivity private boolean mActivityPaused = false; private AtomicBoolean mRedirected = new AtomicBoolean(false); - private Pair mPostponedActivityResult; private boolean mUnprocessedNewIntent = false; - public Uri mPendingEditorContent = null; private boolean showLastSeen = false; long FirstStartTime = -1; @@ -226,18 +180,6 @@ public class ConversationActivity extends XmppActivity } else { mScrollPosition = null; } - String pending_image = savedInstanceState.getString(STATE_PENDING_IMAGE_URI, null); - if (pending_image != null) { - Log.d(Config.LOGTAG, "ConversationActivity.onCreate() - restoring pending image uri"); - mPendingImageUris.clear(); - mPendingImageUris.add(Uri.parse(pending_image)); - } - String pending_photo = savedInstanceState.getString(STATE_PENDING_PHOTO_URI, null); - if (pending_photo != null) { - Log.d(Config.LOGTAG, "ConversationActivity.onCreate() - restoring pending photo uri"); - mPendingPhotoUris.clear(); - mPendingPhotoUris.add(Uri.parse(pending_photo)); - } } setContentView(R.layout.fragment_conversations_overview); @@ -471,319 +413,9 @@ public class ConversationActivity extends XmppActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.conversations, menu); - final MenuItem menuSecure = menu.findItem(R.id.action_security); - final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat); - final MenuItem menuArchiveMuc = menu.findItem(R.id.action_archive_muc); - final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); - final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); - final MenuItem menuAdd = menu.findItem(R.id.action_add); - final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); - final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice); - final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location); - final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates); - final MenuItem menuInviteUser = menu.findItem(R.id.action_invite_user); - final MenuItem menuSearchHistory = menu.findItem(R.id.action_search_history); - final MenuItem menuActionAccounts = menu.findItem(R.id.action_accounts); - if (xmppConnectionServiceBound) { - if (xmppConnectionService.getAccounts().size() == 1 && !xmppConnectionService.multipleAccounts()) { - menuActionAccounts.setTitle(R.string.mgmt_account_edit); - } else { - menuActionAccounts.setTitle(R.string.action_accounts); - } - } - if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { - menuArchiveChat.setVisible(false); - menuArchiveMuc.setVisible(false); - menuSecure.setVisible(false); - menuInviteContact.setVisible(false); - menuAttach.setVisible(false); - menuAttachLocation.setVisible(false); - menuAttachSoundRecorder.setVisible(false); - menuClearHistory.setVisible(false); - menuSearchHistory.setVisible(false); - if (xmppConnectionService.installedFromFDroid()) { - menuUpdater.setVisible(false); - } else { - menuUpdater.setVisible(true); - } - } else { - menuAdd.setVisible(!isConversationsOverviewHideable()); - //hide settings, accounts and updater in all menus except in main window - menuUpdater.setVisible(false); - menuInviteUser.setVisible(false); - - if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getMode() == Conversation.MODE_SINGLE) { - menuArchiveMuc.setVisible(false); - } else { - menuArchiveChat.setVisible(false); - } - if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { - menuSecure.setIcon(R.drawable.ic_lock_white_24dp); - } - if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { - menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); - menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); - menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice - } else { - menuSecure.setVisible(Config.multipleEncryptionChoices()); - menuInviteContact.setVisible(xmppConnectionService != null && xmppConnectionService.findConferenceServer(getSelectedConversation().getAccount()) != null); - } - menuAttachLocation.setVisible(true); - menuAttachSoundRecorder.setVisible(true); - configureEncryptionMenu(getSelectedConversation(), menu); - } - } return super.onCreateOptionsMenu(menu); } - private static void configureEncryptionMenu(Conversation conversation, Menu menu) { - MenuItem otr = menu.findItem(R.id.encryption_choice_otr); - MenuItem none = menu.findItem(R.id.encryption_choice_none); - MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp); - MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl); - pgp.setVisible(Config.supportOpenPgp()); - none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); - otr.setVisible(Config.supportOtr()); - axolotl.setVisible(Config.supportOmemo()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setVisible(false); - } - if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) { - axolotl.setEnabled(false); - } - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - none.setChecked(true); - break; - case Message.ENCRYPTION_OTR: - otr.setChecked(true); - break; - case Message.ENCRYPTION_PGP: - pgp.setChecked(true); - break; - case Message.ENCRYPTION_AXOLOTL: - axolotl.setChecked(true); - break; - default: - none.setChecked(true); - break; - } - } - - protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { - final Conversation conversation = getSelectedConversation(); - final Account account = conversation.getAccount(); - final OnPresenceSelected callback = () -> { - final Intent intent = new Intent(); - boolean chooser = false; - String fallbackPackageId = null; - switch (attachmentChoice) { - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - intent.setAction(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } - intent.setType("image/*"); - chooser = true; - break; - case ATTACHMENT_CHOICE_CHOOSE_VIDEO: - chooser = true; - intent.setType("video/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: - AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this); - builder.setTitle(getString(R.string.attach_take_from_camera)); - builder.setNegativeButton(getString(R.string.action_take_photo), - (dialog, which) -> { - Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - mPendingPhotoUris.clear(); - mPendingPhotoUris.add(uri); - startActivityForResult(intent, attachmentChoice); - }); - builder.setPositiveButton(getString(R.string.action_take_video), - (dialog, which) -> { - Uri uri = xmppConnectionService.getFileBackend().getTakeVideoUri(); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); - mPendingFileUris.clear(); - mPendingFileUris.add(uri); - startActivityForResult(intent, attachmentChoice); - }); - builder.create().show(); - break; - case ATTACHMENT_CHOICE_CHOOSE_FILE: - chooser = true; - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; - case ATTACHMENT_CHOICE_RECORD_VOICE: - startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class), attachmentChoice); - break; - case ATTACHMENT_CHOICE_LOCATION: - startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class), attachmentChoice); - break; - } - if (intent.resolveActivity(getPackageManager()) != null) { - Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice); - if (chooser) { - startActivityForResult( - Intent.createChooser(intent, getString(R.string.perform_action_with)), - attachmentChoice); - } else { - startActivityForResult(intent, attachmentChoice); - } - } else if (fallbackPackageId != null) { - startActivity(getInstallApkIntent(fallbackPackageId)); - } - }; - if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { - conversation.setNextCounterpart(null); - callback.onPresenceSelected(); - } else { - selectPresence(conversation, callback); - } - } - - private Intent getInstallApkIntent(final String packageId) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("market://details?id=" + packageId)); - if (intent.resolveActivity(getPackageManager()) != null) { - return intent; - } else { - intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId)); - return intent; - } - } - - public void attachFile(final int attachmentChoice) { - if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { - if (!hasStoragePermission(attachmentChoice)) { - return; - } - } - if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) { - if (!hasMicPermission(attachmentChoice)) { - return; - } - } - if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION) { - if (!hasLocationPermission(attachmentChoice)) { - return; - } - } - try { - getPreferences().edit() - .putString(RECENTLY_USED_QUICK_ACTION, SendButtonAction.of(attachmentChoice).toString()) - .apply(); - } catch (IllegalArgumentException e) { - //just do not save - } - final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(); - final int mode = conversation.getMode(); - if (encryption == Message.ENCRYPTION_PGP) { - if (hasPgp()) { - if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) { - xmppConnectionService.getPgpEngine().hasKey( - conversation.getContact(), - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Contact contact) { - ConversationActivity.this.runIntent(pi, attachmentChoice); - } - - @Override - public void success(Contact contact) { - selectPresenceToAttachFile(attachmentChoice, encryption); - } - - @Override - public void error(int error, Contact contact) { - replaceToast(getString(error)); - } - }); - } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { - if (!conversation.getMucOptions().everybodyHasKeys()) { - Toast warning = Toast - .makeText(this, - R.string.missing_public_keys, - Toast.LENGTH_LONG); - warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); - warning.show(); - } - selectPresenceToAttachFile(attachmentChoice, encryption); - } else { - final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); - if (fragment != null) { - fragment.showNoPGPKeyDialog(false, - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - xmppConnectionService.updateConversation(conversation); - selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE); - } - }); - } - } - } else { - showInstallPgpDialog(); - } - } else { - if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { - selectPresenceToAttachFile(attachmentChoice, encryption); - } - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if (grantResults.length > 0) - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (requestCode == REQUEST_START_DOWNLOAD) { - if (this.mPendingDownloadableMessage != null) { - startDownloadable(this.mPendingDownloadableMessage); - } - } else if (requestCode == REQUEST_ADD_EDITOR_CONTENT) { - if (this.mPendingEditorContent != null) { - attachImageToConversation(this.mPendingEditorContent); - } - } else { - attachFile(requestCode); - } - } else { - Toast.makeText(this, R.string.no_permission, Toast.LENGTH_SHORT).show(); - } - } - - public void startDownloadable(Message message) { - if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { - this.mPendingDownloadableMessage = message; - return; - } - Transferable transferable = message.getTransferable(); - if (transferable != null) { - if (!transferable.start()) { - Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); - } - } else if (message.treatAsDownloadable()) { - xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); - } - } - @Override public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -792,57 +424,6 @@ public class ConversationActivity extends XmppActivity } else if (item.getItemId() == R.id.action_add) { startActivity(new Intent(this, StartConversationActivity.class)); return true; - } else if (getSelectedConversation() != null) { - switch (item.getItemId()) { - case R.id.encryption_choice_axolotl: - case R.id.encryption_choice_otr: - case R.id.encryption_choice_pgp: - case R.id.encryption_choice_none: - handleEncryptionSelection(item); - break; - case R.id.attach_choose_picture: - case R.id.attach_take_picture: - case R.id.attach_choose_file: - case R.id.attach_record_voice: - case R.id.attach_location: - handleAttachmentSelection(item); - break; - case R.id.action_archive_chat: - this.endConversation(getSelectedConversation()); - break; - case R.id.action_archive_muc: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.action_end_conversation_muc)); - builder.setMessage(getString(R.string.leave_conference_warning)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.action_end_conversation_muc), - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - endConversation(getSelectedConversation()); - } - }); - builder.create().show(); - break; - case R.id.action_invite: - inviteToConversation(getSelectedConversation()); - break; - case R.id.action_clear_history: - clearHistoryDialog(getSelectedConversation()); - break; - case R.id.action_block: - BlockContactDialog.show(this, getSelectedConversation()); - break; - case R.id.action_unblock: - BlockContactDialog.show(this, getSelectedConversation()); - break; - case R.id.action_search_history: - mConversationFragment.showSearchField(); - break; - default: - break; - } - return super.onOptionsItemSelected(item); } else { return super.onOptionsItemSelected(item); } @@ -873,50 +454,6 @@ public class ConversationActivity extends XmppActivity } } - @SuppressLint("InflateParams") - protected void clearHistoryDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.clear_conversation_history)); - final View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null); - final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - endConversationCheckBox.setVisibility(View.VISIBLE); - endConversationCheckBox.setChecked(true); - } - builder.setView(dialogView); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.delete_messages), (dialog, which) -> { - ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); - if (endConversationCheckBox.isChecked()) { - endConversation(conversation); - } else { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - } - }); - builder.create().show(); - } - - private void handleAttachmentSelection(MenuItem item) { - switch (item.getItemId()) { - case R.id.attach_choose_picture: - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA); - break; - case R.id.attach_choose_file: - attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); - break; - case R.id.attach_record_voice: - attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); - break; - case R.id.attach_location: - attachFile(ATTACHMENT_CHOICE_LOCATION); - break; - } - } - public void verifyOtrSessionDialog(final Conversation conversation, View view) { if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); @@ -952,49 +489,6 @@ public class ConversationActivity extends XmppActivity popup.show(); } - private void handleEncryptionSelection(MenuItem item) { - Conversation conversation = getSelectedConversation(); - if (conversation == null) { - return; - } - final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); - switch (item.getItemId()) { - case R.id.encryption_choice_none: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - item.setChecked(true); - break; - case R.id.encryption_choice_otr: - conversation.setNextEncryption(Message.ENCRYPTION_OTR); - item.setChecked(true); - break; - case R.id.encryption_choice_pgp: - if (hasPgp()) { - if (conversation.getAccount().getPgpSignature() != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - item.setChecked(true); - } else { - announcePgp(conversation.getAccount(), conversation, null, onOpenPGPKeyPublished); - } - } else { - showInstallPgpDialog(); - } - break; - case R.id.encryption_choice_axolotl: - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) - + "Enabled axolotl for Contact " + conversation.getContact().getJid()); - conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); - item.setChecked(true); - break; - default: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - break; - } - xmppConnectionService.updateConversation(conversation); - fragment.updateChatMsgHint(); - invalidateOptionsMenu(); - refreshUi(); - } - @Override public void onBackPressed() { if (!isConversationsOverviewVisable() && mConversationFragment.isSearchFieldVisible()) { @@ -1189,7 +683,7 @@ public class ConversationActivity extends XmppActivity savedInstanceState.remove(STATE_OPEN_CONVERSATION); } savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); - if (this.mPendingImageUris.size() >= 1) { + /*if (this.mPendingImageUris.size() >= 1) { Log.d(Config.LOGTAG, "ConversationActivity.onSaveInstanceState() - saving pending image uri"); savedInstanceState.putString(STATE_PENDING_IMAGE_URI, this.mPendingImageUris.get(0).toString()); } else if (this.mPendingPhotoUris.size() >= 1) { @@ -1197,16 +691,12 @@ public class ConversationActivity extends XmppActivity } else { savedInstanceState.remove(STATE_PENDING_IMAGE_URI); savedInstanceState.remove(STATE_PENDING_PHOTO_URI); - } + }*/ super.onSaveInstanceState(savedInstanceState); } private void clearPending() { - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingPhotoUris.clear(); - mPendingGeoUri = null; - mPostponedActivityResult = null; + mConversationFragment.clearPending(); } private void redirectToStartConversationActivity(boolean noAnimation) { @@ -1325,42 +815,7 @@ public class ConversationActivity extends XmppActivity } } - if (this.mPostponedActivityResult != null) { - this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); - } - - final boolean stopping = isStopping(); - - if (!forbidProcessingPendings) { - int ImageUrisCount = mPendingImageUris.size(); - if (ImageUrisCount == 1) { - Uri uri = mPendingImageUris.get(0); - Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching image to conversations. stopping=" + Boolean.toString(stopping)); - attachImageToConversation(getSelectedConversation(), uri, false); - } else { - for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - Uri foo = i.next(); - Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching images to conversations. stopping=" + Boolean.toString(stopping)); - attachImagesToConversation(getSelectedConversation(), foo); - } - } - - for (Iterator i = mPendingPhotoUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching photo to conversations. stopping=" + Boolean.toString(stopping)); - attachPhotoToConversation(getSelectedConversation(), i.next()); - } - - for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationActivity.onBackendConnected() - attaching file to conversations. stopping=" + Boolean.toString(stopping)); - attachFileToConversation(getSelectedConversation(), i.next()); - } - - if (mPendingGeoUri != null) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - mPendingGeoUri = null; - } - } - forbidProcessingPendings = false; + mConversationFragment.onBackendConnected(); if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService) && !mRedirected.get()) { openBatteryOptimizationDialogIfNeeded(); @@ -1421,11 +876,12 @@ public class ConversationActivity extends XmppActivity if (downloadUuid != null) { final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); if (message != null) { - startDownloadable(message); + //startDownloadable(message); } - } else { - mUnprocessedNewIntent = false; } + } else { + mUnprocessedNewIntent = false; + } } @@ -1448,146 +904,17 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.getNotificationService().setOpenConversation(null); } - @SuppressLint("NewApi") - private static List extractUriFromIntent(final Intent intent) { - List uris = new ArrayList<>(); - if (intent == null) { - return uris; - } - Uri uri = intent.getData(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { - final ClipData clipData = intent.getClipData(); - if (clipData != null) { - for (int i = 0; i < clipData.getItemCount(); ++i) { - uris.add(clipData.getItemAt(i).getUri()); - } - } - } else { - uris.add(uri); - } - return uris; - } - @Override protected void onActivityResult(final int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - if (requestCode == REQUEST_DECRYPT_PGP) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - } else if (requestCode == REQUEST_CHOOSE_PGP_ID) { - // the user chose OpenPGP for encryption and selected his key in the PGP provider - if (xmppConnectionServiceBound) { - if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { - // associate selected PGP keyId with the account - mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); - // we need to announce the key as described in XEP-027 - announcePgp(mSelectedConversation.getAccount(), null, null, onOpenPGPKeyPublished); - } else { - choosePgpSignId(mSelectedConversation.getAccount()); - } - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } else if (requestCode == REQUEST_ANNOUNCE_PGP) { - if (xmppConnectionServiceBound) { - announcePgp(mSelectedConversation.getAccount(), mSelectedConversation, data, onOpenPGPKeyPublished); - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { - mPendingImageUris.clear(); - mPendingImageUris.addAll(extractUriFromIntent(data)); - int ImageUrisCount = mPendingImageUris.size(); - if (xmppConnectionServiceBound) { - if (ImageUrisCount == 1) { - Uri uri = mPendingImageUris.get(0); - Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); - attachImageToConversation(getSelectedConversation(), uri, false); - } else { - for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching images to conversations. CHOOSE_IMAGES"); - attachImagesToConversation(getSelectedConversation(), i.next()); - } - } - } - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE - || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE - || requestCode == ATTACHMENT_CHOICE_CHOOSE_VIDEO - || requestCode == ATTACHMENT_CHOICE_TAKE_FROM_CAMERA) { - final List uris = extractUriFromIntent(data); - final Conversation c = getSelectedConversation(); - final OnPresenceSelected callback = new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - mPendingFileUris.clear(); - mPendingFileUris.addAll(uris); - if (xmppConnectionServiceBound) { - for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - if (requestCode == ATTACHMENT_CHOICE_TAKE_FROM_CAMERA && getMimeType(i.next().toString()).contains("image")) { - return; - } - Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); - attachFileToConversation(c, i.next()); - } - } - } - }; - if (c == null || c.getMode() == Conversation.MODE_MULTI - || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c)) - || c.getNextEncryption() == Message.ENCRYPTION_OTR) { - callback.onPresenceSelected(); - } else { - selectPresence(c, callback); - } - } else if (requestCode == ATTACHMENT_CHOICE_TAKE_FROM_CAMERA) { - if (mPendingPhotoUris.size() == 1) { - Uri uri = FileBackend.getIndexableTakePhotoUri(mPendingPhotoUris.get(0)); - mPendingPhotoUris.set(0, uri); - if (xmppConnectionServiceBound) { - Log.d(Config.LOGTAG, "ConversationActivity.onActivityResult() - attaching photo to conversations. TAKE_FROM_CAMERA"); - attachPhotoToConversation(getSelectedConversation(), uri); - mPendingPhotoUris.clear(); - } - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - intent.setData(uri); - sendBroadcast(intent); - } else { - mPendingPhotoUris.clear(); - } - } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) { - double latitude = data.getDoubleExtra("latitude", 0); - double longitude = data.getDoubleExtra("longitude", 0); - this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); - if (xmppConnectionServiceBound) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - this.mPendingGeoUri = null; - } - } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { - this.forbidProcessingPendings = true; - if (xmppConnectionServiceBound) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - this.mPostponedActivityResult = null; - } else { - this.mPostponedActivityResult = new Pair<>(requestCode, data); - } - - } - } else { - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingPhotoUris.clear(); - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - mConversationFragment.onActivityResult(requestCode, resultCode, data); - } + if (resultCode != RESULT_OK) { if (requestCode == REQUEST_BATTERY_OP) { setNeverAskForBatteryOptimizationsAgain(); } } } - private long getMaxHttpUploadSize(Conversation conversation) { + public long getMaxHttpUploadSize(Conversation conversation) { final XmppConnection connection = conversation.getAccount().getXmppConnection(); return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); } @@ -1634,263 +961,6 @@ public class ConversationActivity extends XmppActivity return false; } - private void attachLocationToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback() { - - @Override - public void success(Message message) { - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(int errorCode, Message object) { - - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - - } - }); - } - - private void attachFileToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_file), Toast.LENGTH_LONG); - prepareFileToast.show(); - delegateUriPermissionsToService(uri); - xmppConnectionService.attachFileToConversation(conversation, uri, new UiInformableCallback() { - @Override - public void inform(final String text) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(() -> replaceToast(text)); - } - - @Override - public void success(Message message) { - runOnUiThread(() -> hideToast()); - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int errorCode, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(() -> replaceToast(getString(errorCode))); - - } - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - hidePrepareFileToast(prepareFileToast); - } - }); - } - - private void attachPhotoToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); - prepareFileToast.show(); - delegateUriPermissionsToService(uri); - xmppConnectionService.attachImageToConversation(conversation, uri, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - hidePrepareFileToast(prepareFileToast); - } - - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int error, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(error)); - } - }); - } - }); - } - - private void attachImagesToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } - final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); - prepareFileToast.show(); - delegateUriPermissionsToService(uri); - xmppConnectionService.attachImageToConversation(conversation, uri, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - hidePrepareFileToast(prepareFileToast); - } - - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int error, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(error)); - } - }); - } - }); - } - - public void attachImageToConversation(Uri uri) { - this.attachImageToConversation(getSelectedConversation(), uri, true); - } - - private void attachImageToConversation(Conversation conversation, Uri uri, boolean sendAsIs) { - if (conversation == null) { - return; - } - if (sendAsIs) { - sendImage(conversation, uri); - return; - } - final Conversation conversation_preview = conversation; - final Uri uri_preview = uri; - Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(this, uri)); - File file = null; - ExifInterface exif = null; - int orientation = 0; - try { - file = new File(FileUtils.getPath(this, uri)); - } catch (Exception e) { - e.printStackTrace(); - } - if (file != null) { - try { - exif = new ExifInterface(file.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - } - Log.d(Config.LOGTAG, "EXIF: " + orientation); - Bitmap rotated_image = null; - Log.d(Config.LOGTAG, "Rotate image"); - rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation); - if (rotated_image != null) { - int scaleSize = 600; - int originalWidth = rotated_image.getWidth(); - int originalHeight = rotated_image.getHeight(); - int newWidth = -1; - int newHeight = -1; - float multFactor; - if (originalHeight > originalWidth) { - newHeight = scaleSize; - multFactor = (float) originalWidth / (float) originalHeight; - newWidth = (int) (newHeight * multFactor); - } else if (originalWidth > originalHeight) { - newWidth = scaleSize; - multFactor = (float) originalHeight / (float) originalWidth; - newHeight = (int) (newWidth * multFactor); - } else if (originalHeight == originalWidth) { - newHeight = scaleSize; - newWidth = scaleSize; - } - Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px"); - Bitmap preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false); - ImageView ImagePreview = new ImageView(this); - LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); - ImagePreview.setLayoutParams(vp); - ImagePreview.setMaxWidth(newWidth); - ImagePreview.setMaxHeight(newHeight); - //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY); - //ImagePreview.setAdjustViewBounds(true); - ImagePreview.setPadding(5, 5, 5, 5); - ImagePreview.setImageBitmap(preview); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(ImagePreview); - builder.setTitle(R.string.send_image); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - sendImage(conversation_preview, uri_preview); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mPendingImageUris.clear(); - } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mPendingImageUris.clear(); - } - }); - } - AlertDialog alertDialog = builder.create(); - alertDialog.show(); - } else { - Toast.makeText(getApplicationContext(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show(); - } - } - - private void sendImage(Conversation conversation, Uri uri) { - final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG); - prepareFileToast.show(); - xmppConnectionService.attachImageToConversation(conversation, uri, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - hidePrepareFileToast(prepareFileToast); - } - - @Override - public void success(Message message) { - hidePrepareFileToast(prepareFileToast); - xmppConnectionService.sendMessage(message); - } - - @Override - public void error(final int error, Message message) { - hidePrepareFileToast(prepareFileToast); - runOnUiThread(new Runnable() { - @Override - public void run() { - replaceToast(getString(error)); - } - }); - } - }); - } - - private void hidePrepareFileToast(final Toast prepareFileToast) { - if (prepareFileToast != null) { - runOnUiThread(() -> prepareFileToast.cancel()); - } - } - public void updateConversationList() { xmppConnectionService.populateWithOrderedConversations(conversationList); if (!conversationList.contains(mSelectedConversation)) { @@ -1912,43 +982,6 @@ public class ConversationActivity extends XmppActivity } } - public void encryptTextMessage(Message message) { - xmppConnectionService.getPgpEngine().encrypt(message, - new UiCallback() { - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_MESSAGE); - } - - @Override - public void success(Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTED); - xmppConnectionService.sendMessage(message); - runOnUiThread(new Runnable() { - @Override - public void run() { - mConversationFragment.messageSent(); - } - }); - } - - @Override - public void error(final int error, Message message) { - runOnUiThread(new Runnable() { - @Override - public void run() { - mConversationFragment.doneSendingPgpMessage(); - Toast.makeText(ConversationActivity.this, - R.string.unable_to_connect_to_keychain, - Toast.LENGTH_SHORT - ).show(); - } - }); - } - }); - } - public boolean useSendButtonToIndicateStatus() { return getPreferences().getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status)); } @@ -1965,36 +998,6 @@ public class ConversationActivity extends XmppActivity return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); } - protected boolean trustKeysIfNeeded(int requestCode) { - return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); - } - - protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { - AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); - final List targets = axolotlService.getCryptoTargets(mSelectedConversation); - boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets); - boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty(); - boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); - boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); - boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); - if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { - axolotlService.createSessionsIfNeeded(mSelectedConversation); - Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); - String[] contacts = new String[targets.size()]; - for (int i = 0; i < contacts.length; ++i) { - contacts[i] = targets.get(i).toString(); - } - intent.putExtra("contacts", contacts); - intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); - intent.putExtra("choice", attachmentChoice); - intent.putExtra("conversation", mSelectedConversation.getUuid()); - startActivityForResult(intent, requestCode); - return true; - } else { - return false; - } - } - @Override protected void refreshUiReal() { updateConversationList(); @@ -2037,10 +1040,6 @@ public class ConversationActivity extends XmppActivity this.refreshUi(); } - public void unblockConversation(final Blockable conversation) { - xmppConnectionService.sendUnblockRequest(conversation); - } - public boolean enterIsSend() { return getPreferences().getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send)); } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index c00be0ffb..ec11f84d4 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -1,5 +1,6 @@ package de.pixart.messenger.ui; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; import android.app.PendingIntent; @@ -8,9 +9,16 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; +import android.provider.MediaStore; +import android.support.media.ExifInterface; import android.support.v13.view.inputmethod.InputConnectionCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.support.v7.app.AlertDialog; @@ -22,8 +30,9 @@ import android.util.Pair; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; -import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; @@ -35,8 +44,11 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.RelativeLayout; @@ -46,11 +58,16 @@ import android.widget.Toast; import net.java.otr4j.session.SessionStatus; +import org.openintents.openpgp.util.OpenPgpApi; + +import java.io.File; +import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; @@ -60,6 +77,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.crypto.axolotl.AxolotlService; +import de.pixart.messenger.crypto.axolotl.FingerprintStatus; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Blockable; import de.pixart.messenger.entities.Contact; @@ -75,28 +93,56 @@ import de.pixart.messenger.http.HttpDownloadConnection; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.XmppConnectionService; -import de.pixart.messenger.ui.XmppActivity.OnPresenceSelected; -import de.pixart.messenger.ui.XmppActivity.OnValueEdited; import de.pixart.messenger.ui.adapter.MessageAdapter; +import de.pixart.messenger.ui.util.ActivityResult; +import de.pixart.messenger.ui.util.AttachmentTool; +import de.pixart.messenger.ui.util.ConversationMenuConfigurator; +import de.pixart.messenger.ui.util.PresenceSelector; +import de.pixart.messenger.ui.util.SendButtonAction; +import de.pixart.messenger.ui.util.SendButtonTool; import de.pixart.messenger.ui.widget.EditMessage; +import de.pixart.messenger.utils.FileUtils; import de.pixart.messenger.utils.MessageUtils; import de.pixart.messenger.utils.NickValidityChecker; -import de.pixart.messenger.utils.SendButtonAction; import de.pixart.messenger.utils.StylingHelper; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.XmppConnection; import de.pixart.messenger.xmpp.chatstate.ChatState; import de.pixart.messenger.xmpp.jid.Jid; +import static de.pixart.messenger.ui.XmppActivity.EXTRA_ACCOUNT; +import static de.pixart.messenger.ui.XmppActivity.REQUEST_ANNOUNCE_PGP; +import static de.pixart.messenger.ui.XmppActivity.REQUEST_CHOOSE_PGP_ID; import static de.pixart.messenger.xmpp.Patches.ENCRYPTION_EXCEPTIONS; public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { + public static final int REQUEST_SEND_MESSAGE = 0x0201; + public static final int REQUEST_DECRYPT_PGP = 0x0202; + public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; + public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; + public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; + public static final int REQUEST_START_DOWNLOAD = 0x0210; + public static final int REQUEST_ADD_EDITOR_CONTENT = 0x0211; + public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; + public static final int ATTACHMENT_CHOICE_TAKE_FROM_CAMERA = 0x0302; + public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; + public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; + public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + public static final int ATTACHMENT_CHOICE_CHOOSE_VIDEO = 0x0306; + public static final int ATTACHMENT_CHOICE_INVALID = 0x0399; + + public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; + final protected List messageList = new ArrayList<>(); + final private List mPendingImageUris = new ArrayList<>(); + public EditText searchfield_input; + public Uri mPendingEditorContent = null; protected Conversation conversation; protected ListView messagesView; protected MessageAdapter messageListAdapter; protected Message lastHistoryMessage = null; + SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd. MMM yyyy", Locale.getDefault()); private EditMessage mEditMessage; private ImageButton mSendButton; private RelativeLayout snackbar; @@ -104,13 +150,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private TextView messagehint_message; private RelativeLayout textsend; private RelativeLayout searchfield; - public EditText searchfield_input; private ImageButton searchUp; private ImageButton searchDown; private TextView snackbarMessage; private TextView snackbarAction; private Toast messageLoaderToast; - SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd. MMM yyyy", Locale.getDefault()); + private ActivityResult postponedActivityResult = null; + private ConversationActivity activity; + protected OnClickListener clickToVerify = new OnClickListener() { + @Override + public void onClick(View v) { + activity.verifyOtrSessionDialog(conversation, v); + } + }; private OnClickListener clickToMuc = new OnClickListener() { @Override @@ -121,7 +173,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa startActivity(intent); } }; - private ConversationActivity activity; private OnClickListener leaveMuc = new OnClickListener() { @Override @@ -145,17 +196,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (password == null) { password = ""; } - activity.quickPasswordEdit(password, new OnValueEdited() { - - @Override - public String onValueEdited(String value) { - activity.xmppConnectionService.providePasswordForMuc(conversation, value); - return null; - } + activity.quickPasswordEdit(password, value -> { + activity.xmppConnectionService.providePasswordForMuc(conversation, value); + return null; }); } }; - private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override @@ -168,7 +214,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void onScroll(final AbsListView view, int firstVisibleItem, int visibleItemCount, final int totalItemCount) { synchronized (ConversationFragment.this.messageList) { - if (firstVisibleItem < 25 && conversation != null && conversation.messagesLoaded.compareAndSet(true,false) && messageList.size() > 0) { + if (firstVisibleItem < 25 && conversation != null && conversation.messagesLoaded.compareAndSet(true, false) && messageList.size() > 0) { long timestamp; if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) { timestamp = messageList.get(1).getTimeSent(); @@ -182,71 +228,56 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa conversation.messagesLoaded.set(true); return; } - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - final int oldPosition = messagesView.getFirstVisiblePosition(); - Message message = null; - int childPos; - for (childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) { - message = messageList.get(oldPosition + childPos); - if (message.getType() != Message.TYPE_STATUS) { - break; - } - } - final String uuid = message != null ? message.getUuid() : null; - View v = messagesView.getChildAt(childPos); - final int pxOffset = (v == null) ? 0 : v.getTop(); - ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); - try { - updateStatusMessages(); - } catch (IllegalStateException e) { - Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages"); + activity.runOnUiThread(() -> { + final int oldPosition = messagesView.getFirstVisiblePosition(); + Message message = null; + int childPos; + for (childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) { + message = messageList.get(oldPosition + childPos); + if (message.getType() != Message.TYPE_STATUS) { + break; } - messageListAdapter.notifyDataSetChanged(); - int pos = Math.max(getIndexOf(uuid, messageList), 0); - messagesView.setSmoothScrollbarEnabled(true); - messagesView.setFastScrollEnabled(false); - messagesView.setSelectionFromTop(pos, pxOffset); - if (messageLoaderToast != null) { - messageLoaderToast.cancel(); - } - conversation.messagesLoaded.set(true); } + final String uuid = message != null ? message.getUuid() : null; + View v = messagesView.getChildAt(childPos); + final int pxOffset = (v == null) ? 0 : v.getTop(); + ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); + try { + updateStatusMessages(); + } catch (IllegalStateException e) { + Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages"); + } + messageListAdapter.notifyDataSetChanged(); + int pos = Math.max(getIndexOf(uuid, messageList), 0); + messagesView.setSmoothScrollbarEnabled(true); + messagesView.setFastScrollEnabled(false); + messagesView.setSelectionFromTop(pos, pxOffset); + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + conversation.messagesLoaded.set(true); }); } @Override public void informUser(final int resId) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (messageLoaderToast != null) { - messageLoaderToast.cancel(); - } - if (ConversationFragment.this.conversation != conversation) { - return; - } - messageLoaderToast = Toast.makeText(view.getContext(), resId, Toast.LENGTH_LONG); - messageLoaderToast.show(); + activity.runOnUiThread(() -> { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + if (ConversationFragment.this.conversation != conversation) { + return; } + messageLoaderToast = Toast.makeText(view.getContext(), resId, Toast.LENGTH_LONG); + messageLoaderToast.show(); }); - } }); - } } } }; - - protected OnClickListener clickToVerify = new OnClickListener() { - @Override - public void onClick(View v) { - activity.verifyOtrSessionDialog(conversation, v); - } - }; private EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() { @Override public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) { @@ -256,18 +287,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa inputContentInfo.requestPermission(); } catch (Exception e) { Log.e(Config.LOGTAG, "InputContentInfoCompat#requestPermission() failed.", e); - Toast.makeText( - activity, - activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), - Toast.LENGTH_LONG + Toast.makeText(getActivity(), activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), Toast.LENGTH_LONG ).show(); return false; } } - if (activity.hasStoragePermission(ConversationActivity.REQUEST_ADD_EDITOR_CONTENT)) { - activity.attachImageToConversation(inputContentInfo.getContentUri()); + if (activity.hasStoragePermission(REQUEST_ADD_EDITOR_CONTENT)) { + attachImageToConversation(inputContentInfo.getContentUri()); } else { - activity.mPendingEditorContent = inputContentInfo.getContentUri(); + mPendingEditorContent = inputContentInfo.getContentUri(); } return true; } @@ -286,25 +314,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private OnClickListener mUnblockClickListener = new OnClickListener() { @Override public void onClick(final View v) { - v.post(new Runnable() { - @Override - public void run() { - v.setVisibility(View.INVISIBLE); - } - }); + v.post(() -> v.setVisibility(View.INVISIBLE)); if (conversation.isDomainBlocked()) { BlockContactDialog.show(activity, conversation); } else { - activity.unblockConversation(conversation); + unblockConversation(conversation); } } }; - private OnClickListener mBlockClickListener = new OnClickListener() { - @Override - public void onClick(final View view) { - showBlockSubmenu(view); - } - }; + private OnClickListener mBlockClickListener = this::showBlockSubmenu; private OnClickListener mAddBackClickListener = new OnClickListener() { @Override @@ -316,12 +334,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } }; - private View.OnLongClickListener mLongPressBlockListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - showBlockSubmenu(v); - return true; - } + private View.OnLongClickListener mLongPressBlockListener = v -> { + showBlockSubmenu(v); + return true; }; private OnClickListener mHideUnencryptionHint = new OnClickListener() { @@ -352,7 +367,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void onClick(View view) { Intent intent = new Intent(activity, VerifyOTRActivity.class); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); startActivity(intent); @@ -366,14 +381,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent(); if (pendingIntent != null) { try { - activity.startIntentSenderForResult(pendingIntent.getIntentSender(), - ConversationActivity.REQUEST_DECRYPT_PGP, + getActivity().startIntentSenderForResult(pendingIntent.getIntentSender(), + REQUEST_DECRYPT_PGP, null, 0, 0, 0); } catch (SendIntentException e) { - Toast.makeText(activity, R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); conversation.getAccount().getPgpDecryptionService().continueDecryption(true); } } @@ -381,21 +396,17 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }; private AtomicBoolean mSendingPgpMessage = new AtomicBoolean(false); - private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - InputMethodManager imm = (InputMethodManager) v.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm.isFullscreenMode()) { - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - sendMessage(); - return true; - } else { - return false; + private OnEditorActionListener mEditorActionListener = (v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_SEND) { + InputMethodManager imm = (InputMethodManager) v.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm.isFullscreenMode()) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } + sendMessage(); + return true; + } else { + return false; } }; @@ -411,7 +422,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case SEND_LOCATION: case RECORD_VOICE: case CHOOSE_PICTURE: - activity.attachFile(action.toChoice()); + attachFile(action.toChoice()); break; case CANCEL: if (conversation != null) { @@ -451,6 +462,54 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private int lastCompletionCursor; private boolean firstWord = false; + private Message mPendingDownloadableMessage; + private TextWatcher mSearchTextWatcher = new TextWatcher() { + + @Override + public void afterTextChanged(Editable editable) { + String query = editable.toString().trim(); + + if ((!query.isEmpty() || !query.contains("")) && query.length() >= 3) { + searchUp.setVisibility(View.VISIBLE); + searchDown.setVisibility(View.VISIBLE); + Message found = searchHistory(query); + if (found != null) { + searchUp.setVisibility(View.VISIBLE); + searchDown.setVisibility(View.VISIBLE); + } else { + searchUp.setVisibility(View.GONE); + searchDown.setVisibility(View.GONE); + } + searchUp.setEnabled(found != null); + searchDown.setEnabled(found != null); + View.OnClickListener upDownListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + String searchQuery = searchfield_input.getText().toString().trim(); + if (!searchQuery.isEmpty() || !searchQuery.contains("")) { + searchHistory(searchQuery, view.getId() == R.id.search_up); + } + + } + }; + searchUp.setOnClickListener(upDownListener); + searchDown.setOnClickListener(upDownListener); + } else { + searchUp.setVisibility(View.GONE); + searchDown.setVisibility(View.GONE); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + activity.refreshUi(); + } + }; + private int getIndexOf(String uuid, List messages) { if (uuid == null) { return messages.size() - 1; @@ -493,6 +552,235 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + private void attachLocationToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + activity.xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback() { + + @Override + public void success(Message message) { + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(int errorCode, Message object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + + } + }); + } + + private void attachFileToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_file), Toast.LENGTH_LONG); + prepareFileToast.show(); + activity.delegateUriPermissionsToService(uri); + activity.xmppConnectionService.attachFileToConversation(conversation, uri, new UiInformableCallback() { + @Override + public void inform(final String text) { + hidePrepareFileToast(prepareFileToast); + activity.runOnUiThread(() -> activity.replaceToast(text)); + } + + @Override + public void success(Message message) { + activity.runOnUiThread(() -> activity.hideToast()); + hidePrepareFileToast(prepareFileToast); + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int errorCode, Message message) { + hidePrepareFileToast(prepareFileToast); + activity.runOnUiThread(() -> activity.replaceToast(getString(errorCode))); + + } + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + hidePrepareFileToast(prepareFileToast); + } + }); + } + + private void attachPhotoToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + activity.delegateUriPermissionsToService(uri); + activity.xmppConnectionService.attachImageToConversation(conversation, uri, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int error, Message message) { + hidePrepareFileToast(prepareFileToast); + getActivity().runOnUiThread(() -> activity.replaceToast(getString(error))); + } + }); + } + + private void attachImagesToConversation(Conversation conversation, Uri uri) { + if (conversation == null) { + return; + } + final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + activity.delegateUriPermissionsToService(uri); + activity.xmppConnectionService.attachImageToConversation(conversation, uri, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int error, Message message) { + hidePrepareFileToast(prepareFileToast); + getActivity().runOnUiThread(() -> activity.replaceToast(getString(error))); + } + }); + } + + public void attachImageToConversation(Uri uri) { + this.attachImageToConversation(conversation, uri, true); + } + + private void attachImageToConversation(Conversation conversation, Uri uri, boolean sendAsIs) { + if (conversation == null) { + return; + } + if (sendAsIs) { + sendImage(conversation, uri); + return; + } + final Conversation conversation_preview = conversation; + final Uri uri_preview = uri; + Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(activity, uri)); + File file = null; + ExifInterface exif = null; + int orientation = 0; + try { + file = new File(FileUtils.getPath(activity, uri)); + } catch (Exception e) { + e.printStackTrace(); + } + if (file != null) { + try { + exif = new ExifInterface(file.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + } + Log.d(Config.LOGTAG, "EXIF: " + orientation); + Bitmap rotated_image = null; + Log.d(Config.LOGTAG, "Rotate image"); + rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation); + if (rotated_image != null) { + int scaleSize = 600; + int originalWidth = rotated_image.getWidth(); + int originalHeight = rotated_image.getHeight(); + int newWidth = -1; + int newHeight = -1; + float multFactor; + if (originalHeight > originalWidth) { + newHeight = scaleSize; + multFactor = (float) originalWidth / (float) originalHeight; + newWidth = (int) (newHeight * multFactor); + } else if (originalWidth > originalHeight) { + newWidth = scaleSize; + multFactor = (float) originalHeight / (float) originalWidth; + newHeight = (int) (newWidth * multFactor); + } else if (originalHeight == originalWidth) { + newHeight = scaleSize; + newWidth = scaleSize; + } + Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px"); + Bitmap preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false); + ImageView ImagePreview = new ImageView(activity); + LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + ImagePreview.setLayoutParams(vp); + ImagePreview.setMaxWidth(newWidth); + ImagePreview.setMaxHeight(newHeight); + //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY); + //ImagePreview.setAdjustViewBounds(true); + ImagePreview.setPadding(5, 5, 5, 5); + ImagePreview.setImageBitmap(preview); + getActivity().runOnUiThread(() -> { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setView(ImagePreview); + builder.setTitle(R.string.send_image); + builder.setPositiveButton(R.string.ok, (dialog, which) -> sendImage(conversation_preview, uri_preview)); + builder.setOnCancelListener(dialog -> mPendingImageUris.clear()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setOnDismissListener(dialog -> mPendingImageUris.clear()); + } + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + }); + } else { + getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show()); + } + } + + private void sendImage(Conversation conversation, Uri uri) { + final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); + prepareFileToast.show(); + activity.xmppConnectionService.attachImageToConversation(conversation, uri, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + hidePrepareFileToast(prepareFileToast); + } + + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int error, Message message) { + hidePrepareFileToast(prepareFileToast); + getActivity().runOnUiThread(() -> activity.replaceToast(getString(error))); + } + }); + } + + private void hidePrepareFileToast(final Toast prepareFileToast) { + if (prepareFileToast != null) { + getActivity().runOnUiThread(prepareFileToast::cancel); + } + } + private void sendMessage() { final String body = mEditMessage.getText().toString(); final Conversation conversation = this.conversation; @@ -524,7 +812,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa sendPgpMessage(message); break; case Message.ENCRYPTION_AXOLOTL: - if (!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + if (!trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) { sendAxolotlMessage(message); } break; @@ -533,6 +821,36 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + protected boolean trustKeysIfNeeded(int requestCode) { + return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); + } + + protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { + AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + final List targets = axolotlService.getCryptoTargets(conversation); + boolean hasUnaccepted = !conversation.getAcceptedCryptoTargets().containsAll(targets); + boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty(); + boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); + boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(conversation).isEmpty(); + boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); + if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { + axolotlService.createSessionsIfNeeded(conversation); + Intent intent = new Intent(getActivity(), TrustKeysActivity.class); + String[] contacts = new String[targets.size()]; + for (int i = 0; i < contacts.length; ++i) { + contacts[i] = targets.get(i).toString(); + } + intent.putExtra("contacts", contacts); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("choice", attachmentChoice); + intent.putExtra("conversation", conversation.getUuid()); + startActivityForResult(intent, requestCode); + return true; + } else { + return false; + } + } + public void updateChatMsgHint() { final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; if (conversation.getCorrectingMessage() != null) { @@ -549,13 +867,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.mEditMessage.setHint(R.string.you_are_not_participating); hideMessageHint(); } else { - this.mEditMessage.setHint(UIHelper.getMessageHint(activity, conversation)); + this.mEditMessage.setHint(UIHelper.getMessageHint(getActivity(), conversation)); hideMessageHint(); getActivity().invalidateOptionsMenu(); } } public void setupIme() { + ; if (activity != null) { if (activity.usingEnterKey() && activity.enterIsSend()) { mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); @@ -570,18 +889,139 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + private void handleActivityResult(ActivityResult activityResult) { + if (activityResult.resultCode == Activity.RESULT_OK) { + handlePositiveActivityResult(activityResult.requestCode, activityResult.data); + } else { + handleNegativeActivityResult(activityResult.requestCode); + } + } + + private void handlePositiveActivityResult(int requestCode, final Intent data) { + switch (requestCode) { + case REQUEST_DECRYPT_PGP: + conversation.getAccount().getPgpDecryptionService().continueDecryption(data); + break; + case REQUEST_TRUST_KEYS_TEXT: + final String body = mEditMessage.getText().toString(); + Message message = new Message(conversation, body, conversation.getNextEncryption()); + sendAxolotlMessage(message); + break; + case REQUEST_TRUST_KEYS_MENU: + int choice = data.getIntExtra("choice", ATTACHMENT_CHOICE_INVALID); + selectPresenceToAttachFile(choice); + break; + case REQUEST_CHOOSE_PGP_ID: + long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0); + if (id != 0) { + conversation.getAccount().setPgpSignId(id); + activity.announcePgp(conversation.getAccount(), null, null, activity.onOpenPGPKeyPublished); + } else { + activity.choosePgpSignId(conversation.getAccount()); + } + break; + case REQUEST_ANNOUNCE_PGP: + activity.announcePgp(conversation.getAccount(), conversation, data, activity.onOpenPGPKeyPublished); + break; + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + List imageUris = AttachmentTool.extractUriFromIntent(data); + for (Iterator i = imageUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); + attachImageToConversation(conversation, i.next(), false); + } + break; + case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + final List PhotoUris = AttachmentTool.extractUriFromIntent(data); + for (Iterator i = PhotoUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. TAKE_FROM_CAMERA"); + attachPhotoToConversation(conversation, i.next()); + } + case ATTACHMENT_CHOICE_CHOOSE_FILE: + case ATTACHMENT_CHOICE_RECORD_VOICE: + final List fileUris = AttachmentTool.extractUriFromIntent(data); + final PresenceSelector.OnPresenceSelected callback = () -> { + for (Iterator i = fileUris.iterator(); i.hasNext(); i.remove()) { + Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); + attachFileToConversation(conversation, i.next()); + } + }; + if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), fileUris, activity.getMaxHttpUploadSize(conversation))) { + callback.onPresenceSelected(); + } else { + activity.selectPresence(conversation, callback); + } + break; + case ATTACHMENT_CHOICE_LOCATION: + double latitude = data.getDoubleExtra("latitude", 0); + double longitude = data.getDoubleExtra("longitude", 0); + Uri geo = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); + attachLocationToConversation(conversation, geo); + break; + } + } + + private void handleNegativeActivityResult(int requestCode) { + switch (requestCode) { + case REQUEST_DECRYPT_PGP: + // discard the message to prevent decryption being blocked + conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption(); + break; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data); + if (activity != null && activity.xmppConnectionService != null) { + handleActivityResult(activityResult); + } else { + this.postponedActivityResult = activityResult; + } + } + + public void unblockConversation(final Blockable conversation) { + activity.xmppConnectionService.sendUnblockRequest(conversation); + } + + @Override + public void onAttach(Context context) { + if (context instanceof ConversationActivity) { + this.activity = (ConversationActivity) context; + } else { + throw new IllegalStateException("Trying to attach fragment to activity that is not the ConversationActivity"); + } + super.onAttach(context); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + menuInflater.inflate(R.menu.fragment_conversation, menu); + final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates); + final MenuItem menuSearchHistory = menu.findItem(R.id.action_search_history); + + if (conversation != null) { + ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu); + ConversationMenuConfigurator.configureEncryptionMenu(conversation, menu); + } + super.onCreateOptionsMenu(menu, menuInflater); + } + @Override public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); view.setOnClickListener(null); mEditMessage = view.findViewById(R.id.textinput); - mEditMessage.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (activity != null) { - activity.hideConversationsOverview(); - } + mEditMessage.setOnClickListener(v -> { + if (activity != null) { + activity.hideConversationsOverview(); } }); mEditMessage.addTextChangedListener(new StylingHelper.MessageEditorStyler(mEditMessage)); @@ -617,7 +1057,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa Jid user = message.getCounterpart(); if (user != null && !user.isBareJid()) { if (!message.getConversation().getMucOptions().isUserInRoom(user)) { - Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, getActivity().getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); } highlightInConference(user.getResourcepart()); } @@ -640,7 +1080,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa Intent intent; if (activity.manuallyChangePresence() && !received) { intent = new Intent(activity, SetPresenceActivity.class); - intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString()); } else { intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); @@ -662,7 +1102,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getConversation().getMode() == Conversation.MODE_MULTI) { final MucOptions mucOptions = conversation.getMucOptions(); if (!mucOptions.allowPm()) { - Toast.makeText(activity, R.string.private_messages_are_disabled, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.private_messages_are_disabled, Toast.LENGTH_SHORT).show(); return; } Jid user = message.getCounterpart(); @@ -670,7 +1110,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (mucOptions.isUserInRoom(user)) { privateMessageWith(user); } else { - Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); } } } @@ -758,92 +1198,435 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { retryDecryption.setVisible(true); } - if (relevantForCorrection.getType() == Message.TYPE_TEXT - && relevantForCorrection.isLastCorrectableMessage() - && (m.getConversation().getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) { - correctMessage.setVisible(true); + if (relevantForCorrection.getType() == Message.TYPE_TEXT + && relevantForCorrection.isLastCorrectableMessage() + && (m.getConversation().getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) { + correctMessage.setVisible(true); + } + if (treatAsFile || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())) { + shareWith.setVisible(true); + + } + if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isFileOrImage()) { + sendAgain.setVisible(true); + } + if (m.hasFileOnRemoteHost() + || m.isGeoUri() + || m.isXmppUri() + || m.treatAsDownloadable() + || (t != null && t instanceof HttpDownloadConnection)) { + copyUrl.setVisible(true); + } + if ((m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())) { + downloadFile.setVisible(true); + downloadFile.setTitle(activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, m))); + } + boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING + || m.getStatus() == Message.STATUS_UNSEND + || m.getStatus() == Message.STATUS_OFFERED; + if ((t != null && !(t instanceof TransferablePlaceholder)) || waitingOfferedSending && m.needsUploading()) { + cancelTransmission.setVisible(true); + } + if (treatAsFile) { + String path = m.getRelativeFilePath(); + Log.d(Config.LOGTAG, "Path = " + path); + if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null", false))) { + deleteFile.setVisible(true); + deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); + } + } + if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) { + showErrorMessage.setVisible(true); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.share_with: + shareWith(selectedMessage); + return true; + case R.id.correct_message: + correctMessage(selectedMessage); + return true; + case R.id.copy_message: + copyMessage(selectedMessage); + return true; + case R.id.quote_message: + quoteMessage(selectedMessage); + return true; + case R.id.send_again: + resendMessage(selectedMessage); + return true; + case R.id.copy_url: + copyUrl(selectedMessage); + return true; + case R.id.download_file: + downloadFile(selectedMessage); + return true; + case R.id.cancel_transmission: + cancelTransmission(selectedMessage); + return true; + case R.id.retry_decryption: + retryDecryption(selectedMessage); + return true; + case R.id.delete_file: + deleteFile(selectedMessage); + return true; + case R.id.show_error_message: + showErrorMessage(selectedMessage); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (conversation == null) { + return super.onOptionsItemSelected(item); + } + switch (item.getItemId()) { + case R.id.encryption_choice_axolotl: + case R.id.encryption_choice_otr: + case R.id.encryption_choice_pgp: + case R.id.encryption_choice_none: + handleEncryptionSelection(item); + break; + case R.id.attach_choose_picture: + case R.id.attach_take_picture: + case R.id.attach_choose_file: + case R.id.attach_record_voice: + case R.id.attach_location: + handleAttachmentSelection(item); + break; + case R.id.action_archive_chat: + activity.endConversation(conversation); + break; + case R.id.action_archive_muc: + getActivity().runOnUiThread(() -> { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getString(R.string.action_end_conversation_muc)); + builder.setMessage(getString(R.string.leave_conference_warning)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.action_end_conversation_muc), + (dialog, which) -> activity.endConversation(conversation)); + builder.create().show(); + }); + break; + case R.id.action_invite: + activity.inviteToConversation(conversation); + break; + case R.id.action_clear_history: + clearHistoryDialog(conversation); + break; + case R.id.action_block: + case R.id.action_unblock: + final Activity activity = getActivity(); + if (activity instanceof XmppActivity) { + BlockContactDialog.show((XmppActivity) activity, conversation); + } + break; + case R.id.action_search_history: + showSearchField(); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + private void handleAttachmentSelection(MenuItem item) { + switch (item.getItemId()) { + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA); + break; + case R.id.attach_choose_file: + attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case R.id.attach_location: + attachFile(ATTACHMENT_CHOICE_LOCATION); + break; + } + } + + private void handleEncryptionSelection(MenuItem item) { + if (conversation == null) { + return; + } + final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + switch (item.getItemId()) { + case R.id.encryption_choice_none: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + item.setChecked(true); + break; + case R.id.encryption_choice_otr: + conversation.setNextEncryption(Message.ENCRYPTION_OTR); + item.setChecked(true); + break; + case R.id.encryption_choice_pgp: + if (activity.hasPgp()) { + if (conversation.getAccount().getPgpSignature() != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + item.setChecked(true); + } else { + activity.announcePgp(conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished); + } + } else { + activity.showInstallPgpDialog(); + } + break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + break; + default: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + break; + } + activity.xmppConnectionService.updateConversation(conversation); + fragment.updateChatMsgHint(); + getActivity().invalidateOptionsMenu(); + activity.refreshUi(); + } + + public void attachFile(final int attachmentChoice) { + if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { + if (!Config.ONLY_INTERNAL_STORAGE && !activity.hasStoragePermission(attachmentChoice)) { + return; + } + } + if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) { + if (!activity.hasMicPermission(attachmentChoice)) { + return; + } + } + if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION) { + if (!activity.hasLocationPermission(attachmentChoice)) { + return; + } + } + try { + activity.getPreferences().edit() + .putString(RECENTLY_USED_QUICK_ACTION, SendButtonAction.of(attachmentChoice).toString()) + .apply(); + } catch (IllegalArgumentException e) { + //just do not save + } + final int encryption = conversation.getNextEncryption(); + final int mode = conversation.getMode(); + if (encryption == Message.ENCRYPTION_PGP) { + if (activity.hasPgp()) { + if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) { + activity.xmppConnectionService.getPgpEngine().hasKey( + conversation.getContact(), + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Contact contact) { + activity.runIntent(pi, attachmentChoice); + } + + @Override + public void success(Contact contact) { + selectPresenceToAttachFile(attachmentChoice); + } + + @Override + public void error(int error, Contact contact) { + activity.replaceToast(getString(error)); + } + }); + } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + getActivity().runOnUiThread(() -> { + Toast warning = Toast.makeText(activity, R.string.missing_public_keys, Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + }); + } + selectPresenceToAttachFile(attachmentChoice); + } else { + final ConversationFragment fragment = (ConversationFragment) getFragmentManager() + .findFragmentByTag("conversation"); + if (fragment != null) { + fragment.showNoPGPKeyDialog(false, (dialog, which) -> { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + activity.xmppConnectionService.updateConversation(conversation); + selectPresenceToAttachFile(attachmentChoice); + }); + } + } + } else { + activity.showInstallPgpDialog(); + } + } else { + if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { + selectPresenceToAttachFile(attachmentChoice); } - if (treatAsFile || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())) { - shareWith.setVisible(true); + } + } + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_START_DOWNLOAD) { + if (this.mPendingDownloadableMessage != null) { + startDownloadable(this.mPendingDownloadableMessage); + } + } else if (requestCode == REQUEST_ADD_EDITOR_CONTENT) { + if (this.mPendingEditorContent != null) { + attachImageToConversation(this.mPendingEditorContent); + } + } else { + attachFile(requestCode); + } + } else { + getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), R.string.no_permission, Toast.LENGTH_SHORT).show()); } - if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isFileOrImage()) { - sendAgain.setVisible(true); - } - if (m.hasFileOnRemoteHost() - || m.isGeoUri() - || m.isXmppUri() - || m.treatAsDownloadable() - || (t != null && t instanceof HttpDownloadConnection)) { - copyUrl.setVisible(true); + } + } + + public void startDownloadable(Message message) { + if (!Config.ONLY_INTERNAL_STORAGE && !activity.hasStoragePermission(REQUEST_START_DOWNLOAD)) { + this.mPendingDownloadableMessage = message; + return; + } + Transferable transferable = message.getTransferable(); + if (transferable != null) { + if (!transferable.start()) { + Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); } - if ((m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())) { - downloadFile.setVisible(true); - downloadFile.setTitle(activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, m))); + } else if (message.treatAsDownloadable()) { + activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); + } + } + + @SuppressLint("InflateParams") + protected void clearHistoryDialog(final Conversation conversation) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getString(R.string.clear_conversation_history)); + final View dialogView = getActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null); + final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + endConversationCheckBox.setVisibility(View.VISIBLE); + endConversationCheckBox.setChecked(true); + } + builder.setView(dialogView); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.delete_messages), (dialog, which) -> { + this.activity.xmppConnectionService.clearConversationHistory(conversation); + if (endConversationCheckBox.isChecked()) { + this.activity.endConversation(conversation); + } else { + activity.updateConversationList(); + updateMessages(); } - boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING - || m.getStatus() == Message.STATUS_UNSEND - || m.getStatus() == Message.STATUS_OFFERED; - if ((t != null && !(t instanceof TransferablePlaceholder)) || waitingOfferedSending && m.needsUploading()) { - cancelTransmission.setVisible(true); + }); + builder.create().show(); + } + + protected void selectPresenceToAttachFile(final int attachmentChoice) { + final int encryption = conversation.getNextEncryption(); + final Account account = conversation.getAccount(); + final PresenceSelector.OnPresenceSelected callback = () -> { + final Intent intent = new Intent(); + boolean chooser = false; + String fallbackPackageId = null; + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_CHOOSE_VIDEO: + chooser = true; + intent.setType("video/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getString(R.string.attach_take_from_camera)); + builder.setNegativeButton(getString(R.string.action_take_photo), + (dialog, which) -> { + Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri(); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + startActivityForResult(intent, attachmentChoice); + }); + builder.setPositiveButton(getString(R.string.action_take_video), + (dialog, which) -> { + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); + startActivityForResult(intent, attachmentChoice); + }); + builder.create().show(); + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + chooser = true; + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + startActivityForResult(new Intent(getActivity(), RecordingActivity.class), attachmentChoice); + break; + case ATTACHMENT_CHOICE_LOCATION: + startActivityForResult(new Intent(getActivity(), ShareLocationActivity.class), attachmentChoice); + break; } - if (treatAsFile) { - String path = m.getRelativeFilePath(); - Log.d(Config.LOGTAG, "Path = " + path); - if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null", false))) { - deleteFile.setVisible(true); - deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice); + if (chooser) { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.perform_action_with)), + attachmentChoice); + } else { + startActivityForResult(intent, attachmentChoice); } + } else if (fallbackPackageId != null) { + startActivity(getInstallApkIntent(fallbackPackageId)); } - if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) { - showErrorMessage.setVisible(true); + }; + if (account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) { + if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { + conversation.setNextCounterpart(null); + callback.onPresenceSelected(); + } else { + activity.selectPresence(conversation, callback); } } } - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.share_with: - shareWith(selectedMessage); - return true; - case R.id.correct_message: - correctMessage(selectedMessage); - return true; - case R.id.copy_message: - copyMessage(selectedMessage); - return true; - case R.id.quote_message: - quoteMessage(selectedMessage); - return true; - case R.id.send_again: - resendMessage(selectedMessage); - return true; - case R.id.copy_url: - copyUrl(selectedMessage); - return true; - case R.id.download_file: - downloadFile(selectedMessage); - return true; - case R.id.cancel_transmission: - cancelTransmission(selectedMessage); - return true; - case R.id.retry_decryption: - retryDecryption(selectedMessage); - return true; - case R.id.delete_file: - deleteFile(selectedMessage); - return true; - case R.id.show_error_message: - showErrorMessage(selectedMessage); - return true; - default: - return super.onContextItemSelected(item); + private Intent getInstallApkIntent(final String packageId) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("market://details?id=" + packageId)); + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + return intent; + } else { + intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId)); + return intent; } } + @Override + public void onResume() { + new Handler().post(() -> { + getActivity().invalidateOptionsMenu(); + }); + super.onResume(); + } + private void showErrorMessage(final Message message) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.error_message); builder.setMessage(message.getErrorMessage()); builder.setPositiveButton(R.string.ok, null); @@ -862,9 +1645,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } else { final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); try { - shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file)); + shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(getActivity(), file)); } catch (SecurityException e) { - Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); return; } shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -875,16 +1658,16 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa shareIntent.setType(mime); } try { - activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); + startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); } catch (ActivityNotFoundException e) { //This should happen only on faulty androids because normally chooser is always available - Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); } } private void copyMessage(Message message) { if (activity.copyTextToClipboard(message.getMergedBody().toString(), R.string.message)) { - Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } } @@ -905,12 +1688,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (!message.hasFileOnRemoteHost() && xmppConnection != null && !xmppConnection.getFeatures().httpUpload(message.getFileParams().size)) { - activity.selectPresence(conversation, new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - activity.xmppConnectionService.resendFailedMessages(message); - } + activity.selectPresence(conversation, () -> { + message.setCounterpart(conversation.getNextCounterpart()); + activity.xmppConnectionService.resendFailedMessages(message); }); return; } @@ -942,7 +1722,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa resId = R.string.file_url; } if (activity.copyTextToClipboard(url, resId)) { - Toast.makeText(activity, R.string.url_copied_to_clipboard, + Toast.makeText(getActivity(), R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } } @@ -1019,13 +1799,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onStop() { super.onStop(); + final Activity activity = getActivity(); if (activity == null || !activity.isChangingConfigurations()) { messageListAdapter.stopAudioPlayer(); } if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); if (this.conversation.setNextMessage(msg)) { - activity.xmppConnectionService.updateConversation(this.conversation); + this.activity.xmppConnectionService.updateConversation(this.conversation); } updateChatState(this.conversation, msg); } @@ -1043,7 +1824,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation == null) { return false; } - this.activity = (ConversationActivity) getActivity(); setupIme(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); @@ -1085,30 +1865,28 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void showBlockSubmenu(View view) { + private boolean showBlockSubmenu(View view) { final Jid jid = conversation.getJid(); if (jid.isDomainJid()) { BlockContactDialog.show(activity, conversation); } else { - PopupMenu popupMenu = new PopupMenu(activity, view); + PopupMenu popupMenu = new PopupMenu(getActivity(), view); popupMenu.inflate(R.menu.block); - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - Blockable blockable; - switch (menuItem.getItemId()) { - case R.id.block_domain: - blockable = conversation.getAccount().getRoster().getContact(jid.toDomainJid()); - break; - default: - blockable = conversation; - } - BlockContactDialog.show(activity, blockable); - return true; + popupMenu.setOnMenuItemClickListener(menuItem -> { + Blockable blockable; + switch (menuItem.getItemId()) { + case R.id.block_domain: + blockable = conversation.getAccount().getRoster().getContact(jid.toDomainJid()); + break; + default: + blockable = conversation; } + BlockContactDialog.show(activity, blockable); + return true; }); popupMenu.show(); } + return true; } private void updateSnackBar(final Conversation conversation) { @@ -1262,90 +2040,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa mSendingPgpMessage.set(false); } - private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) { - switch (action) { - case TEXT: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_text_online; - case AWAY: - return R.drawable.ic_send_text_away; - case XA: - case DND: - return R.drawable.ic_send_text_dnd; - default: - return R.drawable.ic_send_text_offline; - } - case TAKE_FROM_CAMERA: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_photo_online; - case AWAY: - return R.drawable.ic_send_photo_away; - case XA: - case DND: - return R.drawable.ic_send_photo_dnd; - default: - return R.drawable.ic_send_photo_offline; - } - case RECORD_VOICE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_voice_online; - case AWAY: - return R.drawable.ic_send_voice_away; - case XA: - case DND: - return R.drawable.ic_send_voice_dnd; - default: - return R.drawable.ic_send_voice_offline; - } - case SEND_LOCATION: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_location_online; - case AWAY: - return R.drawable.ic_send_location_away; - case XA: - case DND: - return R.drawable.ic_send_location_dnd; - default: - return R.drawable.ic_send_location_offline; - } - case CANCEL: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_cancel_online; - case AWAY: - return R.drawable.ic_send_cancel_away; - case XA: - case DND: - return R.drawable.ic_send_cancel_dnd; - default: - return R.drawable.ic_send_cancel_offline; - } - case CHOOSE_PICTURE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_picture_online; - case AWAY: - return R.drawable.ic_send_picture_away; - case XA: - case DND: - return R.drawable.ic_send_picture_dnd; - default: - return R.drawable.ic_send_picture_offline; - } - } - return R.drawable.ic_send_text_offline; - } - private void updateEditablity() { boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating() || this.conversation.getNextCounterpart() != null; this.mEditMessage.setFocusable(canWrite); @@ -1356,40 +2050,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void updateSendButton() { final Conversation c = this.conversation; - final SendButtonAction action; final Presence.Status status; final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); - final boolean empty = text.length() == 0; - final boolean conference = c.getMode() == Conversation.MODE_MULTI; - if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { - action = SendButtonAction.CANCEL; - } else if (conference && !c.getAccount().httpUploadAvailable()) { - if (empty && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - action = SendButtonAction.TEXT; - } - } else { - if (empty) { - if (conference && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - String setting = activity.getPreferences().getString("quick_action", activity.getResources().getString(R.string.quick_action)); - if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { - action = SendButtonAction.SEND_LOCATION; - } else { - if (setting.equals("recent")) { - setting = activity.getPreferences().getString(ConversationActivity.RECENTLY_USED_QUICK_ACTION, SendButtonAction.TEXT.toString()); - action = SendButtonAction.valueOfOrDefault(setting, SendButtonAction.TEXT); - } else { - action = SendButtonAction.valueOfOrDefault(setting, SendButtonAction.TEXT); - } - } - } - } else { - action = SendButtonAction.TEXT; - } - } + final SendButtonAction action = SendButtonTool.getAction(getActivity(), c, text); if (activity.useSendButtonToIndicateStatus() && c.getAccount().getStatus() == Account.State.ONLINE) { if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) { status = Presence.Status.OFFLINE; @@ -1402,7 +2065,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa status = Presence.Status.OFFLINE; } this.mSendButton.setTag(action); - this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); + this.mSendButton.setImageResource(SendButtonTool.getSendButtonImageResource(getActivity(), action, status)); } protected void updateStatusMessages() { @@ -1554,53 +2217,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa return searchfield.getVisibility() == View.VISIBLE; } - private TextWatcher mSearchTextWatcher = new TextWatcher() { - - @Override - public void afterTextChanged(Editable editable) { - String query = editable.toString().trim(); - - if ((!query.isEmpty() || !query.contains("")) && query.length() >= 3) { - searchUp.setVisibility(View.VISIBLE); - searchDown.setVisibility(View.VISIBLE); - Message found = searchHistory(query); - if (found != null) { - searchUp.setVisibility(View.VISIBLE); - searchDown.setVisibility(View.VISIBLE); - } else { - searchUp.setVisibility(View.GONE); - searchDown.setVisibility(View.GONE); - } - searchUp.setEnabled(found != null); - searchDown.setEnabled(found != null); - View.OnClickListener upDownListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - String searchQuery = searchfield_input.getText().toString().trim(); - if (!searchQuery.isEmpty() || !searchQuery.contains("")) { - searchHistory(searchQuery, view.getId() == R.id.search_up); - } - - } - }; - searchUp.setOnClickListener(upDownListener); - searchDown.setOnClickListener(upDownListener); - } else { - searchUp.setVisibility(View.GONE); - searchDown.setVisibility(View.GONE); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - activity.refreshUi(); - } - }; - protected void sendPlainTextMessage(Message message) { ConversationActivity activity = (ConversationActivity) getActivity(); activity.xmppConnectionService.sendMessage(message); @@ -1628,47 +2244,33 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa new UiCallback() { @Override - public void userInputRequried(PendingIntent pi, - Contact contact) { - activity.runIntent( - pi, - ConversationActivity.REQUEST_ENCRYPT_MESSAGE); + public void userInputRequried(PendingIntent pi, Contact contact) { + activity.runIntent(pi, REQUEST_ENCRYPT_MESSAGE); } @Override public void success(Contact contact) { - activity.encryptTextMessage(message); + encryptTextMessage(message); } @Override public void error(int error, Contact contact) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, - R.string.unable_to_connect_to_keychain, - Toast.LENGTH_SHORT - ).show(); - } - }); + activity.runOnUiThread(() -> Toast.makeText(activity, + R.string.unable_to_connect_to_keychain, + Toast.LENGTH_SHORT + ).show()); mSendingPgpMessage.set(false); } }); } else { showNoPGPKeyDialog(false, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - xmppService.updateConversation(conversation); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.sendMessage(message); - messageSent(); - } + (dialog, which) -> { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppService.updateConversation(conversation); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); }); } } else { @@ -1681,28 +2283,48 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); warning.show(); } - activity.encryptTextMessage(message); + encryptTextMessage(message); } else { showNoPGPKeyDialog(true, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.updateConversation(conversation); - xmppService.sendMessage(message); - messageSent(); - } + (dialog, which) -> { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.updateConversation(conversation); + xmppService.sendMessage(message); + messageSent(); }); } } } - public void showNoPGPKeyDialog(boolean plural, - DialogInterface.OnClickListener listener) { + public void encryptTextMessage(Message message) { + activity.xmppConnectionService.getPgpEngine().encrypt(message, + new UiCallback() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + activity.runIntent(pi, REQUEST_SEND_MESSAGE); + } + + @Override + public void success(Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTED); + activity.xmppConnectionService.sendMessage(message); + getActivity().runOnUiThread(() -> messageSent()); + } + + @Override + public void error(final int error, Message message) { + getActivity().runOnUiThread(() -> { + doneSendingPgpMessage(); + Toast.makeText(getActivity(), R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); + }); + + } + }); + } + + public void showNoPGPKeyDialog(boolean plural, DialogInterface.OnClickListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIconAttribute(android.R.attr.alertDialogIcon); if (plural) { @@ -1713,8 +2335,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.setMessage(getText(R.string.contact_has_no_pgp_key)); } builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.send_unencrypted), - listener); + builder.setPositiveButton(getString(R.string.send_unencrypted), listener); builder.create().show(); } @@ -1729,14 +2350,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } + () -> { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); }); } @@ -1836,27 +2453,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa return true; } - @Override - public void onActivityResult(int requestCode, int resultCode, final Intent data) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - activity.getSelectedConversation().getAccount().getPgpDecryptionService().continueDecryption(data); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { - final String body = mEditMessage.getText().toString(); - Message message = new Message(conversation, body, conversation.getNextEncryption()); - sendAxolotlMessage(message); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { - int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); - activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - // discard the message to prevent decryption being blocked - conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption(); - } - } - } - public Message searchHistory(String query) { return searchHistory(query, null); } @@ -1929,4 +2525,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private boolean messageContainsQuery(Message m, String q) { return m != null && m.getMergedBody().toString().toLowerCase().contains(q.toLowerCase()); } + + public void onBackendConnected() { + if (postponedActivityResult != null) { + handleActivityResult(postponedActivityResult); + } + postponedActivityResult = null; + } + + public void clearPending() { + if (postponedActivityResult != null) { + Log.d(Config.LOGTAG, "cleared pending intent with unhandled result left"); + } + postponedActivityResult = null; + } } diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java index 64671c1f2..f0d41e998 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java @@ -26,6 +26,7 @@ import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.adapter.ConversationAdapter; +import de.pixart.messenger.ui.util.PresenceSelector; import de.pixart.messenger.xmpp.XmppConnection; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; @@ -339,7 +340,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer return; } if (share.uris.size() != 0) { - OnPresenceSelected callback = () -> { + PresenceSelector.OnPresenceSelected callback = () -> { attachmentCounter.set(share.uris.size()); if (share.image) { Log.d(Config.LOGTAG, "ShareWithActivity share() image " + share.uris.size() + " uri(s) " + share.uris.toString()); @@ -370,7 +371,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer } } else { if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty()) { - final OnPresenceSelected callback = new OnPresenceSelected() { + final PresenceSelector.OnPresenceSelected callback = new PresenceSelector.OnPresenceSelected() { private void finishAndSend(Message message) { xmppConnectionService.sendMessage(message); replaceToast(getString(R.string.shared_text_with_x, conversation.getName())); @@ -423,7 +424,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer callback.onPresenceSelected(); } } else { - final OnPresenceSelected callback = () -> { + final PresenceSelector.OnPresenceSelected callback = () -> { Message message = new Message(conversation, share.text, conversation.getNextEncryption()); if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { message.setCounterpart(conversation.getNextCounterpart()); diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index c36f0fcbc..385d0a68f 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -383,7 +383,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat private void finishOk() { Intent data = new Intent(); - data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); + data.putExtra("choice", getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); setResult(RESULT_OK, data); finish(); } diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index f226613d7..7e675dab0 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -3,7 +3,6 @@ package de.pixart.messenger.ui; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.support.v7.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.content.ActivityNotFoundException; @@ -38,6 +37,7 @@ import android.preference.PreferenceManager; import android.provider.Settings; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.InputType; import android.util.DisplayMetrics; @@ -75,9 +75,9 @@ import de.pixart.messenger.services.BarcodeProvider; import de.pixart.messenger.services.UpdateService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.XmppConnectionBinder; +import de.pixart.messenger.ui.util.PresenceSelector; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.ExceptionHelper; -import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.OnKeyStatusUpdated; import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.jid.InvalidJidException; @@ -833,9 +833,9 @@ public abstract class XmppActivity extends AppCompatActivity { } } - public void selectPresence(final Conversation conversation, - final OnPresenceSelected listener) { + public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) { final Contact contact = conversation.getContact(); + if (conversation.hasValidOtrSession()) { SessionID id = conversation.getOtrSession().getSessionID(); Jid jid; @@ -857,7 +857,7 @@ public abstract class XmppActivity extends AppCompatActivity { showAskForPresenceDialog(contact); } else if (!contact.getOption(Contact.Options.TO) || !contact.getOption(Contact.Options.FROM)) { - warnMutalPresenceSubscription(conversation, listener); + PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener); } else { conversation.setNextCounterpart(null); listener.onPresenceSelected(); @@ -871,7 +871,7 @@ public abstract class XmppActivity extends AppCompatActivity { } listener.onPresenceSelected(); } else { - showPresenceSelectionDialog(presences, conversation, listener); + PresenceSelector.showPresenceSelectionDialog(this, conversation, listener); } } } @@ -895,16 +895,16 @@ public abstract class XmppActivity extends AppCompatActivity { String name = resourceNameMap.get(resource); if (type != null) { if (Collections.frequency(resourceTypeMap.values(), type) == 1) { - readableIdentities[i] = UIHelper.tranlasteType(this, type); + readableIdentities[i] = PresenceSelector.translateType(this, type); } else if (name != null) { if (Collections.frequency(resourceNameMap.values(), name) == 1 || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) { - readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + name + ")"; + readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + name + ")"; } else { - readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + name + " / " + resource + ")"; + readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + name + " / " + resource + ")"; } } else { - readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + resource + ")"; + readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + resource + ")"; } } else { readableIdentities[i] = resource; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 181bce607..3c9f32913 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -594,7 +594,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie @Override public void onClick(View v) { - activity.startDownloadable(message); + activity.mConversationFragment.startDownloadable(message); } }); } diff --git a/src/main/java/de/pixart/messenger/ui/util/ActivityResult.java b/src/main/java/de/pixart/messenger/ui/util/ActivityResult.java new file mode 100644 index 000000000..b9c4b2870 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/ActivityResult.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.content.Intent; + +public class ActivityResult { + + public final int requestCode; + public final int resultCode; + public final Intent data; + + private ActivityResult(int requestCode, int resultCode, final Intent data) { + this.requestCode = requestCode; + this.resultCode = resultCode; + this.data = data; + } + + public static ActivityResult of(int requestCode, int resultCode, Intent data) { + return new ActivityResult(requestCode, resultCode, data); + } + +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java b/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java new file mode 100644 index 000000000..28b8cd42e --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; + +import java.util.ArrayList; +import java.util.List; + +public class AttachmentTool { + @SuppressLint("NewApi") + public static List extractUriFromIntent(final Intent intent) { + List uris = new ArrayList<>(); + if (intent == null) { + return uris; + } + Uri uri = intent.getData(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { + final ClipData clipData = intent.getClipData(); + if (clipData != null) { + for (int i = 0; i < clipData.getItemCount(); ++i) { + uris.add(clipData.getItemAt(i).getUri()); + } + } + } else { + uris.add(uri); + } + return uris; + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java new file mode 100644 index 000000000..3986ca52c --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.crypto.axolotl.AxolotlService; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Message; + +public class ConversationMenuConfigurator { + + public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu) { + final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); + + final boolean visible; + if (conversation.getMode() == Conversation.MODE_MULTI) { + visible = conversation.getAccount().httpUploadAvailable() && conversation.getMucOptions().participating(); + } else { + visible = true; + } + menuAttach.setVisible(visible); + } + + public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu) { + final MenuItem menuSecure = menu.findItem(R.id.action_security); + final MenuItem none = menu.findItem(R.id.encryption_choice_none); + final MenuItem otr = menu.findItem(R.id.encryption_choice_otr); + final MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp); + final MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl); + + boolean visible; + if (conversation.getMode() == Conversation.MODE_MULTI) { + visible = (Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices(); + } else { + visible = Config.multipleEncryptionChoices(); + } + + menuSecure.setVisible(visible); + + if (!visible) { + return; + } + + if (conversation.getNextEncryption() != Message.ENCRYPTION_NONE) { + menuSecure.setIcon(R.drawable.ic_lock_white_24dp); + } + + otr.setVisible(Config.supportOtr()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + otr.setVisible(false); + } + pgp.setVisible(Config.supportOpenPgp()); + none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); + axolotl.setVisible(Config.supportOmemo()); + final AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + if (axolotlService == null || !axolotlService.isConversationAxolotlCapable(conversation)) { + axolotl.setEnabled(false); + } + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_NONE: + none.setChecked(true); + break; + case Message.ENCRYPTION_OTR: + otr.setChecked(true); + break; + case Message.ENCRYPTION_PGP: + pgp.setChecked(true); + break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; + default: + none.setChecked(true); + break; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/PresenceSelector.java b/src/main/java/de/pixart/messenger/ui/util/PresenceSelector.java new file mode 100644 index 000000000..bc72cd889 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/PresenceSelector.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.util.Pair; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import de.pixart.messenger.R; +import de.pixart.messenger.entities.Contact; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Presences; +import de.pixart.messenger.utils.CryptoHelper; +import de.pixart.messenger.xmpp.jid.InvalidJidException; +import de.pixart.messenger.xmpp.jid.Jid; + +public class PresenceSelector { + + public static void showPresenceSelectionDialog(Activity activity, final Conversation conversation, final OnPresenceSelected listener) { + final Contact contact = conversation.getContact(); + final Presences presences = contact.getPresences(); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.choose_presence)); + final String[] resourceArray = presences.toResourceArray(); + Pair, Map> typeAndName = presences.toTypeAndNameMap(); + final Map resourceTypeMap = typeAndName.first; + final Map resourceNameMap = typeAndName.second; + final String[] readableIdentities = new String[resourceArray.length]; + final AtomicInteger selectedResource = new AtomicInteger(0); + for (int i = 0; i < resourceArray.length; ++i) { + String resource = resourceArray[i]; + if (resource.equals(contact.getLastResource())) { + selectedResource.set(i); + } + String type = resourceTypeMap.get(resource); + String name = resourceNameMap.get(resource); + if (type != null) { + if (Collections.frequency(resourceTypeMap.values(), type) == 1) { + readableIdentities[i] = translateType(activity, type); + } else if (name != null) { + if (Collections.frequency(resourceNameMap.values(), name) == 1 + || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) { + readableIdentities[i] = translateType(activity, type) + " (" + name + ")"; + } else { + readableIdentities[i] = translateType(activity, type) + " (" + name + " / " + resource + ")"; + } + } else { + readableIdentities[i] = translateType(activity, type) + " (" + resource + ")"; + } + } else { + readableIdentities[i] = resource; + } + } + builder.setSingleChoiceItems(readableIdentities, + selectedResource.get(), + (dialog, which) -> selectedResource.set(which)); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, (dialog, which) -> { + try { + Jid next = Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), resourceArray[selectedResource.get()]); + conversation.setNextCounterpart(next); + } catch (InvalidJidException e) { + conversation.setNextCounterpart(null); + } + listener.onPresenceSelected(); + }); + builder.create().show(); + } + + 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()); + builder.setMessage(R.string.without_mutual_presence_updates); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ignore, (dialog, which) -> { + conversation.setNextCounterpart(null); + if (listener != null) { + listener.onPresenceSelected(); + } + }); + builder.create().show(); + } + + public static String translateType(Context context, String type) { + switch (type.toLowerCase()) { + case "pc": + return context.getString(R.string.type_pc); + case "phone": + return context.getString(R.string.type_phone); + case "tablet": + return context.getString(R.string.type_tablet); + case "web": + return context.getString(R.string.type_web); + case "console": + return context.getString(R.string.type_console); + default: + return type; + } + } + + public interface OnPresenceSelected { + void onPresenceSelected(); + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java new file mode 100644 index 000000000..6258b49c6 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_CHOOSE_IMAGE; +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_LOCATION; +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE; +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; + +public enum SendButtonAction { + TEXT, TAKE_FROM_CAMERA, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE; + + public static SendButtonAction valueOfOrDefault(String setting, SendButtonAction text) { + try { + return valueOf(setting); + } catch (IllegalArgumentException e) { + return TEXT; + } + } + + public static SendButtonAction of(int attachmentChoice) { + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_LOCATION: + return SEND_LOCATION; + case ATTACHMENT_CHOICE_RECORD_VOICE: + return RECORD_VOICE; + case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + return TAKE_FROM_CAMERA; + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + return CHOOSE_PICTURE; + default: + throw new IllegalArgumentException("Not a known attachment choice"); + } + } + + public int toChoice() { + switch (this) { + case TAKE_FROM_CAMERA: + return ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; + case SEND_LOCATION: + return ATTACHMENT_CHOICE_LOCATION; + case RECORD_VOICE: + return ATTACHMENT_CHOICE_RECORD_VOICE; + case CHOOSE_PICTURE: + return ATTACHMENT_CHOICE_CHOOSE_IMAGE; + default: + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java new file mode 100644 index 000000000..099365cc8 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.preference.PreferenceManager; + +import de.pixart.messenger.R; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Presence; +import de.pixart.messenger.ui.ConversationFragment; +import de.pixart.messenger.utils.UIHelper; + +public class SendButtonTool { + + public static SendButtonAction getAction(Activity activity, Conversation c, String text) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + final boolean empty = text.length() == 0; + final boolean conference = c.getMode() == Conversation.MODE_MULTI; + if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { + return SendButtonAction.CANCEL; + } else if (conference && !c.getAccount().httpUploadAvailable()) { + if (empty && c.getNextCounterpart() != null) { + return SendButtonAction.CANCEL; + } else { + return SendButtonAction.TEXT; + } + } else { + if (empty) { + if (conference && c.getNextCounterpart() != null) { + return SendButtonAction.CANCEL; + } else { + String setting = preferences.getString("quick_action", activity.getResources().getString(R.string.quick_action)); + if (!setting.equals("none") && UIHelper.receivedLocationQuestion(c.getLatestMessage())) { + return SendButtonAction.SEND_LOCATION; + } else { + if (setting.equals("recent")) { + setting = preferences.getString(ConversationFragment.RECENTLY_USED_QUICK_ACTION, SendButtonAction.TEXT.toString()); + return SendButtonAction.valueOfOrDefault(setting, SendButtonAction.TEXT); + } else { + return SendButtonAction.valueOfOrDefault(setting, SendButtonAction.TEXT); + } + } + } + } else { + return SendButtonAction.TEXT; + } + } + } + + public static int getSendButtonImageResource(Activity activity, SendButtonAction action, Presence.Status status) { + switch (action) { + case TEXT: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_text_online; + case AWAY: + return R.drawable.ic_send_text_away; + case XA: + case DND: + return R.drawable.ic_send_text_dnd; + default: + return R.drawable.ic_send_text_offline; + } + case TAKE_FROM_CAMERA: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_photo_online; + case AWAY: + return R.drawable.ic_send_photo_away; + case XA: + case DND: + return R.drawable.ic_send_photo_dnd; + default: + return R.drawable.ic_send_photo_offline; + } + case RECORD_VOICE: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_voice_online; + case AWAY: + return R.drawable.ic_send_voice_away; + case XA: + case DND: + return R.drawable.ic_send_voice_dnd; + default: + return R.drawable.ic_send_voice_offline; + } + case SEND_LOCATION: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_location_online; + case AWAY: + return R.drawable.ic_send_location_away; + case XA: + case DND: + return R.drawable.ic_send_location_dnd; + default: + return R.drawable.ic_send_location_offline; + } + case CANCEL: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_cancel_online; + case AWAY: + return R.drawable.ic_send_cancel_away; + case XA: + case DND: + return R.drawable.ic_send_cancel_dnd; + default: + return R.drawable.ic_send_cancel_offline; + } + case CHOOSE_PICTURE: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_picture_online; + case AWAY: + return R.drawable.ic_send_picture_away; + case XA: + case DND: + return R.drawable.ic_send_picture_dnd; + default: + return R.drawable.ic_send_picture_offline; + } + } + return R.drawable.ic_send_text_offline; + } + + private static int getThemeResource(Activity activity, int r_attr_name, int r_drawable_def) { + int[] attrs = {r_attr_name}; + TypedArray ta = activity.getTheme().obtainStyledAttributes(attrs); + + int res = ta.getResourceId(0, r_drawable_def); + ta.recycle(); + + return res; + } + +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/SendButtonAction.java b/src/main/java/de/pixart/messenger/utils/SendButtonAction.java deleted file mode 100644 index a3d548c15..000000000 --- a/src/main/java/de/pixart/messenger/utils/SendButtonAction.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package de.pixart.messenger.utils; - -import static de.pixart.messenger.ui.ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE; -import static de.pixart.messenger.ui.ConversationActivity.ATTACHMENT_CHOICE_LOCATION; -import static de.pixart.messenger.ui.ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE; -import static de.pixart.messenger.ui.ConversationActivity.ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; - -public enum SendButtonAction { - TEXT, TAKE_FROM_CAMERA, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE; - - public static SendButtonAction valueOfOrDefault(String setting, SendButtonAction text) { - try { - return valueOf(setting); - } catch (IllegalArgumentException e) { - return TEXT; - } - } - - public static SendButtonAction of(int attachmentChoice) { - switch (attachmentChoice) { - case ATTACHMENT_CHOICE_LOCATION: - return SEND_LOCATION; - case ATTACHMENT_CHOICE_RECORD_VOICE: - return RECORD_VOICE; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: - return TAKE_FROM_CAMERA; - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - return CHOOSE_PICTURE; - default: - throw new IllegalArgumentException("Not a known attachment choice"); - } - } - - public int toChoice() { - switch (this) { - case TAKE_FROM_CAMERA: - return ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; - case SEND_LOCATION: - return ATTACHMENT_CHOICE_LOCATION; - case RECORD_VOICE: - return ATTACHMENT_CHOICE_RECORD_VOICE; - case CHOOSE_PICTURE: - return ATTACHMENT_CHOICE_CHOOSE_IMAGE; - default: - return 0; - } - } -} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index 08e1d7dad..f42d6b73e 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -540,21 +540,4 @@ public class UIHelper { return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24, 0); } } - - public static String tranlasteType(Context context, String type) { - switch (type.toLowerCase()) { - case "pc": - return context.getString(R.string.type_pc); - case "phone": - return context.getString(R.string.type_phone); - case "tablet": - return context.getString(R.string.type_tablet); - case "web": - return context.getString(R.string.type_web); - case "console": - return context.getString(R.string.type_console); - default: - return type; - } - } } diff --git a/src/main/res/menu/activity_conversations.xml b/src/main/res/menu/activity_conversations.xml new file mode 100644 index 000000000..80a4f2bd3 --- /dev/null +++ b/src/main/res/menu/activity_conversations.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/conversations.xml b/src/main/res/menu/conversations.xml deleted file mode 100644 index 9687e45ff..000000000 --- a/src/main/res/menu/conversations.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/res/menu/fragment_conversation.xml b/src/main/res/menu/fragment_conversation.xml new file mode 100644 index 000000000..9687e45ff --- /dev/null +++ b/src/main/res/menu/fragment_conversation.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2d5d6fa20..3ca30dfea 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -764,7 +764,7 @@ The server certificate is not signed by a known Certificate Authority. The server certificate is expired. Accept Mismatching Server Name? - Server could not authenticate as \"%s\". The certificate is only valid for: + Server could not authenticate as \"%s\". The certificate is only valid for: Do you want to connect anyway? Certificate details: Certificate Verification -- cgit v1.2.3