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