Contact.java

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