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