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