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