Account.java

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