Account.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5import android.net.Uri;
  6import android.os.SystemClock;
  7import android.util.Log;
  8
  9import androidx.core.graphics.ColorUtils;
 10
 11import com.google.common.base.Strings;
 12import com.google.common.collect.ImmutableList;
 13
 14import org.json.JSONException;
 15import org.json.JSONObject;
 16
 17import java.util.ArrayList;
 18import java.util.Collection;
 19import java.util.HashMap;
 20import java.util.HashSet;
 21import java.util.List;
 22import java.util.Map;
 23import java.util.Set;
 24import java.util.concurrent.CopyOnWriteArraySet;
 25
 26import eu.siacs.conversations.Config;
 27import eu.siacs.conversations.R;
 28import eu.siacs.conversations.crypto.PgpDecryptionService;
 29import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 30import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 31import eu.siacs.conversations.crypto.sasl.ChannelBinding;
 32import eu.siacs.conversations.crypto.sasl.ChannelBindingMechanism;
 33import eu.siacs.conversations.crypto.sasl.HashedToken;
 34import eu.siacs.conversations.crypto.sasl.HashedTokenSha256;
 35import eu.siacs.conversations.crypto.sasl.HashedTokenSha512;
 36import eu.siacs.conversations.crypto.sasl.SaslMechanism;
 37import eu.siacs.conversations.services.AvatarService;
 38import eu.siacs.conversations.services.XmppConnectionService;
 39import eu.siacs.conversations.utils.UIHelper;
 40import eu.siacs.conversations.utils.XmppUri;
 41import eu.siacs.conversations.xmpp.Jid;
 42import eu.siacs.conversations.xmpp.XmppConnection;
 43import eu.siacs.conversations.xmpp.jingle.RtpCapability;
 44
 45public class Account extends AbstractEntity implements AvatarService.Avatarable {
 46
 47    public static final String TABLENAME = "accounts";
 48
 49    public static final String USERNAME = "username";
 50    public static final String SERVER = "server";
 51    public static final String PASSWORD = "password";
 52    public static final String OPTIONS = "options";
 53    public static final String ROSTERVERSION = "rosterversion";
 54    public static final String KEYS = "keys";
 55    public static final String AVATAR = "avatar";
 56    public static final String DISPLAY_NAME = "display_name";
 57    public static final String HOSTNAME = "hostname";
 58    public static final String PORT = "port";
 59    public static final String STATUS = "status";
 60    public static final String STATUS_MESSAGE = "status_message";
 61    public static final String RESOURCE = "resource";
 62    public static final String PINNED_MECHANISM = "pinned_mechanism";
 63    public static final String PINNED_CHANNEL_BINDING = "pinned_channel_binding";
 64    public static final String FAST_MECHANISM = "fast_mechanism";
 65    public static final String FAST_TOKEN = "fast_token";
 66
 67    public static final int OPTION_DISABLED = 1;
 68    public static final int OPTION_REGISTER = 2;
 69    public static final int OPTION_MAGIC_CREATE = 4;
 70    public static final int OPTION_REQUIRES_ACCESS_MODE_CHANGE = 5;
 71    public static final int OPTION_LOGGED_IN_SUCCESSFULLY = 6;
 72    public static final int OPTION_HTTP_UPLOAD_AVAILABLE = 7;
 73    public static final int OPTION_UNVERIFIED = 8;
 74    public static final int OPTION_FIXED_USERNAME = 9;
 75    public static final int OPTION_QUICKSTART_AVAILABLE = 10;
 76    public static final int OPTION_SOFT_DISABLED = 11;
 77
 78    private static final String KEY_PGP_SIGNATURE = "pgp_signature";
 79    private static final String KEY_PGP_ID = "pgp_id";
 80    private static final String KEY_PINNED_MECHANISM = "pinned_mechanism";
 81    public static final String KEY_PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";
 82
 83    protected final JSONObject keys;
 84    private final Roster roster = new Roster(this);
 85    private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
 86    public final Set<Conversation> pendingConferenceJoins = new HashSet<>();
 87    public final Set<Conversation> pendingConferenceLeaves = new HashSet<>();
 88    public final Set<Conversation> inProgressConferenceJoins = new HashSet<>();
 89    public final Set<Conversation> inProgressConferencePings = new HashSet<>();
 90    protected Jid jid;
 91    protected String password;
 92    protected int options = 0;
 93    protected State status = State.OFFLINE;
 94    private State lastErrorStatus = State.OFFLINE;
 95    protected String resource;
 96    protected String avatar;
 97    protected String hostname = null;
 98    protected int port = 5222;
 99    protected boolean online = false;
100    private String rosterVersion;
101    private String displayName = null;
102    private AxolotlService axolotlService = null;
103    private PgpDecryptionService pgpDecryptionService = null;
104    private XmppConnection xmppConnection = null;
105    private long mEndGracePeriod = 0L;
106    private final Map<Jid, Bookmark> bookmarks = new HashMap<>();
107    private boolean bookmarksLoaded = false;
108    private Presence.Status presenceStatus;
109    private String presenceStatusMessage;
110    private String pinnedMechanism;
111    private String pinnedChannelBinding;
112    private String fastMechanism;
113    private String fastToken;
114    private Integer color = null;
115
116    public Account(final Jid jid, final String password) {
117        this(
118                java.util.UUID.randomUUID().toString(),
119                jid,
120                password,
121                0,
122                null,
123                "",
124                null,
125                null,
126                null,
127                5222,
128                Presence.Status.ONLINE,
129                null,
130                null,
131                null,
132                null,
133                null);
134    }
135
136    private Account(
137            final String uuid,
138            final Jid jid,
139            final String password,
140            final int options,
141            final String rosterVersion,
142            final String keys,
143            final String avatar,
144            String displayName,
145            String hostname,
146            int port,
147            final Presence.Status status,
148            String statusMessage,
149            final String pinnedMechanism,
150            final String pinnedChannelBinding,
151            final String fastMechanism,
152            final String fastToken) {
153        this.uuid = uuid;
154        this.jid = jid;
155        this.password = password;
156        this.options = options;
157        this.rosterVersion = rosterVersion;
158        JSONObject tmp;
159        try {
160            tmp = new JSONObject(keys);
161        } catch (JSONException e) {
162            tmp = new JSONObject();
163        }
164        this.keys = tmp;
165        this.avatar = avatar;
166        this.displayName = displayName;
167        this.hostname = hostname;
168        this.port = port;
169        this.presenceStatus = status;
170        this.presenceStatusMessage = statusMessage;
171        this.pinnedMechanism = pinnedMechanism;
172        this.pinnedChannelBinding = pinnedChannelBinding;
173        this.fastMechanism = fastMechanism;
174        this.fastToken = fastToken;
175    }
176
177    public static Account fromCursor(final Cursor cursor) {
178        final Jid jid;
179        try {
180            final String resource = cursor.getString(cursor.getColumnIndexOrThrow(RESOURCE));
181            jid =
182                    Jid.of(
183                            cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)),
184                            cursor.getString(cursor.getColumnIndexOrThrow(SERVER)),
185                            resource == null || resource.trim().isEmpty() ? null : resource);
186        } catch (final IllegalArgumentException e) {
187            Log.d(
188                    Config.LOGTAG,
189                    cursor.getString(cursor.getColumnIndexOrThrow(USERNAME))
190                            + "@"
191                            + cursor.getString(cursor.getColumnIndexOrThrow(SERVER)));
192            throw new AssertionError(e);
193        }
194        return new Account(
195                cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
196                jid,
197                cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD)),
198                cursor.getInt(cursor.getColumnIndexOrThrow(OPTIONS)),
199                cursor.getString(cursor.getColumnIndexOrThrow(ROSTERVERSION)),
200                cursor.getString(cursor.getColumnIndexOrThrow(KEYS)),
201                cursor.getString(cursor.getColumnIndexOrThrow(AVATAR)),
202                cursor.getString(cursor.getColumnIndexOrThrow(DISPLAY_NAME)),
203                cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)),
204                cursor.getInt(cursor.getColumnIndexOrThrow(PORT)),
205                Presence.Status.fromShowString(
206                        cursor.getString(cursor.getColumnIndexOrThrow(STATUS))),
207                cursor.getString(cursor.getColumnIndexOrThrow(STATUS_MESSAGE)),
208                cursor.getString(cursor.getColumnIndexOrThrow(PINNED_MECHANISM)),
209                cursor.getString(cursor.getColumnIndexOrThrow(PINNED_CHANNEL_BINDING)),
210                cursor.getString(cursor.getColumnIndexOrThrow(FAST_MECHANISM)),
211                cursor.getString(cursor.getColumnIndexOrThrow(FAST_TOKEN)));
212    }
213
214    public boolean httpUploadAvailable(long size) {
215        return xmppConnection != null && xmppConnection.getFeatures().httpUpload(size);
216    }
217
218    public boolean httpUploadAvailable() {
219        return isOptionSet(OPTION_HTTP_UPLOAD_AVAILABLE) || httpUploadAvailable(0);
220    }
221
222    public String getDisplayName() {
223        return displayName;
224    }
225
226    public void setDisplayName(String displayName) {
227        this.displayName = displayName;
228    }
229
230    public Contact getSelfContact() {
231        return getRoster().getContact(jid);
232    }
233
234    public boolean hasPendingPgpIntent(Conversation conversation) {
235        return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation);
236    }
237
238    public boolean isPgpDecryptionServiceConnected() {
239        return pgpDecryptionService != null && pgpDecryptionService.isConnected();
240    }
241
242    public void setColor(Integer color) {
243        this.color = color;
244    }
245
246    public int getColor(boolean dark) {
247        if (color != null) return color.intValue();
248
249        return ColorUtils.setAlphaComponent(
250            getAvatarBackgroundColor(),
251            dark ? 25 : 20
252        );
253    }
254
255    public Integer getColorToSave() {
256        return color;
257    }
258
259    public boolean setShowErrorNotification(boolean newValue) {
260        boolean oldValue = showErrorNotification();
261        setKey("show_error", Boolean.toString(newValue));
262        return newValue != oldValue;
263    }
264
265    public boolean showErrorNotification() {
266        String key = getKey("show_error");
267        return key == null || Boolean.parseBoolean(key);
268    }
269
270    public boolean isEnabled() {
271        return !isOptionSet(Account.OPTION_DISABLED);
272    }
273
274    public boolean isConnectionEnabled() {
275        return !isOptionSet(Account.OPTION_DISABLED) && !isOptionSet(Account.OPTION_SOFT_DISABLED);
276    }
277
278    public boolean isOptionSet(final int option) {
279        return ((options & (1 << option)) != 0);
280    }
281
282    public boolean setOption(final int option, final boolean value) {
283        if (value && (option == OPTION_DISABLED || option == OPTION_SOFT_DISABLED)) {
284            this.setStatus(State.OFFLINE);
285        }
286        final int before = this.options;
287        if (value) {
288            this.options |= 1 << option;
289        } else {
290            this.options &= ~(1 << option);
291        }
292        return before != this.options;
293    }
294
295    public String getUsername() {
296        return jid.getEscapedLocal();
297    }
298
299    public boolean setJid(final Jid next) {
300        final Jid previousFull = this.jid;
301        final Jid prev = this.jid != null ? this.jid.asBareJid() : null;
302        final boolean changed = prev == null || (next != null && !prev.equals(next.asBareJid()));
303        if (changed) {
304            final AxolotlService oldAxolotlService = this.axolotlService;
305            if (oldAxolotlService != null) {
306                oldAxolotlService.destroy();
307                this.jid = next;
308                this.axolotlService = oldAxolotlService.makeNew();
309            }
310        }
311        this.jid = next;
312        return next != null && !next.equals(previousFull);
313    }
314
315    public Jid getDomain() {
316        return jid.getDomain();
317    }
318
319    public String getServer() {
320        return jid.getDomain().toEscapedString();
321    }
322
323    public String getPassword() {
324        return password;
325    }
326
327    public void setPassword(final String password) {
328        this.password = password;
329    }
330
331    public String getHostname() {
332        return Strings.nullToEmpty(this.hostname);
333    }
334
335    public void setHostname(String hostname) {
336        this.hostname = hostname;
337    }
338
339    public boolean isOnion() {
340        final String server = getServer();
341        return server != null && server.endsWith(".onion");
342    }
343
344    public int getPort() {
345        return this.port;
346    }
347
348    public void setPort(int port) {
349        this.port = port;
350    }
351
352    public State getStatus() {
353        if (isOptionSet(OPTION_DISABLED)) {
354            return State.DISABLED;
355        } else if (isOptionSet(OPTION_SOFT_DISABLED)) {
356            return State.LOGGED_OUT;
357        } else {
358            return this.status;
359        }
360    }
361
362    public boolean unauthorized() {
363        return this.status == State.UNAUTHORIZED || this.lastErrorStatus == State.UNAUTHORIZED;
364    }
365
366    public State getLastErrorStatus() {
367        return this.lastErrorStatus;
368    }
369
370    public void setStatus(final State status) {
371        this.status = status;
372        if (status.isError || status == State.ONLINE) {
373            this.lastErrorStatus = status;
374        }
375    }
376
377    public void setPinnedMechanism(final SaslMechanism mechanism) {
378        this.pinnedMechanism = mechanism.getMechanism();
379        if (mechanism instanceof ChannelBindingMechanism) {
380            this.pinnedChannelBinding =
381                    ((ChannelBindingMechanism) mechanism).getChannelBinding().toString();
382        } else {
383            this.pinnedChannelBinding = null;
384        }
385    }
386
387    public void setFastToken(final HashedToken.Mechanism mechanism, final String token) {
388        this.fastMechanism = mechanism.name();
389        this.fastToken = token;
390    }
391
392    public void resetFastToken() {
393        this.fastMechanism = null;
394        this.fastToken = null;
395    }
396
397    public void resetPinnedMechanism() {
398        this.pinnedMechanism = null;
399        this.pinnedChannelBinding = null;
400        setKey(Account.KEY_PINNED_MECHANISM, String.valueOf(-1));
401    }
402
403    public int getPinnedMechanismPriority() {
404        final int fallback = getKeyAsInt(KEY_PINNED_MECHANISM, -1);
405        if (Strings.isNullOrEmpty(this.pinnedMechanism)) {
406            return fallback;
407        }
408        final SaslMechanism saslMechanism = getPinnedMechanism();
409        if (saslMechanism == null) {
410            return fallback;
411        } else {
412            return saslMechanism.getPriority();
413        }
414    }
415
416    private SaslMechanism getPinnedMechanism() {
417        final String mechanism = Strings.nullToEmpty(this.pinnedMechanism);
418        final ChannelBinding channelBinding = ChannelBinding.get(this.pinnedChannelBinding);
419        return new SaslMechanism.Factory(this).of(mechanism, channelBinding);
420    }
421
422    public HashedToken getFastMechanism() {
423        final HashedToken.Mechanism fastMechanism = HashedToken.Mechanism.ofOrNull(this.fastMechanism);
424        final String token = this.fastToken;
425        if (fastMechanism == null || Strings.isNullOrEmpty(token)) {
426            return null;
427        }
428        if (fastMechanism.hashFunction.equals("SHA-256")) {
429            return new HashedTokenSha256(this, fastMechanism.channelBinding);
430        } else if (fastMechanism.hashFunction.equals("SHA-512")) {
431            return new HashedTokenSha512(this, fastMechanism.channelBinding);
432        } else {
433            return null;
434        }
435    }
436
437    public SaslMechanism getQuickStartMechanism() {
438        final HashedToken hashedTokenMechanism = getFastMechanism();
439        if (hashedTokenMechanism != null) {
440            return hashedTokenMechanism;
441        }
442        return getPinnedMechanism();
443    }
444
445    public String getFastToken() {
446        return this.fastToken;
447    }
448
449    public State getTrueStatus() {
450        return this.status;
451    }
452
453    public boolean errorStatus() {
454        return getStatus().isError();
455    }
456
457    public boolean hasErrorStatus() {
458        return getXmppConnection() != null
459                && (getStatus().isError() || getStatus() == State.CONNECTING)
460                && getXmppConnection().getAttempt() >= 3;
461    }
462
463    public Presence.Status getPresenceStatus() {
464        return this.presenceStatus;
465    }
466
467    public void setPresenceStatus(Presence.Status status) {
468        this.presenceStatus = status;
469    }
470
471    public String getPresenceStatusMessage() {
472        return this.presenceStatusMessage;
473    }
474
475    public void setPresenceStatusMessage(String message) {
476        this.presenceStatusMessage = message;
477    }
478
479    public String getResource() {
480        return jid.getResource();
481    }
482
483    public void setResource(final String resource) {
484        this.jid = this.jid.withResource(resource);
485    }
486
487    public Jid getJid() {
488        return jid;
489    }
490
491    public JSONObject getKeys() {
492        return keys;
493    }
494
495    public String getKey(final String name) {
496        synchronized (this.keys) {
497            return this.keys.optString(name, null);
498        }
499    }
500
501    public int getKeyAsInt(final String name, int defaultValue) {
502        String key = getKey(name);
503        try {
504            return key == null ? defaultValue : Integer.parseInt(key);
505        } catch (NumberFormatException e) {
506            return defaultValue;
507        }
508    }
509
510    public boolean setKey(final String keyName, final String keyValue) {
511        synchronized (this.keys) {
512            try {
513                this.keys.put(keyName, keyValue);
514                return true;
515            } catch (final JSONException e) {
516                return false;
517            }
518        }
519    }
520
521    public void setPrivateKeyAlias(final String alias) {
522        setKey("private_key_alias", alias);
523    }
524
525    public String getPrivateKeyAlias() {
526        return getKey("private_key_alias");
527    }
528
529    @Override
530    public ContentValues getContentValues() {
531        final ContentValues values = new ContentValues();
532        values.put(UUID, uuid);
533        values.put(USERNAME, jid.getLocal());
534        values.put(SERVER, jid.getDomain().toEscapedString());
535        values.put(PASSWORD, password);
536        values.put(OPTIONS, options);
537        synchronized (this.keys) {
538            values.put(KEYS, this.keys.toString());
539        }
540        values.put(ROSTERVERSION, rosterVersion);
541        values.put(AVATAR, avatar);
542        values.put(DISPLAY_NAME, displayName);
543        values.put(HOSTNAME, hostname);
544        values.put(PORT, port);
545        values.put(STATUS, presenceStatus.toShowString());
546        values.put(STATUS_MESSAGE, presenceStatusMessage);
547        values.put(RESOURCE, jid.getResource());
548        values.put(PINNED_MECHANISM, pinnedMechanism);
549        values.put(PINNED_CHANNEL_BINDING, pinnedChannelBinding);
550        values.put(FAST_MECHANISM, this.fastMechanism);
551        values.put(FAST_TOKEN, this.fastToken);
552        return values;
553    }
554
555    public AxolotlService getAxolotlService() {
556        return axolotlService;
557    }
558
559    public void initAccountServices(final XmppConnectionService context) {
560        this.axolotlService = new AxolotlService(this, context);
561        this.pgpDecryptionService = new PgpDecryptionService(context);
562        if (xmppConnection != null) {
563            xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
564        }
565    }
566
567    public PgpDecryptionService getPgpDecryptionService() {
568        return this.pgpDecryptionService;
569    }
570
571    public XmppConnection getXmppConnection() {
572        return this.xmppConnection;
573    }
574
575    public void setXmppConnection(final XmppConnection connection) {
576        this.xmppConnection = connection;
577    }
578
579    public String getRosterVersion() {
580        if (this.rosterVersion == null) {
581            return "";
582        } else {
583            return this.rosterVersion;
584        }
585    }
586
587    public void setRosterVersion(final String version) {
588        this.rosterVersion = version;
589    }
590
591    public int countPresences() {
592        return this.getSelfContact().getPresences().size();
593    }
594
595    public int activeDevicesWithRtpCapability() {
596        int i = 0;
597        for (Presence presence : getSelfContact().getPresences().getPresences()) {
598            if (RtpCapability.check(presence) != RtpCapability.Capability.NONE) {
599                i++;
600            }
601        }
602        return i;
603    }
604
605    public String getPgpSignature() {
606        return getKey(KEY_PGP_SIGNATURE);
607    }
608
609    public boolean setPgpSignature(String signature) {
610        return setKey(KEY_PGP_SIGNATURE, signature);
611    }
612
613    public boolean unsetPgpSignature() {
614        synchronized (this.keys) {
615            return keys.remove(KEY_PGP_SIGNATURE) != null;
616        }
617    }
618
619    public long getPgpId() {
620        synchronized (this.keys) {
621            if (keys.has(KEY_PGP_ID)) {
622                try {
623                    return keys.getLong(KEY_PGP_ID);
624                } catch (JSONException e) {
625                    return 0;
626                }
627            } else {
628                return 0;
629            }
630        }
631    }
632
633    public boolean setPgpSignId(long pgpID) {
634        synchronized (this.keys) {
635            try {
636                if (pgpID == 0) {
637                    keys.remove(KEY_PGP_ID);
638                } else {
639                    keys.put(KEY_PGP_ID, pgpID);
640                }
641            } catch (JSONException e) {
642                return false;
643            }
644            return true;
645        }
646    }
647
648    public Roster getRoster() {
649        return this.roster;
650    }
651
652    public Collection<Bookmark> getBookmarks() {
653        synchronized (this.bookmarks) {
654            return ImmutableList.copyOf(this.bookmarks.values());
655        }
656    }
657
658    public boolean areBookmarksLoaded() {
659        // No way to tell if old PEP bookmarks are all loaded yet if they are empty
660        // because we don't manually fetch them...
661        if (getXmppConnection().getFeatures().bookmarksConversion()) return true;
662
663        return bookmarksLoaded;
664    }
665
666    public void setBookmarks(final Map<Jid, Bookmark> bookmarks) {
667        synchronized (this.bookmarks) {
668            this.bookmarks.clear();
669            this.bookmarks.putAll(bookmarks);
670            this.bookmarksLoaded = true;
671        }
672    }
673
674    public void putBookmark(final Bookmark bookmark) {
675        synchronized (this.bookmarks) {
676            this.bookmarks.put(bookmark.getJid(), bookmark);
677        }
678    }
679
680    public void removeBookmark(Bookmark bookmark) {
681        synchronized (this.bookmarks) {
682            this.bookmarks.remove(bookmark.getJid());
683        }
684    }
685
686    public void removeBookmark(Jid jid) {
687        synchronized (this.bookmarks) {
688            this.bookmarks.remove(jid);
689        }
690    }
691
692    public Set<Jid> getBookmarkedJids() {
693        synchronized (this.bookmarks) {
694            return new HashSet<>(this.bookmarks.keySet());
695        }
696    }
697
698    public Bookmark getBookmark(final Jid jid) {
699        synchronized (this.bookmarks) {
700            return this.bookmarks.get(jid.asBareJid());
701        }
702    }
703
704    public boolean setAvatar(final String filename) {
705        if (this.avatar != null && this.avatar.equals(filename)) {
706            return false;
707        } else {
708            this.avatar = filename;
709            return true;
710        }
711    }
712
713    public String getAvatar() {
714        return this.avatar;
715    }
716
717    public void activateGracePeriod(final long duration) {
718        if (duration > 0) {
719            this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
720        }
721    }
722
723    public void deactivateGracePeriod() {
724        this.mEndGracePeriod = 0L;
725    }
726
727    public boolean inGracePeriod() {
728        return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
729    }
730
731    public String getShareableUri() {
732        List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
733        String uri = "xmpp:" + Uri.encode(getJid().asBareJid().toEscapedString(), "@/+");
734        if (fingerprints.size() > 0) {
735            return XmppUri.getFingerprintUri(uri, fingerprints, ';');
736        } else {
737            return uri;
738        }
739    }
740
741    public String getShareableLink() {
742        List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
743        String uri =
744                "https://conversations.im/i/"
745                        + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString());
746        if (fingerprints.size() > 0) {
747            return XmppUri.getFingerprintUri(uri, fingerprints, '&');
748        } else {
749            return uri;
750        }
751    }
752
753    private List<XmppUri.Fingerprint> getFingerprints() {
754        ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
755        if (axolotlService == null) {
756            return fingerprints;
757        }
758        fingerprints.add(
759                new XmppUri.Fingerprint(
760                        XmppUri.FingerprintType.OMEMO,
761                        axolotlService.getOwnFingerprint().substring(2),
762                        axolotlService.getOwnDeviceId()));
763        for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
764            if (session.getTrust().isVerified() && session.getTrust().isActive()) {
765                fingerprints.add(
766                        new XmppUri.Fingerprint(
767                                XmppUri.FingerprintType.OMEMO,
768                                session.getFingerprint().substring(2).replaceAll("\\s", ""),
769                                session.getRemoteAddress().getDeviceId()));
770            }
771        }
772        return fingerprints;
773    }
774
775    public boolean isBlocked(final ListItem contact) {
776        final Jid jid = contact.getJid();
777        return jid != null
778                && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain()));
779    }
780
781    public boolean isBlocked(final Jid jid) {
782        return jid != null && blocklist.contains(jid.asBareJid());
783    }
784
785    public Collection<Jid> getBlocklist() {
786        return this.blocklist;
787    }
788
789    public void clearBlocklist() {
790        getBlocklist().clear();
791    }
792
793    public boolean isOnlineAndConnected() {
794        return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
795    }
796
797    @Override
798    public int getAvatarBackgroundColor() {
799        return UIHelper.getColorForName(jid.asBareJid().toString());
800    }
801
802    @Override
803    public String getAvatarName() {
804        throw new IllegalStateException("This method should not be called");
805    }
806
807    public enum State {
808        DISABLED(false, false),
809        LOGGED_OUT(false,false),
810        OFFLINE(false),
811        CONNECTING(false),
812        ONLINE(false),
813        NO_INTERNET(false),
814        UNAUTHORIZED,
815        TEMPORARY_AUTH_FAILURE,
816        SERVER_NOT_FOUND,
817        REGISTRATION_SUCCESSFUL(false),
818        REGISTRATION_FAILED(true, false),
819        REGISTRATION_WEB(true, false),
820        REGISTRATION_CONFLICT(true, false),
821        REGISTRATION_NOT_SUPPORTED(true, false),
822        REGISTRATION_PLEASE_WAIT(true, false),
823        REGISTRATION_INVALID_TOKEN(true, false),
824        REGISTRATION_PASSWORD_TOO_WEAK(true, false),
825        TLS_ERROR,
826        TLS_ERROR_DOMAIN,
827        INCOMPATIBLE_SERVER,
828        INCOMPATIBLE_CLIENT,
829        TOR_NOT_AVAILABLE,
830        DOWNGRADE_ATTACK,
831        SESSION_FAILURE,
832        BIND_FAILURE,
833        HOST_UNKNOWN,
834        STREAM_ERROR,
835        SEE_OTHER_HOST,
836        STREAM_OPENING_ERROR,
837        POLICY_VIOLATION,
838        PAYMENT_REQUIRED,
839        MISSING_INTERNET_PERMISSION(false);
840
841        private final boolean isError;
842        private final boolean attemptReconnect;
843
844        State(final boolean isError) {
845            this(isError, true);
846        }
847
848        State(final boolean isError, final boolean reconnect) {
849            this.isError = isError;
850            this.attemptReconnect = reconnect;
851        }
852
853        State() {
854            this(true, true);
855        }
856
857        public boolean isError() {
858            return this.isError;
859        }
860
861        public boolean isAttemptReconnect() {
862            return this.attemptReconnect;
863        }
864
865        public int getReadableId() {
866            switch (this) {
867                case DISABLED:
868                    return R.string.account_status_disabled;
869                case LOGGED_OUT:
870                    return R.string.account_state_logged_out;
871                case ONLINE:
872                    return R.string.account_status_online;
873                case CONNECTING:
874                    return R.string.account_status_connecting;
875                case OFFLINE:
876                    return R.string.account_status_offline;
877                case UNAUTHORIZED:
878                    return R.string.account_status_unauthorized;
879                case SERVER_NOT_FOUND:
880                    return R.string.account_status_not_found;
881                case NO_INTERNET:
882                    return R.string.account_status_no_internet;
883                case REGISTRATION_FAILED:
884                    return R.string.account_status_regis_fail;
885                case REGISTRATION_WEB:
886                    return R.string.account_status_regis_web;
887                case REGISTRATION_CONFLICT:
888                    return R.string.account_status_regis_conflict;
889                case REGISTRATION_SUCCESSFUL:
890                    return R.string.account_status_regis_success;
891                case REGISTRATION_NOT_SUPPORTED:
892                    return R.string.account_status_regis_not_sup;
893                case REGISTRATION_INVALID_TOKEN:
894                    return R.string.account_status_regis_invalid_token;
895                case TLS_ERROR:
896                    return R.string.account_status_tls_error;
897                case TLS_ERROR_DOMAIN:
898                    return R.string.account_status_tls_error_domain;
899                case INCOMPATIBLE_SERVER:
900                    return R.string.account_status_incompatible_server;
901                case INCOMPATIBLE_CLIENT:
902                    return R.string.account_status_incompatible_client;
903                case TOR_NOT_AVAILABLE:
904                    return R.string.account_status_tor_unavailable;
905                case BIND_FAILURE:
906                    return R.string.account_status_bind_failure;
907                case SESSION_FAILURE:
908                    return R.string.session_failure;
909                case DOWNGRADE_ATTACK:
910                    return R.string.sasl_downgrade;
911                case HOST_UNKNOWN:
912                    return R.string.account_status_host_unknown;
913                case POLICY_VIOLATION:
914                    return R.string.account_status_policy_violation;
915                case REGISTRATION_PLEASE_WAIT:
916                    return R.string.registration_please_wait;
917                case REGISTRATION_PASSWORD_TOO_WEAK:
918                    return R.string.registration_password_too_weak;
919                case STREAM_ERROR:
920                    return R.string.account_status_stream_error;
921                case STREAM_OPENING_ERROR:
922                    return R.string.account_status_stream_opening_error;
923                case PAYMENT_REQUIRED:
924                    return R.string.payment_required;
925                case SEE_OTHER_HOST:
926                    return R.string.reconnect_on_other_host;
927                case MISSING_INTERNET_PERMISSION:
928                    return R.string.missing_internet_permission;
929                case TEMPORARY_AUTH_FAILURE:
930                    return R.string.account_status_temporary_auth_failure;
931                default:
932                    return R.string.account_status_unknown;
933            }
934        }
935    }
936}