Account.java

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