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