dummy Jingle activity

Daniel Gultsch created

Change summary

src/main/AndroidManifest.xml                                                   |  2 
src/main/java/eu/siacs/conversations/generator/MessageGenerator.java           |  5 
src/main/java/eu/siacs/conversations/parser/MessageParser.java                 | 29 
src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java                | 46 
src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java |  2 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java  | 27 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java      | 14 
src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java            | 68 
src/main/res/drawable-hdpi/ic_call_end_white_48dp.png                          |  0 
src/main/res/drawable-hdpi/ic_call_white_48dp.png                              |  0 
src/main/res/drawable-mdpi/ic_call_end_white_48dp.png                          |  0 
src/main/res/drawable-mdpi/ic_call_white_48dp.png                              |  0 
src/main/res/drawable-xhdpi/ic_call_end_white_48dp.png                         |  0 
src/main/res/drawable-xhdpi/ic_call_white_48dp.png                             |  0 
src/main/res/drawable-xxhdpi/ic_call_end_white_48dp.png                        |  0 
src/main/res/drawable-xxhdpi/ic_call_white_48dp.png                            |  0 
src/main/res/drawable-xxxhdpi/ic_call_end_white_48dp.png                       |  0 
src/main/res/drawable-xxxhdpi/ic_call_white_48dp.png                           |  0 
src/main/res/layout/activity_rtp_session.xml                                   | 75 
src/main/res/values/colors.xml                                                 |  1 
src/main/res/values/styles.xml                                                 |  7 
src/main/res/values/themes.xml                                                 |  1 
22 files changed, 263 insertions(+), 14 deletions(-)

Detailed changes

src/main/AndroidManifest.xml 🔗

@@ -10,6 +10,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@@ -286,6 +287,7 @@
         <activity
             android:name=".ui.ChannelDiscoveryActivity"
             android:label="@string/discover_channels" />
+        <activity android:name=".ui.RtpSessionActivity" />
     </application>
 
 </manifest>

src/main/java/eu/siacs/conversations/generator/MessageGenerator.java 🔗

@@ -19,6 +19,7 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
+import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
 
@@ -233,12 +234,14 @@ public class MessageGenerator extends AbstractGenerator {
 		return packet;
 	}
 
-	public MessagePacket sessionProposal(JingleConnectionManager.RtpSessionProposal proposal) {
+	public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
 		final MessagePacket packet = new MessagePacket();
 		packet.setTo(proposal.with);
+		packet.setId(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX+proposal.sessionId);
 		final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
 		propose.setAttribute("id", proposal.sessionId);
 		propose.addChild("description", Namespace.JINGLE_APPS_RTP);
+		packet.addChild("request", "urn:xmpp:receipts");
 		return packet;
 	}
 }

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -43,6 +43,8 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.InvalidJid;
 import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
+import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
+import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
@@ -301,11 +303,18 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 
     private boolean handleErrorMessage(Account account, MessagePacket packet) {
         if (packet.getType() == MessagePacket.TYPE_ERROR) {
-            Jid from = packet.getFrom();
-            if (from != null) {
+            final Jid from = packet.getFrom();
+            final String id = packet.getId();
+            if (from != null && id != null) {
+                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX)) {
+                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX.length());
+                    mXmppConnectionService.getJingleConnectionManager()
+                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
+                    return true;
+                }
                 mXmppConnectionService.markMessage(account,
                         from.asBareJid(),
-                        packet.getId(),
+                        id,
                         Message.STATUS_SEND_FAILED,
                         extractErrorMessage(packet));
                 final Element error = packet.findChild("error");
@@ -815,7 +824,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             if (!isTypeGroupChat) {
                 for (Element child : packet.getChildren()) {
                     if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
+                        if (!account.getJid().asBareJid().equals(from.asBareJid())) {
+                            processMessageReceipts(account, packet, query);
+                        }
                         mXmppConnectionService.getJingleConnectionManager().deliverMessage(account, packet.getTo(), packet.getFrom(), child);
+                        break;
                     }
                 }
             }
