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