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