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