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