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 refreshCaps();
302 }
303
304 public void clearPresences() {
305 this.presences.clearPresences();
306 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
307 refreshCaps();
308 }
309
310 public Presence.Status getShownStatus() {
311 return this.presences.getShownStatus();
312 }
313
314 public Jid resourceWhichSupport(final String namespace) {
315 final String resource = getPresences().firstWhichSupport(namespace);
316 if (resource == null) return null;
317
318 return resource.equals("") ? getJid() : getJid().withResource(resource);
319 }
320
321 public boolean setPhotoUri(String uri) {
322 if (uri != null && !uri.equals(this.photoUri)) {
323 this.photoUri = uri;
324 return true;
325 } else if (this.photoUri != null && uri == null) {
326 this.photoUri = null;
327 return true;
328 } else {
329 return false;
330 }
331 }
332
333 public void setServerName(String serverName) {
334 this.serverName = serverName;
335 }
336
337 public boolean setSystemName(String systemName) {
338 final String old = getDisplayName();
339 this.systemName = systemName;
340 return !old.equals(getDisplayName());
341 }
342
343 public boolean setSystemTags(Collection<String> systemTags) {
344 final JSONArray old = this.systemTags;
345 this.systemTags = new JSONArray();
346 for(String tag : systemTags) {
347 this.systemTags.put(tag);
348 }
349 return !old.equals(this.systemTags);
350 }
351
352 public boolean setPresenceName(String presenceName) {
353 final String old = getDisplayName();
354 this.presenceName = presenceName;
355 return !old.equals(getDisplayName());
356 }
357
358 public Uri getSystemAccount() {
359 return systemAccount;
360 }
361
362 public void setSystemAccount(Uri lookupUri) {
363 this.systemAccount = lookupUri;
364 }
365
366 public void setGroups(List<String> groups) {
367 this.groups = new JSONArray(groups);
368 }
369
370 private Collection<String> getGroups(final boolean unique) {
371 final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
372 for (int i = 0; i < this.groups.length(); ++i) {
373 try {
374 groups.add(this.groups.getString(i));
375 } catch (final JSONException ignored) {
376 }
377 }
378 return groups;
379 }
380
381 public void copySystemTagsToGroups() {
382 for (String tag : getSystemTags(true)) {
383 this.groups.put(tag);
384 }
385 }
386
387 private Collection<String> getSystemTags(final boolean unique) {
388 final Collection<String> tags = unique ? new HashSet<>() : new ArrayList<>();
389 for (int i = 0; i < this.systemTags.length(); ++i) {
390 try {
391 tags.add(this.systemTags.getString(i));
392 } catch (final JSONException ignored) {
393 }
394 }
395 return tags;
396 }
397
398 public long getPgpKeyId() {
399 synchronized (this.keys) {
400 if (this.keys.has("pgp_keyid")) {
401 try {
402 return this.keys.getLong("pgp_keyid");
403 } catch (JSONException e) {
404 return 0;
405 }
406 } else {
407 return 0;
408 }
409 }
410 }
411
412 public boolean setPgpKeyId(long keyId) {
413 final long previousKeyId = getPgpKeyId();
414 synchronized (this.keys) {
415 try {
416 this.keys.put("pgp_keyid", keyId);
417 return previousKeyId != keyId;
418 } catch (final JSONException ignored) {
419 }
420 }
421 return false;
422 }
423
424 public void setOption(int option) {
425 this.subscription |= 1 << option;
426 }
427
428 public void resetOption(int option) {
429 this.subscription &= ~(1 << option);
430 }
431
432 public boolean getOption(int option) {
433 return ((this.subscription & (1 << option)) != 0);
434 }
435
436 public boolean canInferPresence() {
437 return showInContactList() || isSelf();
438 }
439
440 public boolean showInRoster() {
441 return (this.getOption(Contact.Options.IN_ROSTER) && (!this
442 .getOption(Contact.Options.DIRTY_DELETE)))
443 || (this.getOption(Contact.Options.DIRTY_PUSH));
444 }
445
446 public boolean showInContactList() {
447 return showInRoster()
448 || getOption(Options.SYNCED_VIA_OTHER)
449 || systemAccount != null;
450 }
451
452 public void parseSubscriptionFromElement(Element item) {
453 String ask = item.getAttribute("ask");
454 String subscription = item.getAttribute("subscription");
455
456 if (subscription == null) {
457 this.resetOption(Options.FROM);
458 this.resetOption(Options.TO);
459 } else {
460 switch (subscription) {
461 case "to":
462 this.resetOption(Options.FROM);
463 this.setOption(Options.TO);
464 break;
465 case "from":
466 this.resetOption(Options.TO);
467 this.setOption(Options.FROM);
468 this.resetOption(Options.PREEMPTIVE_GRANT);
469 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
470 break;
471 case "both":
472 this.setOption(Options.TO);
473 this.setOption(Options.FROM);
474 this.resetOption(Options.PREEMPTIVE_GRANT);
475 this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
476 break;
477 case "none":
478 this.resetOption(Options.FROM);
479 this.resetOption(Options.TO);
480 break;
481 }
482 }
483
484 // do NOT override asking if pending push request
485 if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
486 if ((ask != null) && (ask.equals("subscribe"))) {
487 this.setOption(Contact.Options.ASKING);
488 } else {
489 this.resetOption(Contact.Options.ASKING);
490 }
491 }
492 }
493
494 public void parseGroupsFromElement(Element item) {
495 this.groups = new JSONArray();
496 for (Element element : item.getChildren()) {
497 if (element.getName().equals("group") && element.getContent() != null) {
498 this.groups.put(element.getContent());
499 }
500 }
501 }
502
503 public Element asElement() {
504 final Element item = new Element("item");
505 item.setAttribute("jid", this.jid);
506 if (this.serverName != null) {
507 item.setAttribute("name", this.serverName);
508 } else {
509 item.setAttribute("name", getDisplayName());
510 }
511 for (String group : getGroups(false)) {
512 item.addChild("group").setContent(group);
513 }
514 return item;
515 }
516
517 @Override
518 public int compareTo(@NonNull final ListItem another) {
519 if (getJid().isDomainJid() && !another.getJid().isDomainJid()) {
520 return -1;
521 } else if (!getJid().isDomainJid() && another.getJid().isDomainJid()) {
522 return 1;
523 }
524
525 if (getDisplayName().equals(another.getDisplayName())) {
526 return getJid().compareTo(another.getJid());
527 }
528
529 return this.getDisplayName().compareToIgnoreCase(
530 another.getDisplayName());
531 }
532
533 public String getServer() {
534 return getJid().getDomain().toEscapedString();
535 }
536
537 public void setAvatar(Avatar avatar) {
538 setAvatar(avatar, false);
539 }
540
541 public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
542 if (this.avatar != null && this.avatar.equals(avatar)) {
543 return;
544 }
545 if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
546 return;
547 }
548 this.avatar = avatar;
549 }
550
551 public String getAvatarFilename() {
552 return avatar == null ? null : avatar.getFilename();
553 }
554
555 public Avatar getAvatar() {
556 return avatar;
557 }
558
559 public boolean mutualPresenceSubscription() {
560 return getOption(Options.FROM) && getOption(Options.TO);
561 }
562
563 @Override
564 public boolean isBlocked() {
565 return getAccount().isBlocked(this);
566 }
567
568 @Override
569 public boolean isDomainBlocked() {
570 return getAccount().isBlocked(this.getJid().getDomain());
571 }
572
573 @Override
574 public Jid getBlockedJid() {
575 if (isDomainBlocked()) {
576 return getJid().getDomain();
577 } else {
578 return getJid();
579 }
580 }
581
582 public boolean isSelf() {
583 return account.getJid().asBareJid().equals(jid.asBareJid());
584 }
585
586 boolean isOwnServer() {
587 return account.getJid().getDomain().equals(jid.asBareJid());
588 }
589
590 public void setCommonName(String cn) {
591 this.commonName = cn;
592 }
593
594 public void flagActive() {
595 this.mActive = true;
596 }
597
598 public void flagInactive() {
599 this.mActive = false;
600 }
601
602 public boolean isActive() {
603 return this.mActive;
604 }
605
606 public boolean setLastseen(long timestamp) {
607 if (timestamp > this.mLastseen) {
608 this.mLastseen = timestamp;
609 return true;
610 } else {
611 return false;
612 }
613 }
614
615 public long getLastseen() {
616 return this.mLastseen;
617 }
618
619 public void setLastResource(String resource) {
620 this.mLastPresence = resource;
621 }
622
623 public String getLastResource() {
624 return this.mLastPresence;
625 }
626
627 public String getServerName() {
628 return serverName;
629 }
630
631 public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) {
632 setOption(getOption(phoneContact.getClass()));
633 setSystemAccount(phoneContact.getLookupUri());
634 boolean changed = setSystemName(phoneContact.getDisplayName());
635 changed |= setPhotoUri(phoneContact.getPhotoUri());
636 return changed;
637 }
638
639 public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
640 resetOption(getOption(clazz));
641 boolean changed = false;
642 if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
643 setSystemAccount(null);
644 changed |= setPhotoUri(null);
645 changed |= setSystemName(null);
646 }
647 return changed;
648 }
649
650 protected String phoneAccountLabel() {
651 return account.getJid().asBareJid().toString() +
652 "/" + getJid().asBareJid().toString();
653 }
654
655 public PhoneAccountHandle phoneAccountHandle() {
656 ComponentName componentName = new ComponentName(
657 BuildConfig.APPLICATION_ID,
658 "com.cheogram.android.ConnectionService"
659 );
660 return new PhoneAccountHandle(componentName, phoneAccountLabel());
661 }
662
663 // This Contact is a gateway to use for voice calls, register it with OS
664 public void registerAsPhoneAccount(XmppConnectionService ctx) {
665 if (Build.VERSION.SDK_INT < 23) return;
666 if (Build.VERSION.SDK_INT >= 33) {
667 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
668 } else {
669 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
670 }
671
672 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
673
674 PhoneAccount phoneAccount = PhoneAccount.builder(
675 phoneAccountHandle(),
676 account.getJid().asBareJid().toString()
677 ).setAddress(
678 Uri.fromParts("xmpp", account.getJid().asBareJid().toString(), null)
679 ).setIcon(
680 Icon.createWithBitmap(FileBackend.drawDrawable(ctx.getAvatarService().get(this, AvatarService.getSystemUiAvatarSize(ctx) / 2, false)))
681 ).setHighlightColor(
682 0x7401CF
683 ).setShortDescription(
684 getJid().asBareJid().toString()
685 ).setCapabilities(
686 PhoneAccount.CAPABILITY_CALL_PROVIDER
687 ).build();
688
689 try {
690 telecomManager.registerPhoneAccount(phoneAccount);
691 } catch (final Exception e) {
692 Log.w(Config.LOGTAG, "Could not registerPhoneAccount: " + e);
693 }
694 }
695
696 // Unregister any associated PSTN gateway integration
697 public void unregisterAsPhoneAccount(Context ctx) {
698 if (Build.VERSION.SDK_INT < 23) return;
699 if (Build.VERSION.SDK_INT >= 33) {
700 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
701 } else {
702 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
703 }
704
705 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
706
707 try {
708 telecomManager.unregisterPhoneAccount(phoneAccountHandle());
709 } catch (final SecurityException e) {
710 Log.w(Config.LOGTAG, "Could not unregister " + getJid() + " as phone account: " + e);
711 }
712 }
713
714 public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
715 if (clazz == JabberIdContact.class) {
716 return Options.SYNCED_VIA_ADDRESSBOOK;
717 } else {
718 return Options.SYNCED_VIA_OTHER;
719 }
720 }
721
722 @Override
723 public int getAvatarBackgroundColor() {
724 return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
725 }
726
727 @Override
728 public String getAvatarName() {
729 return getDisplayName();
730 }
731
732 public boolean hasAvatarOrPresenceName() {
733 return (avatar != null && avatar.getFilename() != null) || presenceName != null;
734 }
735
736 public boolean refreshRtpCapability() {
737 final RtpCapability.Capability previous = this.rtpCapability;
738 this.rtpCapability = RtpCapability.check(this, false);
739 return !Objects.equals(previous, this.rtpCapability);
740 }
741
742 public void refreshCaps() {
743 account.refreshCapsFor(this);
744 }
745
746 public RtpCapability.Capability getRtpCapability() {
747 return this.rtpCapability == null ? RtpCapability.Capability.NONE : this.rtpCapability;
748 }
749
750 public static final class Options {
751 public static final int TO = 0;
752 public static final int FROM = 1;
753 public static final int ASKING = 2;
754 public static final int PREEMPTIVE_GRANT = 3;
755 public static final int IN_ROSTER = 4;
756 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
757 public static final int DIRTY_PUSH = 6;
758 public static final int DIRTY_DELETE = 7;
759 private static final int SYNCED_VIA_ADDRESSBOOK = 8;
760 public static final int SYNCED_VIA_OTHER = 9;
761 }
762}