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 return this.getDisplayName().compareToIgnoreCase(
529 another.getDisplayName());
530 }
531
532 public String getServer() {
533 return getJid().getDomain().toEscapedString();
534 }
535
536 public void setAvatar(Avatar avatar) {
537 setAvatar(avatar, false);
538 }
539
540 public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
541 if (this.avatar != null && this.avatar.equals(avatar)) {
542 return;
543 }
544 if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
545 return;
546 }
547 this.avatar = avatar;
548 }
549
550 public String getAvatarFilename() {
551 return avatar == null ? null : avatar.getFilename();
552 }
553
554 public Avatar getAvatar() {
555 return avatar;
556 }
557
558 public boolean mutualPresenceSubscription() {
559 return getOption(Options.FROM) && getOption(Options.TO);
560 }
561
562 @Override
563 public boolean isBlocked() {
564 return getAccount().isBlocked(this);
565 }
566
567 @Override
568 public boolean isDomainBlocked() {
569 return getAccount().isBlocked(this.getJid().getDomain());
570 }
571
572 @Override
573 public Jid getBlockedJid() {
574 if (isDomainBlocked()) {
575 return getJid().getDomain();
576 } else {
577 return getJid();
578 }
579 }
580
581 public boolean isSelf() {
582 return account.getJid().asBareJid().equals(jid.asBareJid());
583 }
584
585 boolean isOwnServer() {
586 return account.getJid().getDomain().equals(jid.asBareJid());
587 }
588
589 public void setCommonName(String cn) {
590 this.commonName = cn;
591 }
592
593 public void flagActive() {
594 this.mActive = true;
595 }
596
597 public void flagInactive() {
598 this.mActive = false;
599 }
600
601 public boolean isActive() {
602 return this.mActive;
603 }
604
605 public boolean setLastseen(long timestamp) {
606 if (timestamp > this.mLastseen) {
607 this.mLastseen = timestamp;
608 return true;
609 } else {
610 return false;
611 }
612 }
613
614 public long getLastseen() {
615 return this.mLastseen;
616 }
617
618 public void setLastResource(String resource) {
619 this.mLastPresence = resource;
620 }
621
622 public String getLastResource() {
623 return this.mLastPresence;
624 }
625
626 public String getServerName() {
627 return serverName;
628 }
629
630 public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) {
631 setOption(getOption(phoneContact.getClass()));
632 setSystemAccount(phoneContact.getLookupUri());
633 boolean changed = setSystemName(phoneContact.getDisplayName());
634 changed |= setPhotoUri(phoneContact.getPhotoUri());
635 return changed;
636 }
637
638 public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
639 resetOption(getOption(clazz));
640 boolean changed = false;
641 if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
642 setSystemAccount(null);
643 changed |= setPhotoUri(null);
644 changed |= setSystemName(null);
645 }
646 return changed;
647 }
648
649 protected String phoneAccountLabel() {
650 return account.getJid().asBareJid().toString() +
651 "/" + getJid().asBareJid().toString();
652 }
653
654 public PhoneAccountHandle phoneAccountHandle() {
655 ComponentName componentName = new ComponentName(
656 BuildConfig.APPLICATION_ID,
657 "com.cheogram.android.ConnectionService"
658 );
659 return new PhoneAccountHandle(componentName, phoneAccountLabel());
660 }
661
662 // This Contact is a gateway to use for voice calls, register it with OS
663 public void registerAsPhoneAccount(XmppConnectionService ctx) {
664 if (Build.VERSION.SDK_INT < 23) return;
665 if (Build.VERSION.SDK_INT >= 33) {
666 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
667 } else {
668 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
669 }
670
671 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
672
673 PhoneAccount phoneAccount = PhoneAccount.builder(
674 phoneAccountHandle(),
675 account.getJid().asBareJid().toString()
676 ).setAddress(
677 Uri.fromParts("xmpp", account.getJid().asBareJid().toString(), null)
678 ).setIcon(
679 Icon.createWithBitmap(ctx.getAvatarService().get(this, AvatarService.getSystemUiAvatarSize(ctx) / 2, false))
680 ).setHighlightColor(
681 0x7401CF
682 ).setShortDescription(
683 getJid().asBareJid().toString()
684 ).setCapabilities(
685 PhoneAccount.CAPABILITY_CALL_PROVIDER
686 ).build();
687
688 telecomManager.registerPhoneAccount(phoneAccount);
689 }
690
691 // Unregister any associated PSTN gateway integration
692 public void unregisterAsPhoneAccount(Context ctx) {
693 if (Build.VERSION.SDK_INT < 23) return;
694 if (Build.VERSION.SDK_INT >= 33) {
695 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM) && !ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
696 } else {
697 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) return;
698 }
699
700 TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
701
702 try {
703 telecomManager.unregisterPhoneAccount(phoneAccountHandle());
704 } catch (final SecurityException e) {
705 Log.w(Config.LOGTAG, "Could not unregister " + getJid() + " as phone account: " + e);
706 }
707 }
708
709 public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
710 if (clazz == JabberIdContact.class) {
711 return Options.SYNCED_VIA_ADDRESSBOOK;
712 } else {
713 return Options.SYNCED_VIA_OTHER;
714 }
715 }
716
717 @Override
718 public int getAvatarBackgroundColor() {
719 return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
720 }
721
722 @Override
723 public String getAvatarName() {
724 return getDisplayName();
725 }
726
727 public boolean hasAvatarOrPresenceName() {
728 return (avatar != null && avatar.getFilename() != null) || presenceName != null;
729 }
730
731 public boolean refreshRtpCapability() {
732 final RtpCapability.Capability previous = this.rtpCapability;
733 this.rtpCapability = RtpCapability.check(this, false);
734 return !Objects.equals(previous, this.rtpCapability);
735 }
736
737 public RtpCapability.Capability getRtpCapability() {
738 return this.rtpCapability == null ? RtpCapability.Capability.NONE : this.rtpCapability;
739 }
740
741 public static final class Options {
742 public static final int TO = 0;
743 public static final int FROM = 1;
744 public static final int ASKING = 2;
745 public static final int PREEMPTIVE_GRANT = 3;
746 public static final int IN_ROSTER = 4;
747 public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
748 public static final int DIRTY_PUSH = 6;
749 public static final int DIRTY_DELETE = 7;
750 private static final int SYNCED_VIA_ADDRESSBOOK = 8;
751 public static final int SYNCED_VIA_OTHER = 9;
752 }
753}