From 3439f40411c0bca1652933d2d182e4e41617f77c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 12 Apr 2020 17:12:59 +0200 Subject: [PATCH] show call log messages in conversation stream --- .../siacs/conversations/entities/Message.java | 26 + .../entities/RtpSessionStatus.java | 59 + .../ui/adapter/MessageAdapter.java | 1726 +++++++++-------- .../siacs/conversations/utils/UIHelper.java | 2 + .../xmpp/jingle/JingleRtpConnection.java | 62 +- .../drawable-hdpi/ic_call_made_black_18dp.png | Bin 0 -> 159 bytes .../drawable-hdpi/ic_call_made_white_18dp.png | Bin 0 -> 174 bytes .../ic_call_missed_black_18dp.png | Bin 0 -> 179 bytes .../ic_call_missed_outgoing_black_18dp.png | Bin 0 -> 180 bytes .../ic_call_missed_outgoing_white_18dp.png | Bin 0 -> 180 bytes .../ic_call_missed_white_18dp.png | Bin 0 -> 191 bytes .../ic_call_received_black_18dp.png | Bin 0 -> 159 bytes .../ic_call_received_white_18dp.png | Bin 0 -> 169 bytes .../drawable-mdpi/ic_call_made_black_18dp.png | Bin 0 -> 132 bytes .../drawable-mdpi/ic_call_made_white_18dp.png | Bin 0 -> 135 bytes .../ic_call_missed_black_18dp.png | Bin 0 -> 141 bytes .../ic_call_missed_outgoing_black_18dp.png | Bin 0 -> 134 bytes .../ic_call_missed_outgoing_white_18dp.png | Bin 0 -> 136 bytes .../ic_call_missed_white_18dp.png | Bin 0 -> 147 bytes .../ic_call_received_black_18dp.png | Bin 0 -> 133 bytes .../ic_call_received_white_18dp.png | Bin 0 -> 140 bytes .../ic_call_made_black_18dp.png | Bin 0 -> 174 bytes .../ic_call_made_white_18dp.png | Bin 0 -> 189 bytes .../ic_call_missed_black_18dp.png | Bin 0 -> 201 bytes .../ic_call_missed_outgoing_black_18dp.png | Bin 0 -> 188 bytes .../ic_call_missed_outgoing_white_18dp.png | Bin 0 -> 193 bytes .../ic_call_missed_white_18dp.png | Bin 0 -> 215 bytes .../ic_call_received_black_18dp.png | Bin 0 -> 175 bytes .../ic_call_received_white_18dp.png | Bin 0 -> 189 bytes .../ic_call_made_black_18dp.png | Bin 0 -> 202 bytes .../ic_call_made_white_18dp.png | Bin 0 -> 225 bytes .../ic_call_missed_black_18dp.png | Bin 0 -> 247 bytes .../ic_call_missed_outgoing_black_18dp.png | Bin 0 -> 235 bytes .../ic_call_missed_outgoing_white_18dp.png | Bin 0 -> 235 bytes .../ic_call_missed_white_18dp.png | Bin 0 -> 263 bytes .../ic_call_received_black_18dp.png | Bin 0 -> 202 bytes .../ic_call_received_white_18dp.png | Bin 0 -> 228 bytes .../ic_call_made_black_18dp.png | Bin 0 -> 212 bytes .../ic_call_made_white_18dp.png | Bin 0 -> 247 bytes .../ic_call_missed_black_18dp.png | Bin 0 -> 267 bytes .../ic_call_missed_outgoing_black_18dp.png | Bin 0 -> 257 bytes .../ic_call_missed_outgoing_white_18dp.png | Bin 0 -> 248 bytes .../ic_call_missed_white_18dp.png | Bin 0 -> 291 bytes .../ic_call_received_black_18dp.png | Bin 0 -> 214 bytes .../ic_call_received_white_18dp.png | Bin 0 -> 257 bytes src/main/res/layout/message_date_bubble.xml | 15 +- src/main/res/layout/message_rtp_session.xml | 38 + src/main/res/values/strings.xml | 4 + 48 files changed, 1077 insertions(+), 855 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/entities/RtpSessionStatus.java create mode 100644 src/main/res/drawable-hdpi/ic_call_made_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_made_white_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_missed_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_missed_outgoing_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_missed_outgoing_white_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_missed_white_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_received_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_call_received_white_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_made_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_made_white_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_missed_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_missed_outgoing_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_missed_outgoing_white_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_missed_white_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_received_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_call_received_white_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_made_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_made_white_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_missed_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_missed_outgoing_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_missed_outgoing_white_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_missed_white_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_received_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_call_received_white_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_made_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_made_white_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_missed_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_white_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_missed_white_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_received_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_call_received_white_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_made_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_made_white_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_missed_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_white_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_missed_white_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_received_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_call_received_white_18dp.png create mode 100644 src/main/res/layout/message_rtp_session.xml diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e44d2a2c0..f266c18e3 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -57,6 +57,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public static final int TYPE_STATUS = 3; public static final int TYPE_PRIVATE = 4; public static final int TYPE_PRIVATE_FILE = 5; + public static final int TYPE_RTP_SESSION = 6; public static final String CONVERSATION = "conversationUuid"; public static final String COUNTERPART = "counterpart"; @@ -151,6 +152,31 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable null); } + public Message(Conversation conversation, int status, int type, final String remoteMsgId) { + this(conversation, java.util.UUID.randomUUID().toString(), + conversation.getUuid(), + conversation.getJid() == null ? null : conversation.getJid().asBareJid(), + null, + null, + System.currentTimeMillis(), + Message.ENCRYPTION_NONE, + status, + type, + false, + remoteMsgId, + null, + null, + null, + true, + null, + false, + null, + null, + false, + false, + null); + } + protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, final int encryption, final int status, final int type, final boolean carbon, diff --git a/src/main/java/eu/siacs/conversations/entities/RtpSessionStatus.java b/src/main/java/eu/siacs/conversations/entities/RtpSessionStatus.java new file mode 100644 index 000000000..8e360cb27 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/RtpSessionStatus.java @@ -0,0 +1,59 @@ +package eu.siacs.conversations.entities; + +import android.support.annotation.DrawableRes; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.R; + +public class RtpSessionStatus { + + public final boolean successful; + public final long duration; + + + public RtpSessionStatus(boolean successful, long duration) { + this.successful = successful; + this.duration = duration; + } + + @Override + public String toString() { + return successful + ":" + duration; + } + + public static RtpSessionStatus of(final String body) { + final String[] parts = Strings.nullToEmpty(body).split(":", 2); + long duration = 0; + if (parts.length == 2) { + try { + duration = Long.parseLong(parts[1]); + } catch (NumberFormatException e) { + //do nothing + } + } + boolean made; + try { + made = Boolean.parseBoolean(parts[0]); + } catch (Exception e) { + made = false; + } + return new RtpSessionStatus(made, duration); + } + + public static @DrawableRes int getDrawable(final boolean received, final boolean successful, final boolean darkTheme) { + if (received) { + if (successful) { + return darkTheme ? R.drawable.ic_call_received_white_18dp : R.drawable.ic_call_received_black_18dp; + } else { + return darkTheme ? R.drawable.ic_call_missed_white_18dp : R.drawable.ic_call_missed_black_18dp; + } + } else { + if (successful) { + return darkTheme ? R.drawable.ic_call_made_white_18dp : R.drawable.ic_call_made_black_18dp; + } else { + return darkTheme ? R.drawable.ic_call_missed_outgoing_white_18dp : R.drawable.ic_call_missed_outgoing_black_18dp; + } + } + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 3ee927e00..a96d67b34 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -49,6 +49,7 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message.FileParams; +import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.http.P1S3UrlStreamHandler; import eu.siacs.conversations.persistance.FileBackend; @@ -72,926 +73,959 @@ import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MessageUtils; import eu.siacs.conversations.utils.StylingHelper; +import eu.siacs.conversations.utils.TimeframeUtils; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.mam.MamReference; import rocks.xmpp.addr.Jid; public class MessageAdapter extends ArrayAdapter implements CopyTextView.CopyHandler { - public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR"; - private static final int SENT = 0; - private static final int RECEIVED = 1; - private static final int STATUS = 2; - private static final int DATE_SEPARATOR = 3; - private final XmppActivity activity; - private final ListSelectionManager listSelectionManager = new ListSelectionManager(); - private final AudioPlayer audioPlayer; - private List highlightedTerm = null; - private DisplayMetrics metrics; - private OnContactPictureClicked mOnContactPictureClickedListener; - private OnContactPictureLongClicked mOnContactPictureLongClickedListener; - private boolean mUseGreenBackground = false; - private OnQuoteListener onQuoteListener; - public MessageAdapter(XmppActivity activity, List messages) { - super(activity, 0, messages); - this.audioPlayer = new AudioPlayer(this); - this.activity = activity; - metrics = getContext().getResources().getDisplayMetrics(); - updatePreferences(); - } + public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR"; + private static final int SENT = 0; + private static final int RECEIVED = 1; + private static final int STATUS = 2; + private static final int DATE_SEPARATOR = 3; + private static final int RTP_SESSION = 4; + private final XmppActivity activity; + private final ListSelectionManager listSelectionManager = new ListSelectionManager(); + private final AudioPlayer audioPlayer; + private List highlightedTerm = null; + private DisplayMetrics metrics; + private OnContactPictureClicked mOnContactPictureClickedListener; + private OnContactPictureLongClicked mOnContactPictureLongClickedListener; + private boolean mUseGreenBackground = false; + private OnQuoteListener onQuoteListener; + + public MessageAdapter(XmppActivity activity, List messages) { + super(activity, 0, messages); + this.audioPlayer = new AudioPlayer(this); + this.activity = activity; + metrics = getContext().getResources().getDisplayMetrics(); + updatePreferences(); + } + private static void resetClickListener(View... views) { + for (View view : views) { + view.setOnClickListener(null); + } + } - private static void resetClickListener(View... views) { - for (View view : views) { - view.setOnClickListener(null); - } - } + public void flagScreenOn() { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } - public void flagScreenOn() { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + public void flagScreenOff() { + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } - public void flagScreenOff() { - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + public void setOnContactPictureClicked(OnContactPictureClicked listener) { + this.mOnContactPictureClickedListener = listener; + } - public void setOnContactPictureClicked(OnContactPictureClicked listener) { - this.mOnContactPictureClickedListener = listener; - } + public Activity getActivity() { + return activity; + } - public Activity getActivity() { - return activity; - } + public void setOnContactPictureLongClicked( + OnContactPictureLongClicked listener) { + this.mOnContactPictureLongClickedListener = listener; + } - public void setOnContactPictureLongClicked( - OnContactPictureLongClicked listener) { - this.mOnContactPictureLongClickedListener = listener; - } + public void setOnQuoteListener(OnQuoteListener listener) { + this.onQuoteListener = listener; + } - public void setOnQuoteListener(OnQuoteListener listener) { - this.onQuoteListener = listener; - } + @Override + public int getViewTypeCount() { + return 5; + } - @Override - public int getViewTypeCount() { - return 4; - } + private int getItemViewType(Message message) { + if (message.getType() == Message.TYPE_STATUS) { + if (DATE_SEPARATOR_BODY.equals(message.getBody())) { + return DATE_SEPARATOR; + } else { + return STATUS; + } + } else if (message.getType() == Message.TYPE_RTP_SESSION) { + return RTP_SESSION; + } else if (message.getStatus() <= Message.STATUS_RECEIVED) { + return RECEIVED; + } else { + return SENT; + } + } - private int getItemViewType(Message message) { - if (message.getType() == Message.TYPE_STATUS) { - if (DATE_SEPARATOR_BODY.equals(message.getBody())) { - return DATE_SEPARATOR; - } else { - return STATUS; - } - } else if (message.getStatus() <= Message.STATUS_RECEIVED) { - return RECEIVED; - } + @Override + public int getItemViewType(int position) { + return this.getItemViewType(getItem(position)); + } - return SENT; - } + private int getMessageTextColor(boolean onDark, boolean primary) { + if (onDark) { + return ContextCompat.getColor(activity, primary ? R.color.white : R.color.white70); + } else { + return ContextCompat.getColor(activity, primary ? R.color.black87 : R.color.black54); + } + } - @Override - public int getItemViewType(int position) { - return this.getItemViewType(getItem(position)); - } + private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { + String filesize = null; + String info = null; + boolean error = false; + if (viewHolder.indicatorReceived != null) { + viewHolder.indicatorReceived.setVisibility(View.GONE); + } - private int getMessageTextColor(boolean onDark, boolean primary) { - if (onDark) { - return ContextCompat.getColor(activity, primary ? R.color.white : R.color.white70); - } else { - return ContextCompat.getColor(activity, primary ? R.color.black87 : R.color.black54); - } - } + if (viewHolder.edit_indicator != null) { + if (message.edited()) { + viewHolder.edit_indicator.setVisibility(View.VISIBLE); + viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp); + viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f); + } else { + viewHolder.edit_indicator.setVisibility(View.GONE); + } + } + final Transferable transferable = message.getTransferable(); + boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI + && message.getMergedStatus() <= Message.STATUS_RECEIVED; + if (message.isFileOrImage() || transferable != null || MessageUtils.unInitiatedButKnownSize(message)) { + FileParams params = message.getFileParams(); + filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null; + if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) { + error = true; + } + } + switch (message.getMergedStatus()) { + case Message.STATUS_WAITING: + info = getContext().getString(R.string.waiting); + break; + case Message.STATUS_UNSEND: + if (transferable != null) { + info = getContext().getString(R.string.sending_file, transferable.getProgress()); + } else { + info = getContext().getString(R.string.sending); + } + break; + case Message.STATUS_OFFERED: + info = getContext().getString(R.string.offering); + break; + case Message.STATUS_SEND_RECEIVED: + case Message.STATUS_SEND_DISPLAYED: + viewHolder.indicatorReceived.setImageResource(darkBackground ? R.drawable.ic_done_white_18dp : R.drawable.ic_done_black_18dp); + viewHolder.indicatorReceived.setAlpha(darkBackground ? 0.7f : 0.57f); + viewHolder.indicatorReceived.setVisibility(View.VISIBLE); + break; + case Message.STATUS_SEND_FAILED: + final String errorMessage = message.getErrorMessage(); + if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) { + info = getContext().getString(R.string.cancelled); + } else if (errorMessage != null) { + final String[] errorParts = errorMessage.split("\\u001f", 2); + if (errorParts.length == 2) { + switch (errorParts[0]) { + case "file-too-large": + info = getContext().getString(R.string.file_too_large); + break; + default: + info = getContext().getString(R.string.send_failed); + break; + } + } else { + info = getContext().getString(R.string.send_failed); + } + } else { + info = getContext().getString(R.string.send_failed); + } + error = true; + break; + default: + if (multiReceived) { + info = UIHelper.getMessageDisplayName(message); + } + break; + } + if (error && type == SENT) { + if (darkBackground) { + viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_Warning_OnDark); + } else { + viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_Warning); + } + } else { + if (darkBackground) { + viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_OnDark); + } else { + viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption); + } + viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground, false)); + } + if (message.getEncryption() == Message.ENCRYPTION_NONE) { + viewHolder.indicator.setVisibility(View.GONE); + } else { + boolean verified = false; + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + final FingerprintStatus status = message.getConversation() + .getAccount().getAxolotlService().getFingerprintTrust( + message.getFingerprint()); + if (status != null && status.isVerified()) { + verified = true; + } + } + if (verified) { + viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_verified_user_white_18dp : R.drawable.ic_verified_user_black_18dp); + } else { + viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); + } + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + viewHolder.indicator.setVisibility(View.VISIBLE); + } - private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { - String filesize = null; - String info = null; - boolean error = false; - if (viewHolder.indicatorReceived != null) { - viewHolder.indicatorReceived.setVisibility(View.GONE); - } + final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent()); + final String bodyLanguage = message.getBodyLanguage(); + final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US)); + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo); + } else if ((filesize == null) && (info != null)) { + viewHolder.time.setText(formattedTime + " \u00B7 " + info + bodyLanguageInfo); + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo); + } else { + viewHolder.time.setText(formattedTime + bodyLanguageInfo); + } + } else { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(filesize + " \u00B7 " + info + bodyLanguageInfo); + } else if ((filesize == null) && (info != null)) { + if (error) { + viewHolder.time.setText(info + " \u00B7 " + formattedTime + bodyLanguageInfo); + } else { + viewHolder.time.setText(info); + } + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo); + } else { + viewHolder.time.setText(formattedTime + bodyLanguageInfo); + } + } + } - if (viewHolder.edit_indicator != null) { - if (message.edited()) { - viewHolder.edit_indicator.setVisibility(View.VISIBLE); - viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp); - viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f); - } else { - viewHolder.edit_indicator.setVisibility(View.GONE); - } - } - final Transferable transferable = message.getTransferable(); - boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getMergedStatus() <= Message.STATUS_RECEIVED; - if (message.isFileOrImage() || transferable != null || MessageUtils.unInitiatedButKnownSize(message)) { - FileParams params = message.getFileParams(); - filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null; - if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) { - error = true; - } - } - switch (message.getMergedStatus()) { - case Message.STATUS_WAITING: - info = getContext().getString(R.string.waiting); - break; - case Message.STATUS_UNSEND: - if (transferable != null) { - info = getContext().getString(R.string.sending_file, transferable.getProgress()); - } else { - info = getContext().getString(R.string.sending); - } - break; - case Message.STATUS_OFFERED: - info = getContext().getString(R.string.offering); - break; - case Message.STATUS_SEND_RECEIVED: - case Message.STATUS_SEND_DISPLAYED: - viewHolder.indicatorReceived.setImageResource(darkBackground ? R.drawable.ic_done_white_18dp : R.drawable.ic_done_black_18dp); - viewHolder.indicatorReceived.setAlpha(darkBackground ? 0.7f : 0.57f); - viewHolder.indicatorReceived.setVisibility(View.VISIBLE); - break; - case Message.STATUS_SEND_FAILED: - final String errorMessage = message.getErrorMessage(); - if (Message.ERROR_MESSAGE_CANCELLED.equals(errorMessage)) { - info = getContext().getString(R.string.cancelled); - } else if (errorMessage != null) { - final String[] errorParts = errorMessage.split("\\u001f", 2); - if (errorParts.length == 2) { - switch (errorParts[0]) { - case "file-too-large": - info = getContext().getString(R.string.file_too_large); - break; - default: - info = getContext().getString(R.string.send_failed); - break; - } - } else { - info = getContext().getString(R.string.send_failed); - } - } else { - info = getContext().getString(R.string.send_failed); - } - error = true; - break; - default: - if (multiReceived) { - info = UIHelper.getMessageDisplayName(message); - } - break; - } - if (error && type == SENT) { - if (darkBackground) { - viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_Warning_OnDark); - } else { - viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_Warning); - } - } else { - if (darkBackground) { - viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption_OnDark); - } else { - viewHolder.time.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Caption); - } - viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground, false)); - } - if (message.getEncryption() == Message.ENCRYPTION_NONE) { - viewHolder.indicator.setVisibility(View.GONE); - } else { - boolean verified = false; - if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - final FingerprintStatus status = message.getConversation() - .getAccount().getAxolotlService().getFingerprintTrust( - message.getFingerprint()); - if (status != null && status.isVerified()) { - verified = true; - } - } - if (verified) { - viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_verified_user_white_18dp : R.drawable.ic_verified_user_black_18dp); - } else { - viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); - } - if (darkBackground) { - viewHolder.indicator.setAlpha(0.7f); - } else { - viewHolder.indicator.setAlpha(0.57f); - } - viewHolder.indicator.setVisibility(View.VISIBLE); - } + private void displayInfoMessage(ViewHolder viewHolder, CharSequence text, boolean darkBackground) { + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setText(text); + if (darkBackground) { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary_OnDark); + } else { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary); + } + viewHolder.messageBody.setTextIsSelectable(false); + } - final String formattedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getMergedTimeSent()); - final String bodyLanguage = message.getBodyLanguage(); - final String bodyLanguageInfo = bodyLanguage == null ? "" : String.format(" \u00B7 %s", bodyLanguage.toUpperCase(Locale.US)); - if (message.getStatus() <= Message.STATUS_RECEIVED) { ; - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + " \u00B7 " + info + bodyLanguageInfo); - } else if ((filesize == null) && (info != null)) { - viewHolder.time.setText(formattedTime + " \u00B7 " + info + bodyLanguageInfo); - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo); - } else { - viewHolder.time.setText(formattedTime+bodyLanguageInfo); - } - } else { - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info + bodyLanguageInfo); - } else if ((filesize == null) && (info != null)) { - if (error) { - viewHolder.time.setText(info + " \u00B7 " + formattedTime + bodyLanguageInfo); - } else { - viewHolder.time.setText(info); - } - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo); - } else { - viewHolder.time.setText(formattedTime+bodyLanguageInfo); - } - } - } + private void displayEmojiMessage(final ViewHolder viewHolder, final String body, final boolean darkBackground) { + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + if (darkBackground) { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji_OnDark); + } else { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji); + } + Spannable span = new SpannableString(body); + float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f; + span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(EmojiWrapper.transform(span)); + } - private void displayInfoMessage(ViewHolder viewHolder, CharSequence text, boolean darkBackground) { - viewHolder.download_button.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setText(text); - if (darkBackground) { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary_OnDark); - } else { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary); - } - viewHolder.messageBody.setTextIsSelectable(false); - } + private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) { + if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) { + body.insert(start++, "\n"); + body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + end++; + } + if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) { + body.insert(end, "\n"); + body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + int color = darkBackground ? this.getMessageTextColor(darkBackground, false) + : ContextCompat.getColor(activity, R.color.green700_desaturated); + DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + body.setSpan(new QuoteSpan(color, metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } - private void displayEmojiMessage(final ViewHolder viewHolder, final String body, final boolean darkBackground) { - viewHolder.download_button.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - if (darkBackground) { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji_OnDark); - } else { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji); - } - Spannable span = new SpannableString(body); - float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f; - span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - viewHolder.messageBody.setText(EmojiWrapper.transform(span)); - } + /** + * Applies QuoteSpan to group of lines which starts with > or » characters. + * Appends likebreaks and applies DividerSpan to them to show a padding between quote and text. + */ + private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) { + boolean startsWithQuote = false; + char previous = '\n'; + int lineStart = -1; + int lineTextStart = -1; + int quoteStart = -1; + for (int i = 0; i <= body.length(); i++) { + char current = body.length() > i ? body.charAt(i) : '\n'; + if (lineStart == -1) { + if (previous == '\n') { + if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i)) + || current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) { + // Line start with quote + lineStart = i; + if (quoteStart == -1) quoteStart = i; + if (i == 0) startsWithQuote = true; + } else if (quoteStart >= 0) { + // Line start without quote, apply spans there + applyQuoteSpan(body, quoteStart, i - 1, darkBackground); + quoteStart = -1; + } + } + } else { + // Remove extra spaces between > and first character in the line + // > character will be removed too + if (current != ' ' && lineTextStart == -1) { + lineTextStart = i; + } + if (current == '\n') { + body.delete(lineStart, lineTextStart); + i -= lineTextStart - lineStart; + if (i == lineStart) { + // Avoid empty lines because span over empty line can be hidden + body.insert(i++, " "); + } + lineStart = -1; + lineTextStart = -1; + } + } + previous = current; + } + if (quoteStart >= 0) { + // Apply spans to finishing open quote + applyQuoteSpan(body, quoteStart, body.length(), darkBackground); + } + return startsWithQuote; + } - private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) { - if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) { - body.insert(start++, "\n"); - body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - end++; - } - if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) { - body.insert(end, "\n"); - body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - int color = darkBackground ? this.getMessageTextColor(darkBackground, false) - : ContextCompat.getColor(activity, R.color.green700_desaturated); - DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - body.setSpan(new QuoteSpan(color, metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } + private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) { + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); - /** - * Applies QuoteSpan to group of lines which starts with > or » characters. - * Appends likebreaks and applies DividerSpan to them to show a padding between quote and text. - */ - private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) { - boolean startsWithQuote = false; - char previous = '\n'; - int lineStart = -1; - int lineTextStart = -1; - int quoteStart = -1; - for (int i = 0; i <= body.length(); i++) { - char current = body.length() > i ? body.charAt(i) : '\n'; - if (lineStart == -1) { - if (previous == '\n') { - if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i)) - || current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) { - // Line start with quote - lineStart = i; - if (quoteStart == -1) quoteStart = i; - if (i == 0) startsWithQuote = true; - } else if (quoteStart >= 0) { - // Line start without quote, apply spans there - applyQuoteSpan(body, quoteStart, i - 1, darkBackground); - quoteStart = -1; - } - } - } else { - // Remove extra spaces between > and first character in the line - // > character will be removed too - if (current != ' ' && lineTextStart == -1) { - lineTextStart = i; - } - if (current == '\n') { - body.delete(lineStart, lineTextStart); - i -= lineTextStart - lineStart; - if (i == lineStart) { - // Avoid empty lines because span over empty line can be hidden - body.insert(i++, " "); - } - lineStart = -1; - lineTextStart = -1; - } - } - previous = current; - } - if (quoteStart >= 0) { - // Apply spans to finishing open quote - applyQuoteSpan(body, quoteStart, body.length(), darkBackground); - } - return startsWithQuote; - } + if (darkBackground) { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_OnDark); + } else { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1); + } + viewHolder.messageBody.setHighlightColor(ContextCompat.getColor(activity, darkBackground + ? (type == SENT || !mUseGreenBackground ? R.color.black26 : R.color.grey800) : R.color.grey500)); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) { - viewHolder.download_button.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); + if (message.getBody() != null) { + final String nick = UIHelper.getMessageDisplayName(message); + SpannableStringBuilder body = message.getMergedBody(); + boolean hasMeCommand = message.hasMeCommand(); + if (hasMeCommand) { + body = body.replace(0, Message.ME_COMMAND.length(), nick + " "); + } + if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) { + body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS); + body.append("\u2026"); + } + Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class); + for (Message.MergeSeparator mergeSeparator : mergeSeparators) { + int start = body.getSpanStart(mergeSeparator); + int end = body.getSpanEnd(mergeSeparator); + body.setSpan(new DividerSpan(true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + boolean startsWithQuote = handleTextQuotes(body, darkBackground); + if (!message.isPrivateMessage()) { + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else { + String privateMarker; + if (message.getStatus() <= Message.STATUS_RECEIVED) { + privateMarker = activity.getString(R.string.private_message); + } else { + Jid cp = message.getCounterpart(); + privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource())); + } + body.insert(0, privateMarker); + int privateMarkerIndex = privateMarker.length(); + if (startsWithQuote) { + body.insert(privateMarkerIndex, "\n\n"); + body.setSpan(new DividerSpan(false), privateMarkerIndex, privateMarkerIndex + 2, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + body.insert(privateMarkerIndex, " "); + } + body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1, + privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + if (message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() == Message.STATUS_RECEIVED) { + if (message.getConversation() instanceof Conversation) { + final Conversation conversation = (Conversation) message.getConversation(); + Pattern pattern = NotificationService.generateNickHighlightPattern(conversation.getMucOptions().getActualNick()); + Matcher matcher = pattern.matcher(body); + while (matcher.find()) { + body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + Matcher matcher = Emoticons.getEmojiPattern(body).matcher(body); + while (matcher.find()) { + if (matcher.start() < matcher.end()) { + body.setSpan(new RelativeSizeSpan(1.2f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } - if (darkBackground) { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_OnDark); - } else { - viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1); - } - viewHolder.messageBody.setHighlightColor(ContextCompat.getColor(activity, darkBackground - ? (type == SENT || !mUseGreenBackground ? R.color.black26 : R.color.grey800) : R.color.grey500)); - viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor()); + if (highlightedTerm != null) { + StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody)); + } + MyLinkify.addLinks(body, true); + viewHolder.messageBody.setAutoLinkMask(0); + viewHolder.messageBody.setText(EmojiWrapper.transform(body)); + viewHolder.messageBody.setTextIsSelectable(true); + viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance()); + listSelectionManager.onUpdate(viewHolder.messageBody, message); + } else { + viewHolder.messageBody.setText(""); + viewHolder.messageBody.setTextIsSelectable(false); + } + } - if (message.getBody() != null) { - final String nick = UIHelper.getMessageDisplayName(message); - SpannableStringBuilder body = message.getMergedBody(); - boolean hasMeCommand = message.hasMeCommand(); - if (hasMeCommand) { - body = body.replace(0, Message.ME_COMMAND.length(), nick + " "); - } - if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) { - body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS); - body.append("\u2026"); - } - Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class); - for (Message.MergeSeparator mergeSeparator : mergeSeparators) { - int start = body.getSpanStart(mergeSeparator); - int end = body.getSpanEnd(mergeSeparator); - body.setSpan(new DividerSpan(true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - boolean startsWithQuote = handleTextQuotes(body, darkBackground); - if (!message.isPrivateMessage()) { - if (hasMeCommand) { - body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - String privateMarker; - if (message.getStatus() <= Message.STATUS_RECEIVED) { - privateMarker = activity.getString(R.string.private_message); - } else { - Jid cp = message.getCounterpart(); - privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource())); - } - body.insert(0, privateMarker); - int privateMarkerIndex = privateMarker.length(); - if (startsWithQuote) { - body.insert(privateMarkerIndex, "\n\n"); - body.setSpan(new DividerSpan(false), privateMarkerIndex, privateMarkerIndex + 2, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { - body.insert(privateMarkerIndex, " "); - } - body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - if (hasMeCommand) { - body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1, - privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - if (message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() == Message.STATUS_RECEIVED) { - if (message.getConversation() instanceof Conversation) { - final Conversation conversation = (Conversation) message.getConversation(); - Pattern pattern = NotificationService.generateNickHighlightPattern(conversation.getMucOptions().getActualNick()); - Matcher matcher = pattern.matcher(body); - while (matcher.find()) { - body.setSpan(new StyleSpan(Typeface.BOLD), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - Matcher matcher = Emoticons.getEmojiPattern(body).matcher(body); - while (matcher.find()) { - if (matcher.start() < matcher.end()) { - body.setSpan(new RelativeSizeSpan(1.2f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } + private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text, final boolean darkBackground) { + toggleWhisperInfo(viewHolder, message, darkBackground); + viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(text); + viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message)); + } - StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor()); - if (highlightedTerm != null) { - StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody)); - } - MyLinkify.addLinks(body,true); - viewHolder.messageBody.setAutoLinkMask(0); - viewHolder.messageBody.setText(EmojiWrapper.transform(body)); - viewHolder.messageBody.setTextIsSelectable(true); - viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance()); - listSelectionManager.onUpdate(viewHolder.messageBody, message); - } else { - viewHolder.messageBody.setText(""); - viewHolder.messageBody.setTextIsSelectable(false); - } - } + private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { + toggleWhisperInfo(viewHolder, message, darkBackground); + viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message))); + viewHolder.download_button.setOnClickListener(v -> openDownloadable(message)); + } - private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text, final boolean darkBackground) { - toggleWhisperInfo(viewHolder, message, darkBackground); - viewHolder.image.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(text); - viewHolder.download_button.setOnClickListener(v -> ConversationFragment.downloadFile(activity, message)); - } + private void displayLocationMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { + toggleWhisperInfo(viewHolder, message, darkBackground); + viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(R.string.show_location); + viewHolder.download_button.setOnClickListener(v -> showLocation(message)); + } - private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { - toggleWhisperInfo(viewHolder, message, darkBackground); - viewHolder.image.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message))); - viewHolder.download_button.setOnClickListener(v -> openDownloadable(message)); - } + private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) { + toggleWhisperInfo(viewHolder, message, darkBackground); + viewHolder.image.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.GONE); + final RelativeLayout audioPlayer = viewHolder.audioPlayer; + audioPlayer.setVisibility(View.VISIBLE); + AudioPlayer.ViewHolder.get(audioPlayer).setDarkBackground(darkBackground); + this.audioPlayer.init(audioPlayer, message); + } - private void displayLocationMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { - toggleWhisperInfo(viewHolder, message, darkBackground); - viewHolder.image.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button.setText(R.string.show_location); - viewHolder.download_button.setOnClickListener(v -> showLocation(message)); - } + private void displayMediaPreviewMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { + toggleWhisperInfo(viewHolder, message, darkBackground); + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.VISIBLE); + final FileParams params = message.getFileParams(); + final double target = metrics.density * 288; + final int scaledW; + final int scaledH; + if (Math.max(params.height, params.width) * metrics.density <= target) { + scaledW = (int) (params.width * metrics.density); + scaledH = (int) (params.height * metrics.density); + } else if (Math.max(params.height, params.width) <= target) { + scaledW = params.width; + scaledH = params.height; + } else if (params.width <= params.height) { + scaledW = (int) (params.width / ((double) params.height / target)); + scaledH = (int) target; + } else { + scaledW = (int) target; + scaledH = (int) (params.height / ((double) params.width / target)); + } + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); + layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); + viewHolder.image.setLayoutParams(layoutParams); + activity.loadBitmap(message, viewHolder.image); + viewHolder.image.setOnClickListener(v -> openDownloadable(message)); + } - private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) { - toggleWhisperInfo(viewHolder, message, darkBackground); - viewHolder.image.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.GONE); - final RelativeLayout audioPlayer = viewHolder.audioPlayer; - audioPlayer.setVisibility(View.VISIBLE); - AudioPlayer.ViewHolder.get(audioPlayer).setDarkBackground(darkBackground); - this.audioPlayer.init(audioPlayer, message); - } + private void toggleWhisperInfo(ViewHolder viewHolder, final Message message, final boolean darkBackground) { + if (message.isPrivateMessage()) { + final String privateMarker; + if (message.getStatus() <= Message.STATUS_RECEIVED) { + privateMarker = activity.getString(R.string.private_message); + } else { + Jid cp = message.getCounterpart(); + privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource())); + } + final SpannableString body = new SpannableString(privateMarker); + body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + viewHolder.messageBody.setText(body); + viewHolder.messageBody.setVisibility(View.VISIBLE); + } else { + viewHolder.messageBody.setVisibility(View.GONE); + } + } - private void displayMediaPreviewMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { - toggleWhisperInfo(viewHolder, message, darkBackground); - viewHolder.download_button.setVisibility(View.GONE); - viewHolder.audioPlayer.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.VISIBLE); - final FileParams params = message.getFileParams(); - final double target = metrics.density * 288; - final int scaledW; - final int scaledH; - if (Math.max(params.height, params.width) * metrics.density <= target) { - scaledW = (int) (params.width * metrics.density); - scaledH = (int) (params.height * metrics.density); - } else if (Math.max(params.height, params.width) <= target) { - scaledW = params.width; - scaledH = params.height; - } else if (params.width <= params.height) { - scaledW = (int) (params.width / ((double) params.height / target)); - scaledH = (int) target; - } else { - scaledW = (int) target; - scaledH = (int) (params.height / ((double) params.width / target)); - } - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scaledW, scaledH); - layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); - viewHolder.image.setLayoutParams(layoutParams); - activity.loadBitmap(message, viewHolder.image); - viewHolder.image.setOnClickListener(v -> openDownloadable(message)); - } + private void loadMoreMessages(Conversation conversation) { + conversation.setLastClearHistory(0, null); + activity.xmppConnectionService.updateConversation(conversation); + conversation.setHasMessagesLeftOnServer(true); + conversation.setFirstMamReference(null); + long timestamp = conversation.getLastMessageTransmitted().getTimestamp(); + if (timestamp == 0) { + timestamp = System.currentTimeMillis(); + } + conversation.messagesLoaded.set(true); + MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(conversation, new MamReference(0), timestamp, false); + if (query != null) { + Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(activity, R.string.not_fetching_history_retention_period, Toast.LENGTH_SHORT).show(); + } + } - private void toggleWhisperInfo(ViewHolder viewHolder, final Message message, final boolean darkBackground) { - if (message.isPrivateMessage()) { - final String privateMarker; - if (message.getStatus() <= Message.STATUS_RECEIVED) { - privateMarker = activity.getString(R.string.private_message); - } else { - Jid cp = message.getCounterpart(); - privateMarker = activity.getString(R.string.private_message_to, Strings.nullToEmpty(cp == null ? null : cp.getResource())); - } - final SpannableString body = new SpannableString(privateMarker); - body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - body.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - viewHolder.messageBody.setText(body); - viewHolder.messageBody.setVisibility(View.VISIBLE); - } else { - viewHolder.messageBody.setVisibility(View.GONE); - } - } - - private void loadMoreMessages(Conversation conversation) { - conversation.setLastClearHistory(0, null); - activity.xmppConnectionService.updateConversation(conversation); - conversation.setHasMessagesLeftOnServer(true); - conversation.setFirstMamReference(null); - long timestamp = conversation.getLastMessageTransmitted().getTimestamp(); - if (timestamp == 0) { - timestamp = System.currentTimeMillis(); - } - conversation.messagesLoaded.set(true); - MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(conversation, new MamReference(0), timestamp, false); - if (query != null) { - Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(activity, R.string.not_fetching_history_retention_period, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public View getView(int position, View view, ViewGroup parent) { - final Message message = getItem(position); - final boolean omemoEncryption = message.getEncryption() == Message.ENCRYPTION_AXOLOTL; - final boolean isInValidSession = message.isValidInSession() && (!omemoEncryption || message.isTrusted()); - final Conversational conversation = message.getConversation(); - final Account account = conversation.getAccount(); - final int type = getItemViewType(position); - ViewHolder viewHolder; - if (view == null) { - viewHolder = new ViewHolder(); - switch (type) { - case DATE_SEPARATOR: - view = activity.getLayoutInflater().inflate(R.layout.message_date_bubble, parent, false); + @Override + public View getView(int position, View view, ViewGroup parent) { + final Message message = getItem(position); + final boolean omemoEncryption = message.getEncryption() == Message.ENCRYPTION_AXOLOTL; + final boolean isInValidSession = message.isValidInSession() && (!omemoEncryption || message.isTrusted()); + final Conversational conversation = message.getConversation(); + final Account account = conversation.getAccount(); + final int type = getItemViewType(position); + ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + switch (type) { + case DATE_SEPARATOR: + view = activity.getLayoutInflater().inflate(R.layout.message_date_bubble, parent, false); + viewHolder.status_message = view.findViewById(R.id.message_body); + viewHolder.message_box = view.findViewById(R.id.message_box); + viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); + break; + case RTP_SESSION: + view = activity.getLayoutInflater().inflate(R.layout.message_rtp_session, parent, false); viewHolder.status_message = view.findViewById(R.id.message_body); viewHolder.message_box = view.findViewById(R.id.message_box); - break; - case SENT: - view = activity.getLayoutInflater().inflate(R.layout.message_sent, parent, false); - viewHolder.message_box = view.findViewById(R.id.message_box); - viewHolder.contact_picture = view.findViewById(R.id.message_photo); - viewHolder.download_button = view.findViewById(R.id.download_button); - viewHolder.indicator = view.findViewById(R.id.security_indicator); - viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); - viewHolder.image = view.findViewById(R.id.message_image); - viewHolder.messageBody = view.findViewById(R.id.message_body); - viewHolder.time = view.findViewById(R.id.message_time); viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); - viewHolder.audioPlayer = view.findViewById(R.id.audio_player); break; - case RECEIVED: - view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false); - viewHolder.message_box = view.findViewById(R.id.message_box); - viewHolder.contact_picture = view.findViewById(R.id.message_photo); - viewHolder.download_button = view.findViewById(R.id.download_button); - viewHolder.indicator = view.findViewById(R.id.security_indicator); - viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); - viewHolder.image = view.findViewById(R.id.message_image); - viewHolder.messageBody = view.findViewById(R.id.message_body); - viewHolder.time = view.findViewById(R.id.message_time); - viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); - viewHolder.encryption = view.findViewById(R.id.message_encryption); - viewHolder.audioPlayer = view.findViewById(R.id.audio_player); - break; - case STATUS: - view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); - viewHolder.contact_picture = view.findViewById(R.id.message_photo); - viewHolder.status_message = view.findViewById(R.id.status_message); - viewHolder.load_more_messages = view.findViewById(R.id.load_more_messages); - break; - default: - throw new AssertionError("Unknown view type"); - } - if (viewHolder.messageBody != null) { - listSelectionManager.onCreate(viewHolder.messageBody, - new MessageBodyActionModeCallback(viewHolder.messageBody)); - viewHolder.messageBody.setCopyHandler(this); - } - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) view.getTag(); - if (viewHolder == null) { - return view; - } - } + case SENT: + view = activity.getLayoutInflater().inflate(R.layout.message_sent, parent, false); + viewHolder.message_box = view.findViewById(R.id.message_box); + viewHolder.contact_picture = view.findViewById(R.id.message_photo); + viewHolder.download_button = view.findViewById(R.id.download_button); + viewHolder.indicator = view.findViewById(R.id.security_indicator); + viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); + viewHolder.image = view.findViewById(R.id.message_image); + viewHolder.messageBody = view.findViewById(R.id.message_body); + viewHolder.time = view.findViewById(R.id.message_time); + viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); + viewHolder.audioPlayer = view.findViewById(R.id.audio_player); + break; + case RECEIVED: + view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false); + viewHolder.message_box = view.findViewById(R.id.message_box); + viewHolder.contact_picture = view.findViewById(R.id.message_photo); + viewHolder.download_button = view.findViewById(R.id.download_button); + viewHolder.indicator = view.findViewById(R.id.security_indicator); + viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); + viewHolder.image = view.findViewById(R.id.message_image); + viewHolder.messageBody = view.findViewById(R.id.message_body); + viewHolder.time = view.findViewById(R.id.message_time); + viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); + viewHolder.encryption = view.findViewById(R.id.message_encryption); + viewHolder.audioPlayer = view.findViewById(R.id.audio_player); + break; + case STATUS: + view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); + viewHolder.contact_picture = view.findViewById(R.id.message_photo); + viewHolder.status_message = view.findViewById(R.id.status_message); + viewHolder.load_more_messages = view.findViewById(R.id.load_more_messages); + break; + default: + throw new AssertionError("Unknown view type"); + } + if (viewHolder.messageBody != null) { + listSelectionManager.onCreate(viewHolder.messageBody, + new MessageBodyActionModeCallback(viewHolder.messageBody)); + viewHolder.messageBody.setCopyHandler(this); + } + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + if (viewHolder == null) { + return view; + } + } - boolean darkBackground = type == RECEIVED && (!isInValidSession || mUseGreenBackground) || activity.isDarkTheme(); + boolean darkBackground = type == RECEIVED && (!isInValidSession || mUseGreenBackground) || activity.isDarkTheme(); - if (type == DATE_SEPARATOR) { - if (UIHelper.today(message.getTimeSent())) { - viewHolder.status_message.setText(R.string.today); - } else if (UIHelper.yesterday(message.getTimeSent())) { - viewHolder.status_message.setText(R.string.yesterday); - } else { - viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR)); - } - viewHolder.message_box.setBackgroundResource(activity.isDarkTheme() ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white); - return view; - } else if (type == STATUS) { - if ("LOAD_MORE".equals(message.getBody())) { - viewHolder.status_message.setVisibility(View.GONE); - viewHolder.contact_picture.setVisibility(View.GONE); - viewHolder.load_more_messages.setVisibility(View.VISIBLE); - viewHolder.load_more_messages.setOnClickListener(v -> loadMoreMessages((Conversation) message.getConversation())); - } else { - viewHolder.status_message.setVisibility(View.VISIBLE); - viewHolder.load_more_messages.setVisibility(View.GONE); - viewHolder.status_message.setText(message.getBody()); - boolean showAvatar; - if (conversation.getMode() == Conversation.MODE_SINGLE) { - showAvatar = true; - AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message); - } else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) { - showAvatar = true; - AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message); - } else { - showAvatar = false; - } - if (showAvatar) { - viewHolder.contact_picture.setAlpha(0.5f); - viewHolder.contact_picture.setVisibility(View.VISIBLE); - } else { - viewHolder.contact_picture.setVisibility(View.GONE); - } - } - return view; - } else { - AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar); - } + if (type == DATE_SEPARATOR) { + if (UIHelper.today(message.getTimeSent())) { + viewHolder.status_message.setText(R.string.today); + } else if (UIHelper.yesterday(message.getTimeSent())) { + viewHolder.status_message.setText(R.string.yesterday); + } else { + viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR)); + } + viewHolder.message_box.setBackgroundResource(activity.isDarkTheme() ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white); + return view; + } else if (type == RTP_SESSION) { + final boolean isDarkTheme = activity.isDarkTheme(); + final boolean received = message.getStatus() <= Message.STATUS_RECEIVED; + final RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody()); + final long duration = rtpSessionStatus.duration; + if (received) { + if (duration > 0) { + viewHolder.status_message.setText(activity.getString(R.string.incoming_call_duration, TimeframeUtils.resolve(activity,duration))); + } else { + viewHolder.status_message.setText(R.string.incoming_call); + } + } else { + if (duration > 0) { + viewHolder.status_message.setText(activity.getString(R.string.outgoing_call_duration, TimeframeUtils.resolve(activity,duration))); + } else { + viewHolder.status_message.setText(R.string.outgoing_call); + } + } + viewHolder.indicatorReceived.setImageResource(RtpSessionStatus.getDrawable(received,rtpSessionStatus.successful,isDarkTheme)); + viewHolder.indicatorReceived.setAlpha(isDarkTheme ? 0.7f : 0.57f); + viewHolder.message_box.setBackgroundResource(isDarkTheme ? R.drawable.date_bubble_grey : R.drawable.date_bubble_white); + return view; + } else if (type == STATUS) { + if ("LOAD_MORE".equals(message.getBody())) { + viewHolder.status_message.setVisibility(View.GONE); + viewHolder.contact_picture.setVisibility(View.GONE); + viewHolder.load_more_messages.setVisibility(View.VISIBLE); + viewHolder.load_more_messages.setOnClickListener(v -> loadMoreMessages((Conversation) message.getConversation())); + } else { + viewHolder.status_message.setVisibility(View.VISIBLE); + viewHolder.load_more_messages.setVisibility(View.GONE); + viewHolder.status_message.setText(message.getBody()); + boolean showAvatar; + if (conversation.getMode() == Conversation.MODE_SINGLE) { + showAvatar = true; + AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message); + } else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) { + showAvatar = true; + AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message); + } else { + showAvatar = false; + } + if (showAvatar) { + viewHolder.contact_picture.setAlpha(0.5f); + viewHolder.contact_picture.setVisibility(View.VISIBLE); + } else { + viewHolder.contact_picture.setVisibility(View.GONE); + } + } + return view; + } else { + AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar); + } - resetClickListener(viewHolder.message_box, viewHolder.messageBody); + resetClickListener(viewHolder.message_box, viewHolder.messageBody); - viewHolder.contact_picture.setOnClickListener(v -> { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } + viewHolder.contact_picture.setOnClickListener(v -> { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); + } - }); - viewHolder.contact_picture.setOnLongClickListener(v -> { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(v, message); - return true; - } else { - return false; - } - }); + }); + viewHolder.contact_picture.setOnLongClickListener(v -> { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(v, message); + return true; + } else { + return false; + } + }); - final Transferable transferable = message.getTransferable(); - final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message); - if (unInitiatedButKnownSize || message.isDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) { - if (unInitiatedButKnownSize || transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) { - displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)), darkBackground); - } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { - displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground); - } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground); - } - } else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - if (message.getFileParams().width > 0 && message.getFileParams().height > 0) { - displayMediaPreviewMessage(viewHolder, message, darkBackground); - } else if (message.getFileParams().runtime > 0) { - displayAudioMessage(viewHolder, message, darkBackground); - } else { - displayOpenableMessage(viewHolder, message, darkBackground); - } - } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - if (account.isPgpDecryptionServiceConnected()) { - if (conversation instanceof Conversation && !account.hasPendingPgpIntent((Conversation) conversation)) { - displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); - } - } else { - displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain), darkBackground); - viewHolder.message_box.setOnClickListener(this::promptOpenKeychainInstall); - viewHolder.messageBody.setOnClickListener(this::promptOpenKeychainInstall); - } - } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), darkBackground); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { - displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), darkBackground); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { - displayInfoMessage(viewHolder, activity.getString(R.string.omemo_decryption_failed), darkBackground); - } else { - if (message.isGeoUri()) { - displayLocationMessage(viewHolder, message, darkBackground); - } else if (message.bodyIsOnlyEmojis() && message.getType() != Message.TYPE_PRIVATE) { - displayEmojiMessage(viewHolder, message.getBody().trim(), darkBackground); - } else if (message.treatAsDownloadable()) { - try { - URL url = new URL(message.getBody()); - if (P1S3UrlStreamHandler.PROTOCOL_NAME.equalsIgnoreCase(url.getProtocol())) { - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize, - UIHelper.getFileDescriptionString(activity, message)), - darkBackground); - } else { - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize_on_host, - UIHelper.getFileDescriptionString(activity, message), - url.getHost()), - darkBackground); - } - } catch (Exception e) { - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize, - UIHelper.getFileDescriptionString(activity, message)), - darkBackground); - } - } else { - displayTextMessage(viewHolder, message, darkBackground, type); - } - } + final Transferable transferable = message.getTransferable(); + final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message); + if (unInitiatedButKnownSize || message.isDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) { + if (unInitiatedButKnownSize || transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)), darkBackground); + } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground); + } else { + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground); + } + } else if (message.isFileOrImage() && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { + if (message.getFileParams().width > 0 && message.getFileParams().height > 0) { + displayMediaPreviewMessage(viewHolder, message, darkBackground); + } else if (message.getFileParams().runtime > 0) { + displayAudioMessage(viewHolder, message, darkBackground); + } else { + displayOpenableMessage(viewHolder, message, darkBackground); + } + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + if (account.isPgpDecryptionServiceConnected()) { + if (conversation instanceof Conversation && !account.hasPendingPgpIntent((Conversation) conversation)) { + displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); + } + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.install_openkeychain), darkBackground); + viewHolder.message_box.setOnClickListener(this::promptOpenKeychainInstall); + viewHolder.messageBody.setOnClickListener(this::promptOpenKeychainInstall); + } + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), darkBackground); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { + displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), darkBackground); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { + displayInfoMessage(viewHolder, activity.getString(R.string.omemo_decryption_failed), darkBackground); + } else { + if (message.isGeoUri()) { + displayLocationMessage(viewHolder, message, darkBackground); + } else if (message.bodyIsOnlyEmojis() && message.getType() != Message.TYPE_PRIVATE) { + displayEmojiMessage(viewHolder, message.getBody().trim(), darkBackground); + } else if (message.treatAsDownloadable()) { + try { + URL url = new URL(message.getBody()); + if (P1S3UrlStreamHandler.PROTOCOL_NAME.equalsIgnoreCase(url.getProtocol())) { + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize, + UIHelper.getFileDescriptionString(activity, message)), + darkBackground); + } else { + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize_on_host, + UIHelper.getFileDescriptionString(activity, message), + url.getHost()), + darkBackground); + } + } catch (Exception e) { + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize, + UIHelper.getFileDescriptionString(activity, message)), + darkBackground); + } + } else { + displayTextMessage(viewHolder, message, darkBackground, type); + } + } - if (type == RECEIVED) { - if (isInValidSession) { - int bubble; - if (!mUseGreenBackground) { - bubble = activity.getThemeResource(R.attr.message_bubble_received_monochrome, R.drawable.message_bubble_received_white); - } else { - bubble = activity.getThemeResource(R.attr.message_bubble_received_green, R.drawable.message_bubble_received); - } - viewHolder.message_box.setBackgroundResource(bubble); - viewHolder.encryption.setVisibility(View.GONE); - } else { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); - viewHolder.encryption.setVisibility(View.VISIBLE); - if (omemoEncryption && !message.isTrusted()) { - viewHolder.encryption.setText(R.string.not_trusted); - } else { - viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); - } - } - } + if (type == RECEIVED) { + if (isInValidSession) { + int bubble; + if (!mUseGreenBackground) { + bubble = activity.getThemeResource(R.attr.message_bubble_received_monochrome, R.drawable.message_bubble_received_white); + } else { + bubble = activity.getThemeResource(R.attr.message_bubble_received_green, R.drawable.message_bubble_received); + } + viewHolder.message_box.setBackgroundResource(bubble); + viewHolder.encryption.setVisibility(View.GONE); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + viewHolder.encryption.setVisibility(View.VISIBLE); + if (omemoEncryption && !message.isTrusted()) { + viewHolder.encryption.setText(R.string.not_trusted); + } else { + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); + } + } + } - displayStatus(viewHolder, message, type, darkBackground); + displayStatus(viewHolder, message, type, darkBackground); - return view; - } + return view; + } - private void promptOpenKeychainInstall(View view) { - activity.showInstallPgpDialog(); - } + private void promptOpenKeychainInstall(View view) { + activity.showInstallPgpDialog(); + } - @Override - public void notifyDataSetChanged() { - listSelectionManager.onBeforeNotifyDataSetChanged(); - super.notifyDataSetChanged(); - listSelectionManager.onAfterNotifyDataSetChanged(); - } + @Override + public void notifyDataSetChanged() { + listSelectionManager.onBeforeNotifyDataSetChanged(); + super.notifyDataSetChanged(); + listSelectionManager.onAfterNotifyDataSetChanged(); + } - private String transformText(CharSequence text, int start, int end, boolean forCopy) { - SpannableStringBuilder builder = new SpannableStringBuilder(text); - Object copySpan = new Object(); - builder.setSpan(copySpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - DividerSpan[] dividerSpans = builder.getSpans(0, builder.length(), DividerSpan.class); - for (DividerSpan dividerSpan : dividerSpans) { - builder.replace(builder.getSpanStart(dividerSpan), builder.getSpanEnd(dividerSpan), - dividerSpan.isLarge() ? "\n\n" : "\n"); - } - start = builder.getSpanStart(copySpan); - end = builder.getSpanEnd(copySpan); - if (start == -1 || end == -1) return ""; - builder = new SpannableStringBuilder(builder, start, end); - if (forCopy) { - QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); - for (QuoteSpan quoteSpan : quoteSpans) { - builder.insert(builder.getSpanStart(quoteSpan), "> "); - } - } - return builder.toString(); - } + private String transformText(CharSequence text, int start, int end, boolean forCopy) { + SpannableStringBuilder builder = new SpannableStringBuilder(text); + Object copySpan = new Object(); + builder.setSpan(copySpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + DividerSpan[] dividerSpans = builder.getSpans(0, builder.length(), DividerSpan.class); + for (DividerSpan dividerSpan : dividerSpans) { + builder.replace(builder.getSpanStart(dividerSpan), builder.getSpanEnd(dividerSpan), + dividerSpan.isLarge() ? "\n\n" : "\n"); + } + start = builder.getSpanStart(copySpan); + end = builder.getSpanEnd(copySpan); + if (start == -1 || end == -1) return ""; + builder = new SpannableStringBuilder(builder, start, end); + if (forCopy) { + QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); + for (QuoteSpan quoteSpan : quoteSpans) { + builder.insert(builder.getSpanStart(quoteSpan), "> "); + } + } + return builder.toString(); + } - @Override - public String transformTextForCopy(CharSequence text, int start, int end) { - if (text instanceof Spanned) { - return transformText(text, start, end, true); - } else { - return text.toString().substring(start, end); - } - } + @Override + public String transformTextForCopy(CharSequence text, int start, int end) { + if (text instanceof Spanned) { + return transformText(text, start, end, true); + } else { + return text.toString().substring(start, end); + } + } - public FileBackend getFileBackend() { - return activity.xmppConnectionService.getFileBackend(); - } + public FileBackend getFileBackend() { + return activity.xmppConnectionService.getFileBackend(); + } - public void stopAudioPlayer() { - audioPlayer.stop(); - } + public void stopAudioPlayer() { + audioPlayer.stop(); + } - public void unregisterListenerInAudioPlayer() { - audioPlayer.unregisterListener(); - } + public void unregisterListenerInAudioPlayer() { + audioPlayer.unregisterListener(); + } - public void startStopPending() { - audioPlayer.startStopPending(); - } + public void startStopPending() { + audioPlayer.startStopPending(); + } - public void openDownloadable(Message message) { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ConversationFragment.registerPendingMessage(activity, message); - ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE); - return; - } - final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - ViewUtil.view(activity, file); - } + public void openDownloadable(Message message) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ConversationFragment.registerPendingMessage(activity, message); + ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE); + return; + } + final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + ViewUtil.view(activity, file); + } - private void showLocation(Message message) { - for (Intent intent : GeoHelper.createGeoIntentsFromMessage(activity, message)) { - if (intent.resolveActivity(getContext().getPackageManager()) != null) { - getContext().startActivity(intent); - return; - } - } - Toast.makeText(activity, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); - } + private void showLocation(Message message) { + for (Intent intent : GeoHelper.createGeoIntentsFromMessage(activity, message)) { + if (intent.resolveActivity(getContext().getPackageManager()) != null) { + getContext().startActivity(intent); + return; + } + } + Toast.makeText(activity, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); + } - public void updatePreferences() { - SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); - this.mUseGreenBackground = p.getBoolean("use_green_background", activity.getResources().getBoolean(R.bool.use_green_background)); - } + public void updatePreferences() { + SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); + this.mUseGreenBackground = p.getBoolean("use_green_background", activity.getResources().getBoolean(R.bool.use_green_background)); + } - public void setHighlightedTerm(List terms) { - this.highlightedTerm = terms == null ? null : StylingHelper.filterHighlightedWords(terms); - } + public void setHighlightedTerm(List terms) { + this.highlightedTerm = terms == null ? null : StylingHelper.filterHighlightedWords(terms); + } - public interface OnQuoteListener { - void onQuote(String text); - } + public interface OnQuoteListener { + void onQuote(String text); + } - public interface OnContactPictureClicked { - void onContactPictureClicked(Message message); - } + public interface OnContactPictureClicked { + void onContactPictureClicked(Message message); + } - public interface OnContactPictureLongClicked { - void onContactPictureLongClicked(View v, Message message); - } + public interface OnContactPictureLongClicked { + void onContactPictureLongClicked(View v, Message message); + } - private static class ViewHolder { + private static class ViewHolder { - public Button load_more_messages; - public ImageView edit_indicator; - public RelativeLayout audioPlayer; - protected LinearLayout message_box; - protected Button download_button; - protected ImageView image; - protected ImageView indicator; - protected ImageView indicatorReceived; - protected TextView time; - protected CopyTextView messageBody; - protected ImageView contact_picture; - protected TextView status_message; - protected TextView encryption; - } + public Button load_more_messages; + public ImageView edit_indicator; + public RelativeLayout audioPlayer; + protected LinearLayout message_box; + protected Button download_button; + protected ImageView image; + protected ImageView indicator; + protected ImageView indicatorReceived; + protected TextView time; + protected CopyTextView messageBody; + protected ImageView contact_picture; + protected TextView status_message; + protected TextView encryption; + } - private class MessageBodyActionModeCallback implements ActionMode.Callback { + private class MessageBodyActionModeCallback implements ActionMode.Callback { - private final TextView textView; + private final TextView textView; - public MessageBodyActionModeCallback(TextView textView) { - this.textView = textView; - } + public MessageBodyActionModeCallback(TextView textView) { + this.textView = textView; + } - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - if (onQuoteListener != null) { - int quoteResId = activity.getThemeResource(R.attr.icon_quote, R.drawable.ic_action_reply); - // 3rd item is placed after "copy" item - menu.add(0, android.R.id.button1, 3, R.string.quote).setIcon(quoteResId) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - return false; - } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (onQuoteListener != null) { + int quoteResId = activity.getThemeResource(R.attr.icon_quote, R.drawable.ic_action_reply); + // 3rd item is placed after "copy" item + menu.add(0, android.R.id.button1, 3, R.string.quote).setIcon(quoteResId) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + return false; + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (item.getItemId() == android.R.id.button1) { - int start = textView.getSelectionStart(); - int end = textView.getSelectionEnd(); - if (end > start) { - String text = transformText(textView.getText(), start, end, false); - if (onQuoteListener != null) { - onQuoteListener.onQuote(text); - } - mode.finish(); - } - return true; - } - return false; - } + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getItemId() == android.R.id.button1) { + int start = textView.getSelectionStart(); + int end = textView.getSelectionEnd(); + if (end > start) { + String text = transformText(textView.getText(), start, end, false); + if (onQuoteListener != null) { + onQuoteListener.onQuote(text); + } + mode.finish(); + } + return true; + } + return false; + } - @Override - public void onDestroyActionMode(ActionMode mode) { - } - } + @Override + public void onDestroyActionMode(ActionMode mode) { + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 47fec58c6..908e572cd 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -299,6 +299,8 @@ public class UIHelper { return new Pair<>(context.getString(R.string.omemo_decryption_failed), true); } else if (message.isFileOrImage()) { return new Pair<>(getFileDescriptionString(context, message), true); + } else if (message.getType() == Message.TYPE_RTP_SESSION) { + return new Pair<>(context.getString(message.getStatus() == Message.STATUS_RECEIVED ? R.string.incoming_call : R.string.outgoing_call), true); } else { final String body = MessageUtils.filterLtrRtl(message.getBody()); if (body.startsWith(Message.ME_COMMAND)) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index e21e69a3d..a375ac657 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.xmpp.jingle; +import android.os.SystemClock; import android.util.Log; import com.google.common.base.Strings; @@ -18,6 +19,10 @@ import java.util.List; import java.util.Map; import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Conversational; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; @@ -94,13 +99,27 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this); private final ArrayDeque pendingIceCandidates = new ArrayDeque<>(); + private final Message message; private State state = State.NULL; private RtpContentMap initiatorRtpContentMap; private RtpContentMap responderRtpContentMap; + private long rtpConnectionStarted = 0; //time of 'connected' JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) { super(jingleConnectionManager, id, initiator); + final Conversation conversation = jingleConnectionManager.getXmppConnectionService().findOrCreateConversation( + id.account, + id.with.asBareJid(), + false, + false + ); + this.message = new Message( + conversation, + isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED, + Message.TYPE_RTP_SESSION, + id.sessionId + ); } private static State reasonToState(Reason reason) { @@ -153,7 +172,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return; } webRTCWrapper.close(); - transitionOrThrow(reasonToState(wrapper.reason)); + final State target = reasonToState(wrapper.reason); + transitionOrThrow(target); + writeLogMessage(target); if (previous == State.PROPOSED || previous == State.SESSION_INITIALIZED) { xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); } @@ -455,7 +476,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (transition(State.RETRACTED)) { xmppConnectionService.getNotificationService().cancelIncomingCallNotification(); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted"); - //TODO create missed call notification/message + writeLogMessageMissed(); jingleConnectionManager.finishConnection(this); } else { Log.d(Config.LOGTAG, "ignoring retract because already in " + this.state); @@ -509,6 +530,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void sendSessionTerminate(final Reason reason, final String text) { final State target = reasonToState(reason); transitionOrThrow(target); + writeLogMessage(target); final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); jinglePacket.setReason(reason, text); send(jinglePacket); @@ -773,9 +795,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web public void onConnectionChange(final PeerConnection.PeerConnectionState newState) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState); updateEndUserState(); + if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) { + this.rtpConnectionStarted = SystemClock.elapsedRealtime(); + } if (newState == PeerConnection.PeerConnectionState.FAILED) { if (TERMINATED.contains(this.state)) { - Log.d(Config.LOGTAG,id.account.getJid().asBareJid()+": not sending session-terminate after connectivity error because session is already in state "+this.state); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); return; } sendSessionTerminate(Reason.CONNECTIVITY_ERROR); @@ -850,6 +875,37 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } + private void writeLogMessage(final State state) { + final long started = this.rtpConnectionStarted; + long duration = started <= 0 ? 0 : SystemClock.elapsedRealtime() - started; + if (state == State.TERMINATED_SUCCESS || (state == State.TERMINATED_CONNECTIVITY_ERROR && duration > 0)) { + writeLogMessageSuccess(duration); + } else { + writeLogMessageMissed(); + } + } + + private void writeLogMessageSuccess(final long duration) { + this.message.setBody(new RtpSessionStatus(true, duration).toString()); + this.writeMessage(); + } + + private void writeLogMessageMissed() { + this.message.setBody(new RtpSessionStatus(false,0).toString()); + this.writeMessage(); + } + + private void writeMessage() { + final Conversational conversational = message.getConversation(); + if (conversational instanceof Conversation) { + ((Conversation) conversational).add(this.message); + xmppConnectionService.databaseBackend.createMessage(message); + xmppConnectionService.updateConversationUi(); + } else { + throw new IllegalStateException("Somehow the conversation in a message was a stub"); + } + } + public State getState() { return this.state; } diff --git a/src/main/res/drawable-hdpi/ic_call_made_black_18dp.png b/src/main/res/drawable-hdpi/ic_call_made_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef5b84e9112caae702fd957a79fba148ff2eea2 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(DX`U{QAr*{oFD(>oFc4w6n9HPf zV1nc>Pe#pYLK+XE0|FRt2-mBr3*NkZUN&yAm-n^{niZ2xt=_i034FG#FjB`+efwD! z(L-rX0?(9F?Si0$rNh^WU)V!Le)`IE{YeXRs}Zya{}5NccQVjM22WQ%mvv4FO#s^% BHnso& literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_call_made_white_18dp.png b/src/main/res/drawable-hdpi/ic_call_made_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1b126d2dc570a79242b6df3a89efd8640a1e5d5b GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(DrJgR1Ar*{oFKy&yG8AFCc%LQ2 zp=s&sW2`%FXg}mC;1Uek;NsDf(%C6!{qgj+f5G18BT78?u3j@MGPfqV`kA}`$qUnV zr@L&oQDM%SIf-#SH zRfBV6H}lf&sO1|=rK{UFgme7qaDQ+>%k|#6PoNBmsO dym{L%hWCFW0>1A)vkvGA22WQ%mvv4FO#mi4N7w)W literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_call_missed_outgoing_black_18dp.png b/src/main/res/drawable-hdpi/ic_call_missed_outgoing_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..7cba4f0ac614a35761cd24e376fcbf3b218588aa GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(D)t)YnAr*{o&#ja^B*`{;V2t< z*7ite`NW8#V+Ysp{C%1;t-bE^YVm}I(( zK|HlAX2RLycaloY%fu7^ubdFFr1RkI3oHMfw-o&Jy>)_;aahvX0GDT?&5@Z4`D`C4 zs7+i^u=(Iu&(^FWzfCuPmh_q(3poA!nc^+U`@9w1e9IEJU%6UcPI%&2v*l2W*W56R qstI@54o~nn6rketZuXDrwX7x~_fB#@wfF;c5re0zpUXO@geCxa!cS8G literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_call_received_black_18dp.png b/src/main/res/drawable-hdpi/ic_call_received_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..af45ceb84180e770986ff0437eeea12c257e59ed GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(DX`U{QAr*{oPjBRHF%V(7n8~E2 za8T&(4hHepLK+X23&@JjC}?ddxz}^R_#?A~s+xT6oHu9evSijgSp0=`3g@cs1s6EP zJ0m^LI3}?PZJscr`G}rkyQ$*zzZ@lRQ_nw^kJivnceB0w(lPwQSF_y_KpPo6UHx3v IIVCg!03!7`CIA2c literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_call_received_white_18dp.png b/src/main/res/drawable-hdpi/ic_call_received_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8080c7879fecde2727d0df1096f3930d3ad1b3b8 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(D1)eUBAr*{oPaER$Dm?W;Wf{-c{|hy=2zcN9eqX`!Q-Ki<1&`P0EO4|ueInAO z(RY@v#s=0c3W;3QRw*Ebq RCJeNj!PC{xWt~$(69B>&KVbj> literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_made_black_18dp.png b/src/main/res/drawable-mdpi/ic_call_made_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e7293a0a908533354492db32075a495fce03c286 GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|myggkULn;`PB|H=kJZNBI-LQg@ z>%V&=-w$6ap3oNdFU+Dc44Q}5FOkmK%3P<)Bj8#w^?=C3z@~tfXwE~+nJlIXd=g{~ fUsg|e?eV;MYM{an^LB{Ts5^4KS0 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_made_white_18dp.png b/src/main/res/drawable-mdpi/ic_call_made_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6a23529c5c3178c8c8a59cde53fd8fbd911e43 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m{5)M8Lp07OCrDU)h<~u3nM>rr zvJ(&OwF(a8UrLrwe71HY+yMjvcP!%`TXxjua*@R2l8gl3z)u6d)6h{Fr|o#0+*7Vxhgk5 pZxi{o;osZ~CS9U8#SiT-Vc4=z-NJ8Y+%cf}44$rjF6*2UngC%{Go}Cl literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_missed_outgoing_black_18dp.png b/src/main/res/drawable-mdpi/ic_call_missed_outgoing_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ef4ad9622324dde04abeff9ba9ebbdc0f5d525b1 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|md_7$pLn;`PC2l!1EtKg@KTyEH zr+&b-(d(b_mt>7!ydt&@Ibrq@lDkgi&rtG7<+#VVLn(waMXXJ!Ma9X`NpP7_%T_jr g4&DY|DLn=TmeuOHUh`@~fCe*oy85}Sb4q9e0CYws#sB~S literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_missed_outgoing_white_18dp.png b/src/main/res/drawable-mdpi/ic_call_missed_outgoing_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe42b28cb80dce13ff80b2712a2fa481c6ce8fb GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m{5@S9Ln;{Wo@Ez3V8Fp}@x+Rr z9o;-yoUyUj&DS%t>fZbypE;jT?bzF^p2u@$F}gTvPxu~Jn4T6En7Gd)F!7$t)Vl?) jM*Ejuj&~4yt6#`e-j`@xnen0*Xf%VTtDnm{r-UW|*$FPg literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_missed_white_18dp.png b/src/main/res/drawable-mdpi/ic_call_missed_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6d77962a322eb08c404b91ac1022bc2def1eff GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mqC8z3Ln;`ro;t|epdjFKG4rrq z#<_?it0II-3xCR)2^h&5{5Lsy`P$>fXSrs@%5Xf*bT}T!dQP|dBm_VUF~kvH%H+QQ)J>gTe~DWM4fe5W&x literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_call_received_black_18dp.png b/src/main/res/drawable-mdpi/ic_call_received_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..fe45b55175eb4c6ab1e2bab7687b0465a432c3de GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|md^}woLn;`PC2SVVU)Zr>(tn=2 zDH?(a1q?|{%PbgLWj|;+YB*^eT*1jYo_rZG6)Dy+`Xb+e7ep0L*g6_UDoPtYI79Bt7 nQZRXYk%_pot5yA^eUA3hQ`Py5oR)3{8qeVA>gTe~DWM4f^MNbz literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_call_made_black_18dp.png b/src/main/res/drawable-xhdpi/ic_call_made_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..01cceead35b95f48595e406dde7072d435cfc84d GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8si%u$NCo5D%Lh4`3tc`0~kDA{an^LB{Ts5%ZWOy literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_call_made_white_18dp.png b/src/main/res/drawable-xhdpi/ic_call_made_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ea6a8ab5f2382f8641bd014b7b0e12b0f4e936e8 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8i>HfYNCo5DD+hTU10`4=&Oeo> zFY+n4wPQuVlm|CXwalKrG2!F6H~XKiEY07pr)-%n%S1wR++{XtK*z8y*-&D^qb|x fLV@Uo^)2)A+vZOyKVz)}bPt24tDnm{r-UW|%D6=- literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_call_missed_black_18dp.png b/src/main/res/drawable-xhdpi/ic_call_missed_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..930fa4373f3af1007419f0cc4cfc42fab24989e7 GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8Lp1Wy;okP61Pmv{3XHV`7@mHQewvkr4$gUuLrF0P? yuNiOFuT_|0T|k0wldT1B8K8v!{z=NCo5D%LfG=3`ATnPCv9j zvi8rGiBgxDHk75`JHd5lKNI@`{x9FJYj;YES}b1R+07NqwbsHhe6!5#hFu)0qHEK+ z@^n`#J<+_;t+?!roT!5T!D~DdmM3^Kq-aKPPgt73>okw4P0^k4@Px$;6WxAX=V9&X m*G!YrNRwKzB>n66?TnG~bAIQ2PpSqwhr!d;&t;ucLK6V@E<%R@ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_white_18dp.png b/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b4fc9bc8b77a700ade84b8a974eaad93c42eb0f7 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8ho_5UNCo5D%clho8SuCU21&G} zy>QNJmWpkbkE?!nhf{l>zIODblmEZ`{rP-;%%(!s>mqXDS^H9-oD0#KvuL&C%*0oc z8M9r)y7Jd9<$fYkX81&;q@-=mqO9Z-nk$ybByp8V7Il@&BxR+_7y+>mmt5VtKkFq~ odycPo+!J(Ma`_CPx#s7XKlt=Y7TlN~33L;Kr>mdKI;Vst0LbA|k0wldT1B8LpJWm(LkP61PmmIkc8HltzwBZjc z64##;w>ZrIhH;5YjL(Bq8G$<<|86c3u;bYBr+l-d)~y23)E(T@{>?t#R_JTMKW9fB z|HR7Dq@OIZ5nnG`vE2~h@)DO>yudl2?c{|n-uHPN=QCC)icFPYp4xXyaMs6wWhTiB z=Iv4jk}i?U+OvNB^@wZ>kkdSG(J>{F$K{kEo097>=Q`n87v3*aZ(3t}<@>*u9H2`X NJYD@<);T3K0RSDTP~-pr literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_call_received_black_18dp.png b/src/main/res/drawable-xhdpi/ic_call_received_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4008ba956eb4e269434852097e63bc7713eb82c2 GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8nWu|mNCo5D^9MN_3`CkAzDUUz zE1d8`MCnn>BMz-9&I$olR{q?7**{-6n`y5$T|k0wldT1B8K8i>HfYNCo5DGY2`H90iU&DAk@F zb;QYSwSb_uh_^x9(N_5)p)ac6^Z$59`>cB-`JVsJWa$r2HaN{=wBXh-5HFMC>Suie)>v%*KbMn|s@vJM`)}UW;@btB;wOc3d+%wUBcBNjv*zc!l-}!yA=f74` z3rJ97eHJ|Z&Vih)>6~f7jGtFF{0uozqm^JMYA~N`hCl0>u9|Y; r=JFR4+WjwnJo{7R&P(pAQQ7*LXXC%rx<5|@I*!59)z4*}Q$iB}mbFj~ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_made_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_made_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..41a58b948ebca9850665dd0d733a9656010717b1 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+6`n4RArXh)UJc}8GURD_$hddW zyw&XjB70mM^0)GD)>rB-+wEw~s9Mkefpu2Uf>o~?OhHZYS& zKA$agZDB~n^h*i)S6a;_zq!4gC!JlC_0h)1uXBT)>cMLojNANLla;k3q=OgU5EJdl z4NjcV&UL6JgyY##CIkDeKhDmc<8|Y}qOMDU4CgaN4lplQ;bN$GGR4JqN3c+6=+*aT Z%-_Q$!yQ6j%mlib!PC{xWt~$(69DIyRzUy& literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_missed_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_missed_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2326b5fba588accecb7535c4f22caa4b1e2b4e4e GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+eV#6kAr*{oFCFAOk=os> zR}apJinl9S*KD};-O6=r$FA%O?CsZ;`q6A~R6_oTNU|BbNHVv)(bJ^nV`o2#_BAT4 zS&`VMDSlb}`acK7V=}>JK&-dz^j$NVU^})s{y%TnJr7nZJK*b6xcAxls+SW!YwACl v@n}Z!D!bz%$74#lcI*-bP0l+XkKWgBC| literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f7f8e0746bb7db0f081d0fed08b4de54e45700d1 GIT binary patch literal 235 zcmV0qc8H(sY?Eu2W_islg+JD)(Ckd2NN-3?^R-IZZI^#I1+D;GM zo48&t9A~1&P0UTlwttGb>6p5acroW=KF5EMn+Cv4j!!bioKG>v9G@5f^ElXj1I*)K z=NT}IgI$1G9P9wh;$RgpmxC3+Yz`>EY_38P=5rN_FrTYZQc?dOMVi0?W#n=`<#RqY liSwxmoKH>Qh)OA?l-{ekVYL5oO*;Sp002ovPDHLkV1mfTV0{1p literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a34511a03c4283ab0a8df85f70db9470a6177db5 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+O`a}}Ar*{oFKw4RWWeB(=;SHm zDm`IZDo=RMgUY@qQU2d8L%v_p{JSl2YlP;~DU-h4GJC7N>ygFv^%gIdrgJa%yE9e8 z^GxrtE%F+M=5n{|I1HaB+a0f;&|Y<{rqAE#*c@lWV>0t!NE%z*`0H@yLBjHN9g;F{ zlFmF#U{B^_U)^IVv&JB;ptQ?y^6J2znK8XdXP!K;kGjIUSc0X9`RVgwyQjsEHK$8^ jOWw&i^`OHG=!AQD=R9Aq`rdg7bUlNotDnm{r-UW|(Dz9t4uK-BflIopV#0*hUtPKHuidvt z&wQ^e*}Y47gVR(wtM_e;p;fDH^D=u$UClWz#{0){MpDkxMt{a<#SV(Y9QnCA@};=D zNb=Vi$z6ueW+a_?I3uaYa+!p&#S$Q1Ch=K3BB7C0?1p@z{<7kpudS!PC{x JWt~$(69B(fX%qke literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_received_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_received_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..75b98ec1cb1816263ca18bcb15cfdbfa96f36b6e GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+iJmTwAr*{o&mQD-aui^>$mSdP zc$NYS>tu-!=}!||ZLc2fXRmgAxr$-lr8>=}i`QALUGz?>>eZeusRw_q{MppE>!pC! z75$*4^QQMYtu@TIcRk}LxH=imc>L=3E1b3O+hFwn4PJJsL#DVhK4 q?bo+&+>`Cu7*t=Qxpd0E)#5!K@n14654!>#$KdJe=d#Wzp$P!RpHamC literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_call_received_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_received_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..13d588caf711c39f6dd8cce11b980c84fbbcc2b0 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+)t)YnAr*{ouN~w(lpwUw<3f&^iHpKv+13#&P~h4b^5H@x4q`16!oqT{rG+~m^j0G%eD;R`B`YN@Kt9{Mn(mK}l-YbAfc*+4LuY@L{8H<^G0+=p` zG;n$)%sOT0e{{z3q%*;W&sIuA+fGg904m`!aP)lRnQLY{ZN1d(%J9O=Nvn=YWv|)z bxSIJP<670GojoByXES)Z`njxgN@xNATCG+A literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_call_made_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_made_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d00c654c52ed0fd311bba2d2be07db95f096cf GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawW_!9ghEy=VJ$IVZ$x-CMh257P z{V~zvXgYiLRJcRfDF=Z`CI!#zR{_DVKXnTn8n>0s+s>TBT^`Q_REjw! zSxR>atNYEdc%fN*A$8gr$Lo zw!JF?1gb2W%(t$7qR&0(>+P0pdHvB5ZQ z$>nzn4}30FrVA82)8kcmptI_|V@=LeAA!kpWEccb9$*xh_(z6KH~)N|mU+7)tBP>D zBh#er!wL$LmOQ^)cCF5lVG}%gBvD|pj|`h3m>~mXWQNYP;7JlH%u!HdJkFXlsoz;a zq0&NT!Y@mgPsJTqjxq*`vwOtKD3lg9TsguR^itj%?8cXz{f;GG68j4-0e#2d>FVdQ I&MBb@0J_Lxl>h($ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5117934594e1692df7bdb1ae20a252c9b3fe861e GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawPI$UFhEy=VJ-1ts$xwjh;#U!w zjq}P6KIG)zyn5Lz`^%rcMDES}AC$dz>$Rxt>6U4}xA?*j>^wKmSglf^pv>=+!ZU-H zsS6(0&Gpo0UTZn?OV$LXgD#9rlOAvgOkikh9iqt$)MT>{6`JpP?_bonIDS0v;s zeZte`rGom!Da~_SGT1FSE%rAwY$yedyyPX7d=sBpdPIeTos*sahTFt4s<6F9TL$PA22WQ%mvv4FO#sTXU!ed1 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_call_missed_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_missed_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2374dc5a1158455c8a7ccfc02d821e2144e9952b GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawUU|AWhEy=VJ-5-T#ZaL2;{GoF z<#sD0*1S6%*_)u1C}!y+IN|qQy~GDGnoFkyd1)@KkIxi;xANZWpBMALuh4uR7aBji zL~e`aSLZ5Awk(U;U#d8+6qMz&JMumcJMH+_kk^7CNvME*9@MnelF{r5}E*E!EJp2 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_call_received_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_received_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..81dc0c36797531a9ed390414b571d9c24223da61 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw=6bp~hEy=VJ!i!ELGXlU()!gZT{=KOwBdB&*lTX%_3;k_lB8=7Nd=G>Nf zbfi=Gx%tt98ylD>G&qxq8ja1)-LP2vK;X+?CRVO5{i}drUTS_prMs3{DbS$|p00i_ I>zopr0QLu2B>(^b literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_call_received_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_received_white_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..58421114fd7acefd4713ed01e079a58747d96a7e GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawPI$UFhGg7(d)=3>$v~hjF|5Mk zPn*U9r~Uo04yq z@5#!^dNVD~uiI(S-COYBKqK>W|M~h}2Q0a`enh%EC}tn$=ZZQN7jWa;r$#38UF;%N z@7^>w^&hki__6X1J7=2GwHvkD9-$96#NDAf@tr zjU%UpsK@!04pn~aOR8?DJT`H-lzyL|k%^@P2s|z_H_WiQDQejD0_Z0OPgg&ebxsLQ E015VIaR2}S literal 0 HcmV?d00001 diff --git a/src/main/res/layout/message_date_bubble.xml b/src/main/res/layout/message_date_bubble.xml index 37d43bd3b..5e5cd0c4d 100644 --- a/src/main/res/layout/message_date_bubble.xml +++ b/src/main/res/layout/message_date_bubble.xml @@ -1,24 +1,27 @@ + android:paddingBottom="5dp"> + android:layout_centerHorizontal="true" + android:background="@drawable/date_bubble_white"> + + tools:text="Yesterday" /> \ No newline at end of file diff --git a/src/main/res/layout/message_rtp_session.xml b/src/main/res/layout/message_rtp_session.xml new file mode 100644 index 000000000..ad7f06d6a --- /dev/null +++ b/src/main/res/layout/message_rtp_session.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ef2267a4a..f353145b0 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -903,6 +903,10 @@ Hang up Ongoing call Disable Tor to make calls + Incoming call + Incoming call · %s + Outgoing call + Outgoing call · %s View %1$d Participant View %1$d Participants