show preliminary call button if contact supports it

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java   |  4 
src/main/java/eu/siacs/conversations/xmpp/jingle/RtpCapability.java | 58 
src/main/res/drawable-hdpi/ic_call_white_24dp.png                   |  0 
src/main/res/drawable-mdpi/ic_call_white_24dp.png                   |  0 
src/main/res/drawable-xhdpi/ic_call_white_24dp.png                  |  0 
src/main/res/drawable-xxhdpi/ic_call_white_24dp.png                 |  0 
src/main/res/drawable-xxxhdpi/ic_call_white_24dp.png                |  0 
src/main/res/menu/fragment_conversation.xml                         | 76 
src/main/res/values/attrs.xml                                       |  1 
src/main/res/values/strings.xml                                     |  1 
src/main/res/values/themes.xml                                      |  2 
11 files changed, 107 insertions(+), 35 deletions(-)

Detailed changes

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

@@ -116,6 +116,7 @@ import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
+import eu.siacs.conversations.xmpp.jingle.RtpCapability;
 import rocks.xmpp.addr.Jid;
 
 import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT;
@@ -950,6 +951,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
         final MenuItem menuInviteContact = menu.findItem(R.id.action_invite);
         final MenuItem menuMute = menu.findItem(R.id.action_mute);
         final MenuItem menuUnmute = menu.findItem(R.id.action_unmute);
