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.android.AbstractPhoneContact;
 36import eu.siacs.conversations.android.JabberIdContact;
 37import eu.siacs.conversations.persistance.FileBackend;
 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)) {
179            return this.presenceName + (mutualPresenceSubscription() ? "" : " (" + jid + ")");
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));
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));
219        }
220        Presence.Status status = getShownStatus();
221        if (!showInRoster() && getSystemAccount() != null) {
222            tags.add(new Tag("Android"));
223        }
224        return new ArrayList<>(tags);
225    }
226
227    public boolean match(Context context, String needle) {
228        if (TextUtils.isEmpty(needle)) {
229            return true;
230        }
231        needle = needle.toLowerCase(Locale.US).trim();
232        String[] parts = needle.split("[,\\s]+");
233        if (parts.length > 1) {
234            for (String part : parts) {
235                if (!match(context, part)) {
236                    return false;
237                }
238            }
239            return true;
240        } else if(parts.length > 0) {
241            return jid.toString().contains(parts[0]) ||
242                    getDisplayName().toLowerCase(Locale.US).contains(parts[0]) ||
243                    matchInTag(context, parts[0]);
244        } else {
245            return jid.toString().contains(needle) ||
246                    getDisplayName().toLowerCase(Locale.US).contains(needle);
247        }
248    }
249
250    private boolean matchInTag(Context context, String needle) {
251        needle = needle.toLowerCase(Locale.US);
252        for (Tag tag : getTags(context)) {
253            if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
254                return true;
255            }
256        }
257        return false;
258    }
259
260    public ContentValues getContentValues() {
261        synchronized (this.keys) {
262            final ContentValues values = new ContentValues();
263            values.put(ACCOUNT, accountUuid);
264            values.put(SYSTEMNAME, systemName);
265            values.put(SERVERNAME, serverName);
266            values.put(PRESENCE_NAME, presenceName);
267            values.put(JID, jid.toString());
268            values.put(OPTIONS, subscription);
269            values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
270            values.put(PHOTOURI, photoUri);
271            values.put(KEYS, keys.toString());
272            values.put(AVATAR, avatar == null ? null : avatar.getFilename());
273            values.put(LAST_PRESENCE, mLastPresence);
274            values.put(LAST_TIME, mLastseen);
275            values.put(GROUPS, groups.toString());
276            values.put(RTP_CAPABILITY, rtpCapability == null ? null : rtpCapability.toString());
277            return values;
278        }
279    }
280
281    public Account getAccount() {
282        return this.account;
283    }
284
285    public void setAccount(Account account) {
286        this.account = account;
287        this.accountUuid = account.getUuid();
288    }
289
290    public Presences getPresences() {
291        return this.presences;
292    }
293
294    public void updatePresence(final String resource, final Presence presence) {
295        this.presences.updatePresence(resource, presence);
296    }
297
298    public void removePresence(final String resource) {
299        this.presences.removePresence(resource);
300        refreshCaps();
301    }
302
303    public void clearPresences() {
304        this.presences.clearPresences();
305        this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
306        refreshCaps();
307    }
308
309    public Presence.Status getShownStatus() {
310        return this.presences.getShownStatus();
311    }
312
313    public Jid resourceWhichSupport(final String namespace) {
314        final String resource = getPresences().firstWhichSupport(namespace);
315        if (resource == null) return null;
316
317        return resource.equals("") ? getJid() : getJid().withResource(resource);
318    }
319
320    public boolean setPhotoUri(String uri) {
321        if (uri != null && !uri.equals(this.photoUri)) {
322            this.photoUri = uri;
323            return true;
324        } else if (this.photoUri != null && uri == null) {
325            this.photoUri = null;
326            return true;
327        } else {
328            return false;
329        }
330    }
331
332    public void setServerName(String serverName) {
333        this.serverName = serverName;
334    }
335
336    public boolean setSystemName(String systemName) {
337        final String old = getDisplayName();
338        this.systemName = systemName;
339        return !old.equals(getDisplayName());
340    }
341
342    public boolean setSystemTags(Collection<String> systemTags) {
343        final JSONArray old = this.systemTags;
344        this.systemTags = new JSONArray();
345        for(String tag : systemTags) {
346            this.systemTags.put(tag);
347        }
348        return !old.equals(this.systemTags);
349    }
350
351    public boolean setPresenceName(String presenceName) {
352        final String old = getDisplayName();
353        this.presenceName = presenceName;
354        return !old.equals(getDisplayName());
355    }
356
357    public Uri getSystemAccount() {
358        return systemAccount;
359    }
360
361    public void setSystemAccount(Uri lookupUri) {
362        this.systemAccount = lookupUri;
363    }
364
365    public void setGroups(List<String> groups) {
366        this.groups = new JSONArray(groups);
367    }
368
369    private Collection<String> getGroups(final boolean unique) {
370        final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
371        for (int i = 0; i < this.groups.length(); ++i) {
372            try {
373                groups.add(this.groups.getString(i));
374            } catch (final JSONException ignored) {
375            }
376        }
377        return groups;
378    }
379
380    public void copySystemTagsToGroups() {
381        for (String tag : getSystemTags(true)) {
382            this.groups.put(tag);
383        }
384    }
385
386    private Collection<String> getSystemTags(final boolean unique) {
387        final Collection<String> tags = unique ? new HashSet<>() : new ArrayList<>();
388        for (int i = 0; i < this.systemTags.length(); ++i) {
389            try {
390                tags.add(this.systemTags.getString(i));
391            } catch (final JSONException ignored) {
392            }
393        }
394        return tags;
395    }
396
397    public long getPgpKeyId() {
398        synchronized (this.keys) {
399            if (this.keys.has("pgp_keyid")) {
400                try {
401                    return this.keys.getLong("pgp_keyid");
402                } catch (JSONException e) {
403                    return 0;
404                }
405            } else {
406                return 0;
407            }
408        }
409    }
410
411    public boolean setPgpKeyId(long keyId) {
412        final long previousKeyId = getPgpKeyId();
413        synchronized (this.keys) {
414            try {
415                this.keys.put("pgp_keyid", keyId);
416                return previousKeyId != keyId;
417            } catch (final JSONException ignored) {
418            }
419        }
420        return false;
421    }
422
423    public void setOption(int option) {
424        this.subscription |= 1 << option;
425    }
426
427    public void resetOption(int option) {
428        this.subscription &= ~(1 << option);
429    }
430
431    public boolean getOption(int option) {
432        return ((this.subscription & (1 << option)) != 0);
433    }
434
435    public boolean canInferPresence() {
436        return showInContactList() || isSelf();
437    }
438
439    public boolean showInRoster() {
440        return (this.getOption(Contact.Options.IN_ROSTER) && (!this
441                .getOption(Contact.Options.DIRTY_DELETE)))
442                || (this.getOption(Contact.Options.DIRTY_PUSH));
443    }
444
445    public boolean showInContactList() {
446        return showInRoster()
447                || getOption(Options.SYNCED_VIA_OTHER)
448                || systemAccount != null;
449    }
450
451    public void parseSubscriptionFromElement(Element item) {
452        String ask = item.getAttribute("ask");
453        String subscription = item.getAttribute("subscription");
454
455        if (subscription == null) {
456            this.resetOption(Options.FROM);
457            this.resetOption(Options.TO);
458        } else {
459            switch (subscription) {
460                case "to":
461                    this.resetOption(Options.FROM);
462                    this.setOption(Options.TO);
463                    break;
464                case "from":
465                    this.resetOption(Options.TO);
466                    this.setOption(Options.FROM);
467                    this.resetOption(Options.PREEMPTIVE_GRANT);
468                    this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
469                    break;
470                case "both":
471                    this.setOption(Options.TO);
472                    this.setOption(Options.FROM);
473                    this.resetOption(Options.PREEMPTIVE_GRANT);
474                    this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
475                    break;
476                case "none":
477                    this.resetOption(Options.FROM);
478                    this.resetOption(Options.TO);
479                    break;
480            }
481        }
482
483        // do NOT override asking if pending push request
484        if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
485            if ((ask != null) && (ask.equals("subscribe"))) {
486                this.setOption(Contact.Options.ASKING);
487            } else {
488                this.resetOption(Contact.Options.ASKING);
489            }
490        }
491    }
492
493    public void parseGroupsFromElement(Element item) {
494        this.groups = new JSONArray();
495        for (Element element : item.getChildren()) {
496            if (element.getName().equals("group") && element.getContent() != null) {
497                this.groups.put(element.getContent());
498            }
499        }
500    }
501
502    public Element asElement() {
503        final Element item = new Element("item");
504        item.setAttribute("jid", this.jid);
505        if (this.serverName != null) {
506            item.setAttribute("name", this.serverName);
507        } else {
508            item.setAttribute("name", getDisplayName());
509        }
510        for (String group : getGroups(false)) {
511            item.addChild("group").setContent(group);
512        }
513        return item;
514    }
515
516    @Override
517    public int compareTo(@NonNull final ListItem another) {
518        if (getJid().isDomainJid() && !another.getJid().isDomainJid()) {
519            return -1;
520        } else if (!getJid().isDomainJid() && another.getJid().isDomainJid()) {
521            return 1;
522        }
523
524        if (getDisplayName().equals(another.getDisplayName())) {
525            return getJid().compareTo(another.getJid());
526        }
527
528        final var anotherName = another.getDisplayName();
529        return this.getDisplayName().compareToIgnoreCase(anotherName == null ? "" : anotherName);
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_ADDRESS_BOOK) && !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(FileBackend.drawDrawable(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        try {
689            telecomManager.registerPhoneAccount(phoneAccount);
690        } catch (final Exception e) {
691            Log.w(Config.LOGTAG, "Could not registerPhoneAccount: " + e);
692        }
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_ADDRESS_BOOK;
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 void refreshCaps() {
742        account.refreshCapsFor(this);
743    }
744
745    public RtpCapability.Capability getRtpCapability() {
746        return this.rtpCapability == null ? RtpCapability.Capability.NONE : this.rtpCapability;
747    }
748
749    public static final class Options {
750        public static final int TO = 0;
751        public static final int FROM = 1;
752        public static final int ASKING = 2;
753        public static final int PREEMPTIVE_GRANT = 3;
754        public static final int IN_ROSTER = 4;
755        public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
756        public static final int DIRTY_PUSH = 6;
757        public static final int DIRTY_DELETE = 7;
758        private static final int SYNCED_VIA_ADDRESS_BOOK = 8;
759        public static final int SYNCED_VIA_OTHER = 9;
760    }
761}