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 ? 20 : 15
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() { return bookmarksLoaded; }
659
660    public void setBookmarks(final Map<Jid, Bookmark> bookmarks) {
661        synchronized (this.bookmarks) {
662            this.bookmarks.clear();
663            this.bookmarks.putAll(bookmarks);
664            this.bookmarksLoaded = true;
665        }
666    }
667
668    public void putBookmark(final Bookmark bookmark) {
669        synchronized (this.bookmarks) {
670            this.bookmarks.put(bookmark.getJid(), bookmark);
671        }
672    }
673
674    public void removeBookmark(Bookmark bookmark) {
675        synchronized (this.bookmarks) {
676            this.bookmarks.remove(bookmark.getJid());
677        }
678    }
679
680    public void removeBookmark(Jid jid) {
681        synchronized (this.bookmarks) {
682            this.bookmarks.remove(jid);
683        }
684    }
685
686    public Set<Jid> getBookmarkedJids() {
687        synchronized (this.bookmarks) {
688            return new HashSet<>(this.bookmarks.keySet());
689        }
690    }
691
692    public Bookmark getBookmark(final Jid jid) {
693        synchronized (this.bookmarks) {
694            return this.bookmarks.get(jid.asBareJid());
695        }
696    }
697
698    public boolean setAvatar(final String filename) {
699        if (this.avatar != null && this.avatar.equals(filename)) {
700            return false;
701        } else {
702            this.avatar = filename;
703            return true;
704        }
705    }
706
707    public String getAvatar() {
708        return this.avatar;
709    }
710
711    public void activateGracePeriod(final long duration) {
712        if (duration > 0) {
713            this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
714        }
715    }
716
717    public void deactivateGracePeriod() {
718        this.mEndGracePeriod = 0L;
719    }
720
721    public boolean inGracePeriod() {
722        return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
723    }
724
725    public String getShareableUri() {
726        List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
727        String uri = "xmpp:" + Uri.encode(getJid().asBareJid().toEscapedString(), "@/+");
728        if (fingerprints.size() > 0) {
729            return XmppUri.getFingerprintUri(uri, fingerprints, ';');
730        } else {
731            return uri;
732        }
733    }
734
735    public String getShareableLink() {
736        List<XmppUri.Fingerprint> fingerprints = this.getFingerprints();
737        String uri =
738                "https://conversations.im/i/"
739                        + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString());
740        if (fingerprints.size() > 0) {
741            return XmppUri.getFingerprintUri(uri, fingerprints, '&');
742        } else {
743            return uri;
744        }
745    }
746
747    private List<XmppUri.Fingerprint> getFingerprints() {
748        ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
749        if (axolotlService == null) {
750            return fingerprints;
751        }
752        fingerprints.add(
753                new XmppUri.Fingerprint(
754                        XmppUri.FingerprintType.OMEMO,
755                        axolotlService.getOwnFingerprint().substring(2),
756                        axolotlService.getOwnDeviceId()));
757        for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
758            if (session.getTrust().isVerified() && session.getTrust().isActive()) {
759                fingerprints.add(
760                        new XmppUri.Fingerprint(
761                                XmppUri.FingerprintType.OMEMO,
762                                session.getFingerprint().substring(2).replaceAll("\\s", ""),
763                                session.getRemoteAddress().getDeviceId()));
764            }
765        }
766        return fingerprints;
767    }
768
769    public boolean isBlocked(final ListItem contact) {
770        final Jid jid = contact.getJid();
771        return jid != null
772                && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain()));
773    }
774
775    public boolean isBlocked(final Jid jid) {
776        return jid != null && blocklist.contains(jid.asBareJid());
777    }
778
779    public Collection<Jid> getBlocklist() {
780        return this.blocklist;
781    }
782
783    public void clearBlocklist() {
784        getBlocklist().clear();
785    }
786
787    public boolean isOnlineAndConnected() {
788        return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
789    }
790
791    @Override
792    public int getAvatarBackgroundColor() {
793        return UIHelper.getColorForName(jid.asBareJid().toString());
794    }
795
796    @Override
797    public String getAvatarName() {
798        throw new IllegalStateException("This method should not be called");
799    }
800
801    public enum State {
802        DISABLED(false, false),
803        LOGGED_OUT(false,false),
804        OFFLINE(false),
805        CONNECTING(false),
806        ONLINE(false),
807        NO_INTERNET(false),
808        UNAUTHORIZED,
809        TEMPORARY_AUTH_FAILURE,
810        SERVER_NOT_FOUND,
811        REGISTRATION_SUCCESSFUL(false),
812        REGISTRATION_FAILED(true, false),
813        REGISTRATION_WEB(true, false),
814        REGISTRATION_CONFLICT(true, false),
815        REGISTRATION_NOT_SUPPORTED(true, false),
816        REGISTRATION_PLEASE_WAIT(true, false),
817        REGISTRATION_INVALID_TOKEN(true, false),
818        REGISTRATION_PASSWORD_TOO_WEAK(true, false),
819        TLS_ERROR,
820        TLS_ERROR_DOMAIN,
821        INCOMPATIBLE_SERVER,
822        INCOMPATIBLE_CLIENT,
823        TOR_NOT_AVAILABLE,
824        DOWNGRADE_ATTACK,
825        SESSION_FAILURE,
826        BIND_FAILURE,
827        HOST_UNKNOWN,
828        STREAM_ERROR,
829        SEE_OTHER_HOST,
830        STREAM_OPENING_ERROR,
831        POLICY_VIOLATION,
832        PAYMENT_REQUIRED,
833        MISSING_INTERNET_PERMISSION(false);
834
835        private final boolean isError;
836        private final boolean attemptReconnect;
837
838        State(final boolean isError) {
839            this(isError, true);
840        }
841
842        State(final boolean isError, final boolean reconnect) {
843            this.isError = isError;
844            this.attemptReconnect = reconnect;
845        }
846
847        State() {
848            this(true, true);
849        }
850
851        public boolean isError() {
852            return this.isError;
853        }
854
855        public boolean isAttemptReconnect() {
856            return this.attemptReconnect;
857        }
858
859        public int getReadableId() {
860            switch (this) {
861                case DISABLED:
862                    return R.string.account_status_disabled;
863                case LOGGED_OUT:
864                    return R.string.account_state_logged_out;
865                case ONLINE:
866                    return R.string.account_status_online;
867                case CONNECTING:
868                    return R.string.account_status_connecting;
869                case OFFLINE:
870                    return R.string.account_status_offline;
871                case UNAUTHORIZED:
872                    return R.string.account_status_unauthorized;
873                case SERVER_NOT_FOUND:
874                    return R.string.account_status_not_found;
875                case NO_INTERNET:
876                    return R.string.account_status_no_internet;
877                case REGISTRATION_FAILED:
878                    return R.string.account_status_regis_fail;
879                case REGISTRATION_WEB:
880                    return R.string.account_status_regis_web;
881                case REGISTRATION_CONFLICT:
882                    return R.string.account_status_regis_conflict;
883                case REGISTRATION_SUCCESSFUL:
884                    return R.string.account_status_regis_success;
885                case REGISTRATION_NOT_SUPPORTED:
886                    return R.string.account_status_regis_not_sup;
887                case REGISTRATION_INVALID_TOKEN:
888                    return R.string.account_status_regis_invalid_token;
889                case TLS_ERROR:
890                    return R.string.account_status_tls_error;
891                case TLS_ERROR_DOMAIN:
892                    return R.string.account_status_tls_error_domain;
893                case INCOMPATIBLE_SERVER:
894                    return R.string.account_status_incompatible_server;
895                case INCOMPATIBLE_CLIENT:
896                    return R.string.account_status_incompatible_client;
897                case TOR_NOT_AVAILABLE:
898                    return R.string.account_status_tor_unavailable;
899                case BIND_FAILURE:
900                    return R.string.account_status_bind_failure;
901                case SESSION_FAILURE:
902                    return R.string.session_failure;
903                case DOWNGRADE_ATTACK:
904                    return R.string.sasl_downgrade;
905                case HOST_UNKNOWN:
906                    return R.string.account_status_host_unknown;
907                case POLICY_VIOLATION:
908                    return R.string.account_status_policy_violation;
909                case REGISTRATION_PLEASE_WAIT:
910                    return R.string.registration_please_wait;
911                case REGISTRATION_PASSWORD_TOO_WEAK:
912                    return R.string.registration_password_too_weak;
913                case STREAM_ERROR:
914                    return R.string.account_status_stream_error;
915                case STREAM_OPENING_ERROR:
916                    return R.string.account_status_stream_opening_error;
917                case PAYMENT_REQUIRED:
918                    return R.string.payment_required;
919                case SEE_OTHER_HOST:
920                    return R.string.reconnect_on_other_host;
921                case MISSING_INTERNET_PERMISSION:
922                    return R.string.missing_internet_permission;
923                case TEMPORARY_AUTH_FAILURE:
924                    return R.string.account_status_temporary_auth_failure;
925                default:
926                    return R.string.account_status_unknown;
927            }
928        }
929    }
930}