diff options
Diffstat (limited to 'src/main/java/eu/siacs/conversations/ui/ConversationFragment.java')
-rw-r--r-- | src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 1221 |
1 files changed, 1221 insertions, 0 deletions
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java new file mode 100644 index 00000000..fcc4d373 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -0,0 +1,1221 @@ +package eu.siacs.conversations.ui; + +import android.app.AlertDialog; +import android.app.Fragment; +import android.app.PendingIntent; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.InputType; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; +import android.widget.Toast; + +import net.java.otr4j.session.SessionStatus; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentLinkedQueue; + +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog; +import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.TransferablePlaceholder; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.MucOptions; +import eu.siacs.conversations.entities.Presences; +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.adapter.MessageAdapter.OnContactPictureClicked; +import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; +import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener; +import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.chatstate.ChatState; +import eu.siacs.conversations.xmpp.jid.Jid; +import github.ankushsachdeva.emojicon.EmojiconGridView; +import github.ankushsachdeva.emojicon.EmojiconsPopup; +import github.ankushsachdeva.emojicon.emoji.Emojicon; + +public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { + + protected Conversation conversation; + private OnClickListener leaveMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.endConversation(conversation); + } + }; + private OnClickListener joinMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.xmppConnectionService.joinMuc(conversation); + } + }; + private OnClickListener enterPassword = new OnClickListener() { + + @Override + public void onClick(View v) { + MucOptions muc = conversation.getMucOptions(); + String password = muc.getPassword(); + if (password == null) { + password = ""; + } + activity.quickPasswordEdit(password, new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + activity.xmppConnectionService.providePasswordForMuc( + conversation, value); + } + }); + } + }; + protected ListView messagesView; + protected SwipeRefreshLayout swipeLayout; + final protected List<Message> messageList = new ArrayList<>(); + protected MessageAdapter messageListAdapter; + private EditMessage mEditMessage; + private ImageButton mSendButton; + private ImageView mEmojButton; + private View mRootView; + private EmojiconsPopup mEmojPopup; + private RelativeLayout snackbar; + private TextView snackbarMessage; + private TextView snackbarAction; + private IntentSender askForPassphraseIntent = null; + protected OnClickListener clickToDecryptListener = new OnClickListener() { + + @Override + public void onClick(View v) { + if (activity.hasPgp() && askForPassphraseIntent != null) { + try { + getActivity().startIntentSenderForResult( + askForPassphraseIntent, + ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, + 0, 0); + askForPassphraseIntent = null; + } catch (SendIntentException e) { + // + } + } + } + }; + protected OnClickListener clickToVerify = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.verifyOtrSessionDialog(conversation, v); + } + }; + private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); + private boolean mDecryptJobRunning = 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); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + sendMessage(); + return true; + } else { + return false; + } + } + }; + private OnClickListener mSendButtonListener = new OnClickListener() { + + @Override + public void onClick(View v) { + Object tag = v.getTag(); + if (tag instanceof SendButtonAction) { + SendButtonAction action = (SendButtonAction) tag; + switch (action) { + case TAKE_PHOTO: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case SEND_LOCATION: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION); + break; + case RECORD_VOICE: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case CHOOSE_PICTURE: + activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case CANCEL: + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextCounterpart(null); + updateChatMsgHint(); + updateSendButton(); + } + break; + default: + sendMessage(); + } + } else { + sendMessage(); + } + } + }; + private OnClickListener clickToMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + startActivity(intent); + } + }; + private ConversationActivity activity; + private Message selectedMessage; + + private void sendMessage() { + final String body = mEditMessage.getText().toString(); + if (body.length() == 0 || this.conversation == null) { + return; + } + boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); + Message message = new Message(conversation, body, conversation.getNextEncryption(forceEncryption)); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (conversation.getNextCounterpart() != null) { + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_PRIVATE); + } + } + if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_OTR) { + sendOtrMessage(message); + } else if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + sendPgpMessage(message); + } else { + sendPlainTextMessage(message); + } + } + + public void updateChatMsgHint() { + if (conversation.getMode() == Conversation.MODE_MULTI + && conversation.getNextCounterpart() != null) { + this.mEditMessage.setHint(getString( + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); + } else { + switch (conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption())) { + case Message.ENCRYPTION_NONE: + mEditMessage + .setHint(getString(R.string.send_plain_text_message)); + break; + case Message.ENCRYPTION_OTR: + mEditMessage.setHint(getString(R.string.send_otr_message)); + break; + case Message.ENCRYPTION_PGP: + mEditMessage.setHint(getString(R.string.send_pgp_message)); + break; + default: + break; + } + getActivity().invalidateOptionsMenu(); + } + } + + private void setupIme() { + if (ConversationsPlusPreferences.displayEnterKey()) { + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); + } else { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); + } + } + + @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); + setupIme(); + mEditMessage.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (activity != null) { + activity.hideConversationsOverview(); + } + } + }); + mEditMessage.setOnEditorActionListener(mEditorActionListener); + + // Start of emojicon + mEmojButton = (ImageView) view.findViewById(R.id.emoji_btn); + mRootView = view.findViewById(R.id.textsend); + + // Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height + mEmojPopup = new EmojiconsPopup(mRootView, this.getActivity()); + + //Will automatically set size according to the soft keyboard size + mEmojPopup.setSizeForSoftKeyboard(); + + //If the emoji popup is dismissed, change emojiButton to smiley icon + mEmojPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { + + @Override + public void onDismiss() { + changeEmojiKeyboardIcon(mEmojButton, R.drawable.smiley); + } + }); + + //If the text keyboard closes, also dismiss the emoji popup + mEmojPopup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() { + + @Override + public void onKeyboardOpen(int keyBoardHeight) { + + } + + @Override + public void onKeyboardClose() { + if (mEmojPopup.isShowing()) + mEmojPopup.dismiss(); + } + }); + + //On emoji clicked, add it to edittext + mEmojPopup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() { + + @Override + public void onEmojiconClicked(Emojicon emojicon) { + if (mEditMessage == null || emojicon == null) { + return; + } + + int start = mEditMessage.getSelectionStart(); + int end = mEditMessage.getSelectionEnd(); + if (start < 0) { + mEditMessage.append(emojicon.getEmoji()); + } else { + mEditMessage.getText().replace(Math.min(start, end), + Math.max(start, end), emojicon.getEmoji(), 0, + emojicon.getEmoji().length()); + } + } + }); + + //On backspace clicked, emulate the KEYCODE_DEL key event + mEmojPopup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() { + + @Override + public void onEmojiconBackspaceClicked(View v) { + KeyEvent event = new KeyEvent( + 0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL); + mEditMessage.dispatchKeyEvent(event); + } + }); + + // To toggle between text keyboard and emoji keyboard keyboard(Popup) + mEmojButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + + //If popup is not showing => emoji keyboard is not visible, we need to show it + if(!mEmojPopup.isShowing()){ + + //If keyboard is visible, simply show the emoji popup + if(mEmojPopup.isKeyBoardOpen()){ + mEmojPopup.showAtBottom(); + changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); + } + + //else, open the text keyboard first and immediately after that show the emoji popup + else{ + mEditMessage.setFocusableInTouchMode(true); + mEditMessage.requestFocus(); + mEmojPopup.showAtBottomPending(); + final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT); + changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); + } + } + + //If popup is showing, simply dismiss it to show the undelying text keyboard + else{ + mEmojPopup.dismiss(); + } + } + }); + + // End of emojicon + + mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); + mSendButton.setOnClickListener(this.mSendButtonListener); + + snackbar = (RelativeLayout) view.findViewById(R.id.snackbar); + snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message); + snackbarAction = (TextView) view.findViewById(R.id.snackbar_action); + + messagesView = (ListView) view.findViewById(R.id.messages_view); + messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); + messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); + messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { + + @Override + public void onContactPictureClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getCounterpart() != null) { + if (!message.getCounterpart().isBareJid()) { + highlightInConference(message.getCounterpart().getResourcepart()); + } else { + highlightInConference(message.getCounterpart().toString()); + } + } + } else { + activity.switchToContactDetails(message.getContact()); + } + } else { + Account account = message.getConversation().getAccount(); + Intent intent = new Intent(activity, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + startActivity(intent); + } + } + }); + messageListAdapter + .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { + + @Override + public void onContactPictureLongClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getCounterpart() != null) { + privateMessageWith(message.getCounterpart()); + } + } + } else { + activity.showQrCode(); + } + } + }); + messagesView.setAdapter(messageListAdapter); + + registerForContextMenu(messagesView); + + // Start of swipe refresh + // New Swipe refresh + swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_container); + swipeLayout.setOnRefreshListener(new ConversationSwipeRefreshListener(messageList, swipeLayout, this, messagesView, messageListAdapter)); + // End of swipe refresh + + return view; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + synchronized (this.messageList) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + this.selectedMessage = this.messageList.get(acmi.position); + populateContextMenu(menu); + } + } + + private void populateContextMenu(ContextMenu menu) { + final Message m = this.selectedMessage; + if (m.getType() != Message.TYPE_STATUS) { + activity.getMenuInflater().inflate(R.menu.message_context, menu); + menu.setHeaderTitle(R.string.message_options); + MenuItem copyText = menu.findItem(R.id.copy_text); + MenuItem shareWith = menu.findItem(R.id.share_with); + MenuItem sendAgain = menu.findItem(R.id.send_again); + MenuItem copyUrl = menu.findItem(R.id.copy_url); + MenuItem downloadFile = menu.findItem(R.id.download_file); + MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); + if ((m.getType() == Message.TYPE_TEXT || m.getType() == Message.TYPE_PRIVATE) + && m.getTransferable() == null + && !GeoHelper.isGeoUri(m.getBody()) + && m.treatAsDownloadable() != Message.Decision.MUST) { + copyText.setVisible(true); + } + if ((m.getType() != Message.TYPE_TEXT + && m.getType() != Message.TYPE_PRIVATE + && m.getTransferable() == null) + || (GeoHelper.isGeoUri(m.getBody()))) { + shareWith.setVisible(true); + } + if (m.getStatus() == Message.STATUS_SEND_FAILED) { + sendAgain.setVisible(true); + } + if (m.hasFileOnRemoteHost() + || GeoHelper.isGeoUri(m.getBody()) + || m.treatAsDownloadable() == Message.Decision.MUST) { + copyUrl.setVisible(true); + } + if (m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) { + downloadFile.setVisible(true); + downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); + } + if ((m.getTransferable() != null && !(m.getTransferable() instanceof TransferablePlaceholder)) + || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING + || m.getStatus() == Message.STATUS_OFFERED))) { + cancelTransmission.setVisible(true); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.msg_ctx_mnu_details: + new MessageDetailsDialog(getActivity(), selectedMessage).show(); + return true; + case R.id.share_with: + shareWith(selectedMessage); + return true; + case R.id.copy_text: + copyText(selectedMessage); + return true; + case R.id.send_again: + resendMessage(selectedMessage); + return true; + case R.id.copy_url: + copyUrl(selectedMessage); + return true; + case R.id.download_file: + downloadFile(selectedMessage); + return true; + case R.id.cancel_transmission: + cancelTransmission(selectedMessage); + return true; + default: + return super.onContextItemSelected(item); + } + } + + private void shareWith(Message message) { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + if (GeoHelper.isGeoUri(message.getBody())) { + shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); + shareIntent.setType("text/plain"); + } else { + shareIntent.putExtra(Intent.EXTRA_STREAM, + activity.xmppConnectionService.getFileBackend() + .getJingleFileUri(message)); + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + String mime = message.getMimeType(); + if (mime == null) { + mime = "image/webp"; + } + shareIntent.setType(mime); + } + try { + activity.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(); + } + } + + private void copyText(Message message) { + if (activity.copyTextToClipboard(message.getMergedBody(), + R.string.message_text)) { + Toast.makeText(activity, R.string.message_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } + + private void resendMessage(Message message) { + if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { + DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + if (!file.exists()) { + Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + return; + } + } + activity.xmppConnectionService.resendFailedMessages(message); + } + + private void copyUrl(Message message) { + final String url; + final int resId; + if (GeoHelper.isGeoUri(message.getBody())) { + resId = R.string.location; + url = message.getBody(); + } else if (message.hasFileOnRemoteHost()) { + resId = R.string.file_url; + url = message.getFileParams().url.toString(); + } else { + url = message.getBody(); + resId = R.string.file_url; + } + if (activity.copyTextToClipboard(url, resId)) { + Toast.makeText(activity, R.string.url_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } + + private void downloadFile(Message message) { + activity.xmppConnectionService.getHttpConnectionManager() + .createNewDownloadConnection(message); + } + + private void cancelTransmission(Message message) { + Transferable transferable = message.getTransferable(); + if (transferable != null) { + transferable.cancel(); + } else { + activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + } + } + + protected void privateMessageWith(final Jid counterpart) { + this.mEditMessage.setText(""); + this.conversation.setNextCounterpart(counterpart); + updateChatMsgHint(); + updateSendButton(); + } + + protected void highlightInConference(String nick) { + String oldString = mEditMessage.getText().toString(); + if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { + mEditMessage.getText().insert(0, nick + ": "); + } else { + if (mEditMessage.getText().charAt( + mEditMessage.getSelectionStart() - 1) != ' ') { + nick = " " + nick; + } + mEditMessage.getText().insert(mEditMessage.getSelectionStart(), + nick + " "); + } + } + + @Override + public void onStop() { + mDecryptJobRunning = false; + super.onStop(); + if (this.conversation != null) { + final String msg = mEditMessage.getText().toString(); + this.conversation.setNextMessage(msg); + updateChatState(this.conversation, msg); + } + } + + private void updateChatState(final Conversation conversation, final String msg) { + ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED; + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { + activity.xmppConnectionService.sendChatState(conversation); + } + } + + public void reInit(Conversation conversation) { + if (conversation == null) { + return; + } + + this.activity = (ConversationActivity) getActivity(); + + if (this.conversation != null) { + final String msg = mEditMessage.getText().toString(); + this.conversation.setNextMessage(msg); + if (this.conversation != conversation) { + updateChatState(this.conversation, msg); + } + this.conversation.trim(); + } + + this.askForPassphraseIntent = null; + this.conversation = conversation; + this.mDecryptJobRunning = false; + this.mEncryptedMessages.clear(); + if (this.conversation.getMode() == Conversation.MODE_MULTI) { + this.conversation.setNextCounterpart(null); + } + this.mEditMessage.setKeyboardListener(null); + this.mEditMessage.setText(""); + this.mEditMessage.append(this.conversation.getNextMessage()); + this.mEditMessage.setKeyboardListener(this); + this.messagesView.setAdapter(messageListAdapter); + updateMessages(); + int size = this.messageList.size(); + if (size > 0) { + messagesView.setSelection(size - 1); + } + swipeLayout.setRefreshing(false); + } + + private OnClickListener mUnblockClickListener = new OnClickListener() { + @Override + public void onClick(final View v) { + v.post(new Runnable() { + @Override + public void run() { + v.setVisibility(View.INVISIBLE); + } + }); + if (conversation.isDomainBlocked()) { + BlockContactDialog.show(activity, activity.xmppConnectionService, conversation); + } else { + activity.unblockConversation(conversation); + } + } + }; + + private OnClickListener mAddBackClickListener = new OnClickListener() { + + @Override + public void onClick(View v) { + final Contact contact = conversation == null ? null : conversation.getContact(); + if (contact != null) { + activity.xmppConnectionService.createContact(contact); + activity.switchToContactDetails(contact); + } + } + }; + + private OnClickListener mUnmuteClickListener = new OnClickListener() { + + @Override + public void onClick(final View v) { + activity.unmuteConversation(conversation); + } + }; + + private OnClickListener mAnswerSmpClickListener = new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(activity, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); + startActivity(intent); + } + }; + + private void updateSnackBar(final Conversation conversation) { + final Account account = conversation.getAccount(); + final Contact contact = conversation.getContact(); + final int mode = conversation.getMode(); + if (conversation.isBlocked()) { + showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); + } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); + } else if (mode == Conversation.MODE_MULTI + && !conversation.getMucOptions().online() + && account.getStatus() == Account.State.ONLINE) { + switch (conversation.getMucOptions().getError()) { + case MucOptions.ERROR_NICK_IN_USE: + showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); + break; + case MucOptions.ERROR_UNKNOWN: + showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc); + break; + case MucOptions.ERROR_PASSWORD_REQUIRED: + showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); + break; + case MucOptions.ERROR_BANNED: + showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc); + break; + case MucOptions.ERROR_MEMBERS_ONLY: + showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); + break; + case MucOptions.KICKED_FROM_ROOM: + showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); + break; + default: + break; + } + } else if (askForPassphraseIntent != null) { + showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.smpRequested()) { + showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); + } else if (mode == Conversation.MODE_SINGLE + && conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) + && (!conversation.isOtrFingerprintVerified())) { + showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); + } else if (conversation.isMuted()) { + showSnackbar(R.string.notifications_disabled, R.string.enable, this.mUnmuteClickListener); + } else { + hideSnackbar(); + } + } + + public void updateMessages() { + synchronized (this.messageList) { + if (getView() == null) { + return; + } + final ConversationActivity activity = (ConversationActivity) getActivity(); + if (this.conversation != null) { + updateSnackBar(this.conversation); + conversation.populateWithMessages(ConversationFragment.this.messageList); + for (final Message message : this.messageList) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message + .getStatus() >= Message.STATUS_SEND) + && message.getTransferable() == null) { + if (!mEncryptedMessages.contains(message)) { + mEncryptedMessages.add(message); + } + } + } + decryptNext(); + updateStatusMessages(); + this.messageListAdapter.notifyDataSetChanged(); + updateChatMsgHint(); + if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { + activity.sendReadMarkerIfNecessary(conversation); + } + this.updateSendButton(); + } + } + } + + private void decryptNext() { + Message next = this.mEncryptedMessages.peek(); + PgpEngine engine = activity.xmppConnectionService.getPgpEngine(); + + if (next != null && engine != null && !mDecryptJobRunning) { + mDecryptJobRunning = true; + engine.decrypt(next, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + mDecryptJobRunning = false; + askForPassphraseIntent = pi.getIntentSender(); + updateSnackBar(conversation); + } + + @Override + public void success(Message message) { + mDecryptJobRunning = false; + try { + mEncryptedMessages.remove(); + } catch (final NoSuchElementException ignored) { + + } + askForPassphraseIntent = null; + activity.xmppConnectionService.updateMessage(message); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + mDecryptJobRunning = false; + try { + mEncryptedMessages.remove(); + } catch (final NoSuchElementException ignored) { + + } + activity.xmppConnectionService.updateConversationUi(); + } + }); + } + } + + private void messageSent() { + int size = this.messageList.size(); + messagesView.setSelection(size - 1); + mEditMessage.setText(""); + updateChatMsgHint(); + } + + enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} + + private int getSendButtonImageResource(SendButtonAction action, int status) { + switch (action) { + case TEXT: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_text_online; + case Presences.AWAY: + return R.drawable.ic_send_text_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_text_dnd; + default: + return R.drawable.ic_send_text_offline; + } + case TAKE_PHOTO: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_photo_online; + case Presences.AWAY: + return R.drawable.ic_send_photo_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_photo_dnd; + default: + return R.drawable.ic_send_photo_offline; + } + case RECORD_VOICE: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_voice_online; + case Presences.AWAY: + return R.drawable.ic_send_voice_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_voice_dnd; + default: + return R.drawable.ic_send_voice_offline; + } + case SEND_LOCATION: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_location_online; + case Presences.AWAY: + return R.drawable.ic_send_location_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_location_dnd; + default: + return R.drawable.ic_send_location_offline; + } + case CANCEL: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_cancel_online; + case Presences.AWAY: + return R.drawable.ic_send_cancel_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_cancel_dnd; + default: + return R.drawable.ic_send_cancel_offline; + } + case CHOOSE_PICTURE: + switch (status) { + case Presences.CHAT: + case Presences.ONLINE: + return R.drawable.ic_send_picture_online; + case Presences.AWAY: + return R.drawable.ic_send_picture_away; + case Presences.XA: + case Presences.DND: + return R.drawable.ic_send_picture_dnd; + default: + return R.drawable.ic_send_picture_offline; + } + } + return R.drawable.ic_send_text_offline; + } + + public void updateSendButton() { + final Conversation c = this.conversation; + final SendButtonAction action; + final int status; + final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0; + final boolean conference = c.getMode() == Conversation.MODE_MULTI; + 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 = ConversationsPlusPreferences.quickAction(); + if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { + setting = "location"; + } else if (setting.equals("recent")) { + setting = ConversationsPlusPreferences.recentlyUsedQuickAction(); + } + switch (setting) { + case "photo": + action = SendButtonAction.TAKE_PHOTO; + break; + case "location": + action = SendButtonAction.SEND_LOCATION; + break; + case "voice": + action = SendButtonAction.RECORD_VOICE; + break; + case "picture": + action = SendButtonAction.CHOOSE_PICTURE; + break; + default: + action = SendButtonAction.TEXT; + break; + } + } + } else { + action = SendButtonAction.TEXT; + } + } + if (ConversationsPlusPreferences.sendButtonStatus() && c != null + && c.getAccount().getStatus() == Account.State.ONLINE) { + if (c.getMode() == Conversation.MODE_SINGLE) { + status = c.getContact().getMostAvailableStatus(); + } else { + status = c.getMucOptions().online() ? Presences.ONLINE : Presences.OFFLINE; + } + } else { + status = Presences.OFFLINE; + } + this.mSendButton.setTag(action); + this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); + } + + public void updateStatusMessages() { + synchronized (this.messageList) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + ChatState state = conversation.getIncomingChatState(); + if (state == ChatState.COMPOSING) { + this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName()))); + } else if (state == ChatState.PAUSED) { + this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName()))); + } else { + for (int i = this.messageList.size() - 1; i >= 0; --i) { + if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) { + return; + } else { + if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) { + this.messageList.add(i + 1, + Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName()))); + return; + } + } + } + } + } + } + } + + protected void showSnackbar(final int message, final int action, + final OnClickListener clickListener) { + snackbar.setVisibility(View.VISIBLE); + snackbar.setOnClickListener(null); + snackbarMessage.setText(message); + snackbarMessage.setOnClickListener(null); + snackbarAction.setVisibility(View.VISIBLE); + snackbarAction.setText(action); + snackbarAction.setOnClickListener(clickListener); + } + + protected void hideSnackbar() { + snackbar.setVisibility(View.GONE); + } + + protected void sendPlainTextMessage(Message message) { + ConversationActivity activity = (ConversationActivity) getActivity(); + activity.xmppConnectionService.sendMessage(message); + messageSent(); + } + + protected void sendPgpMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + final Contact contact = message.getConversation().getContact(); + if (activity.hasPgp()) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + if (contact.getPgpKeyId() != 0) { + xmppService.getPgpEngine().hasKey(contact, + new UiCallback<Contact>() { + + @Override + public void userInputRequried(PendingIntent pi, + Contact contact) { + activity.runIntent( + pi, + ConversationActivity.REQUEST_ENCRYPT_MESSAGE); + } + + @Override + public void success(Contact contact) { + messageSent(); + activity.encryptTextMessage(message); + } + + @Override + public void error(int error, Contact contact) { + + } + }); + + } else { + showNoPGPKeyDialog(false, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation + .setNextEncryption(Message.ENCRYPTION_NONE); + xmppService.databaseBackend + .updateConversation(conversation); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); + } + }); + } + } else { + if (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(); + } + activity.encryptTextMessage(message); + messageSent(); + } 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.databaseBackend + .updateConversation(conversation); + xmppService.sendMessage(message); + messageSent(); + } + }); + } + } + } else { + activity.showInstallPgpDialog(); + } + } + + public void showNoPGPKeyDialog(boolean plural, + DialogInterface.OnClickListener listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + if (plural) { + builder.setTitle(getString(R.string.no_pgp_keys)); + builder.setMessage(getText(R.string.contacts_have_no_pgp_keys)); + } else { + builder.setTitle(getString(R.string.no_pgp_key)); + 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.create().show(); + } + + protected void sendOtrMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + activity.selectPresence(message.getConversation(), + new OnPresenceSelected() { + + @Override + public void onPresenceSelected() { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + }); + } + + public void appendText(String text) { + if (text == null) { + return; + } + String previous = this.mEditMessage.getText().toString(); + if (previous.length() != 0 && !previous.endsWith(" ")) { + text = " " + text; + } + this.mEditMessage.append(text); + } + + @Override + public boolean onEnterPressed() { + if (ConversationsPlusPreferences.enterIsSend()) { + sendMessage(); + return true; + } else { + return false; + } + } + + @Override + public void onTypingStarted() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { + activity.xmppConnectionService.sendChatState(conversation); + } + updateSendButton(); + } + + @Override + public void onTypingStopped() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) { + activity.xmppConnectionService.sendChatState(conversation); + } + } + + @Override + public void onTextDeleted() { + Account.State status = conversation.getAccount().getStatus(); + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + activity.xmppConnectionService.sendChatState(conversation); + } + updateSendButton(); + } + + private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){ + iconToBeChanged.setImageResource(drawableResourceId); + } + +} |