1package eu.siacs.conversations.entities;
2
3import android.content.ComponentName;
4import android.content.ContentValues;
5import android.content.Context;
6import android.content.pm.PackageManager;
7import android.database.Cursor;
8import android.graphics.drawable.Icon;
9import android.net.Uri;
10import android.os.Build;
11import android.os.Bundle;
12import android.telecom.PhoneAccount;
13import android.telecom.PhoneAccountHandle;
14import android.telecom.TelecomManager;
15import android.text.TextUtils;
16import android.util.Log;
17
18import androidx.annotation.NonNull;
19
20import com.google.common.base.Strings;
21
22import org.json.JSONArray;
23import org.json.JSONException;
24import org.json.JSONObject;
25
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Locale;
31import java.util.Objects;
32
33import eu.siacs.conversations.BuildConfig;
34import eu.siacs.conversations.Config;
35import eu.siacs.conversations.R;
36import eu.siacs.conversations.android.AbstractPhoneContact;
37import eu.siacs.conversations.android.JabberIdContact;
38import eu.siacs.conversations.persistance.FileBackend;
39import eu.siacs.conversations.services.AvatarService;
40import eu.siacs.conversations.services.QuickConversationsService;
41import eu.siacs.conversations.services.XmppConnectionService;
42import eu.siacs.conversations.utils.JidHelper;
43import eu.siacs.conversations.utils.UIHelper;
44import eu.siacs.conversations.xml.Element;
45import eu.siacs.conversations.xmpp.Jid;
46import eu.siacs.conversations.xmpp.jingle.RtpCapability;
47import eu.siacs.conversations.xmpp.pep.Avatar;
48
49public class Contact implements ListItem, Blockable {
50 public static final String TABLENAME = "contacts";
51
52 public static final String SYSTEMNAME = "systemname";
53 public static final String SERVERNAME = "servername";
54 public static final String PRESENCE_NAME = "presence_name";
55 public static final String JID = "jid";
56 public static final String OPTIONS = "options";
57 public static final String SYSTEMACCOUNT = "systemaccount";
58 public static final String PHOTOURI = "photouri";
59 public static final String KEYS = "pgpkey";
60 public static final String ACCOUNT = "accountUuid";
61 public static final String AVATAR = "avatar";
62 public static final String LAST_PRESENCE = "last_presence";
63 public static final String LAST_TIME = "last_time";
64 public static final String GROUPS = "groups";
65 public static final String RTP_CAPABILITY = "rtpCapability";
66 private String accountUuid;
67 private String systemName;
68 private String serverName;
69 private String presenceName;
70 private String commonName;
71 protected Jid jid;
72 private int subscription = 0;
73 private Uri systemAccount;
74 private String photoUri;
75 private final JSONObject keys;
76 private JSONArray groups = new JSONArray();
77 private JSONArray systemTags = new JSONArray();
78 private final Presences presences = new Presences();
79 protected Account account;
80 protected Avatar avatar;
81
82 private boolean mActive = false;
83 private long mLastseen = 0;
84 private String mLastPresence = null;
85 private RtpCapability.Capability rtpCapability;
86
87 public Contact(Contact other) {
88 this(null, other.systemName, other.serverName, other.presenceName, other.jid, other.subscription, other.photoUri, other.systemAccount, other.keys == null ? null : other.keys.toString(), other.getAvatar() == null ? null : other.getAvatar().sha1sum, other.mLastseen, other.mLastPresence, other.groups == null ? null : other.groups.toString(), other.rtpCapability);
89 setAccount(other.getAccount());
90 }
91
92 public Contact(final String account, final String systemName, final String serverName, final String presenceName,
93 final Jid jid, final int subscription, final String photoUri,
94 final Uri systemAccount, final String keys, final String avatar, final long lastseen,
95 final String presence, final String groups, final RtpCapability.Capability rtpCapability) {
96 this.accountUuid = account;
97 this.systemName = systemName;
98 this.serverName = serverName;
99 this.presenceName = presenceName;
100 this.jid = jid;
101 this.subscription = subscription;
102 this.photoUri = photoUri;
103 this.systemAccount = systemAccount;
104 JSONObject tmpJsonObject;
105 try {
106 tmpJsonObject = (keys == null ? new JSONObject("") : new JSONObject(keys));
107 } catch (JSONException e) {
108 tmpJsonObject = new JSONObject();
109 }
110 this.keys = tmpJsonObject;
111 if (avatar != null) {
112 this.avatar = new Avatar();
113 this.avatar.sha1sum = avatar;
114 this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
115 }
116 try {
117 this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
118 } catch (JSONException e) {
119 this.groups = new JSONArray();
120 }
121 this.mLastseen = lastseen;
122 this.mLastPresence = presence;
123 this.rtpCapability = rtpCapability;
124 }
125
126 public Contact(final Jid jid) {
127 this.jid = jid;
128 this.keys = new JSONObject();
129 }
130
131 public static Contact fromCursor(final Cursor cursor) {
132 final Jid jid;
133 try {
134 jid = Jid.of(cursor.getString(cursor.getColumnIndex(JID)));
135 } catch (final IllegalArgumentException e) {
136 // TODO: Borked DB... handle this somehow?
137 return null;
138 }
139 Uri systemAccount;
140 try {
141 systemAccount = Uri.parse(cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)));
142 } catch (Exception e) {
143 systemAccount = null;
144 }
145 return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
146 cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
147 cursor.getString(cursor.getColumnIndex(SERVERNAME)),
148 cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)),
149 jid,
150 cursor.getInt(cursor.getColumnIndex(OPTIONS)),
151 cursor.getString(cursor.getColumnIndex(PHOTOURI)),
152 systemAccount,
153 cursor.getString(cursor.getColumnIndex(KEYS)),
154 cursor.getString(cursor.getColumnIndex(AVATAR)),
155 cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
156 cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
157 cursor.getString(cursor.getColumnIndex(GROUPS)),
158 RtpCapability.Capability.of(cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY))));
159 }
160
161 public String getDisplayName() {
162 if (isSelf() && TextUtils.isEmpty(this.systemName)) {
163 final String displayName = account.getDisplayName();
164 if (!Strings.isNullOrEmpty(displayName)) {
165 return displayName;
166 }
167 }
168 if (Config.X509_VERIFICATION && !TextUtils.isEmpty(this.commonName)) {
169 return this.commonName;
170 } else if (!TextUtils.isEmpty(this.systemName)) {
171 return this.systemName;
172 } else if (!TextUtils.isEmpty(this.serverName)) {
173 return this.serverName;
174 }
175
176 ListItem bookmark = account.getBookmark(jid);
177 if (bookmark != null) {
178 return bookmark.getDisplayName();
179 } else if (!TextUtils.isEmpty(this.presenceName)) {
180 return this.presenceName + (mutualPresenceSubscription() ? "" : " (" + jid + ")");
181 } else if (jid.getLocal() != null) {
182 return JidHelper.localPartOrFallback(jid);
183 } else {
184 return jid.getDomain().toEscapedString();
185 }
186 }
187
188 public String getPublicDisplayName() {
189 if (!TextUtils.isEmpty(this.presenceName)) {
190 return this.presenceName;
191 } else if (jid.getLocal() != null) {
192 return JidHelper.localPartOrFallback(jid);
193 } else {
194 return jid.getDomain().toEscapedString();
195 }
196 }
197
198 public String getProfilePhoto() {
199 return this.photoUri;
200 }
201
202 public Jid getJid() {
203 return jid;
204 }
205
206 public List<Tag> getGroupTags() {
207 final ArrayList<Tag> tags = new ArrayList<>();
208 for (final String group : getGroups(true)) {
209 tags.add(new Tag(group));
210 }
211 return tags;
212 }
213
214 @Override
215 public List<Tag> getTags(Context context) {
216 final HashSet<Tag> tags = new HashSet<>();
217 tags.addAll(getGroupTags());
218 for (final String tag : getSystemTags(true)) {
219 tags.add(new Tag(tag));
220 }
221 Presence.Status status = getShownStatus();
222 if (!showInRoster() && getSystemAccount() != null) {
223 tags.add(new Tag("Android"));
224 }
225 return new ArrayList<>(tags);
226 }
227
228 public boolean match(Context context, String needle) {
229 if (TextUtils.isEmpty(needle)) {
230 return true;
231 }
232 needle = needle.toLowerCase(Locale.US).trim();
233 String[] parts = needle.split("[,\\s]+");
234 if (parts.length > 1) {
235 for (String part : parts) {
236 if (!match(context, part)) {
237 return false;
238 }
239 }
240 return true;
241 } else if(parts.length > 0) {
242 return jid.toString().contains(parts[0]) ||
243 getDisplayName().toLowerCase(Locale.US).contains(parts[0]) ||
244 matchInTag(context, parts[0]);
245 } else {
246 return jid.toString().contains(needle) ||
247 getDisplayName().toLowerCase(Locale.US).contains(needle);
248 }
249 }
250
251 private boolean matchInTag(Context context, String needle) {
252 needle = needle.toLowerCase(Locale.US);
253 for (Tag tag : getTags(context)) {
254 if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
255 return true;
256 }
257 }
258 return false;
259 }
260
261 public ContentValues getContentValues() {
262 synchronized (this.keys) {
263 final ContentValues values = new ContentValues();
264 values.put(ACCOUNT, accountUuid);
265 values.put(SYSTEMNAME, systemName);
266 values.put(SERVERNAME, serverName);
267 values.put(PRESENCE_NAME, presenceName);
268 values.put(JID, jid.toString());
269 values.put(OPTIONS, subscription);
270 values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
271 values.put(PHOTOURI, photoUri);
272 values.put(KEYS, keys.toString());
273 values.put(AVATAR, avatar == null ? null : avatar.getFilename());
274 values.put(LAST_PRESENCE, mLastPresence);
275 values.put(LAST_TIME, mLastseen);
276 values.put(GROUPS, groups.toString());
277 values.put(RTP_CAPABILITY, rtpCapability == null ? null : rtpCapability.toString());
278 return values;
279 }
280 }
281
282 public Account getAccount() {
283 return this.account;
284 }
285
286 public void setAccount(Account account) {
287 this.account = account;
288 this.accountUuid = account.getUuid();
289 }
290
291 public Presences getPresences() {
292 return this.presences;
293 }
294
295 public void updatePresence(final String resource, final Presence presence) {
296 this.presences.updatePresence(resource, presence);
297 }
298
299 public void removePresence(final String resource) {
300 this.presences.removePresence(resource);
301 }
302
303 public void clearPresences() {
304 this.presences.clearPresences();
305 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
306 }
307
308 public Presence.Status getShownStatus() {
309 return this.presences.getShownStatus();
310 }
311
312 public Jid resourceWhichSupport(final String namespace) {
313 final String resource = getPresences().firstWhichSupport(namespace);
314 if (resource == null) return null;
315
316 return resource.equals("") ? getJid() : getJid().withResource(resource);
317 }
318
319 public boolean setPhotoUri(String uri) {
320 if (uri != null && !uri.equals(this.photoUri)) {
321 this.photoUri = uri;
322 return true;
323 } else if (this.photoUri != null && uri == null) {
324 this.photoUri = null;
325 return true;
326 } else {
327 return false;
328 }
329 }
330
331 public void setServerName(String serverName) {
332 this.serverName = serverName;
333 }
334
335 public boolean setSystemName(String systemName) {
336 final String old = getDisplayName();
337 this.systemName = systemName;
338 return !old.equals(getDisplayName());
339 }
340
341 public boolean setSystemTags(Collection<String> systemTags) {
342 final JSONArray old = this.systemTags;
343 this.systemTags = new JSONArray();
344 for(String tag : systemTags) {
345 this.systemTags.put(tag);
346 }
347 return !old.equals(this.systemTags);
348 }
349
350 public boolean setPresenceName(String presenceName) {
351 final String old = getDisplayName();
352 this.presenceName = presenceName;
353 return !old.equals(getDisplayName());
354 }
355
356 public Uri getSystemAccount() {
357 return systemAccount;
358 }
359
360 public void setSystemAccount(Uri lookupUri) {
361 this.systemAccount = lookupUri;
362 }
363
364 public void setGroups(List<String> groups) {
365 this.groups = new JSONArray(groups);
366 }
367
368 private Collection<String> getGroups(final boolean unique) {
369 final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
370 for (int i = 0; i < this.groups.length(); ++i) {
371 try {
372 groups.add(this.groups.getString(i));
373 } catch (final JSONException ignored) {
374 }
375 }
376 return groups;
377 }
378
379 public void copySystemTagsToGroups() {
380 for (String tag : getSystemTags(true)) {
381 this.groups.put(tag);
382 }
383 }
384
385 private Collection<String> getSystemTags(final boolean unique) {
386 final Collection<String> tags = unique ? new HashSet<>() : new ArrayList<>();
387 for (int i = 0; i < this.systemTags.length(); ++i) {
388 try {
389 tags.add(this.systemTags.getString(i));
390 } catch (final JSONException ignored) {
391 }
392 }
393 return tags;
394 }
395
396 public long getPgpKeyId() {
397 synchronized (this.keys) {
398 if (this.keys.has("pgp_keyid")) {
399 try {
400 return this.keys.getLong("pgp_keyid");
401 } catch (JSONException e) {
402 return 0;
403 }
404 } else {
405 return 0;
406 }
407 }
408 }
409
410 public boolean setPgpKeyId(long keyId) {
411 final long previousKeyId = getPgpKeyId();
412 synchronized (this.keys) {
413 try {
414 this.keys.put("pgp_keyid", keyId);
415 return previousKeyId != keyId;
416 } catch (final JSONException ignored) {
417 }
418 }
419 return false;
420 }
421
422 public void setOption(int option) {
423 this.subscription |= 1 << option;
424 }
425
426 public void resetOption(int option) {
427 this.subscription &= ~(1 << option);
428 }
429
430 public boolean getOption(int option) {
431 return ((this.subscription & (1 << option)) != 0);
432 }
433
434 public boolean canInferPresence() {
435 return showInContactList() || isSelf();
436 }
437
438 public boolean showInRoster() {
439 return (this.getOption(Contact.Options.IN_ROSTER) && (!this
440 .getOption(Contact.Options.DIRTY_DELETE)))
441 || (this.getOption(Contact.Options.DIRTY_PUSH));
442 }
443
444 public boolean showInContactList() {
445 return showInRoster()
446 || getOption(Options.SYNCED_VIA_OTHER)
447 || (QuickConversationsService.isQuicksy() && systemAccount != null);
448 }
449
450 public void parseSubscriptionFromElement(Element item) {
451 String ask = item.getAttribute("ask");
452 String subscription = item.getAttribute("subscription");
453
454 if (subscription == null) {
455 this.resetOption(Options.FROM);
456 this.resetOption(Options.TO);
457 } else {
458 switch (subscription) {
459 case "to":
460 this.resetOption(Options.FROM);
461 this.setOption(Options.TO);
462 break;
463 case "from":
464 this.resetOption(Options.TO);
465 this.setOption(Options.FROM);
466 this.resetOption(Options.PREEMPTIVE_GRANT);
467 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
468 break;
469 case "both":
470 this.setOption(Options.TO);
471 this.setOption(Options.FROM);
472 this.resetOption(Options.PREEMPTIVE_GRANT);
473 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
474 break;
475 case "none":
476 this.resetOption(Options.FROM);
477 this.resetOption(Options.TO);
478 break;
479 }
480 }
481
482 // do NOT override asking if pending push request
483 if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
484 if ((ask != null) && (ask.equals("subscribe"))) {
485 this.setOption(Contact.Options.ASKING);
486 } else {
487 this.resetOption(Contact.Options.ASKING);
488 }
489 }
490 }
491
492 public void parseGroupsFromElement(Element item) {
493 this.groups = new JSONArray();
494 for (Element element : item.getChildren()) {
495 if (element.getName().equals("group") && element.getContent() != null) {
496 this.groups.put(element.getContent());
497 }
498 }
499 }
500
501 public Element asElement() {
502 final Element item = new Element("item");
503 item.setAttribute("jid", this.jid);
504 if (this.serverName != null) {
505 item.setAttribute("name", this.serverName);
506 } else {
507 item.setAttribute("name", getDisplayName());
508 }
509 for (String group : getGroups(false)) {
510 item.addChild("group").setContent(group);
511 }
512 return item;
513 }
514
515 @Override
516 public int compareTo(@NonNull final ListItem another) {
517 if (getJid().isDomainJid() && !another.getJid().isDomainJid()) {
518 return -1;
519 } else if (!getJid().isDomainJid() && another.getJid().isDomainJid()) {
520 return 1;
521 }
522
523 if (getDisplayName().equals(another.getDisplayName())) {
524 return getJid().compareTo(another.getJid());
525 }
526
527 return this.getDisplayName().compareToIgnoreCase(
528 another.getDisplayName());
529 }
530
531 public String getServer() {
532 return getJid().getDomain().toEscapedString();
533 }
534
535 public void setAvatar(Avatar avatar) {
536 setAvatar(avatar, false);
537 }
538
539 public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
540 if (this.avatar != null && this.avatar.equals(avatar)) {
541 return;
542 }
543 if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
544 return;
545 }
546 this.avatar = avatar;
547 }
548
549 public String getAvatarFilename() {
550 return avatar == null ? null : avatar.getFilename();
551 }
552
553 public Avatar getAvatar() {
554 return avatar;
555 }
556
557 public boolean mutualPresenceSubscription() {
558 return getOption(Options.FROM) && getOption(Options.TO);
559 }
560
561 @Override
562 public boolean isBlocked() {
563 return getAccount().isBlocked(this);
564 }
565
566 @Override
567 public boolean isDomainBlocked() {
568 return getAccount().isBlocked(this.getJid().getDomain());
569 }
570
571 @Override
572 public Jid getBlockedJid() {
573 if (isDomainBlocked()) {
574 return getJid().getDomain();
575 } else {
576 return getJid();
577 }
578 }
579
580 public boolean isSelf() {
581 return account.getJid().asBareJid().equals(jid.asBareJid());
582 }
583
584 boolean isOwnServer() {
585 return account.getJid().getDomain().equals(jid.asBareJid());
586 }
587
588 public void setCommonName(String cn) {
589 this.commonName = cn;
590 }
591
592 public void flagActive() {
593 this.mActive = true;
594 }
595
596 public void flagInactive() {
597 this.mActive = false;
598 }
599
600 public boolean isActive() {
601 return this.mActive;
602 }
603
604 public boolean setLastseen(long timestamp) {
605 if (timestamp > this.mLastseen) {
606 this.mLastseen = timestamp;
607 return true;
608 } else {
609 return false;
610 }
611 }
612
613 public long getLastseen() {
614 return this.mLastseen;
615 }
616
617 public void setLastResource(String resource) {
618 this.mLastPresence = resource;
619 }
620
621 public String getLastResource() {
622 return this.mLastPresence;
623 }
624
625 public String getServerName() {
626 return serverName;
627 }
628
629 public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) {
630 setOption(getOption(phoneContact.getClass()));
631 setSystemAccount(phoneContact.getLookupUri());
632 boolean changed = setSystemName(phoneContact.getDisplayName());
633 changed |= setPhotoUri(phoneContact.getPhotoUri());
634 return changed;
635 }
636
637 public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
638 resetOption(getOption(clazz));
639 boolean changed = false;
640 if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
641 setSystemAccount(null);
642 changed |= setPhotoUri(null);
643 changed |= setSystemName(null);
644 }
645 return changed;
646 }
647
648 protected String phoneAccountLabel() {
649 return account.getJid().asBareJid().toString() +
650 "/" + getJid().asBareJid().toString();
651 }
652
653 public PhoneAccountHandle phoneAccountHandle() {
654 ComponentName componentName = new ComponentName(
655 BuildConfig.APPLICATION_ID,
656 "com.cheogram.android.ConnectionService"
657 );
658 return new PhoneAccountHandle(componentName, phoneAccountLabel());
659 }
660
661 // This Contact is a gateway to use for voice calls, register it with OS
662 public void registerAsPhoneAccount(XmppConnectionService ctx) {
663 if (Build.VERSION.SDK_INT < 23) return;
664 if (Build.VERSION.SDK_INT >= 33) {
665 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
666 } else {
667 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
668 }
669
670 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
671
672 PhoneAccount phoneAccount = PhoneAccount.builder(
673 phoneAccountHandle(),
674 account.getJid().asBareJid().toString()
675 ).setAddress(
676 Uri.fromParts("xmpp", account.getJid().asBareJid().toString(), null)
677 ).setIcon(
678 Icon.createWithBitmap(FileBackend.drawDrawable(ctx.getAvatarService().get(this, AvatarService.getSystemUiAvatarSize(ctx) / 2, false)))
679 ).setHighlightColor(
680 0x7401CF
681 ).setShortDescription(
682 getJid().asBareJid().toString()
683 ).setCapabilities(
684 PhoneAccount.CAPABILITY_CALL_PROVIDER
685 ).build();
686
687 try {
688 telecomManager.registerPhoneAccount(phoneAccount);
689 } catch (final Exception e) {
690 Log.w(Config.LOGTAG, "Could not registerPhoneAccount: " + e);
691 }
692 }
693
694 // Unregister any associated PSTN gateway integration
695 public void unregisterAsPhoneAccount(Context ctx) {
696 if (Build.VERSION.SDK_INT < 23) return;
697 if (Build.VERSION.SDK_INT >= 33) {
698 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
699 } else {
700 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
701 }
702
703 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
704
705 try {
706 telecomManager.unregisterPhoneAccount(phoneAccountHandle());
707 } catch (final SecurityException e) {
708 Log.w(Config.LOGTAG, "Could not unregister " + getJid() + " as phone account: " + e);
709 }
710 }
711
712 public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
713 if (clazz == JabberIdContact.class) {
714 return Options.SYNCED_VIA_ADDRESSBOOK;
715 } else {
716 return Options.SYNCED_VIA_OTHER;
717 }
718 }
719
720 @Override
721 public int getAvatarBackgroundColor() {
722 return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
723 }
724
725 @Override
726 public String getAvatarName() {
727 return getDisplayName();
728 }
729
730 public boolean hasAvatarOrPresenceName() {
731 return (avatar != null && avatar.getFilename() != null) || presenceName != null;
732 }
733
734 public boolean refreshRtpCapability() {
735 final RtpCapability.Capability previous = this.rtpCapability;
736 this.rtpCapability = RtpCapability.check(this, false);
737 return !Objects.equals(previous, this.rtpCapability);
738 }
739
740 public RtpCapability.Capability getRtpCapability() {
741 return this.rtpCapability == null ? RtpCapability.Capability.NONE : this.rtpCapability;
742 }
743
744 public static final class Options {
745 public static final int TO = 0;
746 public static final int FROM = 1;
747 public static final int ASKING = 2;
748 public static final int PREEMPTIVE_GRANT = 3;
749 public static final int IN_ROSTER = 4;
750 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
751 public static final int DIRTY_PUSH = 6;
752 public static final int DIRTY_DELETE = 7;
753 private static final int SYNCED_VIA_ADDRESSBOOK = 8;
754 public static final int SYNCED_VIA_OTHER = 9;
755 }
756}