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