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