show call log messages in conversation stream

This commit is contained in:
Daniel Gultsch 2020-04-12 17:12:59 +02:00
parent 1dc88f38ca
commit 3439f40411
48 changed files with 1077 additions and 855 deletions

View File

@ -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,

View File

@ -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;
}
}
}
}

View File

@ -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,6 +73,7 @@ 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;
@ -83,6 +85,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
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;
@ -92,6 +95,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
private boolean mUseGreenBackground = false;
private OnQuoteListener onQuoteListener;
public MessageAdapter(XmppActivity activity, List<Message> messages) {
super(activity, 0, messages);
this.audioPlayer = new AudioPlayer(this);
@ -101,7 +105,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
private static void resetClickListener(View... views) {
for (View view : views) {
view.setOnClickListener(null);
@ -135,7 +138,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
@Override
public int getViewTypeCount() {
return 4;
return 5;
}
private int getItemViewType(Message message) {
@ -145,12 +148,14 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} 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;
}
}
@Override
public int getItemViewType(int position) {
@ -283,7 +288,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
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 (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)) {
@ -291,7 +296,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else if ((filesize != null) && (info == null)) {
viewHolder.time.setText(formattedTime + " \u00B7 " + filesize + bodyLanguageInfo);
} else {
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
viewHolder.time.setText(formattedTime + bodyLanguageInfo);
}
} else {
if ((filesize != null) && (info != null)) {
@ -305,7 +310,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else if ((filesize != null) && (info == null)) {
viewHolder.time.setText(filesize + " \u00B7 " + formattedTime + bodyLanguageInfo);
} else {
viewHolder.time.setText(formattedTime+bodyLanguageInfo);
viewHolder.time.setText(formattedTime + bodyLanguageInfo);
}
}
}
@ -491,7 +496,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
if (highlightedTerm != null) {
StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody));
}
MyLinkify.addLinks(body,true);
MyLinkify.addLinks(body, true);
viewHolder.messageBody.setAutoLinkMask(0);
viewHolder.messageBody.setText(EmojiWrapper.transform(body));
viewHolder.messageBody.setTextIsSelectable(true);
@ -622,6 +627,13 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
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);
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
break;
case SENT:
view = activity.getLayoutInflater().inflate(R.layout.message_sent, parent, false);
@ -684,6 +696,28 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
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);

View File

@ -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)) {

View File

@ -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<IceCandidate> 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

View File

@ -1,24 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingLeft="8dp"
android:paddingTop="5dp"
android:paddingRight="8dp"
android:paddingTop="5dp">
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/message_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/date_bubble_white"
android:id="@+id/message_box"
android:layout_centerHorizontal="true">
android:layout_centerHorizontal="true"
android:background="@drawable/date_bubble_white">
<TextView
android:id="@+id/message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
android:id="@+id/message_body" />
tools:text="Yesterday" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingTop="5dp"
android:paddingRight="8dp"
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/message_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="@drawable/date_bubble_white"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/indicator_received"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="4sp"
android:layout_marginLeft="0sp"
tools:alpha="0.57"
tools:src="@drawable/ic_call_received_black_18dp" />
<TextView
android:id="@+id/message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/incoming_call"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary" />
</LinearLayout>
</RelativeLayout>

View File

@ -903,6 +903,10 @@
<string name="hang_up">Hang up</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disable_tor_to_make_call">Disable Tor to make calls</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_duration">Incoming call · %s</string>
<string name="outgoing_call">Outgoing call</string>
<string name="outgoing_call_duration">Outgoing call · %s</string>
<plurals name="view_users">
<item quantity="one">View %1$d Participant</item>
<item quantity="other">View %1$d Participants</item>