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