1package eu.siacs.conversations.xmpp.jingle;
2
3import com.google.common.base.Strings;
4import com.google.common.collect.Collections2;
5import com.google.common.collect.ImmutableSet;
6import eu.siacs.conversations.entities.Contact;
7import eu.siacs.conversations.entities.Presences;
8import eu.siacs.conversations.xml.Namespace;
9import eu.siacs.conversations.xmpp.manager.DiscoManager;
10import im.conversations.android.xmpp.model.disco.info.InfoQuery;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.List;
16import java.util.Set;
17
18import eu.siacs.conversations.entities.Contact;
19import eu.siacs.conversations.entities.Presences;
20import eu.siacs.conversations.xml.Namespace;
21import eu.siacs.conversations.xmpp.Jid;
22
23public class RtpCapability {
24
25 private static final List<String> BASIC_RTP_REQUIREMENTS =
26 Arrays.asList(
27 Namespace.JINGLE,
28 Namespace.JINGLE_TRANSPORT_ICE_UDP,
29 Namespace.JINGLE_APPS_RTP,
30 Namespace.JINGLE_APPS_DTLS);
31 private static final Collection<String> VIDEO_REQUIREMENTS =
32 Arrays.asList(Namespace.JINGLE_FEATURE_AUDIO, Namespace.JINGLE_FEATURE_VIDEO);
33
34 public static Capability check(final InfoQuery infoQuery) {
35 final Set<String> features =
36 infoQuery == null
37 ? Collections.emptySet()
38 : ImmutableSet.copyOf(infoQuery.getFeatureStrings());
39 if (features.containsAll(BASIC_RTP_REQUIREMENTS)) {
40 if (features.containsAll(VIDEO_REQUIREMENTS)) {
41 return Capability.VIDEO;
42 }
43 if (features.contains(Namespace.JINGLE_FEATURE_AUDIO)) {
44 return Capability.AUDIO;
45 }
46 }
47 return Capability.NONE;
48 }
49
50 public static String[] filterPresences(final Contact contact, Capability required) {
51 final var connection = contact.getAccount().getXmppConnection();
52 if (connection == null) {
53 return new String[0];
54 }
55 final Presences presences = contact.getPresences();
56 final ArrayList<String> resources = new ArrayList<>();
57 for (final String resource : presences.getPresencesMap().keySet()) {
58 final var jid =
59 Strings.isNullOrEmpty(resource)
60 ? contact.getJid().asBareJid()
61 : contact.getJid().withResource(resource);
62 final Capability capability = check(connection.getManager(DiscoManager.class).get(jid));
63 if (capability == Capability.NONE) {
64 continue;
65 }
66 if (required == Capability.AUDIO || capability == required) {
67 resources.add(resource);
68 }
69 }
70 return resources.toArray(new String[0]);
71 }
72
73 public static Capability check(final Contact contact) {
74 return check(contact, true);
75 }
76
77 public static Capability check(final Contact contact, final boolean allowFallback) {
78 final Presences presences = contact.getPresences();
79
80 if (presences.isEmpty() && allowFallback && contact.getAccount().isEnabled()) {
81 Contact gateway = contact.getAccount().getRoster().getContact(Jid.of(contact.getJid().getDomain()));
82 if (gateway.showInRoster() && gateway.getPresences().anyIdentity("gateway", "pstn")) {
83 return Capability.AUDIO;
84 }
85
86 return contact.getRtpCapability();
87 }
88 final var connection = contact.getAccount().getXmppConnection();
89 if (connection == null) {
90 return Capability.NONE;
91 }
92 Capability result = Capability.NONE;
93 for (final String resource : presences.getPresencesMap().keySet()) {
94 final var jid =
95 Strings.isNullOrEmpty(resource)
96 ? contact.getJid().asBareJid()
97 : contact.getJid().withResource(resource);
98 final Capability capability = check(connection.getManager(DiscoManager.class).get(jid));
99 if (capability == Capability.VIDEO) {
100 result = capability;
101 } else if (capability == Capability.AUDIO && result == Capability.NONE) {
102 result = capability;
103 }
104 }
105 return result;
106 }
107
108 // do all devices that support Rtp Call also support JMI?
109 public static boolean jmiSupport(final Contact contact) {
110 final var connection = contact.getAccount().getXmppConnection();
111 if (connection == null) {
112 return false;
113 }
114 return !Collections2.transform(
115 Collections2.filter(
116 contact.getPresences().getPresencesMap().keySet(),
117 p ->
118 RtpCapability.check(
119 connection
120 .getManager(DiscoManager.class)
121 .get(
122 Strings.isNullOrEmpty(p)
123 ? contact.getJid()
124 .asBareJid()
125 : contact.getJid()
126 .withResource(
127 p)))
128 != Capability.NONE),
129 p -> {
130 final var disco =
131 connection
132 .getManager(DiscoManager.class)
133 .get(
134 Strings.isNullOrEmpty(p)
135 ? contact.getJid().asBareJid()
136 : contact.getJid().withResource(p));
137 return disco != null
138 && disco.getFeatureStrings().contains(Namespace.JINGLE_MESSAGE);
139 })
140 .contains(false);
141 }
142
143 public enum Capability {
144 NONE,
145 AUDIO,
146 VIDEO;
147
148 public static Capability of(String value) {
149 if (Strings.isNullOrEmpty(value)) {
150 return NONE;
151 }
152 try {
153 return valueOf(value);
154 } catch (IllegalArgumentException e) {
155 return NONE;
156 }
157 }
158 }
159}