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