@@ -831,8 +844,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                 if (query != null && id != null && packet.getTo() != null) {
                     query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
                 }
-            } else {
-                mXmppConnectionService.markMessage(account, from.asBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
+            } else if (id != null) {
+                if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX)) {
+                    final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_ID_PREFIX.length());
+                    mXmppConnectionService.getJingleConnectionManager()
+                            .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
+                } else {
+                    mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
+                }
             }
         }
         Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java 🔗

@@ -0,0 +1,46 @@
+package eu.siacs.conversations.ui;
+
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityRtpSessionBinding;
+
+public class RtpSessionActivity extends XmppActivity {
+
+    public static final String EXTRA_WITH = "with";
+
+    private ActivityRtpSessionBinding binding;
+
+    public void onCreate(Bundle savedInstanceState) {
+        getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        super.onCreate(savedInstanceState);
+        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
+        this.binding.acceptCall.setOnClickListener(this::acceptCall);
+        this.binding.rejectCall.setOnClickListener(this::rejectCall);
+    }
+
+    private void rejectCall(View view) {
+
+    }
+
+    private void acceptCall(View view) {
+
+    }
+
+    @Override
+    protected void refreshUiReal() {
+
+    }
+
+    @Override
+    void onBackendConnected() {
+
+    }
+}

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -30,7 +30,7 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
 
 public class JingleConnectionManager extends AbstractConnectionManager {
-    private final Set<RtpSessionProposal> rtpSessionProposals = new HashSet<>();
+    private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
     private final Map<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
 
     private HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
@@ -108,7 +108,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             }
             final RtpSessionProposal proposal = new RtpSessionProposal(account, with.asBareJid(), sessionId);
             synchronized (rtpSessionProposals) {
-                if (rtpSessionProposals.remove(proposal)) {
+                if (rtpSessionProposals.remove(proposal) != null) {
                     final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
                     this.connections.put(id, rtpConnection);
                     rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
@@ -190,7 +190,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
     public void proposeJingleRtpSession(final Account account, final Contact contact) {
         final RtpSessionProposal proposal = RtpSessionProposal.of(account, contact.getJid().asBareJid());
         synchronized (this.rtpSessionProposals) {
-            this.rtpSessionProposals.add(proposal);
+            this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
             final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
             Log.d(Config.LOGTAG,messagePacket.toString());
             mXmppConnectionService.sendMessagePacket(account, messagePacket);
@@ -244,6 +244,23 @@ public class JingleConnectionManager extends AbstractConnectionManager {
         }
     }
 
+    public void updateProposedSessionDiscovered(Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
+        final RtpSessionProposal sessionProposal = new RtpSessionProposal(account,from.asBareJid(),sessionId);
+        synchronized (this.rtpSessionProposals) {
+            final DeviceDiscoveryState currentState = rtpSessionProposals.get(sessionProposal);
+            if (currentState == null) {
+                Log.d(Config.LOGTAG,"unable to find session proposal for session id "+sessionId);
+                return;
+            }
+            if (currentState == DeviceDiscoveryState.DISCOVERED) {
+                Log.d(Config.LOGTAG,"session proposal already at discovered. not going to fall back");
+                return;
+            }
+            this.rtpSessionProposals.put(sessionProposal, target);
+            Log.d(Config.LOGTAG,account.getJid().asBareJid()+": flagging session "+sessionId+" as "+target);
+        }
+    }
+
     public static class RtpSessionProposal {
         private final Account account;
         public final Jid with;
@@ -274,4 +291,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
             return Objects.hashCode(account.getJid(), with, sessionId);
         }
     }
+
+    public enum DeviceDiscoveryState {
+        SEARCHING, DISCOVERED, FAILED
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -1,5 +1,6 @@
 package eu.siacs.conversations.xmpp.jingle;
 
+import android.content.Intent;
 import android.util.Log;
 
 import com.google.common.collect.ImmutableList;
@@ -15,6 +16,7 @@ import java.util.List;
 import java.util.Map;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.ui.RtpSessionActivity;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
@@ -217,13 +219,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         if (originatedFromMyself) {
             Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring");
         } else if (transition(State.PROPOSED)) {
-            //TODO start ringing or something
-            pickUpCall();
+            startRinging();
         } else {
             Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring session proposal because already in " + state);
         }
     }
 
+    private void startRinging() {
+        Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received call from " + id.with + ". start ringing");
+        final Intent intent = new Intent(xmppConnectionService, RtpSessionActivity.class);
+        intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
+        intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        xmppConnectionService.startActivity(intent);
+    }
+
     private void receiveProceed(final Jid from, final Element proceed) {
         if (from.equals(id.with)) {
             if (isInitiator()) {

src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java 🔗

@@ -11,6 +11,9 @@ import com.google.common.util.concurrent.SettableFuture;
 
 import org.webrtc.AudioSource;
 import org.webrtc.AudioTrack;
+import org.webrtc.Camera1Capturer;
+import org.webrtc.Camera1Enumerator;
+import org.webrtc.CameraVideoCapturer;
 import org.webrtc.DataChannel;
 import org.webrtc.IceCandidate;
 import org.webrtc.MediaConstraints;
@@ -20,6 +23,9 @@ import org.webrtc.PeerConnectionFactory;
 import org.webrtc.RtpReceiver;
 import org.webrtc.SdpObserver;
 import org.webrtc.SessionDescription;
+import org.webrtc.VideoCapturer;
+import org.webrtc.VideoSource;
+import org.webrtc.VideoTrack;
 
 import java.util.List;
 
@@ -30,6 +36,9 @@ import eu.siacs.conversations.Config;
 
 public class WebRTCWrapper {
 
+    private VideoTrack localVideoTrack = null;
+    private VideoTrack remoteVideoTrack = null;
+
     private final EventCallback eventCallback;
 
     private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
@@ -75,6 +84,11 @@ public class WebRTCWrapper {
             for(AudioTrack audioTrack : mediaStream.audioTracks) {
                 Log.d(Config.LOGTAG,"remote? - audioTrack enabled:"+audioTrack.enabled()+" state="+audioTrack.state());
             }
+            final List<VideoTrack> videoTracks = mediaStream.videoTracks;
+            if (videoTracks.size() > 0) {
+                Log.d(Config.LOGTAG, "more than zero remote video tracks found. using first");
+                remoteVideoTrack = videoTracks.get(0);
+            }
         }
 
         @Override
@@ -112,15 +126,65 @@ public class WebRTCWrapper {
     }
 
     public void initializePeerConnection() {
-        final PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
         PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
 
+        CameraVideoCapturer capturer = null;
+        Camera1Enumerator camera1Enumerator = new Camera1Enumerator();
+        for(String deviceName : camera1Enumerator.getDeviceNames()) {
+            Log.d(Config.LOGTAG,"camera device name: "+deviceName);
+            if (camera1Enumerator.isFrontFacing(deviceName)) {
+                capturer = camera1Enumerator.createCapturer(deviceName, new CameraVideoCapturer.CameraEventsHandler() {
+                    @Override
+                    public void onCameraError(String s) {
+
+                    }
+
+                    @Override
+                    public void onCameraDisconnected() {
+
+                    }
+
+                    @Override
+                    public void onCameraFreezed(String s) {
+
+                    }
+
+                    @Override
+                    public void onCameraOpening(String s) {
+                        Log.d(Config.LOGTAG,"onCameraOpening");
+                    }
+
+                    @Override
+                    public void onFirstFrameAvailable() {
+                        Log.d(Config.LOGTAG,"onFirstFrameAvailable");
+                    }
+
+                    @Override
+                    public void onCameraClosed() {
+
+                    }
+                });
+            }
+        }
+
+        /*if (capturer != null) {
+            capturer.initialize();
+            Log.d(Config.LOGTAG,"start capturing");
+            capturer.startCapture(800,600,30);
+        }*/
+
+        final VideoSource videoSource = peerConnectionFactory.createVideoSource(false);
+        final VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("my-video-track", videoSource);
+
         final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
 
         final AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
         Log.d(Config.LOGTAG,"audioTrack enabled:"+audioTrack.enabled()+" state="+audioTrack.state());
         final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
         stream.addTrack(audioTrack);
+        //stream.addTrack(videoTrack);
+
+        this.localVideoTrack = videoTrack;
 
 
         final List<PeerConnection.IceServer> iceServers = ImmutableList.of(
@@ -136,6 +200,8 @@ public class WebRTCWrapper {
         this.peerConnection = peerConnection;
     }
 
+
+
     public ListenableFuture<SessionDescription> createOffer() {
         return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
             final SettableFuture<SessionDescription> future = SettableFuture.create();

src/main/res/layout/activity_rtp_session.xml 🔗

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:background="?colorPrimary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:paddingTop="32dp"
+            android:paddingBottom="32dp"
+            android:orientation="vertical"
+            android:gravity="center_vertical">
+            <TextView
+                android:id="@+id/status"
+                tools:text="Incoming call"
+                android:textColor="@color/white"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.Conversations.Title"/>
+            <TextView
+                android:id="@+id/with"
+                android:textColor="@color/white"
+                android:layout_below="@id/status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.Conversations.Display2"
+                tools:text="Juliet Capulet"/>
+
+        </LinearLayout>
+
+        <RelativeLayout
+            android:layout_width="288dp"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="24dp">
+
+            <android.support.design.widget.FloatingActionButton
+                android:id="@+id/reject_call"
+                android:layout_width="wrap_content"
+                android:layout_margin="16dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_alignParentLeft="true"
+                android:layout_centerVertical="true"
+                android:src="@drawable/ic_call_end_white_48dp"
+                app:backgroundTint="@color/red700"
+                app:elevation="4dp"
+                app:fabCustomSize="72dp"
+                app:maxImageSize="36dp" />
+
+            <android.support.design.widget.FloatingActionButton
+                android:id="@+id/accept_call"
+                android:layout_width="wrap_content"
+                android:layout_margin="16dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:src="@drawable/ic_call_white_48dp"
+                app:backgroundTint="@color/green700"
+                app:elevation="4dp"
+                app:fabCustomSize="72dp"
+                app:maxImageSize="36dp" />
+        </RelativeLayout>
+
+    </RelativeLayout>
+</layout>

src/main/res/values/colors.xml 🔗

@@ -19,6 +19,7 @@
 	<color name="grey800">#ff424242</color>
 	<color name="grey900">#ff282828</color>
 	<color name="red500">#fff44336</color>
+	<color name="red700">#ffD32F2F</color>
 	<color name="red_a700">#ffd50000</color>
 	<color name="red_a100">#ffff8a80</color>
 	<color name="red800">#ffc62828</color>

src/main/res/values/styles.xml 🔗

@@ -1,11 +1,14 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
 
-    <style name="TextAppearance.Conversations.Display2.Monospace" parent="TextAppearance.AppCompat.Display2">
-        <item name="android:textSize">?TextSizeDisplay2</item>
+    <style name="TextAppearance.Conversations.Display2.Monospace" parent="TextAppearance.Conversations.Display2">
         <item name="android:fontFamily" tools:targetApi="jelly_bean">monospace</item>
         <item name="android:typeface">monospace</item>
     </style>
 
+    <style name="TextAppearance.Conversations.Display2" parent="TextAppearance.AppCompat.Display2">
+        <item name="android:textSize">?TextSizeDisplay2</item>
+    </style>
+
     <style name="TextAppearance.Conversations.Title" parent="TextAppearance.AppCompat.Title">
         <item name="android:textSize">?TextSizeTitle</item>
     </style>

src/main/res/values/themes.xml 🔗

@@ -31,6 +31,7 @@
         <item name="TextSizeBody2">14sp</item>
         <item name="TextSizeSubhead">16sp</item>
         <item name="TextSizeTitle">20sp</item>
+        <item name="TextSizeDisplay2">45sp</item>
         <item name="TextSizeInput">16sp</item>
         <item name="TextSeparation">5sp</item>
         <item name="IconSize">18sp</item>