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