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