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 000000000..1ef5b84e9 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_made_black_18dp.png differ 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 000000000..1b126d2dc Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_made_white_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_call_missed_black_18dp.png b/src/main/res/drawable-hdpi/ic_call_missed_black_18dp.png new file mode 100644 index 000000000..5447aedc8 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_missed_black_18dp.png differ 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 000000000..7cba4f0ac Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_missed_outgoing_black_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_call_missed_outgoing_white_18dp.png b/src/main/res/drawable-hdpi/ic_call_missed_outgoing_white_18dp.png new file mode 100644 index 000000000..596012560 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_missed_outgoing_white_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_call_missed_white_18dp.png b/src/main/res/drawable-hdpi/ic_call_missed_white_18dp.png new file mode 100644 index 000000000..81a7aa16c Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_missed_white_18dp.png differ 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 000000000..af45ceb84 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_received_black_18dp.png differ 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 000000000..8080c7879 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_call_received_white_18dp.png differ 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 000000000..e7293a0a9 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_made_black_18dp.png differ 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 000000000..3c6a23529 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_made_white_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_call_missed_black_18dp.png b/src/main/res/drawable-mdpi/ic_call_missed_black_18dp.png new file mode 100644 index 000000000..fb61875a1 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_missed_black_18dp.png differ 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 000000000..ef4ad9622 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_missed_outgoing_black_18dp.png differ 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 000000000..8fe42b28c Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_missed_outgoing_white_18dp.png differ 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 000000000..4d6d77962 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_missed_white_18dp.png differ 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 000000000..fe45b5517 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_received_black_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_call_received_white_18dp.png b/src/main/res/drawable-mdpi/ic_call_received_white_18dp.png new file mode 100644 index 000000000..9accbbf36 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_call_received_white_18dp.png differ 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 000000000..01cceead3 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_made_black_18dp.png differ 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 000000000..ea6a8ab5f Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_made_white_18dp.png differ 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 000000000..930fa4373 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_missed_black_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_black_18dp.png b/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_black_18dp.png new file mode 100644 index 000000000..e222c17fd Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_black_18dp.png differ 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 000000000..b4fc9bc8b Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_missed_outgoing_white_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_call_missed_white_18dp.png b/src/main/res/drawable-xhdpi/ic_call_missed_white_18dp.png new file mode 100644 index 000000000..f188eb9aa Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_missed_white_18dp.png differ 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 000000000..4008ba956 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_received_black_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_call_received_white_18dp.png b/src/main/res/drawable-xhdpi/ic_call_received_white_18dp.png new file mode 100644 index 000000000..ca2ae411a Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_call_received_white_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_call_made_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_made_black_18dp.png new file mode 100644 index 000000000..cf2b55b17 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_made_black_18dp.png differ 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 000000000..41a58b948 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_made_white_18dp.png differ 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 000000000..2326b5fba Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_missed_black_18dp.png differ 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 000000000..f7f8e0746 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_black_18dp.png differ 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 000000000..a34511a03 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_missed_outgoing_white_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_call_missed_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_call_missed_white_18dp.png new file mode 100644 index 000000000..a615f59d5 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_missed_white_18dp.png differ 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 000000000..75b98ec1c Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_received_black_18dp.png differ 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 000000000..13d588caf Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_call_received_white_18dp.png differ 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 000000000..c7d00c654 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_made_black_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_call_made_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_made_white_18dp.png new file mode 100644 index 000000000..ae471c9fc Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_made_white_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_call_missed_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_missed_black_18dp.png new file mode 100644 index 000000000..b689b79cb Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_missed_black_18dp.png differ 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 000000000..511793459 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_black_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_white_18dp.png new file mode 100644 index 000000000..232460ca1 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_missed_outgoing_white_18dp.png differ 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 000000000..2374dc5a1 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_missed_white_18dp.png differ 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 000000000..81dc0c367 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_received_black_18dp.png differ 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 000000000..58421114f Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_call_received_white_18dp.png differ 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