show call log messages in conversation stream
|
@ -57,6 +57,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
public static final int TYPE_STATUS = 3;
|
public static final int TYPE_STATUS = 3;
|
||||||
public static final int TYPE_PRIVATE = 4;
|
public static final int TYPE_PRIVATE = 4;
|
||||||
public static final int TYPE_PRIVATE_FILE = 5;
|
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 CONVERSATION = "conversationUuid";
|
||||||
public static final String COUNTERPART = "counterpart";
|
public static final String COUNTERPART = "counterpart";
|
||||||
|
@ -151,6 +152,31 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
null);
|
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,
|
protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
|
||||||
final Jid trueCounterpart, final String body, final long timeSent,
|
final Jid trueCounterpart, final String body, final long timeSent,
|
||||||
final int encryption, final int status, final int type, final boolean carbon,
|
final int encryption, final int status, final int type, final boolean carbon,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -299,6 +299,8 @@ public class UIHelper {
|
||||||
return new Pair<>(context.getString(R.string.omemo_decryption_failed), true);
|
return new Pair<>(context.getString(R.string.omemo_decryption_failed), true);
|
||||||
} else if (message.isFileOrImage()) {
|
} else if (message.isFileOrImage()) {
|
||||||
return new Pair<>(getFileDescriptionString(context, message), true);
|
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 {
|
} else {
|
||||||
final String body = MessageUtils.filterLtrRtl(message.getBody());
|
final String body = MessageUtils.filterLtrRtl(message.getBody());
|
||||||
if (body.startsWith(Message.ME_COMMAND)) {
|
if (body.startsWith(Message.ME_COMMAND)) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
@ -18,6 +19,10 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
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.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
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 WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this);
|
||||||
private final ArrayDeque<IceCandidate> pendingIceCandidates = new ArrayDeque<>();
|
private final ArrayDeque<IceCandidate> pendingIceCandidates = new ArrayDeque<>();
|
||||||
|
private final Message message;
|
||||||
private State state = State.NULL;
|
private State state = State.NULL;
|
||||||
private RtpContentMap initiatorRtpContentMap;
|
private RtpContentMap initiatorRtpContentMap;
|
||||||
private RtpContentMap responderRtpContentMap;
|
private RtpContentMap responderRtpContentMap;
|
||||||
|
private long rtpConnectionStarted = 0; //time of 'connected'
|
||||||
|
|
||||||
|
|
||||||
JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
|
JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
|
||||||
super(jingleConnectionManager, id, 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) {
|
private static State reasonToState(Reason reason) {
|
||||||
|
@ -153,7 +172,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
webRTCWrapper.close();
|
webRTCWrapper.close();
|
||||||
transitionOrThrow(reasonToState(wrapper.reason));
|
final State target = reasonToState(wrapper.reason);
|
||||||
|
transitionOrThrow(target);
|
||||||
|
writeLogMessage(target);
|
||||||
if (previous == State.PROPOSED || previous == State.SESSION_INITIALIZED) {
|
if (previous == State.PROPOSED || previous == State.SESSION_INITIALIZED) {
|
||||||
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
||||||
}
|
}
|
||||||
|
@ -455,7 +476,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
if (transition(State.RETRACTED)) {
|
if (transition(State.RETRACTED)) {
|
||||||
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": session with " + id.with + " has been retracted");
|
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);
|
jingleConnectionManager.finishConnection(this);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "ignoring retract because already in " + this.state);
|
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) {
|
private void sendSessionTerminate(final Reason reason, final String text) {
|
||||||
final State target = reasonToState(reason);
|
final State target = reasonToState(reason);
|
||||||
transitionOrThrow(target);
|
transitionOrThrow(target);
|
||||||
|
writeLogMessage(target);
|
||||||
final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
final JinglePacket jinglePacket = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
||||||
jinglePacket.setReason(reason, text);
|
jinglePacket.setReason(reason, text);
|
||||||
send(jinglePacket);
|
send(jinglePacket);
|
||||||
|
@ -773,9 +795,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
|
||||||
public void onConnectionChange(final PeerConnection.PeerConnectionState newState) {
|
public void onConnectionChange(final PeerConnection.PeerConnectionState newState) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState);
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnectionState changed to " + newState);
|
||||||
updateEndUserState();
|
updateEndUserState();
|
||||||
|
if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) {
|
||||||
|
this.rtpConnectionStarted = SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
if (newState == PeerConnection.PeerConnectionState.FAILED) {
|
if (newState == PeerConnection.PeerConnectionState.FAILED) {
|
||||||
if (TERMINATED.contains(this.state)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
|
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() {
|
public State getState() {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 169 B |
After Width: | Height: | Size: 132 B |
After Width: | Height: | Size: 135 B |
After Width: | Height: | Size: 141 B |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 136 B |
After Width: | Height: | Size: 147 B |
After Width: | Height: | Size: 133 B |
After Width: | Height: | Size: 140 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 215 B |
After Width: | Height: | Size: 175 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 235 B |
After Width: | Height: | Size: 235 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 267 B |
After Width: | Height: | Size: 257 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 291 B |
After Width: | Height: | Size: 214 B |
After Width: | Height: | Size: 257 B |
|
@ -1,24 +1,27 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="5dp"
|
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
android:paddingTop="5dp">
|
android:paddingBottom="5dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/message_box"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/date_bubble_white"
|
android:layout_centerHorizontal="true"
|
||||||
android:id="@+id/message_box"
|
android:background="@drawable/date_bubble_white">
|
||||||
android:layout_centerHorizontal="true">
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/message_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
|
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
|
||||||
android:id="@+id/message_body" />
|
tools:text="Yesterday" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -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>
|
|
@ -903,6 +903,10 @@
|
||||||
<string name="hang_up">Hang up</string>
|
<string name="hang_up">Hang up</string>
|
||||||
<string name="ongoing_call">Ongoing call</string>
|
<string name="ongoing_call">Ongoing call</string>
|
||||||
<string name="disable_tor_to_make_call">Disable Tor to make calls</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">
|
<plurals name="view_users">
|
||||||
<item quantity="one">View %1$d Participant</item>
|
<item quantity="one">View %1$d Participant</item>
|
||||||
<item quantity="other">View %1$d Participants</item>
|
<item quantity="other">View %1$d Participants</item>
|
||||||
|
|