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