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        }
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}