+        final MenuItem menuCall = menu.findItem(R.id.action_call);
 
 
         if (conversation != null) {
@@ -957,7 +959,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
                 menuContactDetails.setVisible(false);
                 menuInviteContact.setVisible(conversation.getMucOptions().canInvite());
                 menuMucDetails.setTitle(conversation.getMucOptions().isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
+                menuCall.setVisible(false);
             } else {
+                menuCall.setVisible(RtpCapability.check(conversation.getContact()) != RtpCapability.Capability.NONE);
                 menuContactDetails.setVisible(!this.conversation.withSelf());
                 menuMucDetails.setVisible(false);
                 final XmppConnectionService service = activity.xmppConnectionService;

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

@@ -0,0 +1,58 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.entities.ServiceDiscoveryResult;
+import eu.siacs.conversations.xml.Namespace;
+
+public class RtpCapability {
+
+    private static List<String> BASIC_RTP_REQUIREMENTS = Arrays.asList(
+            Namespace.JINGLE,
+            Namespace.JINGLE_TRANSPORT_ICE_UDP,
+            Namespace.JINGLE_APPS_RTP,
+            Namespace.JINGLE_APPS_DTLS
+    );
+    private static List<String> VIDEO_REQUIREMENTS = Arrays.asList(
+            Namespace.JINGLE_FEATURE_AUDIO,
+            Namespace.JINGLE_FEATURE_VIDEO
+    );
+
+    public static Capability check(final Presence presence) {
+        final ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult();
+        final List<String> features = disco == null ? Collections.emptyList() : disco.getFeatures();
+        if (features.containsAll(BASIC_RTP_REQUIREMENTS)) {
+            if (features.containsAll(VIDEO_REQUIREMENTS)) {
+                return Capability.VIDEO;
+            }
+            if (features.contains(Namespace.JINGLE_FEATURE_AUDIO)) {
+                return Capability.AUDIO;
+            }
+        }
+        return Capability.NONE;
+    }
+
+    public static Capability check(final Contact contact) {
+        final Presences presences = contact.getPresences();
+        Capability result = Capability.NONE;
+        for(Presence presence : presences.getPresences().values()) {
+            Capability capability = check(presence);
+            if (capability == Capability.VIDEO) {
+                result = capability;
+            } else if (capability == Capability.AUDIO && result == Capability.NONE) {
+                result = capability;
+            }
+        }
+        return result;
+    }
+
+    public enum Capability {
+        NONE, AUDIO, VIDEO
+    }
+
+}

src/main/res/menu/fragment_conversation.xml 🔗

@@ -1,23 +1,23 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto">
     <item
         android:id="@+id/action_security"
         android:icon="?attr/icon_not_secure"
         android:orderInCategory="20"
-        app:showAsAction="always"
-        android:title="@string/action_secure">
+        android:title="@string/action_secure"
+        app:showAsAction="always">
         <menu>
-            <group android:checkableBehavior="single" >
+            <group android:checkableBehavior="single">
                 <item
                     android:id="@+id/encryption_choice_none"
-                    android:title="@string/encryption_choice_unencrypted"/>
+                    android:title="@string/encryption_choice_unencrypted" />
                 <item
                     android:id="@+id/encryption_choice_axolotl"
-                    android:title="@string/encryption_choice_omemo"/>
+                    android:title="@string/encryption_choice_omemo" />
                 <item
                     android:id="@+id/encryption_choice_pgp"
-                    android:title="@string/encryption_choice_pgp"/>
+                    android:title="@string/encryption_choice_pgp" />
             </group>
         </menu>
     </item>
@@ -25,76 +25,82 @@
         android:id="@+id/action_attach_file"
         android:icon="?attr/icon_new_attachment"
         android:orderInCategory="30"
-        app:showAsAction="always"
-        android:title="@string/attach_file">
+        android:title="@string/attach_file"
+        app:showAsAction="always">
         <menu>
 
             <item
                 android:id="@+id/attach_choose_file"
-                android:title="@string/choose_file"
-                android:icon="?attr/ic_attach_document"/>
+                android:icon="?attr/ic_attach_document"
+                android:title="@string/choose_file" />
 
             <item
                 android:id="@+id/attach_choose_picture"
-                android:title="@string/attach_choose_picture"
-                android:icon="?attr/ic_attach_photo"/>
+                android:icon="?attr/ic_attach_photo"
+                android:title="@string/attach_choose_picture" />
 
             <item
                 android:id="@+id/attach_take_picture"
-                android:title="@string/attach_take_picture"
-                android:icon="?attr/ic_attach_camera"/>
+                android:icon="?attr/ic_attach_camera"
+                android:title="@string/attach_take_picture" />
 
             <item
                 android:id="@+id/attach_record_video"
-                android:title="@string/attach_record_video"
-                android:icon="?attr/ic_attach_videocam"/>
+                android:icon="?attr/ic_attach_videocam"
+                android:title="@string/attach_record_video" />
 
             <item
                 android:id="@+id/attach_record_voice"
-                android:title="@string/attach_record_voice"
-                android:icon="?attr/ic_attach_record"/>
+                android:icon="?attr/ic_attach_record"
+                android:title="@string/attach_record_voice" />
 
             <item
                 android:id="@+id/attach_location"
-                android:title="@string/send_location"
-                android:icon="?attr/ic_attach_location"/>
+                android:icon="?attr/ic_attach_location"
+                android:title="@string/send_location" />
         </menu>
     </item>
+    <item
+        android:id="@+id/action_call"
+        android:icon="?attr/icon_call"
+        android:orderInCategory="35"
+        android:title="@string/make_call"
+        app:showAsAction="always" />
     <item
         android:id="@+id/action_contact_details"
         android:orderInCategory="40"
-        app:showAsAction="never"
-        android:title="@string/action_contact_details"/>
+        android:title="@string/action_contact_details"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_muc_details"
         android:icon="?attr/icon_group"
         android:orderInCategory="40"
-        app:showAsAction="never"
-        android:title="@string/action_muc_details"/>
+        android:title="@string/action_muc_details"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_invite"
         android:orderInCategory="45"
-        app:showAsAction="never"
-        android:title="@string/invite_contact"/>
+        android:title="@string/invite_contact"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_clear_history"
         android:orderInCategory="50"
-        app:showAsAction="never"
-        android:title="@string/action_clear_history"/>
+        android:title="@string/action_clear_history"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_archive"
         android:orderInCategory="60"
-        app:showAsAction="never"
-        android:title="@string/action_end_conversation"/>
+        android:title="@string/action_end_conversation"
+        app:showAsAction="never" />
     <item
         android:id="@+id/action_mute"
         android:orderInCategory="70"
-        app:showAsAction="never"
-        android:title="@string/disable_notifications"/>
+        android:title="@string/disable_notifications"
+        app:showAsAction="never" />
 
     <item
         android:id="@+id/action_unmute"
         android:orderInCategory="71"
-        app:showAsAction="never"
-        android:title="@string/enable_notifications"/>
+        android:title="@string/enable_notifications"
+        app:showAsAction="never" />
 </menu>

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

@@ -85,6 +85,7 @@
     <attr name="icon_new" format="reference"/>
     <attr name="icon_new_attachment" format="reference"/>
     <attr name="icon_not_secure" format="reference"/>
+    <attr name="icon_call" format="reference"/>
     <attr name="icon_quote" format="reference"/>
     <attr name="icon_refresh" format="reference"/>
     <attr name="icon_remove" format="reference"/>

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

@@ -884,6 +884,7 @@
     <string name="backup">Backup</string>
     <string name="category_about">About</string>
     <string name="please_enable_an_account">Please enable an account</string>
+    <string name="make_call">Make call</string>
     <plurals name="view_users">
         <item quantity="one">View %1$d Participant</item>
         <item quantity="other">View %1$d Participants</item>

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

@@ -93,6 +93,7 @@
         <item type="reference" name="icon_refresh">@drawable/ic_refresh_black_24dp</item>
         <item type="reference" name="icon_new_attachment">@drawable/ic_attach_file_white_24dp</item>
         <item type="reference" name="icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
+        <item type="reference" name="icon_call">@drawable/ic_call_white_24dp</item>
         <item type="reference" name="icon_remove">@drawable/ic_delete_black_24dp</item>
         <item type="reference" name="icon_search">@drawable/ic_search_white_24dp</item>
         <item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>
@@ -207,6 +208,7 @@
         <item type="reference" name="icon_refresh">@drawable/ic_refresh_white_24dp</item>
         <item type="reference" name="icon_new_attachment">@drawable/ic_attach_file_white_24dp</item>
         <item type="reference" name="icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
+        <item type="reference" name="icon_call">@drawable/ic_call_white_24dp</item>
         <item type="reference" name="icon_remove">@drawable/ic_delete_white_24dp</item>
         <item type="reference" name="icon_search">@drawable/ic_search_white_24dp</item>
         <item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>