From 8d9d96d4e1e8c8d6626e86187beae314d14e93ef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 20 Feb 2018 14:54:32 +0100 Subject: [PATCH] vastly untested refactor. pushing for backup purposes --- .../conversations/ui/BlockContactDialog.java | 3 +- .../ui/ConversationActivity.java | 801 +----------- .../ui/ConversationFragment.java | 1156 ++++++++++++----- .../conversations/ui/ShareWithActivity.java | 5 +- .../conversations/ui/TrustKeysActivity.java | 2 +- .../siacs/conversations/ui/XmppActivity.java | 141 +- .../ui/adapter/MessageAdapter.java | 3 +- .../conversations/ui/util/ActivityResult.java | 50 + .../conversations/ui/util/AttachmentTool.java | 61 + .../ui/util/ConversationMenuConfigurator.java | 123 ++ .../ui/util/PresenceSelector.java | 136 ++ .../ui/util/SendButtonAction.java | 10 +- .../conversations/ui/util/SendButtonTool.java | 188 +++ .../siacs/conversations/utils/UIHelper.java | 17 - src/main/res/menu/activity_conversations.xml | 22 + ...rsations.xml => fragment_conversation.xml} | 22 +- 16 files changed, 1455 insertions(+), 1285 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/ActivityResult.java create mode 100644 src/main/java/eu/siacs/conversations/ui/util/AttachmentTool.java create mode 100644 src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java create mode 100644 src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java create mode 100644 src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java create mode 100644 src/main/res/menu/activity_conversations.xml rename src/main/res/menu/{conversations.xml => fragment_conversation.xml} (85%) diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java index dd6360674..fe5ab35d5 100644 --- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -18,8 +18,7 @@ import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.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/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 37cabea5c..0fdaa1472 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -6,23 +6,17 @@ import android.app.FragmentTransaction; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ClipData; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; -import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.provider.MediaStore; import android.provider.Settings; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; import android.support.v7.app.ActionBar; import android.util.Log; import android.util.Pair; -import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -31,10 +25,8 @@ import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; -import android.widget.CheckBox; import android.widget.Toast; -import org.openintents.openpgp.util.OpenPgpApi; import java.util.ArrayList; import java.util.Iterator; @@ -48,18 +40,14 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; -import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.entities.Transferable; -import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.ui.service.EmojiService; -import eu.siacs.conversations.ui.util.SendButtonAction; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; @@ -69,8 +57,6 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { - public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; - public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW"; public static final String CONVERSATION = "conversationUuid"; public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid"; @@ -78,20 +64,6 @@ public class ConversationActivity extends XmppActivity 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_PHOTO = 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_INVALID = 0x0306; - public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307; 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_URI = "state_pending_uri"; @@ -102,17 +74,10 @@ public class ConversationActivity extends XmppActivity private boolean mPanelOpen = true; private AtomicBoolean mShouldPanelBeOpen = new AtomicBoolean(false); private Pair mScrollPosition = null; - final private List mPendingImageUris = new ArrayList<>(); - final private List mPendingFileUris = new ArrayList<>(); - private Uri mPendingGeoUri = null; private boolean forbidProcessingPendings = false; - private Message mPendingDownloadableMessage = null; private boolean conversationWasSelectedByKeyboard = false; - private boolean showSoundRecorderAttachment = false; - private boolean showLocationAttachment = false; - private View mContentView; private List conversationList = new ArrayList<>(); @@ -125,9 +90,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; public Conversation getSelectedConversation() { return this.mSelectedConversation; @@ -193,12 +156,6 @@ public class ConversationActivity extends XmppActivity } else { mScrollPosition = null; } - String pending = savedInstanceState.getString(STATE_PENDING_URI, null); - if (pending != null) { - Log.d(Config.LOGTAG, "ConversationsActivity.onCreate() - restoring pending image uri"); - mPendingImageUris.clear(); - mPendingImageUris.add(Uri.parse(pending)); - } } setContentView(R.layout.fragment_conversations_overview); @@ -395,271 +352,10 @@ 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 menuArchive = menu.findItem(R.id.action_archive); - final MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details); - final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details); - 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 menuMute = menu.findItem(R.id.action_mute); - final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); - final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice); - final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location); - - if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { - menuArchive.setVisible(false); - menuMucDetails.setVisible(false); - menuContactDetails.setVisible(false); - menuSecure.setVisible(false); - menuInviteContact.setVisible(false); - menuAttach.setVisible(false); - menuClearHistory.setVisible(false); - menuMute.setVisible(false); - menuUnmute.setVisible(false); - } else { - menuAdd.setVisible(!isConversationsOverviewHideable()); - if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { - menuSecure.setIcon(R.drawable.ic_lock_white_24dp); - } - if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { - menuContactDetails.setVisible(false); - 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 { - menuContactDetails.setVisible(!this.getSelectedConversation().withSelf()); - menuMucDetails.setVisible(false); - menuSecure.setVisible(Config.multipleEncryptionChoices()); - menuInviteContact.setVisible(xmppConnectionService != null && xmppConnectionService.findConferenceServer(getSelectedConversation().getAccount()) != null); - } - if (this.getSelectedConversation().isMuted()) { - menuMute.setVisible(false); - } else { - menuUnmute.setVisible(false); - } - menuAttachLocation.setVisible(showLocationAttachment); - menuAttachSoundRecorder.setVisible(showSoundRecorderAttachment); - configureEncryptionMenu(getSelectedConversation(), menu); - } - } + getMenuInflater().inflate(R.menu.activity_conversations, menu); return super.onCreateOptionsMenu(menu); } - private static void configureEncryptionMenu(Conversation conversation, Menu menu) { - 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); - 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_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 = () -> { - 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_RECORD_VIDEO: - intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); - break; - case ATTACHMENT_CHOICE_TAKE_PHOTO: - Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - mPendingImageUris.clear(); - mPendingImageUris.add(uri); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - 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: - intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); - fallbackPackageId = "eu.siacs.conversations.voicerecorder"; - break; - case ATTACHMENT_CHOICE_LOCATION: - intent.setAction("eu.siacs.conversations.location.request"); - fallbackPackageId = "eu.siacs.conversations.sharelocation"; - break; - } - if (intent.resolveActivity(getPackageManager()) != null) { - 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) { - 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 (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(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_storage_permission, Toast.LENGTH_SHORT).show(); - } - } - - public void startDownloadable(Message message) { - if (!Config.ONLY_INTERNAL_STORAGE && !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) { @@ -668,56 +364,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_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_record_video: - 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: - this.endConversation(getSelectedConversation()); - break; - case R.id.action_contact_details: - switchToContactDetails(getSelectedConversation().getContact()); - break; - case R.id.action_muc_details: - Intent intent = new Intent(this, - ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", getSelectedConversation().getUuid()); - startActivity(intent); - break; - case R.id.action_invite: - inviteToConversation(getSelectedConversation()); - break; - case R.id.action_clear_history: - clearHistoryDialog(getSelectedConversation()); - break; - case R.id.action_mute: - muteConversationDialog(getSelectedConversation()); - break; - case R.id.action_unmute: - unmuteConversation(getSelectedConversation()); - break; - case R.id.action_block: - BlockContactDialog.show(this, getSelectedConversation()); - break; - case R.id.action_unblock: - BlockContactDialog.show(this, getSelectedConversation()); - break; - default: - break; - } - return super.onOptionsItemSelected(item); } else { return super.onOptionsItemSelected(item); } @@ -748,116 +394,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); - 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_PHOTO); - break; - case R.id.attach_record_video: - attachFile(ATTACHMENT_CHOICE_RECORD_VIDEO); - 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) { - 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_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(); - } - - protected void muteConversationDialog(final Conversation conversation) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.disable_notifications); - final int[] durations = getResources().getIntArray(R.array.mute_options_durations); - builder.setItems(R.array.mute_options_descriptions, (dialog, which) -> { - final long till; - if (durations[which] == -1) { - till = Long.MAX_VALUE; - } else { - till = System.currentTimeMillis() + (durations[which] * 1000); - } - conversation.setMutedTill(till); - ConversationActivity.this.xmppConnectionService.updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - }); - builder.create().show(); - } - - public void unmuteConversation(final Conversation conversation) { - conversation.setMutedTill(0); - this.xmppConnectionService.updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - @Override public void onBackPressed() { if (!isConversationsOverviewVisable()) { @@ -1025,11 +561,6 @@ public class ConversationActivity extends XmppActivity if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { sendReadMarkerIfNecessary(getSelectedConversation()); } - new Handler().post(() -> { - showSoundRecorderAttachment = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) != null; - showLocationAttachment = new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) != null; - invalidateOptionsMenu(); - }); } @Override @@ -1046,20 +577,17 @@ 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, "ConversationsActivity.onSaveInstanceState() - saving pending image uri"); savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString()); } else { savedInstanceState.remove(STATE_PENDING_URI); - } + }*/ super.onSaveInstanceState(savedInstanceState); } private void clearPending() { - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingGeoUri = null; - mPostponedActivityResult = null; + mConversationFragment.clearPending(); } private void redirectToStartConversationActivity(boolean noAnimation) { @@ -1142,30 +670,7 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment.setupIme(); } - if (this.mPostponedActivityResult != null) { - this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); - } - - final boolean stopping = isStopping(); - - if (!forbidProcessingPendings) { - for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - Uri foo = i.next(); - Log.d(Config.LOGTAG, "ConversationsActivity.onBackendConnected() - attaching image to conversations. stopping=" + Boolean.toString(stopping)); - attachImageToConversation(getSelectedConversation(), foo); - } - - for (Iterator i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationsActivity.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(); @@ -1225,7 +730,7 @@ public class ConversationActivity extends XmppActivity if (downloadUuid != null) { final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); if (message != null) { - startDownloadable(message); + //startDownloadable(message); } } } else { @@ -1252,134 +757,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(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)); - if (xmppConnectionServiceBound) { - for (Iterator i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); - attachImageToConversation(getSelectedConversation(), i.next()); - } - } - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE || requestCode == ATTACHMENT_CHOICE_RECORD_VIDEO) { - final List uris = extractUriFromIntent(data); - Log.d(Config.LOGTAG, "uris " + uris.toString()); - 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()) { - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO"); - attachFileToConversation(c, i.next()); - } - } - } - }; - if (c == null || c.getMode() == Conversation.MODE_MULTI - || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c))) { - callback.onPresenceSelected(); - } else { - selectPresence(c, callback); - } - } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { - if (mPendingImageUris.size() == 1) { - Uri uri = FileBackend.getIndexableTakePhotoUri(mPendingImageUris.get(0)); - mPendingImageUris.set(0, uri); - if (xmppConnectionServiceBound) { - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. TAKE_PHOTO"); - attachImageToConversation(getSelectedConversation(), uri); - mPendingImageUris.clear(); - } - if (!Config.ONLY_INTERNAL_STORAGE) { - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - intent.setData(uri); - sendBroadcast(intent); - } - } else { - mPendingImageUris.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(); - 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(); } @@ -1428,108 +816,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); - } - }); - } - - public void attachImageToConversation(Uri uri) { - this.attachImageToConversation(getSelectedConversation(), uri); - } - - private void attachImageToConversation(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 hidePrepareFileToast(final Toast prepareFileToast) { - if (prepareFileToast != null) { - runOnUiThread(() -> prepareFileToast.cancel()); - } - } - public void updateConversationList() { xmppConnectionService.populateWithOrderedConversations(conversationList); if (!conversationList.contains(mSelectedConversation)) { @@ -1553,44 +839,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)); } @@ -1603,36 +851,6 @@ public class ConversationActivity extends XmppActivity return getPreferences().getBoolean("use_green_background", getResources().getBoolean(R.bool.use_green_background)); } - 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(); @@ -1675,9 +893,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/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 43f1b9875..8d6ad43aa 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1,6 +1,11 @@ package eu.siacs.conversations.ui; +import android.annotation.SuppressLint; import android.app.Activity; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; import android.support.v7.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; @@ -21,8 +26,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; @@ -34,6 +40,7 @@ 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.ImageButton; import android.widget.ListView; import android.widget.PopupMenu; @@ -42,17 +49,24 @@ import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; +import org.openintents.openpgp.util.OpenPgpApi; + 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.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; @@ -61,6 +75,7 @@ import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.entities.ReadByMarker; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; @@ -68,10 +83,13 @@ import eu.siacs.conversations.http.HttpDownloadConnection; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected; -import eu.siacs.conversations.ui.XmppActivity.OnValueEdited; import eu.siacs.conversations.ui.adapter.MessageAdapter; +import eu.siacs.conversations.ui.util.ActivityResult; +import eu.siacs.conversations.ui.util.AttachmentTool; +import eu.siacs.conversations.ui.util.ConversationMenuConfigurator; +import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.ui.util.SendButtonAction; +import eu.siacs.conversations.ui.util.SendButtonTool; import eu.siacs.conversations.ui.widget.EditMessage; import eu.siacs.conversations.utils.MessageUtils; import eu.siacs.conversations.utils.NickValidityChecker; @@ -79,16 +97,35 @@ import eu.siacs.conversations.utils.StylingHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.chatstate.ChatState; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_LOCATION; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_RECORD_VIDEO; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO; +import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT; +import static eu.siacs.conversations.ui.XmppActivity.REQUEST_ANNOUNCE_PGP; +import static eu.siacs.conversations.ui.XmppActivity.REQUEST_CHOOSE_PGP_ID; + 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_PHOTO = 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_INVALID = 0x0306; + public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307; + + public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; + + final protected List messageList = new ArrayList<>(); protected Conversation conversation; protected ListView messagesView; @@ -99,6 +136,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private TextView snackbarMessage; private TextView snackbarAction; private Toast messageLoaderToast; + + private ActivityResult postponedActivityResult = null; + public Uri mPendingEditorContent = null; + + private ConversationActivity activity; + private OnClickListener clickToMuc = new OnClickListener() { @Override @@ -109,7 +152,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa startActivity(intent); } }; - private ConversationActivity activity; private OnClickListener leaveMuc = new OnClickListener() { @Override @@ -133,13 +175,9 @@ 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; }); } }; @@ -168,53 +206,47 @@ 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; - } + getActivity().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; } - 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.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.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(); + getActivity().runOnUiThread(() -> { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); } + if (ConversationFragment.this.conversation != conversation) { + return; + } + messageLoaderToast = Toast.makeText(view.getContext(), resId, Toast.LENGTH_LONG); + messageLoaderToast.show(); }); } @@ -234,18 +266,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; } @@ -264,25 +293,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 @@ -294,13 +313,7 @@ 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 = this::showBlockSubmenu; private OnClickListener mAllowPresenceSubscription = new OnClickListener() { @Override public void onClick(View v) { @@ -321,14 +334,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); } } @@ -336,21 +349,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; } }; private OnClickListener mSendButtonListener = new OnClickListener() { @@ -366,7 +375,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) { @@ -395,6 +404,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private String incomplete; private int lastCompletionCursor; private boolean firstWord = false; + private Message mPendingDownloadableMessage; private int getIndexOf(String uuid, List messages) { if (uuid == null) { @@ -438,6 +448,104 @@ 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); + getActivity().runOnUiThread(() -> activity.replaceToast(text)); + } + + @Override + public void success(Message message) { + getActivity().runOnUiThread(() -> activity.hideToast()); + hidePrepareFileToast(prepareFileToast); + activity.xmppConnectionService.sendMessage(message); + } + + @Override + public void error(final int errorCode, Message message) { + hidePrepareFileToast(prepareFileToast); + getActivity().runOnUiThread(() -> activity.replaceToast(getString(errorCode))); + + } + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + hidePrepareFileToast(prepareFileToast); + } + }); + } + + public void attachImageToConversation(Uri uri) { + this.attachImageToConversation(conversation, uri); + } + + private void attachImageToConversation(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); + activity.runOnUiThread(() -> activity.replaceToast(getString(error))); + } + }); + } + + private void hidePrepareFileToast(final Toast prepareFileToast) { + if (prepareFileToast != null) { + activity.runOnUiThread(prepareFileToast::cancel); + } + } + private void sendMessage() { final String body = mEditMessage.getText().toString(); final Conversation conversation = this.conversation; @@ -466,7 +574,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; @@ -475,6 +583,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) { @@ -486,12 +624,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } else if (multi && !conversation.getMucOptions().participating()) { this.mEditMessage.setHint(R.string.you_are_not_participating); } else { - this.mEditMessage.setHint(UIHelper.getMessageHint(activity, conversation)); + this.mEditMessage.setHint(UIHelper.getMessageHint(getActivity(), conversation)); getActivity().invalidateOptionsMenu(); } } - public void setupIme() { + public void setupIme() {; if (activity != null) { if (activity.usingEnterKey() && activity.enterIsSend()) { mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); @@ -506,19 +644,153 @@ 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()); + } + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + case ATTACHMENT_CHOICE_RECORD_VIDEO: + 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/RECORD_VIDEO"); + 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 menuMucDetails = menu.findItem(R.id.action_muc_details); + final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details); + final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + final MenuItem menuMute = menu.findItem(R.id.action_mute); + final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); + + + if (conversation != null) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + menuContactDetails.setVisible(false); + menuInviteContact.setVisible(conversation.getMucOptions().canInvite()); + } else { + menuContactDetails.setVisible(!this.conversation.withSelf()); + menuMucDetails.setVisible(false); + final XmppConnectionService service = activity.xmppConnectionService; + menuInviteContact.setVisible(service != null && service.findConferenceServer(conversation.getAccount()) != null); + } + if (conversation.isMuted()) { + menuMute.setVisible(false); + } else { + menuUnmute.setVisible(false); + } + 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 = (EditMessage) 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(); } }); @@ -545,7 +817,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(getActivity(), activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show(); } highlightInConference(user.getResourcepart()); } @@ -568,7 +840,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()); @@ -588,7 +860,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(); @@ -596,7 +868,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(); } } } @@ -765,8 +1037,356 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + @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_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_record_video: + 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: + activity.endConversation(conversation); + break; + case R.id.action_contact_details: + activity.switchToContactDetails(conversation.getContact()); + break; + case R.id.action_muc_details: + Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + startActivity(intent); + break; + case R.id.action_invite: + activity.inviteToConversation(conversation); + break; + case R.id.action_clear_history: + clearHistoryDialog(conversation); + break; + case R.id.action_mute: + muteConversationDialog(conversation); + break; + case R.id.action_unmute: + unmuteConversation(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; + 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_PHOTO); + break; + case R.id.attach_record_video: + attachFile(ATTACHMENT_CHOICE_RECORD_VIDEO); + 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_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; + } + } + 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()) { + Toast warning = Toast.makeText(getActivity(), 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); + } + } + } + + @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(getActivity(), R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + + 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(); + } + } 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); + 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(); + } + }); + builder.create().show(); + } + + protected void muteConversationDialog(final Conversation conversation) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.disable_notifications); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); + builder.setItems(R.array.mute_options_descriptions, (dialog, which) -> { + final long till; + if (durations[which] == -1) { + till = Long.MAX_VALUE; + } else { + till = System.currentTimeMillis() + (durations[which] * 1000); + } + conversation.setMutedTill(till); + activity.xmppConnectionService.updateConversation(conversation); + activity.updateConversationList(); + updateMessages(); + getActivity().invalidateOptionsMenu(); + }); + builder.create().show(); + } + + public void unmuteConversation(final Conversation conversation) { + conversation.setMutedTill(0); + this.activity.xmppConnectionService.updateConversation(conversation); + this.activity.updateConversationList(); + updateMessages(); + getActivity().invalidateOptionsMenu(); + } + + protected void selectPresenceToAttachFile(final int attachmentChoice) { + final Account account = conversation.getAccount(); + final PresenceSelector.OnPresenceSelected callback = () -> { + 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_RECORD_VIDEO: + intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri(); + //mPendingImageUris.clear(); + //mPendingImageUris.add(uri); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + 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: + intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + fallbackPackageId = "eu.siacs.conversations.voicerecorder"; + break; + case ATTACHMENT_CHOICE_LOCATION: + intent.setAction("eu.siacs.conversations.location.request"); + fallbackPackageId = "eu.siacs.conversations.sharelocation"; + break; + } + if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + 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) { + conversation.setNextCounterpart(null); + callback.onPresenceSelected(); + } else { + activity.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(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(() -> { + final PackageManager packageManager = getActivity().getPackageManager(); + ConversationMenuConfigurator.updateAttachmentAvailability(packageManager); + 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.confirm, null); @@ -785,9 +1405,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); @@ -798,16 +1418,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(); } } @@ -828,12 +1448,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; } @@ -862,8 +1479,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.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } } @@ -939,13 +1555,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); } @@ -963,7 +1580,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(); @@ -1006,30 +1622,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) { @@ -1148,102 +1762,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 activity.getThemeResource(R.attr.ic_send_text_offline, R.drawable.ic_send_text_offline); - } - case RECORD_VIDEO: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_videocam_online; - case AWAY: - return R.drawable.ic_send_videocam_away; - case XA: - case DND: - return R.drawable.ic_send_videocam_dnd; - default: - return activity.getThemeResource(R.attr.ic_send_videocam_offline, R.drawable.ic_send_videocam_offline); - } - case TAKE_PHOTO: - 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 activity.getThemeResource(R.attr.ic_send_photo_offline, 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 activity.getThemeResource(R.attr.ic_send_voice_offline, 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 activity.getThemeResource(R.attr.ic_send_location_offline, 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 activity.getThemeResource(R.attr.ic_send_cancel_offline, 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 activity.getThemeResource(R.attr.ic_send_picture_offline, R.drawable.ic_send_picture_offline); - } - } - return activity.getThemeResource(R.attr.ic_send_text_offline, 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; @@ -1255,40 +1773,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; @@ -1301,7 +1788,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 updateDateSeparators() { @@ -1481,47 +1968,32 @@ 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(); - } + showNoPGPKeyDialog(false, (dialog, which) -> { + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppService.updateConversation(conversation); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); }); } } else { @@ -1534,28 +2006,47 @@ 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(); - } + showNoPGPKeyDialog(true, (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) { @@ -1566,8 +2057,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(); } @@ -1670,25 +2160,17 @@ 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 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/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 1c954be70..a7d7cfc09 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -29,6 +29,7 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.ui.service.EmojiService; +import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -312,7 +313,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) { share.multiple = share.uris.size() > 1; @@ -339,7 +340,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); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index e128def93..ae2990c00 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -388,7 +388,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/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 67706a952..19be2bdac 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -70,6 +70,7 @@ import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.BarcodeProvider; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; +import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.UIHelper; @@ -103,7 +104,7 @@ public abstract class XmppActivity extends AppCompatActivity { protected int mTheme; protected boolean mUsingEnterKey = false; protected Toast mToast; - protected Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); + public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); protected ConferenceInvite mPendingConferenceInvite = null; protected ServiceConnection mConnection = new ServiceConnection() { @@ -375,6 +376,38 @@ public abstract class XmppActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } + public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) { + final Contact contact = conversation.getContact(); + if (!contact.showInRoster()) { + showAddToRosterDialog(conversation.getContact()); + } else { + final Presences presences = contact.getPresences(); + if (presences.size() == 0) { + if (!contact.getOption(Contact.Options.TO) + && !contact.getOption(Contact.Options.ASKING) + && contact.getAccount().getStatus() == Account.State.ONLINE) { + showAskForPresenceDialog(contact); + } else if (!contact.getOption(Contact.Options.TO) + || !contact.getOption(Contact.Options.FROM)) { + PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener); + } else { + conversation.setNextCounterpart(null); + listener.onPresenceSelected(); + } + } else if (presences.size() == 1) { + String presence = presences.toResourceArray()[0]; + try { + conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence)); + } catch (InvalidJidException e) { + conversation.setNextCounterpart(null); + } + listener.onPresenceSelected(); + } else { + PresenceSelector.showPresenceSelectionDialog(this, conversation, listener); + } + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -665,10 +698,6 @@ public abstract class XmppActivity extends AppCompatActivity { } - protected void showAddToRosterDialog(final Conversation conversation) { - showAddToRosterDialog(conversation.getContact()); - } - protected void showAddToRosterDialog(final Contact contact) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(contact.getJid().toString()); @@ -701,21 +730,6 @@ public abstract class XmppActivity extends AppCompatActivity { builder.create().show(); } - private void warnMutalPresenceSubscription(final Conversation conversation, - final OnPresenceSelected listener) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - 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(); - } - protected void quickEdit(String previousValue, int hint, OnValueEdited callback) { quickEdit(previousValue, callback, hint, false); } @@ -776,89 +790,6 @@ public abstract class XmppActivity extends AppCompatActivity { } } - public void selectPresence(final Conversation conversation, - final OnPresenceSelected listener) { - final Contact contact = conversation.getContact(); - if (!contact.showInRoster()) { - showAddToRosterDialog(conversation); - } else { - final Presences presences = contact.getPresences(); - if (presences.size() == 0) { - if (!contact.getOption(Contact.Options.TO) - && !contact.getOption(Contact.Options.ASKING) - && contact.getAccount().getStatus() == Account.State.ONLINE) { - showAskForPresenceDialog(contact); - } else if (!contact.getOption(Contact.Options.TO) - || !contact.getOption(Contact.Options.FROM)) { - warnMutalPresenceSubscription(conversation, listener); - } else { - conversation.setNextCounterpart(null); - listener.onPresenceSelected(); - } - } else if (presences.size() == 1) { - String presence = presences.toResourceArray()[0]; - try { - conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(), contact.getJid().getDomainpart(), presence)); - } catch (InvalidJidException e) { - conversation.setNextCounterpart(null); - } - listener.onPresenceSelected(); - } else { - showPresenceSelectionDialog(presences, conversation, listener); - } - } - } - - private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) { - final Contact contact = conversation.getContact(); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(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] = UIHelper.tranlasteType(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 + ")"; - } else { - readableIdentities[i] = UIHelper.tranlasteType(this, type) + " (" + name + " / " + resource + ")"; - } - } else { - readableIdentities[i] = UIHelper.tranlasteType(this, 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(); - } - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { @@ -1048,10 +979,6 @@ public abstract class XmppActivity extends AppCompatActivity { String onValueEdited(String value); } - public interface OnPresenceSelected { - void onPresenceSelected(); - } - public static class ConferenceInvite { private String uuid; private List jids = new ArrayList<>(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 734542c5f..a01ef856b 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -533,7 +533,8 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie @Override public void onClick(View v) { - activity.startDownloadable(message); + //TODO make proper reference to fragment + //activity.startDownloadable(message); } }); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/ActivityResult.java b/src/main/java/eu/siacs/conversations/ui/util/ActivityResult.java new file mode 100644 index 000000000..1d2cf1025 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/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 eu.siacs.conversations.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); + } + +} diff --git a/src/main/java/eu/siacs/conversations/ui/util/AttachmentTool.java b/src/main/java/eu/siacs/conversations/ui/util/AttachmentTool.java new file mode 100644 index 000000000..86b7bfab3 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/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 eu.siacs.conversations.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; + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java b/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java new file mode 100644 index 000000000..ae9f33248 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/ConversationMenuConfigurator.java @@ -0,0 +1,123 @@ +/* + * 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 eu.siacs.conversations.ui.util; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; + +public class ConversationMenuConfigurator { + + private static boolean showSoundRecorderAttachment = false; + private static boolean showLocationAttachment = false; + + + public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu) { + final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice); + final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location); + 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); + + if (!visible) { + return; + } + + menuAttachLocation.setVisible(showLocationAttachment); + menuAttachSoundRecorder.setVisible(showSoundRecorderAttachment); + } + + 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 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); + } + + 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_PGP: + pgp.setChecked(true); + break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; + default: + none.setChecked(true); + break; + } + } + + public static void updateAttachmentAvailability(PackageManager packageManager) { + showSoundRecorderAttachment = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(packageManager) != null; + showLocationAttachment = new Intent("eu.siacs.conversations.location.request").resolveActivity(packageManager) != null; + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java new file mode 100644 index 000000000..51a14cd9e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/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 eu.siacs.conversations.ui.util; + +import android.app.Activity; +import android.content.Context; +import android.support.v7.app.AlertDialog; +import android.util.Pair; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.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(); + } + + private 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(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/util/SendButtonAction.java b/src/main/java/eu/siacs/conversations/ui/util/SendButtonAction.java index f8063fb68..074f57659 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/SendButtonAction.java +++ b/src/main/java/eu/siacs/conversations/ui/util/SendButtonAction.java @@ -29,11 +29,11 @@ package eu.siacs.conversations.ui.util; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_LOCATION; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_RECORD_VIDEO; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE; -import static eu.siacs.conversations.ui.ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO; +import static eu.siacs.conversations.ui.ConversationFragment.ATTACHMENT_CHOICE_CHOOSE_IMAGE; +import static eu.siacs.conversations.ui.ConversationFragment.ATTACHMENT_CHOICE_LOCATION; +import static eu.siacs.conversations.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VIDEO; +import static eu.siacs.conversations.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE; +import static eu.siacs.conversations.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_PHOTO; public enum SendButtonAction { TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE, RECORD_VIDEO; diff --git a/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java b/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java new file mode 100644 index 000000000..4542e065d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java @@ -0,0 +1,188 @@ +/* + * 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 eu.siacs.conversations.ui.util; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.preference.PreferenceManager; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.ui.ConversationFragment; +import eu.siacs.conversations.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 getThemeResource(activity, R.attr.ic_send_text_offline, R.drawable.ic_send_text_offline); + } + case RECORD_VIDEO: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_videocam_online; + case AWAY: + return R.drawable.ic_send_videocam_away; + case XA: + case DND: + return R.drawable.ic_send_videocam_dnd; + default: + return getThemeResource(activity, R.attr.ic_send_videocam_offline, R.drawable.ic_send_videocam_offline); + } + case TAKE_PHOTO: + 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 getThemeResource(activity, R.attr.ic_send_photo_offline, 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 getThemeResource(activity, R.attr.ic_send_voice_offline, 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 getThemeResource(activity, R.attr.ic_send_location_offline, 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 getThemeResource(activity, R.attr.ic_send_cancel_offline, 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 getThemeResource(activity, R.attr.ic_send_picture_offline, R.drawable.ic_send_picture_offline); + } + } + return getThemeResource(activity, R.attr.ic_send_text_offline, 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; + } + +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index b6c56ff1f..c2adeb2ac 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -532,21 +532,4 @@ public class UIHelper { return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); } } - - 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/fragment_conversation.xml similarity index 85% rename from src/main/res/menu/conversations.xml rename to src/main/res/menu/fragment_conversation.xml index d8dffe242..d0d245c99 100644 --- a/src/main/res/menu/conversations.xml +++ b/src/main/res/menu/fragment_conversation.xml @@ -1,12 +1,6 @@ + - - + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - \ No newline at end of file