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