anotherim/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
yushijinhun 48147b7fd1
Give up PGP decryption when intent is cancelled
When receiving a PGP message which is not encrypted with YOUR key,
OpenKeychain shows a dialog, which tells you the private key to decrypt
the message is unavailable. However, Conversations won't give up
decrypting the message. So whether the subsequent messages are
decryptable or not, the decryption is blocked at the current message.
The commit fixes the bug in this way: Give up the current message when
the decryption intent is cancelled, so that subsequent messages can be
handled.
2017-07-25 15:09:18 +08:00

1670 lines
58 KiB
Java

package eu.siacs.conversations.ui;
import android.app.Activity;
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.SendIntentException;
import android.os.Bundle;
import android.os.Handler;
import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
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.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.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.PopupMenu;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
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.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder;
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.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
import eu.siacs.conversations.ui.widget.ListSelectionManager;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.NickValidityChecker;
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.Jid;
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;
final protected List<Message> messageList = new ArrayList<>();
protected MessageAdapter messageListAdapter;
private EditMessage mEditMessage;
private ImageButton mSendButton;
private RelativeLayout snackbar;
private TextView snackbarMessage;
private TextView snackbarAction;
private Toast messageLoaderToast;
private OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
synchronized (ConversationFragment.this.messageList) {
if (firstVisibleItem < 5 && conversation != null && conversation.messagesLoaded.compareAndSet(true,false) && messageList.size() > 0) {
long timestamp;
if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) {
timestamp = messageList.get(1).getTimeSent();
} else {
timestamp = messageList.get(0).getTimeSent();
}
activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
@Override
public void onMoreMessagesLoaded(final int c, final Conversation conversation) {
if (ConversationFragment.this.conversation != conversation) {
conversation.messagesLoaded.set(true);
return;
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
final int oldPosition = messagesView.getFirstVisiblePosition();
Message message = null;
int childPos;
for(childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) {
message = messageList.get(oldPosition + childPos);
if (message.getType() != Message.TYPE_STATUS) {
break;
}
}
final String uuid = message != null ? message.getUuid() : null;
View v = messagesView.getChildAt(childPos);
final int pxOffset = (v == null) ? 0 : v.getTop();
ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
try {
updateStatusMessages();
} catch (IllegalStateException e) {
Log.d(Config.LOGTAG,"caught illegal state exception while updating status messages");
}
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(activity, resId, Toast.LENGTH_LONG);
messageLoaderToast.show();
}
});
}
});
}
}
}
};
private int getIndexOf(String uuid, List<Message> messages) {
if (uuid == null) {
return messages.size() - 1;
}
for(int i = 0; i < messages.size(); ++i) {
if (uuid.equals(messages.get(i).getUuid())) {
return i;
} else {
Message next = messages.get(i);
while(next != null && next.wasMergedIntoPrevious()) {
if (uuid.equals(next.getUuid())) {
return i;
}
next = next.next();
}
}
}
return -1;
}
public Pair<Integer,Integer> getScrollPosition() {
if (this.messagesView.getCount() == 0 ||
this.messagesView.getLastVisiblePosition() == this.messagesView.getCount() - 1) {
return null;
} else {
final int pos = messagesView.getFirstVisiblePosition();
final View view = messagesView.getChildAt(0);
if (view == null) {
return null;
} else {
return new Pair<>(pos, view.getTop());
}
}
}
public void setScrollPosition(Pair<Integer,Integer> scrollPosition) {
if (scrollPosition != null) {
this.messagesView.setSelectionFromTop(scrollPosition.first, scrollPosition.second);
}
}
protected OnClickListener clickToDecryptListener = new OnClickListener() {
@Override
public void onClick(View v) {
PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent();
if (pendingIntent != null) {
try {
activity.startIntentSenderForResult(pendingIntent.getIntentSender(),
ConversationActivity.REQUEST_DECRYPT_PGP,
null,
0,
0,
0);
} catch (SendIntentException e) {
Toast.makeText(activity,R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show();
conversation.getAccount().getPgpDecryptionService().continueDecryption(true);
}
}
updateSnackBar(conversation);
}
};
protected OnClickListener clickToVerify = new OnClickListener() {
@Override
public void onClick(View v) {
activity.verifyOtrSessionDialog(conversation, v);
}
};
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 EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() {
@Override
public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) {
// try to get permission to read the image, if applicable
if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
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
).show();
return false;
}
}
// send the image
activity.attachImageToConversation(inputContentInfo.getContentUri());
// TODO: revoke permissions?
// since uploading an image is async its tough to wire a callback to when
// the image has finished uploading.
// According to the docs: "calling IC#releasePermission() is just to be a
// good citizen. Even if we failed to call that method, the system would eventually revoke
// the permission sometime after inputContentInfo object gets garbage-collected."
// See: https://developer.android.com/samples/CommitContentSampleApp/src/com.example.android.commitcontent.app/MainActivity.html#l164
return true;
}
};
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) {
if(conversation.setCorrectingMessage(null)) {
mEditMessage.setText("");
mEditMessage.append(conversation.getDraftMessage());
conversation.setDraftMessage(null);
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setNextCounterpart(null);
}
updateChatMsgHint();
updateSendButton();
updateEditablity();
}
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();
final Conversation conversation = this.conversation;
if (body.length() == 0 || conversation == null) {
return;
}
final Message message;
if (conversation.getCorrectingMessage() == null) {
message = new Message(conversation, body, conversation.getNextEncryption());
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_PRIVATE);
}
}
} else {
message = conversation.getCorrectingMessage();
message.setBody(body);
message.setEdited(message.getUuid());
message.setUuid(UUID.randomUUID().toString());
}
switch (message.getConversation().getNextEncryption()) {
case Message.ENCRYPTION_OTR:
sendOtrMessage(message);
break;
case Message.ENCRYPTION_PGP:
sendPgpMessage(message);
break;
case Message.ENCRYPTION_AXOLOTL:
if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
sendAxolotlMessage(message);
}
break;
default:
sendPlainTextMessage(message);
}
}
public void updateChatMsgHint() {
final boolean multi = conversation.getMode() == Conversation.MODE_MULTI;
if (conversation.getCorrectingMessage() != null) {
this.mEditMessage.setHint(R.string.send_corrected_message);
} else if (multi && conversation.getNextCounterpart() != null) {
this.mEditMessage.setHint(getString(
R.string.send_private_message_to,
conversation.getNextCounterpart().getResourcepart()));
} else if (multi && !conversation.getMucOptions().participating()) {
this.mEditMessage.setHint(R.string.you_are_not_participating);
} else {
this.mEditMessage.setHint(UIHelper.getMessageHint(activity,conversation));
getActivity().invalidateOptionsMenu();
}
}
public void setupIme() {
if (activity != null) {
if (activity.usingEnterKey() && activity.enterIsSend()) {
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
} else if (activity.usingEnterKey()) {
mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
} else {
mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
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);
String[] allImagesMimeType = {"image/*"};
mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
mEditMessage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (activity != null) {
activity.hideConversationsOverview();
}
}
});
mEditMessage.setOnEditorActionListener(mEditorActionListener);
mEditMessage.setRichContentListener(allImagesMimeType, mEditorContentListener);
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.setOnScrollListener(mOnScrollListener);
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) {
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();
}
highlightInConference(user.getResourcepart());
}
} else {
if (!message.getContact().isSelf()) {
String fingerprint;
if (message.getEncryption() == Message.ENCRYPTION_PGP
|| message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
fingerprint = "pgp";
} else {
fingerprint = message.getFingerprint();
}
activity.switchToContactDetails(message.getContact(), fingerprint);
}
}
} else {
Account account = message.getConversation().getAccount();
Intent intent;
if (activity.manuallyChangePresence()) {
intent = new Intent(activity, SetPresenceActivity.class);
intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
} else {
intent = new Intent(activity, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().toBareJid().toString());
String fingerprint;
if (message.getEncryption() == Message.ENCRYPTION_PGP
|| message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
fingerprint = "pgp";
} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
fingerprint = "otr";
} else {
fingerprint = message.getFingerprint();
}
intent.putExtra("fingerprint", fingerprint);
}
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) {
Jid user = message.getCounterpart();
if (user != null && !user.isBareJid()) {
if (message.getConversation().getMucOptions().isUserInRoom(user)) {
privateMessageWith(user);
} else {
Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show();
}
}
}
} else {
activity.showQrCode();
}
}
});
messageListAdapter.setOnQuoteListener(new MessageAdapter.OnQuoteListener() {
@Override
public void onQuote(String text) {
if (mEditMessage.isEnabled()) {
text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
Editable editable = mEditMessage.getEditableText();
int position = mEditMessage.getSelectionEnd();
if (position == -1) position = editable.length();
if (position > 0 && editable.charAt(position - 1) != '\n') {
editable.insert(position++, "\n");
}
editable.insert(position, text);
position += text.length();
editable.insert(position++, "\n");
if (position < editable.length() && editable.charAt(position) != '\n') {
editable.insert(position, "\n");
}
mEditMessage.setSelection(position);
mEditMessage.requestFocus();
InputMethodManager inputMethodManager = (InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT);
}
}
}
});
messagesView.setAdapter(messageListAdapter);
registerForContextMenu(messagesView);
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;
final Transferable t = m.getTransferable();
Message relevantForCorrection = m;
while(relevantForCorrection.mergeable(relevantForCorrection.next())) {
relevantForCorrection = relevantForCorrection.next();
}
if (m.getType() != Message.TYPE_STATUS) {
final boolean treatAsFile = m.getType() != Message.TYPE_TEXT
&& m.getType() != Message.TYPE_PRIVATE
&& t == null;
activity.getMenuInflater().inflate(R.menu.message_context, menu);
menu.setHeaderTitle(R.string.message_options);
MenuItem selectText = menu.findItem(R.id.select_text);
MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
MenuItem correctMessage = menu.findItem(R.id.correct_message);
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);
MenuItem deleteFile = menu.findItem(R.id.delete_file);
MenuItem showErrorMessage = menu.findItem(R.id.show_error_message);
if (!treatAsFile && !GeoHelper.isGeoUri(m.getBody()) && !m.treatAsDownloadable()) {
selectText.setVisible(ListSelectionManager.isSupported());
}
if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
retryDecryption.setVisible(true);
}
if (relevantForCorrection.getType() == Message.TYPE_TEXT
&& relevantForCorrection.isLastCorrectableMessage()
&& (m.getConversation().getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) {
correctMessage.setVisible(true);
}
if (treatAsFile || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())) {
shareWith.setVisible(true);
}
if (m.getStatus() == Message.STATUS_SEND_FAILED) {
sendAgain.setVisible(true);
}
if (m.hasFileOnRemoteHost()
|| GeoHelper.isGeoUri(m.getBody())
|| m.treatAsDownloadable()
|| (t != null && t instanceof HttpDownloadConnection)) {
copyUrl.setVisible(true);
}
if ((m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())) {
downloadFile.setVisible(true);
downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING
|| m.getStatus() == Message.STATUS_UNSEND
|| m.getStatus() == Message.STATUS_OFFERED;
if ((t != null && !(t instanceof TransferablePlaceholder)) || waitingOfferedSending && m.needsUploading()) {
cancelTransmission.setVisible(true);
}
if (treatAsFile) {
String path = m.getRelativeFilePath();
if (path == null || !path.startsWith("/")) {
deleteFile.setVisible(true);
deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m)));
}
}
if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) {
showErrorMessage.setVisible(true);
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_with:
shareWith(selectedMessage);
return true;
case R.id.select_text:
selectText(selectedMessage);
return true;
case R.id.correct_message:
correctMessage(selectedMessage);
return true;
case R.id.send_again:
resendMessage(selectedMessage);
return true;
case R.id.copy_url:
copyUrl(selectedMessage);
return true;
case R.id.download_file:
downloadFile(selectedMessage);
return true;
case R.id.cancel_transmission:
cancelTransmission(selectedMessage);
return true;
case R.id.retry_decryption:
retryDecryption(selectedMessage);
return true;
case R.id.delete_file:
deleteFile(selectedMessage);
return true;
case R.id.show_error_message:
showErrorMessage(selectedMessage);
return true;
default:
return super.onContextItemSelected(item);
}
}
private void showErrorMessage(final Message message) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.error_message);
builder.setMessage(message.getErrorMessage());
builder.setPositiveButton(R.string.confirm,null);
builder.create().show();
}
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 if (!message.isFileOrImage()) {
shareIntent.putExtra(Intent.EXTRA_TEXT, message.getMergedBody().toString());
shareIntent.setType("text/plain");
} else {
final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
try {
shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file));
} catch (SecurityException e) {
Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
return;
}
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String mime = message.getMimeType();
if (mime == null) {
mime = "*/*";
}
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 selectText(Message message) {
final int index;
synchronized (this.messageList) {
index = this.messageList.indexOf(message);
}
if (index >= 0) {
final int first = this.messagesView.getFirstVisiblePosition();
final int last = first + this.messagesView.getChildCount();
if (index >= first && index < last) {
final View view = this.messagesView.getChildAt(index - first);
final TextView messageBody = this.messageListAdapter.getMessageBody(view);
if (messageBody != null) {
ListSelectionManager.startSelection(messageBody);
}
}
}
}
private void deleteFile(Message message) {
if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
activity.updateConversationList();
updateMessages();
}
}
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));
activity.updateConversationList();
updateMessages();
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().trim();
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,true);
}
private void cancelTransmission(Message message) {
Transferable transferable = message.getTransferable();
if (transferable != null) {
transferable.cancel();
} else if (message.getStatus() != Message.STATUS_RECEIVED) {
activity.xmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
}
}
private void retryDecryption(Message message) {
message.setEncryption(Message.ENCRYPTION_PGP);
activity.updateConversationList();
updateMessages();
conversation.getAccount().getPgpDecryptionService().decrypt(message, false);
}
protected void privateMessageWith(final Jid counterpart) {
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
activity.xmppConnectionService.sendChatState(conversation);
}
this.mEditMessage.setText("");
this.conversation.setNextCounterpart(counterpart);
updateChatMsgHint();
updateSendButton();
updateEditablity();
}
private void correctMessage(Message message) {
while(message.mergeable(message.next())) {
message = message.next();
}
this.conversation.setCorrectingMessage(message);
final Editable editable = mEditMessage.getText();
this.conversation.setDraftMessage(editable.toString());
this.mEditMessage.setText("");
this.mEditMessage.append(message.getBody());
}
protected void highlightInConference(String nick) {
final Editable editable = mEditMessage.getText();
String oldString = editable.toString().trim();
final int pos = mEditMessage.getSelectionStart();
if (oldString.isEmpty() || pos == 0) {
editable.insert(0, nick + ": ");
} else {
final char before = editable.charAt(pos - 1);
final char after = editable.length() > pos ? editable.charAt(pos) : '\0';
if (before == '\n') {
editable.insert(pos, nick + ": ");
} else {
if (pos > 2 && editable.subSequence(pos-2,pos).toString().equals(": ")) {
if (NickValidityChecker.check(conversation,Arrays.asList(editable.subSequence(0,pos-2).toString().split(", ")))) {
editable.insert(pos - 2, ", " + nick);
return;
}
}
editable.insert(pos, (Character.isWhitespace(before) ? "" : " ") + nick + (Character.isWhitespace(after) ? "" : " "));
if (Character.isWhitespace(after)) {
mEditMessage.setSelection(mEditMessage.getSelectionStart() + 1);
}
}
}
}
@Override
public void onStop() {
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 boolean reInit(Conversation conversation) {
if (conversation == null) {
return false;
}
this.activity = (ConversationActivity) getActivity();
setupIme();
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();
}
if (activity != null) {
this.mSendButton.setContentDescription(activity.getString(R.string.send_message_to_x,conversation.getName()));
}
this.conversation = conversation;
this.mEditMessage.setKeyboardListener(null);
this.mEditMessage.setText("");
this.mEditMessage.append(this.conversation.getNextMessage());
this.mEditMessage.setKeyboardListener(this);
messageListAdapter.updatePreferences();
this.messagesView.setAdapter(messageListAdapter);
updateMessages();
this.conversation.messagesLoaded.set(true);
synchronized (this.messageList) {
final Message first = conversation.getFirstUnreadMessage();
final int bottom = Math.max(0, this.messageList.size() - 1);
final int pos;
if (first == null) {
pos = bottom;
} else {
int i = getIndexOf(first.getUuid(), this.messageList);
pos = i < 0 ? bottom : i;
}
messagesView.setSelection(pos);
return pos == bottom;
}
}
private OnClickListener mEnableAccountListener = new OnClickListener() {
@Override
public void onClick(View v) {
final Account account = conversation == null ? null : conversation.getAccount();
if (account != null) {
account.setOption(Account.OPTION_DISABLED, false);
activity.xmppConnectionService.updateAccount(account);
}
}
};
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, conversation);
} else {
activity.unblockConversation(conversation);
}
}
};
private OnClickListener mBlockClickListener = new OnClickListener() {
@Override
public void onClick(final View view) {
final Jid jid = conversation.getJid();
if (jid.isDomainJid()) {
BlockContactDialog.show(activity, conversation);
} else {
PopupMenu popupMenu = new PopupMenu(activity, 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.show();
}
}
};
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 mAllowPresenceSubscription = new OnClickListener() {
@Override
public void onClick(View v) {
final Contact contact = conversation == null ? null : conversation.getContact();
if (contact != null) {
activity.xmppConnectionService.sendPresencePacket(contact.getAccount(),
activity.xmppConnectionService.getPresenceGenerator()
.sendPresenceUpdatesTo(contact));
hideSnackbar();
}
}
};
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(VerifyOTRActivity.EXTRA_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 XmppConnection connection = account.getXmppConnection();
final int mode = conversation.getMode();
final Contact contact = mode == Conversation.MODE_SINGLE ? conversation.getContact() : null;
if (account.getStatus() == Account.State.DISABLED) {
showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener);
} else if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
} else if (contact != null && !contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener);
} else if (contact != null && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription);
} else if (mode == Conversation.MODE_MULTI
&& !conversation.getMucOptions().online()
&& account.getStatus() == Account.State.ONLINE) {
switch (conversation.getMucOptions().getError()) {
case NICK_IN_USE:
showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc);
break;
case NO_RESPONSE:
showSnackbar(R.string.joining_conference, 0, null);
break;
case SERVER_NOT_FOUND:
if (conversation.receivedMessagesCount() > 0) {
showSnackbar(R.string.remote_server_not_found,R.string.try_again, joinMuc);
} else {
showSnackbar(R.string.remote_server_not_found, R.string.leave, leaveMuc);
}
break;
case PASSWORD_REQUIRED:
showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword);
break;
case BANNED:
showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc);
break;
case MEMBERS_ONLY:
showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc);
break;
case KICKED:
showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
break;
case UNKNOWN:
showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc);
break;
case SHUTDOWN:
showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc);
break;
default:
hideSnackbar();
break;
}
} else if (account.hasPendingPgpIntent(conversation)) {
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 (connection != null
&& connection.getFeatures().blocking()
&& conversation.countMessages() != 0
&& !conversation.isBlocked()
&& conversation.isWithStranger()) {
showSnackbar(R.string.received_message_from_stranger,R.string.block, mBlockClickListener);
} else {
hideSnackbar();
}
}
public void updateMessages() {
synchronized (this.messageList) {
if (getView() == null) {
return;
}
final ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
conversation.populateWithMessages(ConversationFragment.this.messageList);
updateSnackBar(conversation);
updateStatusMessages();
this.messageListAdapter.notifyDataSetChanged();
updateChatMsgHint();
if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) {
activity.sendReadMarkerIfNecessary(conversation);
}
updateSendButton();
updateEditablity();
}
}
}
protected void messageSent() {
mSendingPgpMessage.set(false);
mEditMessage.setText("");
if (conversation.setCorrectingMessage(null)) {
mEditMessage.append(conversation.getDraftMessage());
conversation.setDraftMessage(null);
}
conversation.setNextMessage(mEditMessage.getText().toString());
updateChatMsgHint();
new Handler().post(new Runnable() {
@Override
public void run() {
int size = messageList.size();
messagesView.setSelection(size - 1);
}
});
}
public void setFocusOnInputField() {
mEditMessage.requestFocus();
}
public void doneSendingPgpMessage() {
mSendingPgpMessage.set(false);
}
enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE}
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 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;
this.mEditMessage.setFocusable(canWrite);
this.mEditMessage.setFocusableInTouchMode(canWrite);
this.mSendButton.setEnabled(canWrite);
this.mEditMessage.setCursorVisible(canWrite);
}
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())) {
setting = "location";
} else if (setting.equals("recent")) {
setting = activity.getPreferences().getString("recently_used_quick_action", "text");
}
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 (activity.useSendButtonToIndicateStatus() && c.getAccount().getStatus() == Account.State.ONLINE) {
if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) {
status = Presence.Status.OFFLINE;
} else if (c.getMode() == Conversation.MODE_SINGLE) {
status = c.getContact().getShownStatus();
} else {
status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE;
}
} else {
status = Presence.Status.OFFLINE;
}
this.mSendButton.setTag(action);
this.mSendButton.setImageResource(getSendButtonImageResource(action, status));
}
protected void updateDateSeparators() {
synchronized (this.messageList) {
for(int i = 0; i < this.messageList.size(); ++i) {
final Message current = this.messageList.get(i);
if (i == 0 || !UIHelper.sameDay(this.messageList.get(i-1).getTimeSent(),current.getTimeSent())) {
this.messageList.add(i,Message.createDateSeparator(current));
i++;
}
}
}
}
protected void updateStatusMessages() {
updateDateSeparators();
synchronized (this.messageList) {
if (showLoadMoreMessages(conversation)) {
this.messageList.add(0, Message.createLoadMoreMessage(conversation));
}
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;
}
}
}
}
} else {
ChatState state = ChatState.COMPOSING;
List<MucOptions.User> users = conversation.getMucOptions().getUsersWithChatState(state,5);
if (users.size() == 0) {
state = ChatState.PAUSED;
users = conversation.getMucOptions().getUsersWithChatState(state, 5);
}
if (users.size() > 0) {
Message statusMessage;
if (users.size() == 1) {
MucOptions.User user = users.get(0);
int id = state == ChatState.COMPOSING ? R.string.contact_is_typing : R.string.contact_has_stopped_typing;
statusMessage = Message.createStatusMessage(conversation, getString(id, UIHelper.getDisplayName(user)));
statusMessage.setTrueCounterpart(user.getRealJid());
statusMessage.setCounterpart(user.getFullJid());
} else {
StringBuilder builder = new StringBuilder();
for(MucOptions.User user : users) {
if (builder.length() != 0) {
builder.append(", ");
}
builder.append(UIHelper.getDisplayName(user));
}
int id = state == ChatState.COMPOSING ? R.string.contacts_are_typing : R.string.contacts_have_stopped_typing;
statusMessage = Message.createStatusMessage(conversation, getString(id, builder.toString()));
}
this.messageList.add(statusMessage);
}
}
}
}
private boolean showLoadMoreMessages(final Conversation c) {
final boolean mam = hasMamSupport(c);
final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService();
return mam && (c.getLastClearHistory().getTimestamp() != 0 || (c.countMessages() == 0 && c.messagesLoaded.get() && c.hasMessagesLeftOnServer() && !service.queryInProgress(c)));
}
private boolean hasMamSupport(final Conversation c) {
if (c.getMode() == Conversation.MODE_SINGLE) {
final XmppConnection connection = c.getAccount().getXmppConnection();
return connection != null && connection.getFeatures().mam();
} else {
return c.getMucOptions().mamSupport();
}
}
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(clickListener == null ? View.GONE : View.VISIBLE);
if (action != 0) {
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();
}
private AtomicBoolean mSendingPgpMessage = new AtomicBoolean(false);
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()) {
activity.showInstallPgpDialog();
return;
}
if (conversation.getAccount().getPgpSignature() == null) {
activity.announcePgp(conversation.getAccount(), conversation, activity.onOpenPGPKeyPublished);
return;
}
if (!mSendingPgpMessage.compareAndSet(false,true)) {
Log.d(Config.LOGTAG,"sending pgp message already in progress");
}
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) {
activity.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();
}
});
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();
}
});
}
} 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);
} 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();
}
});
}
}
}
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 sendAxolotlMessage(final Message message) {
final ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
xmppService.sendMessage(message);
messageSent();
}
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 (activity.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);
}
activity.hideConversationsOverview();
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();
}
@Override
public void onTextChanged() {
if (conversation != null && conversation.getCorrectingMessage() != null) {
updateSendButton();
}
}
private int completionIndex = 0;
private int lastCompletionLength = 0;
private String incomplete;
private int lastCompletionCursor;
private boolean firstWord = false;
@Override
public boolean onTabPressed(boolean repeated) {
if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) {
return false;
}
if (repeated) {
completionIndex++;
} else {
lastCompletionLength = 0;
completionIndex = 0;
final String content = mEditMessage.getText().toString();
lastCompletionCursor = mEditMessage.getSelectionEnd();
int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0;
firstWord = start == 0;
incomplete = content.substring(start,lastCompletionCursor);
}
List<String> completions = new ArrayList<>();
for(MucOptions.User user : conversation.getMucOptions().getUsers()) {
String name = user.getName();
if (name != null && name.startsWith(incomplete)) {
completions.add(name+(firstWord ? ": " : " "));
}
}
Collections.sort(completions);
if (completions.size() > completionIndex) {
String completion = completions.get(completionIndex).substring(incomplete.length());
mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength);
mEditMessage.getEditableText().insert(lastCompletionCursor, completion);
lastCompletionLength = completion.length();
} else {
completionIndex = -1;
mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength);
lastCompletionLength = 0;
}
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(true);
} 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();
}
}
}
}