Detailed changes
@@ -2,6 +2,7 @@ package eu.siacs.conversations.entities;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -380,7 +381,12 @@ public class MucOptions {
} else if (!conversation.getJid().isBareJid()) {
return conversation.getJid().getResource();
} else {
- return JidHelper.localPartOrFallback(account.getJid());
+ final String displayName = account.getDisplayName();
+ if (TextUtils.isEmpty(displayName)) {
+ return JidHelper.localPartOrFallback(account.getJid());
+ } else {
+ return displayName;
+ }
}
}
@@ -35,7 +35,7 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/caps",
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
- "http://jabber.org/protocol/nick+notify",
+ Namespace.NICK+"+notify",
Namespace.BOOKMARKS+"+notify",
"urn:xmpp:ping",
"jabber:iq:version",
@@ -127,8 +127,15 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket publishNick(String nick) {
final Element item = new Element("item");
- item.addChild("nick", "http://jabber.org/protocol/nick").setContent(nick);
- return publish("http://jabber.org/protocol/nick", item);
+ item.addChild("nick", Namespace.NICK).setContent(nick);
+ return publish(Namespace.NICK, item);
+ }
+
+ public IqPacket deleteNode(String node) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
+ pubsub.addChild("delete").setAttribute("node",node);
+ return packet;
}
public IqPacket publishAvatar(Avatar avatar) {
@@ -43,795 +43,819 @@ import rocks.xmpp.addr.Jid;
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
-
- public MessageParser(XmppConnectionService service) {
- super(service);
- }
-
- private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
- final Jid by;
- final boolean safeToExtract;
- if (isTypeGroupChat) {
- by = conversation.getJid().asBareJid();
- safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
- } else {
- Account account = conversation.getAccount();
- by = account.getJid().asBareJid();
- safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
- }
- return safeToExtract ? extractStanzaId(packet, by) : null;
- }
-
- private static String extractStanzaId(Element packet, Jid by) {
- for (Element child : packet.getChildren()) {
- if (child.getName().equals("stanza-id")
- && Namespace.STANZA_IDS.equals(child.getNamespace())
- && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) {
- return child.getAttribute("id");
- }
- }
- return null;
- }
-
- private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
- final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
- Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
- return result != null ? result : fallback;
- }
-
- private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
- ChatState state = ChatState.parse(packet);
- if (state != null && c != null) {
- final Account account = c.getAccount();
- Jid from = packet.getFrom();
- if (from.asBareJid().equals(account.getJid().asBareJid())) {
- c.setOutgoingChatState(state);
- if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
- mXmppConnectionService.markRead(c);
- activateGracePeriod(account);
- }
- return false;
- } else {
- if (isTypeGroupChat) {
- MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
- if (user != null) {
- return user.setChatState(state);
- } else {
- return false;
- }
- } else {
- return c.setIncomingChatState(state);
- }
- }
- }
- return false;
- }
-
- private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) {
- final AxolotlService service = conversation.getAccount().getAxolotlService();
- final XmppAxolotlMessage xmppAxolotlMessage;
- try {
- xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
- } catch (Exception e) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
- return null;
- }
- if (xmppAxolotlMessage.hasPayload()) {
- final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
- try {
- plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
- } catch (NotEncryptedForThisDeviceException e) {
- return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
- }
- if (plaintextMessage != null) {
- Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
- finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
- return finishedMessage;
- }
- } else {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
- service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
- }
- return null;
- }
-
- private Invite extractInvite(Account account, Element message) {
- Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
- if (x != null) {
- Element invite = x.findChild("invite");
- if (invite != null) {
- String password = x.findChildContent("password");
- Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
- Contact contact = from == null ? null : account.getRoster().getContact(from);
- Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
- if (room == null) {
- return null;
- }
- return new Invite(room, password, contact);
- }
- } else {
- x = message.findChild("x", "jabber:x:conference");
- if (x != null) {
- Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
- Contact contact = from == null ? null : account.getRoster().getContact(from);
- Jid room = InvalidJid.getNullForInvalid(x.getAttributeAsJid("jid"));
- if (room == null) {
- return null;
- }
- return new Invite(room, x.getAttribute("password"), contact);
- }
- }
- return null;
- }
-
- private void parseEvent(final Element event, final Jid from, final Account account) {
- Element items = event.findChild("items");
- String node = items == null ? null : items.getAttribute("node");
- if ("urn:xmpp:avatar:metadata".equals(node)) {
- Avatar avatar = Avatar.parseMetadata(items);
- if (avatar != null) {
- avatar.owner = from.asBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
- if (account.getJid().asBareJid().equals(from)) {
- if (account.setAvatar(avatar.getFilename())) {
- mXmppConnectionService.databaseBackend.updateAccount(account);
- }
- mXmppConnectionService.getAvatarService().clear(account);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateAccountUi();
- } else {
- Contact contact = account.getRoster().getContact(from);
- if (contact.setAvatar(avatar)) {
- mXmppConnectionService.syncRoster(account);
- mXmppConnectionService.getAvatarService().clear(contact);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateRosterUi();
- }
- }
- } else if (mXmppConnectionService.isDataSaverDisabled()) {
- mXmppConnectionService.fetchAvatar(account, avatar);
- }
- }
- } else if ("http://jabber.org/protocol/nick".equals(node)) {
- final Element i = items.findChild("item");
- final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
- if (nick != null) {
- Contact contact = account.getRoster().getContact(from);
- if (contact.setPresenceName(nick)) {
- mXmppConnectionService.getAvatarService().clear(contact);
- }
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateAccountUi();
- }
- } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
- Element item = items.findChild("item");
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
- AxolotlService axolotlService = account.getAxolotlService();
- axolotlService.registerDevices(from, deviceIds);
- } else if (Namespace.BOOKMARKS.equals(node)) {
- Log.d(Config.LOGTAG,"received bookmarks from "+from);
- if (account.getJid().asBareJid().equals(from)) {
- final Element i = items.findChild("item");
- final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
- mXmppConnectionService.processBookmarks(account,storage);
- }
- }
- }
-
- private boolean handleErrorMessage(Account account, MessagePacket packet) {
- if (packet.getType() == MessagePacket.TYPE_ERROR) {
- Jid from = packet.getFrom();
- if (from != null) {
- mXmppConnectionService.markMessage(account,
- from.asBareJid(),
- packet.getId(),
- Message.STATUS_SEND_FAILED,
- extractErrorMessage(packet));
- }
- return true;
- }
- return false;
- }
-
- @Override
- public void onMessagePacketReceived(Account account, MessagePacket original) {
- if (handleErrorMessage(account, original)) {
- return;
- }
- final MessagePacket packet;
- Long timestamp = null;
- boolean isCarbon = false;
- String serverMsgId = null;
- final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
- if (fin != null) {
- mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
- return;
- }
- final Element result = MessageArchiveService.Version.findResult(original);
- final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
- if (query != null && query.validFrom(original.getFrom())) {
- Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
- if (f == null) {
- return;
- }
- timestamp = f.second;
- packet = f.first;
- serverMsgId = result.getAttribute("id");
- query.incrementMessageCount();
- } else if (query != null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result from invalid sender");
- return;
- } else if (original.fromServer(account)) {
- Pair<MessagePacket, Long> f;
- f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2");
- f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f;
- packet = f != null ? f.first : original;
- if (handleErrorMessage(account, packet)) {
- return;
- }
- timestamp = f != null ? f.second : null;
- isCarbon = f != null;
- } else {
- packet = original;
- }
-
- if (timestamp == null) {
- timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
- }
- final String body = packet.getBody();
- final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
- final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
- final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
- final Element oob = packet.findChild("x", Namespace.OOB);
- final Element xP1S3 = packet.findChild("x", Namespace.P1_S3_FILE_TRANSFER);
- final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3);
- final String oobUrl = oob != null ? oob.findChildContent("url") : null;
- final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
- final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
- int status;
- final Jid counterpart;
- final Jid to = packet.getTo();
- final Jid from = packet.getFrom();
- final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
- final String remoteMsgId;
- if (originId != null && originId.getAttribute("id") != null) {
- remoteMsgId = originId.getAttribute("id");
- } else {
- remoteMsgId = packet.getId();
- }
- boolean notify = false;
-
- if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
- Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
- return;
- }
-
- boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
- if (query != null && !query.muc() && isTypeGroupChat) {
- Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
- return;
- }
- boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
- boolean selfAddressed;
- if (packet.fromAccount(account)) {
- status = Message.STATUS_SEND;
- selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
- if (selfAddressed) {
- counterpart = from;
- } else {
- counterpart = to != null ? to : account.getJid();
- }
- } else {
- status = Message.STATUS_RECEIVED;
- counterpart = from;
- selfAddressed = false;
- }
-
- Invite invite = extractInvite(account, packet);
- if (invite != null && invite.execute(account)) {
- return;
- }
-
- if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) {
- final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain());
- final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
- final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
-
- if (serverMsgId == null) {
- serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
- }
-
-
- if (selfAddressed) {
- if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
- return;
- }
- status = Message.STATUS_RECEIVED;
- if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
- return;
- }
- }
-
- if (isTypeGroupChat) {
- if (conversation.getMucOptions().isSelf(counterpart)) {
- status = Message.STATUS_SEND_RECEIVED;
- isCarbon = true; //not really carbon but received from another resource
- if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) {
- return;
- } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
- Message message = conversation.findSentMessageWithBody(packet.getBody());
- if (message != null) {
- mXmppConnectionService.markMessage(message, status);
- return;
- }
- }
- } else {
- status = Message.STATUS_RECEIVED;
- }
- }
- final Message message;
- if (xP1S3url != null) {
- message = new Message(conversation, xP1S3url.toString(), Message.ENCRYPTION_NONE, status);
- message.setOob(true);
- if (CryptoHelper.isPgpEncryptedUrl(xP1S3url.toString())) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- }
- } else if (pgpEncrypted != null && Config.supportOpenPgp()) {
- message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
- } else if (axolotlEncrypted != null && Config.supportOmemo()) {
- Jid origin;
- Set<Jid> fallbacksBySourceId = Collections.emptySet();
- if (conversationMultiMode) {
- final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
- origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
- if (origin == null) {
- try {
- fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
- } catch (IllegalArgumentException e) {
- //ignoring
- }
- }
- if (origin == null && fallbacksBySourceId.size() == 0) {
- Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
- return;
- }
- } else {
- fallbacksBySourceId = Collections.emptySet();
- origin = from;
- }
- if (origin != null) {
- message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null);
- } else {
- Message trial = null;
- for (Jid fallback : fallbacksBySourceId) {
- trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, query != null);
- if (trial != null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
- origin = fallback;
- break;
- }
- }
- message = trial;
- }
- if (message == null) {
- if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
- mXmppConnectionService.updateConversationUi();
- }
- if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
- Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
- if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
- previouslySent.setServerMsgId(serverMsgId);
- mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
- }
- }
- return;
- }
- if (conversationMultiMode) {
- message.setTrueCounterpart(origin);
- }
- } else if (body == null && oobUrl != null) {
- message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
- message.setOob(true);
- if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- }
- } else {
- message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
- }
-
- message.setCounterpart(counterpart);
- message.setRemoteMsgId(remoteMsgId);
- message.setServerMsgId(serverMsgId);
- message.setCarbon(isCarbon);
- message.setTime(timestamp);
- if (body != null && body.equals(oobUrl)) {
- message.setOob(true);
- if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- }
- }
- message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
- if (conversationMultiMode) {
- message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
- final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
- Jid trueCounterpart;
- if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
- trueCounterpart = message.getTrueCounterpart();
- } else if (query != null && query.safeToExtractTrueCounterpart()) {
- trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
- } else {
- trueCounterpart = fallback;
- }
- if (trueCounterpart != null && trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
- status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
- }
- message.setStatus(status);
- message.setTrueCounterpart(trueCounterpart);
- if (!isTypeGroupChat) {
- message.setType(Message.TYPE_PRIVATE);
- }
- } else {
- updateLastseen(account, from);
- }
-
- if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
- final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
- counterpart,
- message.getStatus() == Message.STATUS_RECEIVED,
- message.isCarbon());
- if (replacedMessage != null) {
- final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
- || replacedMessage.getFingerprint().equals(message.getFingerprint());
- final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
- && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
- final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
- final boolean duplicate = conversation.hasDuplicateMessage(message);
- if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
- Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
- synchronized (replacedMessage) {
- final String uuid = replacedMessage.getUuid();
- replacedMessage.setUuid(UUID.randomUUID().toString());
- replacedMessage.setBody(message.getBody());
- replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
- replacedMessage.setRemoteMsgId(remoteMsgId);
- if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
- replacedMessage.setServerMsgId(message.getServerMsgId());
- }
- replacedMessage.setEncryption(message.getEncryption());
- if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
- replacedMessage.markUnread();
- }
- extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
- mXmppConnectionService.updateMessage(replacedMessage, uuid);
- mXmppConnectionService.getNotificationService().updateNotification(false);
- if (mXmppConnectionService.confirmMessages()
- && replacedMessage.getStatus() == Message.STATUS_RECEIVED
- && (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE)
- && remoteMsgId != null
- && !selfAddressed
- && !isTypeGroupChat) {
- processMessageReceipts(account, packet, query);
- }
- if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
- conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
- conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
- }
- }
- return;
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
- }
- }
- }
-
- long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
- if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
- return;
- }
-
- boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
- || message.getType() == Message.TYPE_PRIVATE
- || message.getServerMsgId() != null
- || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
- if (checkForDuplicates) {
- final Message duplicate = conversation.findDuplicateMessage(message);
- if (duplicate != null) {
- final boolean serverMsgIdUpdated;
- if (duplicate.getStatus() != Message.STATUS_RECEIVED
- && duplicate.getUuid().equals(message.getRemoteMsgId())
- && duplicate.getServerMsgId() == null
- && message.getServerMsgId() != null) {
- duplicate.setServerMsgId(message.getServerMsgId());
- if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
- serverMsgIdUpdated = true;
- } else {
- serverMsgIdUpdated = false;
- Log.e(Config.LOGTAG,"failed to update message");
- }
- } else {
- serverMsgIdUpdated = false;
- }
- Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + Boolean.toString(serverMsgIdUpdated));
- return;
- }
- }
-
- if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
- conversation.prepend(query.getActualInThisQuery(), message);
- } else {
- conversation.add(message);
- }
- if (query != null) {
- query.incrementActualMessageCount();
- }
-
- if (query == null || query.isCatchup()) { //either no mam or catchup
- if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
- mXmppConnectionService.markRead(conversation);
- if (query == null) {
- activateGracePeriod(account);
- }
- } else {
- message.markUnread();
- notify = true;
- }
- }
-
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
- } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
- notify = false;
- }
-
- if (query == null) {
- extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
- mXmppConnectionService.updateConversationUi();
- }
-
- if (mXmppConnectionService.confirmMessages()
- && message.getStatus() == Message.STATUS_RECEIVED
- && (message.trusted() || message.getType() == Message.TYPE_PRIVATE)
- && remoteMsgId != null
- && !selfAddressed
- && !isTypeGroupChat) {
- processMessageReceipts(account, packet, query);
- }
-
- mXmppConnectionService.databaseBackend.createMessage(message);
- final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
- manager.createNewDownloadConnection(message);
- } else if (notify) {
- if (query != null && query.isCatchup()) {
- mXmppConnectionService.getNotificationService().pushFromBacklog(message);
- } else {
- mXmppConnectionService.getNotificationService().push(message);
- }
- }
- } else if (!packet.hasChild("body")) { //no body
-
- final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
- if (axolotlEncrypted != null) {
- Jid origin;
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
- origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
- if (origin == null) {
- Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
- return;
- }
- } else if (isTypeGroupChat) {
- return;
- } else {
- origin = from;
- }
- try {
- final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
- account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
- } catch (Exception e) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
- return;
- }
- }
-
- if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
- mXmppConnectionService.updateConversationUi();
- }
-
- if (isTypeGroupChat) {
- if (packet.hasChild("subject")) {
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
- String subject = packet.findInternationalizedChildContent("subject");
- if (conversation.getMucOptions().setSubject(subject)) {
- mXmppConnectionService.updateConversation(conversation);
- }
- mXmppConnectionService.updateConversationUi();
- return;
- }
- }
- }
- if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
- for (Element child : mucUserElement.getChildren()) {
- if ("status".equals(child.getName())) {
- try {
- int code = Integer.parseInt(child.getAttribute("code"));
- if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
- mXmppConnectionService.fetchConferenceConfiguration(conversation);
- break;
- }
- } catch (Exception e) {
- //ignored
- }
- } else if ("item".equals(child.getName())) {
- MucOptions.User user = AbstractParser.parseItem(conversation, child);
- Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
- + user.getRealJid() + " to " + user.getAffiliation() + " in "
- + conversation.getJid().asBareJid());
- if (!user.realJidMatchesAccount()) {
- boolean isNew = conversation.getMucOptions().updateUser(user);
- mXmppConnectionService.getAvatarService().clear(conversation);
- mXmppConnectionService.updateMucRosterUi();
- mXmppConnectionService.updateConversationUi();
- Contact contact = user.getContact();
- if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
- Jid jid = user.getRealJid();
- List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
- if (cryptoTargets.remove(user.getRealJid())) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
- conversation.setAcceptedCryptoTargets(cryptoTargets);
- mXmppConnectionService.updateConversation(conversation);
- }
- } else if (isNew
- && user.getRealJid() != null
- && conversation.getMucOptions().isPrivateAndNonAnonymous()
- && (contact == null || !contact.mutualPresenceSubscription())
- && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
- account.getAxolotlService().fetchDeviceIds(user.getRealJid());
- }
- }
- }
- }
- }
- }
-
- Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
- if (received == null) {
- received = packet.findChild("received", "urn:xmpp:receipts");
- }
- if (received != null) {
- String id = received.getAttribute("id");
- if (packet.fromAccount(account)) {
- if (query != null && id != null && packet.getTo() != null) {
- query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
- }
- } else {
- mXmppConnectionService.markMessage(account, from.asBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
- }
- }
- Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
- if (displayed != null) {
- final String id = displayed.getAttribute("id");
- final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
- if (packet.fromAccount(account) && !selfAddressed) {
- dismissNotification(account, counterpart, query);
- } else if (isTypeGroupChat) {
- Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
- if (conversation != null && id != null && sender != null) {
- Message message = conversation.findMessageWithRemoteId(id, sender);
- if (message != null) {
- final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
- final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
- final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
- if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
- if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
- mXmppConnectionService.markRead(conversation);
- }
- } else if (!counterpart.isBareJid() && trueJid != null) {
- ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
- if (message.addReadByMarker(readByMarker)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'");
- mXmppConnectionService.updateMessage(message, false);
- }
- }
- }
- }
- } else {
- final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
- Message message = displayedMessage == null ? null : displayedMessage.prev();
- while (message != null
- && message.getStatus() == Message.STATUS_SEND_RECEIVED
- && message.getTimeSent() < displayedMessage.getTimeSent()) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
- message = message.prev();
- }
- if (displayedMessage != null && selfAddressed) {
- dismissNotification(account, counterpart, query);
- }
- }
- }
-
- Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
- if (event != null && InvalidJid.hasValidFrom(original)) {
- parseEvent(event, original.getFrom(), account);
- }
-
- final String nick = packet.findChildContent("nick", Namespace.NICK);
- if (nick != null && InvalidJid.hasValidFrom(original)) {
- Contact contact = account.getRoster().getContact(from);
- if (contact.setPresenceName(nick)) {
- mXmppConnectionService.getAvatarService().clear(contact);
- }
- }
- }
-
- private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query) {
- Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
- if (conversation != null && (query == null || query.isCatchup())) {
- mXmppConnectionService.markRead(conversation); //TODO only mark messages read that are older than timestamp
- }
- }
-
- private void processMessageReceipts(Account account, MessagePacket packet, MessageArchiveService.Query query) {
- final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
- final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
- if (query == null) {
- final ArrayList<String> receiptsNamespaces = new ArrayList<>();
- if (markable) {
- receiptsNamespaces.add("urn:xmpp:chat-markers:0");
- }
- if (request) {
- receiptsNamespaces.add("urn:xmpp:receipts");
- }
- if (receiptsNamespaces.size() > 0) {
- MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
- packet,
- receiptsNamespaces,
- packet.getType());
- mXmppConnectionService.sendMessagePacket(account, receipt);
- }
- } else if (query.isCatchup()) {
- if (request) {
- query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), packet.getId()));
- }
- }
- }
-
- private void activateGracePeriod(Account account) {
- long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
- account.activateGracePeriod(duration);
- }
-
- private class Invite {
- final Jid jid;
- final String password;
- final Contact inviter;
-
- Invite(Jid jid, String password, Contact inviter) {
- this.jid = jid;
- this.password = password;
- this.inviter = inviter;
- }
-
- public boolean execute(Account account) {
- if (jid != null) {
- Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
- if (!conversation.getMucOptions().online()) {
- conversation.getMucOptions().setPassword(password);
- mXmppConnectionService.databaseBackend.updateConversation(conversation);
- mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription());
- mXmppConnectionService.updateConversationUi();
- }
- return true;
- }
- return false;
- }
- }
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
+
+ public MessageParser(XmppConnectionService service) {
+ super(service);
+ }
+
+ private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
+ final Jid by;
+ final boolean safeToExtract;
+ if (isTypeGroupChat) {
+ by = conversation.getJid().asBareJid();
+ safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
+ } else {
+ Account account = conversation.getAccount();
+ by = account.getJid().asBareJid();
+ safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
+ }
+ return safeToExtract ? extractStanzaId(packet, by) : null;
+ }
+
+ private static String extractStanzaId(Element packet, Jid by) {
+ for (Element child : packet.getChildren()) {
+ if (child.getName().equals("stanza-id")
+ && Namespace.STANZA_IDS.equals(child.getNamespace())
+ && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) {
+ return child.getAttribute("id");
+ }
+ }
+ return null;
+ }
+
+ private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
+ final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
+ Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
+ return result != null ? result : fallback;
+ }
+
+ private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
+ ChatState state = ChatState.parse(packet);
+ if (state != null && c != null) {
+ final Account account = c.getAccount();
+ Jid from = packet.getFrom();
+ if (from.asBareJid().equals(account.getJid().asBareJid())) {
+ c.setOutgoingChatState(state);
+ if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
+ mXmppConnectionService.markRead(c);
+ activateGracePeriod(account);
+ }
+ return false;
+ } else {
+ if (isTypeGroupChat) {
+ MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
+ if (user != null) {
+ return user.setChatState(state);
+ } else {
+ return false;
+ }
+ } else {
+ return c.setIncomingChatState(state);
+ }
+ }
+ }
+ return false;
+ }
+
+ private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) {
+ final AxolotlService service = conversation.getAccount().getAxolotlService();
+ final XmppAxolotlMessage xmppAxolotlMessage;
+ try {
+ xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
+ return null;
+ }
+ if (xmppAxolotlMessage.hasPayload()) {
+ final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
+ try {
+ plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
+ } catch (NotEncryptedForThisDeviceException e) {
+ return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
+ }
+ if (plaintextMessage != null) {
+ Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
+ finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
+ return finishedMessage;
+ }
+ } else {
+ Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
+ service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
+ }
+ return null;
+ }
+
+ private Invite extractInvite(Account account, Element message) {
+ Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
+ if (x != null) {
+ Element invite = x.findChild("invite");
+ if (invite != null) {
+ String password = x.findChildContent("password");
+ Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
+ Contact contact = from == null ? null : account.getRoster().getContact(from);
+ Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
+ if (room == null) {
+ return null;
+ }
+ return new Invite(room, password, contact);
+ }
+ } else {
+ x = message.findChild("x", "jabber:x:conference");
+ if (x != null) {
+ Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
+ Contact contact = from == null ? null : account.getRoster().getContact(from);
+ Jid room = InvalidJid.getNullForInvalid(x.getAttributeAsJid("jid"));
+ if (room == null) {
+ return null;
+ }
+ return new Invite(room, x.getAttribute("password"), contact);
+ }
+ }
+ return null;
+ }
+
+ private void parseEvent(final Element event, final Jid from, final Account account) {
+ Element items = event.findChild("items");
+ String node = items == null ? null : items.getAttribute("node");
+ if ("urn:xmpp:avatar:metadata".equals(node)) {
+ Avatar avatar = Avatar.parseMetadata(items);
+ if (avatar != null) {
+ avatar.owner = from.asBareJid();
+ if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (account.getJid().asBareJid().equals(from)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ }
+ mXmppConnectionService.getAvatarService().clear(account);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateAccountUi();
+ } else {
+ Contact contact = account.getRoster().getContact(from);
+ if (contact.setAvatar(avatar)) {
+ mXmppConnectionService.syncRoster(account);
+ mXmppConnectionService.getAvatarService().clear(contact);
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateRosterUi();
+ }
+ }
+ } else if (mXmppConnectionService.isDataSaverDisabled()) {
+ mXmppConnectionService.fetchAvatar(account, avatar);
+ }
+ }
+ } else if (Namespace.NICK.equals(node)) {
+ final Element i = items.findChild("item");
+ final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
+ if (nick != null) {
+ setNick(account, from, nick);
+ }
+ } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
+ Element item = items.findChild("item");
+ Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
+ AxolotlService axolotlService = account.getAxolotlService();
+ axolotlService.registerDevices(from, deviceIds);
+ } else if (Namespace.BOOKMARKS.equals(node)) {
+ Log.d(Config.LOGTAG, "received bookmarks from " + from);
+ if (account.getJid().asBareJid().equals(from)) {
+ final Element i = items.findChild("item");
+ final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
+ mXmppConnectionService.processBookmarks(account, storage);
+ }
+ }
+ }
+
+ private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
+ final Element delete = event.findChild("delete");
+ if (delete == null) {
+ return;
+ }
+ String node = delete.getAttribute("node");
+ if (Namespace.NICK.equals(node)) {
+ Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
+ setNick(account, from, null);
+ }
+ }
+
+ private void setNick(Account account, Jid user, String nick) {
+ if (user.asBareJid().equals(account.getJid().asBareJid())) {
+ account.setDisplayName(nick);
+ } else {
+ Contact contact = account.getRoster().getContact(user);
+ if (contact.setPresenceName(nick)) {
+ mXmppConnectionService.getAvatarService().clear(contact);
+ }
+ }
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.updateAccountUi();
+ }
+
+ private boolean handleErrorMessage(Account account, MessagePacket packet) {
+ if (packet.getType() == MessagePacket.TYPE_ERROR) {
+ Jid from = packet.getFrom();
+ if (from != null) {
+ mXmppConnectionService.markMessage(account,
+ from.asBareJid(),
+ packet.getId(),
+ Message.STATUS_SEND_FAILED,
+ extractErrorMessage(packet));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onMessagePacketReceived(Account account, MessagePacket original) {
+ if (handleErrorMessage(account, original)) {
+ return;
+ }
+ final MessagePacket packet;
+ Long timestamp = null;
+ boolean isCarbon = false;
+ String serverMsgId = null;
+ final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
+ if (fin != null) {
+ mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
+ return;
+ }
+ final Element result = MessageArchiveService.Version.findResult(original);
+ final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
+ if (query != null && query.validFrom(original.getFrom())) {
+ Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
+ if (f == null) {
+ return;
+ }
+ timestamp = f.second;
+ packet = f.first;
+ serverMsgId = result.getAttribute("id");
+ query.incrementMessageCount();
+ } else if (query != null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result from invalid sender");
+ return;
+ } else if (original.fromServer(account)) {
+ Pair<MessagePacket, Long> f;
+ f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2");
+ f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f;
+ packet = f != null ? f.first : original;
+ if (handleErrorMessage(account, packet)) {
+ return;
+ }
+ timestamp = f != null ? f.second : null;
+ isCarbon = f != null;
+ } else {
+ packet = original;
+ }
+
+ if (timestamp == null) {
+ timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
+ }
+ final String body = packet.getBody();
+ final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
+ final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
+ final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
+ final Element oob = packet.findChild("x", Namespace.OOB);
+ final Element xP1S3 = packet.findChild("x", Namespace.P1_S3_FILE_TRANSFER);
+ final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3);
+ final String oobUrl = oob != null ? oob.findChildContent("url") : null;
+ final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
+ final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
+ int status;
+ final Jid counterpart;
+ final Jid to = packet.getTo();
+ final Jid from = packet.getFrom();
+ final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
+ final String remoteMsgId;
+ if (originId != null && originId.getAttribute("id") != null) {
+ remoteMsgId = originId.getAttribute("id");
+ } else {
+ remoteMsgId = packet.getId();
+ }
+ boolean notify = false;
+
+ if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
+ Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
+ return;
+ }
+
+ boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
+ if (query != null && !query.muc() && isTypeGroupChat) {
+ Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
+ return;
+ }
+ boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
+ boolean selfAddressed;
+ if (packet.fromAccount(account)) {
+ status = Message.STATUS_SEND;
+ selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
+ if (selfAddressed) {
+ counterpart = from;
+ } else {
+ counterpart = to != null ? to : account.getJid();
+ }
+ } else {
+ status = Message.STATUS_RECEIVED;
+ counterpart = from;
+ selfAddressed = false;
+ }
+
+ Invite invite = extractInvite(account, packet);
+ if (invite != null && invite.execute(account)) {
+ return;
+ }
+
+ if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) {
+ final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain());
+ final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
+ final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
+
+ if (serverMsgId == null) {
+ serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
+ }
+
+
+ if (selfAddressed) {
+ if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
+ return;
+ }
+ status = Message.STATUS_RECEIVED;
+ if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
+ return;
+ }
+ }
+
+ if (isTypeGroupChat) {
+ if (conversation.getMucOptions().isSelf(counterpart)) {
+ status = Message.STATUS_SEND_RECEIVED;
+ isCarbon = true; //not really carbon but received from another resource
+ if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) {
+ return;
+ } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
+ Message message = conversation.findSentMessageWithBody(packet.getBody());
+ if (message != null) {
+ mXmppConnectionService.markMessage(message, status);
+ return;
+ }
+ }
+ } else {
+ status = Message.STATUS_RECEIVED;
+ }
+ }
+ final Message message;
+ if (xP1S3url != null) {
+ message = new Message(conversation, xP1S3url.toString(), Message.ENCRYPTION_NONE, status);
+ message.setOob(true);
+ if (CryptoHelper.isPgpEncryptedUrl(xP1S3url.toString())) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ }
+ } else if (pgpEncrypted != null && Config.supportOpenPgp()) {
+ message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
+ } else if (axolotlEncrypted != null && Config.supportOmemo()) {
+ Jid origin;
+ Set<Jid> fallbacksBySourceId = Collections.emptySet();
+ if (conversationMultiMode) {
+ final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+ origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
+ if (origin == null) {
+ try {
+ fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
+ } catch (IllegalArgumentException e) {
+ //ignoring
+ }
+ }
+ if (origin == null && fallbacksBySourceId.size() == 0) {
+ Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
+ return;
+ }
+ } else {
+ fallbacksBySourceId = Collections.emptySet();
+ origin = from;
+ }
+ if (origin != null) {
+ message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null);
+ } else {
+ Message trial = null;
+ for (Jid fallback : fallbacksBySourceId) {
+ trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, query != null);
+ if (trial != null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
+ origin = fallback;
+ break;
+ }
+ }
+ message = trial;
+ }
+ if (message == null) {
+ if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+ mXmppConnectionService.updateConversationUi();
+ }
+ if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
+ Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
+ if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
+ previouslySent.setServerMsgId(serverMsgId);
+ mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
+ }
+ }
+ return;
+ }
+ if (conversationMultiMode) {
+ message.setTrueCounterpart(origin);
+ }
+ } else if (body == null && oobUrl != null) {
+ message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
+ message.setOob(true);
+ if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ }
+ } else {
+ message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
+ }
+
+ message.setCounterpart(counterpart);
+ message.setRemoteMsgId(remoteMsgId);
+ message.setServerMsgId(serverMsgId);
+ message.setCarbon(isCarbon);
+ message.setTime(timestamp);
+ if (body != null && body.equals(oobUrl)) {
+ message.setOob(true);
+ if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ }
+ }
+ message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
+ if (conversationMultiMode) {
+ message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
+ final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+ Jid trueCounterpart;
+ if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ trueCounterpart = message.getTrueCounterpart();
+ } else if (query != null && query.safeToExtractTrueCounterpart()) {
+ trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
+ } else {
+ trueCounterpart = fallback;
+ }
+ if (trueCounterpart != null && trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
+ status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
+ }
+ message.setStatus(status);
+ message.setTrueCounterpart(trueCounterpart);
+ if (!isTypeGroupChat) {
+ message.setType(Message.TYPE_PRIVATE);
+ }
+ } else {
+ updateLastseen(account, from);
+ }
+
+ if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
+ final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
+ counterpart,
+ message.getStatus() == Message.STATUS_RECEIVED,
+ message.isCarbon());
+ if (replacedMessage != null) {
+ final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
+ || replacedMessage.getFingerprint().equals(message.getFingerprint());
+ final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
+ && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
+ final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
+ final boolean duplicate = conversation.hasDuplicateMessage(message);
+ if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
+ Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
+ synchronized (replacedMessage) {
+ final String uuid = replacedMessage.getUuid();
+ replacedMessage.setUuid(UUID.randomUUID().toString());
+ replacedMessage.setBody(message.getBody());
+ replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
+ replacedMessage.setRemoteMsgId(remoteMsgId);
+ if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
+ replacedMessage.setServerMsgId(message.getServerMsgId());
+ }
+ replacedMessage.setEncryption(message.getEncryption());
+ if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
+ replacedMessage.markUnread();
+ }
+ extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
+ mXmppConnectionService.updateMessage(replacedMessage, uuid);
+ mXmppConnectionService.getNotificationService().updateNotification(false);
+ if (mXmppConnectionService.confirmMessages()
+ && replacedMessage.getStatus() == Message.STATUS_RECEIVED
+ && (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE)
+ && remoteMsgId != null
+ && !selfAddressed
+ && !isTypeGroupChat) {
+ processMessageReceipts(account, packet, query);
+ }
+ if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
+ conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
+ conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
+ }
+ }
+ return;
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
+ }
+ }
+ }
+
+ long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
+ if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
+ return;
+ }
+
+ boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
+ || message.getType() == Message.TYPE_PRIVATE
+ || message.getServerMsgId() != null
+ || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
+ if (checkForDuplicates) {
+ final Message duplicate = conversation.findDuplicateMessage(message);
+ if (duplicate != null) {
+ final boolean serverMsgIdUpdated;
+ if (duplicate.getStatus() != Message.STATUS_RECEIVED
+ && duplicate.getUuid().equals(message.getRemoteMsgId())
+ && duplicate.getServerMsgId() == null
+ && message.getServerMsgId() != null) {
+ duplicate.setServerMsgId(message.getServerMsgId());
+ if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
+ serverMsgIdUpdated = true;
+ } else {
+ serverMsgIdUpdated = false;
+ Log.e(Config.LOGTAG, "failed to update message");
+ }
+ } else {
+ serverMsgIdUpdated = false;
+ }
+ Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + Boolean.toString(serverMsgIdUpdated));
+ return;
+ }
+ }
+
+ if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
+ conversation.prepend(query.getActualInThisQuery(), message);
+ } else {
+ conversation.add(message);
+ }
+ if (query != null) {
+ query.incrementActualMessageCount();
+ }
+
+ if (query == null || query.isCatchup()) { //either no mam or catchup
+ if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
+ mXmppConnectionService.markRead(conversation);
+ if (query == null) {
+ activateGracePeriod(account);
+ }
+ } else {
+ message.markUnread();
+ notify = true;
+ }
+ }
+
+ if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
+ } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) {
+ notify = false;
+ }
+
+ if (query == null) {
+ extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
+ mXmppConnectionService.updateConversationUi();
+ }
+
+ if (mXmppConnectionService.confirmMessages()
+ && message.getStatus() == Message.STATUS_RECEIVED
+ && (message.trusted() || message.getType() == Message.TYPE_PRIVATE)
+ && remoteMsgId != null
+ && !selfAddressed
+ && !isTypeGroupChat) {
+ processMessageReceipts(account, packet, query);
+ }
+
+ mXmppConnectionService.databaseBackend.createMessage(message);
+ final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
+ if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
+ manager.createNewDownloadConnection(message);
+ } else if (notify) {
+ if (query != null && query.isCatchup()) {
+ mXmppConnectionService.getNotificationService().pushFromBacklog(message);
+ } else {
+ mXmppConnectionService.getNotificationService().push(message);
+ }
+ }
+ } else if (!packet.hasChild("body")) { //no body
+
+ final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
+ if (axolotlEncrypted != null) {
+ Jid origin;
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+ origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
+ if (origin == null) {
+ Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
+ return;
+ }
+ } else if (isTypeGroupChat) {
+ return;
+ } else {
+ origin = from;
+ }
+ try {
+ final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
+ account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
+ return;
+ }
+ }
+
+ if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
+ mXmppConnectionService.updateConversationUi();
+ }
+
+ if (isTypeGroupChat) {
+ if (packet.hasChild("subject")) {
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
+ String subject = packet.findInternationalizedChildContent("subject");
+ if (conversation.getMucOptions().setSubject(subject)) {
+ mXmppConnectionService.updateConversation(conversation);
+ }
+ mXmppConnectionService.updateConversationUi();
+ return;
+ }
+ }
+ }
+ if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
+ for (Element child : mucUserElement.getChildren()) {
+ if ("status".equals(child.getName())) {
+ try {
+ int code = Integer.parseInt(child.getAttribute("code"));
+ if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
+ mXmppConnectionService.fetchConferenceConfiguration(conversation);
+ break;
+ }
+ } catch (Exception e) {
+ //ignored
+ }
+ } else if ("item".equals(child.getName())) {
+ MucOptions.User user = AbstractParser.parseItem(conversation, child);
+ Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
+ + user.getRealJid() + " to " + user.getAffiliation() + " in "
+ + conversation.getJid().asBareJid());
+ if (!user.realJidMatchesAccount()) {
+ boolean isNew = conversation.getMucOptions().updateUser(user);
+ mXmppConnectionService.getAvatarService().clear(conversation);
+ mXmppConnectionService.updateMucRosterUi();
+ mXmppConnectionService.updateConversationUi();
+ Contact contact = user.getContact();
+ if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
+ Jid jid = user.getRealJid();
+ List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
+ if (cryptoTargets.remove(user.getRealJid())) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
+ conversation.setAcceptedCryptoTargets(cryptoTargets);
+ mXmppConnectionService.updateConversation(conversation);
+ }
+ } else if (isNew
+ && user.getRealJid() != null
+ && conversation.getMucOptions().isPrivateAndNonAnonymous()
+ && (contact == null || !contact.mutualPresenceSubscription())
+ && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
+ account.getAxolotlService().fetchDeviceIds(user.getRealJid());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
+ if (received == null) {
+ received = packet.findChild("received", "urn:xmpp:receipts");
+ }
+ if (received != null) {
+ String id = received.getAttribute("id");
+ if (packet.fromAccount(account)) {
+ if (query != null && id != null && packet.getTo() != null) {
+ query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
+ }
+ } else {
+ mXmppConnectionService.markMessage(account, from.asBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
+ }
+ }
+ Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
+ if (displayed != null) {
+ final String id = displayed.getAttribute("id");
+ final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
+ if (packet.fromAccount(account) && !selfAddressed) {
+ dismissNotification(account, counterpart, query);
+ } else if (isTypeGroupChat) {
+ Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
+ if (conversation != null && id != null && sender != null) {
+ Message message = conversation.findMessageWithRemoteId(id, sender);
+ if (message != null) {
+ final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+ final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
+ final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
+ if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
+ if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
+ mXmppConnectionService.markRead(conversation);
+ }
+ } else if (!counterpart.isBareJid() && trueJid != null) {
+ ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
+ if (message.addReadByMarker(readByMarker)) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'");
+ mXmppConnectionService.updateMessage(message, false);
+ }
+ }
+ }
+ }
+ } else {
+ final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
+ Message message = displayedMessage == null ? null : displayedMessage.prev();
+ while (message != null
+ && message.getStatus() == Message.STATUS_SEND_RECEIVED
+ && message.getTimeSent() < displayedMessage.getTimeSent()) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
+ message = message.prev();
+ }
+ if (displayedMessage != null && selfAddressed) {
+ dismissNotification(account, counterpart, query);
+ }
+ }
+ }
+
+ final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
+ if (event != null && InvalidJid.hasValidFrom(original)) {
+ if (event.hasChild("items")) {
+ parseEvent(event, original.getFrom(), account);
+ } else if (event.hasChild("delete")) {
+ parseDeleteEvent(event, original.getFrom(), account);
+ }
+ }
+
+ final String nick = packet.findChildContent("nick", Namespace.NICK);
+ if (nick != null && InvalidJid.hasValidFrom(original)) {
+ Contact contact = account.getRoster().getContact(from);
+ if (contact.setPresenceName(nick)) {
+ mXmppConnectionService.getAvatarService().clear(contact);
+ }
+ }
+ }
+
+ private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query) {
+ Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
+ if (conversation != null && (query == null || query.isCatchup())) {
+ mXmppConnectionService.markRead(conversation); //TODO only mark messages read that are older than timestamp
+ }
+ }
+
+ private void processMessageReceipts(Account account, MessagePacket packet, MessageArchiveService.Query query) {
+ final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
+ final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
+ if (query == null) {
+ final ArrayList<String> receiptsNamespaces = new ArrayList<>();
+ if (markable) {
+ receiptsNamespaces.add("urn:xmpp:chat-markers:0");
+ }
+ if (request) {
+ receiptsNamespaces.add("urn:xmpp:receipts");
+ }
+ if (receiptsNamespaces.size() > 0) {
+ MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
+ packet,
+ receiptsNamespaces,
+ packet.getType());
+ mXmppConnectionService.sendMessagePacket(account, receipt);
+ }
+ } else if (query.isCatchup()) {
+ if (request) {
+ query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), packet.getId()));
+ }
+ }
+ }
+
+ private void activateGracePeriod(Account account) {
+ long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
+ account.activateGracePeriod(duration);
+ }
+
+ private class Invite {
+ final Jid jid;
+ final String password;
+ final Contact inviter;
+
+ Invite(Jid jid, String password, Contact inviter) {
+ this.jid = jid;
+ this.password = password;
+ this.inviter = inviter;
+ }
+
+ public boolean execute(Account account) {
+ if (jid != null) {
+ Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
+ if (!conversation.getMucOptions().online()) {
+ conversation.getMucOptions().setPassword(password);
+ mXmppConnectionService.databaseBackend.updateConversation(conversation);
+ mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription());
+ mXmppConnectionService.updateConversationUi();
+ }
+ return true;
+ }
+ return false;
+ }
+ }
}
@@ -285,11 +285,19 @@ public class XmppConnectionService extends Service {
}
}
}
- boolean needsUpdating = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true);
- needsUpdating |= account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, account.getXmppConnection().getFeatures().httpUpload(0));
- if (needsUpdating) {
+ boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true);
+ boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, account.getXmppConnection().getFeatures().httpUpload(0));
+ if (loggedInSuccessfully || gainedFeature) {
databaseBackend.updateAccount(account);
}
+
+ if (loggedInSuccessfully) {
+ if (!TextUtils.isEmpty(account.getDisplayName())) {
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": display name wasn't empty on first log in. publishing");
+ publishDisplayName(account);
+ }
+ }
+
account.getRoster().clearPresences();
mJingleConnectionManager.cancelInTransmission();
mQuickConversationsService.considerSyncBackground(false);
@@ -3828,14 +3836,17 @@ public class XmppConnectionService extends Service {
public void publishDisplayName(Account account) {
String displayName = account.getDisplayName();
- if (displayName != null && !displayName.isEmpty()) {
- IqPacket publish = mIqGenerator.publishNick(displayName);
- sendIqPacket(account, publish, (account1, packet) -> {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": could not publish nick");
- }
- });
- }
+ final IqPacket request;
+ if (TextUtils.isEmpty(displayName)) {
+ request = mIqGenerator.deleteNode(Namespace.NICK);
+ } else {
+ request = mIqGenerator.publishNick(displayName);
+ }
+ sendIqPacket(account, request, (account1, packet) -> {
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": unable to modify nick name "+packet.toString());
+ }
+ });
}
public ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair<String, String> key) {
@@ -74,1157 +74,1172 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
import rocks.xmpp.addr.Jid;
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
- OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
-
- public static final String EXTRA_OPENED_FROM_NOTIFICATION = "opened_from_notification";
-
- private static final int REQUEST_DATA_SAVER = 0xf244;
- private static final int REQUEST_CHANGE_STATUS = 0xee11;
-
- private AlertDialog mCaptchaDialog = null;
-
- private Jid jidToEdit;
- private boolean mInitMode = false;
- private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
- private boolean mShowOptions = false;
- private Account mAccount;
- private String messageFingerprint;
-
- private final PendingItem<PresenceTemplate> mPendingPresenceTemplate = new PendingItem<>();
-
- private boolean mFetchingAvatar = false;
-
- private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
-
- @Override
- public void onClick(final View v) {
- final String password = binding.accountPassword.getText().toString();
- final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED;
- final boolean accountInfoEdited = accountInfoEdited();
-
- if (!mInitMode && passwordChangedInMagicCreateMode()) {
- gotoChangePassword(password);
- return;
- }
- if (mInitMode && mAccount != null) {
- mAccount.setOption(Account.OPTION_DISABLED, false);
- }
- if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) {
- mAccount.setOption(Account.OPTION_DISABLED, false);
- if (!xmppConnectionService.updateAccount(mAccount)) {
- Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
- }
- return;
- }
- final boolean registerNewAccount = binding.accountRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
- if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) {
- binding.accountJidLayout.setError(getString(R.string.invalid_username));
- removeErrorsOnAllBut(binding.accountJidLayout);
- binding.accountJid.requestFocus();
- return;
- }
-
- XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
- boolean openRegistrationUrl = registerNewAccount && !accountInfoEdited && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB;
- boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
- final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
- URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null;
- if (url != null && !wasDisabled) {
- try {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
- return;
- } catch (ActivityNotFoundException e) {
- Toast.makeText(EditAccountActivity.this, R.string.application_found_to_open_website, Toast.LENGTH_SHORT).show();
- return;
- }
- }
-
- final Jid jid;
- try {
- if (mUsernameMode) {
- jid = Jid.of(binding.accountJid.getText().toString(), getUserModeDomain(), null);
- } else {
- jid = Jid.of(binding.accountJid.getText().toString());
- }
- } catch (final NullPointerException | IllegalArgumentException e) {
- if (mUsernameMode) {
- binding.accountJidLayout.setError(getString(R.string.invalid_username));
- } else {
- binding.accountJidLayout.setError(getString(R.string.invalid_jid));
- }
- binding.accountJid.requestFocus();
- removeErrorsOnAllBut(binding.accountJidLayout);
- return;
- }
- String hostname = null;
- int numericPort = 5222;
- if (mShowOptions) {
- hostname = binding.hostname.getText().toString().replaceAll("\\s", "");
- final String port = binding.port.getText().toString().replaceAll("\\s", "");
- if (hostname.contains(" ")) {
- binding.hostnameLayout.setError(getString(R.string.not_valid_hostname));
- binding.hostname.requestFocus();
- removeErrorsOnAllBut(binding.hostnameLayout);
- return;
- }
- try {
- numericPort = Integer.parseInt(port);
- if (numericPort < 0 || numericPort > 65535) {
- binding.portLayout.setError(getString(R.string.not_a_valid_port));
- removeErrorsOnAllBut(binding.portLayout);
- binding.port.requestFocus();
- return;
- }
-
- } catch (NumberFormatException e) {
- binding.portLayout.setError(getString(R.string.not_a_valid_port));
- removeErrorsOnAllBut(binding.portLayout);
- binding.port.requestFocus();
- return;
- }
- }
-
- if (jid.getLocal() == null) {
- if (mUsernameMode) {
- binding.accountJidLayout.setError(getString(R.string.invalid_username));
- } else {
- binding.accountJidLayout.setError(getString(R.string.invalid_jid));
- }
- removeErrorsOnAllBut(binding.accountJidLayout);
- binding.accountJid.requestFocus();
- return;
- }
- if (mAccount != null) {
- if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
- mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
- }
- mAccount.setJid(jid);
- mAccount.setPort(numericPort);
- mAccount.setHostname(hostname);
- binding.accountJidLayout.setError(null);
- mAccount.setPassword(password);
- mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
- if (!xmppConnectionService.updateAccount(mAccount)) {
- Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
- return;
- }
- } else {
- if (xmppConnectionService.findAccountByJid(jid) != null) {
- binding.accountJidLayout.setError(getString(R.string.account_already_exists));
- removeErrorsOnAllBut(binding.accountJidLayout);
- binding.accountJid.requestFocus();
- return;
- }
- mAccount = new Account(jid.asBareJid(), password);
- mAccount.setPort(numericPort);
- mAccount.setHostname(hostname);
- mAccount.setOption(Account.OPTION_USETLS, true);
- mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
- mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
- xmppConnectionService.createAccount(mAccount);
- }
- binding.hostnameLayout.setError(null);
- binding.portLayout.setError(null);
- if (mAccount.isEnabled()
- && !registerNewAccount
- && !mInitMode) {
- finish();
- } else {
- updateSaveButton();
- updateAccountInformation(true);
- }
-
- }
- };
- private final OnClickListener mCancelButtonClickListener = v -> {
- deleteAccountAndReturnIfNecessary();
- finish();
- };
- private Toast mFetchingMamPrefsToast;
- private String mSavedInstanceAccount;
- private boolean mSavedInstanceInit = false;
- private XmppUri pendingUri = null;
- private boolean mUseTor;
- private ActivityEditAccountBinding binding;
-
- public void refreshUiReal() {
- invalidateOptionsMenu();
- if (mAccount != null
- && mAccount.getStatus() != Account.State.ONLINE
- && mFetchingAvatar) {
- Intent intent = new Intent(this, StartConversationActivity.class);
- StartConversationActivity.addInviteUri(intent, getIntent());
- startActivity(intent);
- finish();
- } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
- if (!mFetchingAvatar) {
- mFetchingAvatar = true;
- xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
- }
- }
- if (mAccount != null) {
- updateAccountInformation(false);
- }
- updateSaveButton();
- }
-
- @Override
- public boolean onNavigateUp() {
- deleteAccountAndReturnIfNecessary();
- return super.onNavigateUp();
- }
-
- @Override
- public void onBackPressed() {
- deleteAccountAndReturnIfNecessary();
- super.onBackPressed();
- }
-
- private void deleteAccountAndReturnIfNecessary() {
- if (mInitMode && mAccount != null && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
- xmppConnectionService.deleteAccount(mAccount);
- }
-
- if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
- Intent intent = SignupUtils.getSignUpIntent(this);
- startActivity(intent);
- }
- }
-
- @Override
- public void onAccountUpdate() {
- refreshUi();
- }
-
- private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
-
- @Override
- public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
- finishInitialSetup(avatar);
- }
-
- @Override
- public void success(final Avatar avatar) {
- finishInitialSetup(avatar);
- }
-
- @Override
- public void error(final int errorCode, final Avatar avatar) {
- finishInitialSetup(avatar);
- }
- };
- private final TextWatcher mTextWatcher = new TextWatcher() {
-
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
- updatePortLayout();
- updateSaveButton();
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
-
- }
- };
-
- private View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean b) {
- EditText et = (EditText) view;
- if (b) {
- int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id;
- if (view.getId() == R.id.hostname) {
- resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
- }
- final int res = resId;
- new Handler().postDelayed(() -> et.setHint(res), 200);
- } else {
- et.setHint(null);
- }
- }
- };
-
-
- private final OnClickListener mAvatarClickListener = new OnClickListener() {
- @Override
- public void onClick(final View view) {
- if (mAccount != null) {
- final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
- intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
- startActivity(intent);
- }
- }
- };
-
- protected void finishInitialSetup(final Avatar avatar) {
- runOnUiThread(() -> {
- SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this);
- final Intent intent;
- final XmppConnection connection = mAccount.getXmppConnection();
- final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
- if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
- intent = new Intent(getApplicationContext(), StartConversationActivity.class);
- if (wasFirstAccount) {
- intent.putExtra("init", true);
- }
- } else {
- intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
- intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
- intent.putExtra("setup", true);
- }
- if (wasFirstAccount) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- }
- StartConversationActivity.addInviteUri(intent, getIntent());
- startActivity(intent);
- finish();
- });
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
- updateAccountInformation(mAccount == null);
- }
- if (requestCode == REQUEST_CHANGE_STATUS) {
- PresenceTemplate template = mPendingPresenceTemplate.pop();
- if (template != null && resultCode == Activity.RESULT_OK) {
- generateSignature(data, template);
- } else {
- Log.d(Config.LOGTAG, "pgp result not ok");
- }
- }
- }
-
- @Override
- protected void processFingerprintVerification(XmppUri uri) {
- processFingerprintVerification(uri, true);
- }
-
-
- protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
- if (mAccount != null && mAccount.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
- if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
- Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
- updateAccountInformation(false);
- }
- } else if (showWarningToast) {
- Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
- }
- }
-
- private void updatePortLayout() {
- String hostname = this.binding.hostname.getText().toString();
- this.binding.portLayout.setEnabled(!TextUtils.isEmpty(hostname));
- }
-
- protected void updateSaveButton() {
- boolean accountInfoEdited = accountInfoEdited();
-
- if (!mInitMode && passwordChangedInMagicCreateMode()) {
- this.binding.saveButton.setText(R.string.change_password);
- this.binding.saveButton.setEnabled(true);
- } else if (accountInfoEdited && !mInitMode) {
- this.binding.saveButton.setText(R.string.save);
- this.binding.saveButton.setEnabled(true);
- } else if (mAccount != null
- && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) {
- this.binding.saveButton.setEnabled(false);
- this.binding.saveButton.setText(R.string.account_status_connecting);
- } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
- this.binding.saveButton.setEnabled(true);
- this.binding.saveButton.setText(R.string.enable);
- } else {
- this.binding.saveButton.setEnabled(true);
- if (!mInitMode) {
- if (mAccount != null && mAccount.isOnlineAndConnected()) {
- this.binding.saveButton.setText(R.string.save);
- if (!accountInfoEdited) {
- this.binding.saveButton.setEnabled(false);
- }
- } else {
- XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
- URL url = connection != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED ? connection.getRedirectionUrl() : null;
- if (url != null) {
- this.binding.saveButton.setText(R.string.open_website);
- } else {
- this.binding.saveButton.setText(R.string.connect);
- }
- }
- } else {
- XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
- URL url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null;
- if (url != null && this.binding.accountRegisterNew.isChecked() && !accountInfoEdited) {
- this.binding.saveButton.setText(R.string.open_website);
- } else {
- this.binding.saveButton.setText(R.string.next);
- }
- }
- }
- }
-
- protected boolean accountInfoEdited() {
- if (this.mAccount == null) {
- return false;
- }
- return jidEdited() ||
- !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString()) ||
- !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString()) ||
- !String.valueOf(this.mAccount.getPort()).equals(this.binding.port.getText().toString());
- }
-
- protected boolean jidEdited() {
- final String unmodified;
- if (mUsernameMode) {
- unmodified = this.mAccount.getJid().getLocal();
- } else {
- unmodified = this.mAccount.getJid().asBareJid().toString();
- }
- return !unmodified.equals(this.binding.accountJid.getText().toString());
- }
-
- protected boolean passwordChangedInMagicCreateMode() {
- return mAccount != null
- && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
- && !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString())
- && !this.jidEdited()
- && mAccount.isOnlineAndConnected();
- }
-
- @Override
- protected String getShareableUri(boolean http) {
- if (mAccount != null) {
- return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
- } else {
- return null;
- }
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- this.mSavedInstanceAccount = savedInstanceState.getString("account");
- this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
- }
- this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
- setSupportActionBar((Toolbar) binding.toolbar);
- binding.accountJid.addTextChangedListener(this.mTextWatcher);
- binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
- this.binding.accountPassword.addTextChangedListener(this.mTextWatcher);
-
- this.binding.avater.setOnClickListener(this.mAvatarClickListener);
- this.binding.hostname.addTextChangedListener(mTextWatcher);
- this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
- this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
- this.binding.port.setText("5222");
- this.binding.port.addTextChangedListener(mTextWatcher);
- this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
- this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
- if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
- changeMoreTableVisibility(true);
- }
- final OnCheckedChangeListener OnCheckedShowConfirmPassword = (buttonView, isChecked) -> updateSaveButton();
- this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
- if (Config.DISALLOW_REGISTRATION_IN_UI) {
- this.binding.accountRegisterNew.setVisibility(View.GONE);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- super.onCreateOptionsMenu(menu);
- getMenuInflater().inflate(R.menu.editaccount, menu);
- final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
- final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
- final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
- final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
- final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
- final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
- final MenuItem share = menu.findItem(R.id.action_share);
- renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
-
- share.setVisible(mAccount != null && !mInitMode);
-
- if (mAccount != null && mAccount.isOnlineAndConnected()) {
- if (!mAccount.getXmppConnection().getFeatures().blocking()) {
- showBlocklist.setVisible(false);
- }
-
- if (!mAccount.getXmppConnection().getFeatures().register()) {
- changePassword.setVisible(false);
- }
- mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
- changePresence.setVisible(!mInitMode);
- } else {
- showBlocklist.setVisible(false);
- showMoreInfo.setVisible(false);
- changePassword.setVisible(false);
- mamPrefs.setVisible(false);
- changePresence.setVisible(false);
- }
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
- if (showMoreInfo.isVisible()) {
- showMoreInfo.setChecked(binding.serverInfoMore.getVisibility() == View.VISIBLE);
- }
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- final Intent intent = getIntent();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- } else if (intent != null) {
- try {
- this.jidToEdit = Jid.of(intent.getStringExtra("jid"));
- } catch (final IllegalArgumentException | NullPointerException ignored) {
- this.jidToEdit = null;
- }
- if (jidToEdit != null && intent.getData() != null && intent.getBooleanExtra("scanned", false)) {
- final XmppUri uri = new XmppUri(intent.getData());
- if (xmppConnectionServiceBound) {
- processFingerprintVerification(uri, false);
- } else {
- this.pendingUri = uri;
- }
- }
- boolean init = intent.getBooleanExtra("init", false);
- boolean openedFromNotification = intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false);
- this.mInitMode = init || this.jidToEdit == null;
- this.messageFingerprint = intent.getStringExtra("fingerprint");
- if (!mInitMode) {
- this.binding.accountRegisterNew.setVisibility(View.GONE);
- setTitle(getString(R.string.account_details));
- configureActionBar(getSupportActionBar(), !openedFromNotification);
- } else {
- this.binding.avater.setVisibility(View.GONE);
- configureActionBar(getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null));
- setTitle(R.string.action_add_account);
- }
- }
- SharedPreferences preferences = getPreferences();
- mUseTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor));
- this.mShowOptions = mUseTor || (QuickConversationsService.isConversations() && preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options)));
- this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void onNewIntent(Intent intent) {
- if (intent != null && intent.getData() != null) {
- final XmppUri uri = new XmppUri(intent.getData());
- if (xmppConnectionServiceBound) {
- processFingerprintVerification(uri, false);
- } else {
- this.pendingUri = uri;
- }
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle savedInstanceState) {
- if (mAccount != null) {
- savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString());
- savedInstanceState.putBoolean("initMode", mInitMode);
- savedInstanceState.putBoolean("showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE);
- }
- super.onSaveInstanceState(savedInstanceState);
- }
-
- protected void onBackendConnected() {
- boolean init = true;
- if (mSavedInstanceAccount != null) {
- try {
- this.mAccount = xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount));
- this.mInitMode = mSavedInstanceInit;
- init = false;
- } catch (IllegalArgumentException e) {
- this.mAccount = null;
- }
-
- } else if (this.jidToEdit != null) {
- this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
- }
-
- if (mAccount != null) {
- this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
- this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
- if (this.mAccount.getPrivateKeyAlias() != null) {
- this.binding.accountPassword.setHint(R.string.authenticate_with_certificate);
- if (this.mInitMode) {
- this.binding.accountPassword.requestFocus();
- }
- }
- if (mPendingFingerprintVerificationUri != null) {
- processFingerprintVerification(mPendingFingerprintVerificationUri, false);
- mPendingFingerprintVerificationUri = null;
- }
- updateAccountInformation(init);
- }
-
-
- if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
- this.binding.cancelButton.setEnabled(false);
- }
- if (mUsernameMode) {
- this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
- this.binding.accountJid.setHint(R.string.username_hint);
- } else {
- final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
- R.layout.simple_list_item,
- xmppConnectionService.getKnownHosts());
- this.binding.accountJid.setAdapter(mKnownHostsAdapter);
- }
-
- if (pendingUri != null) {
- processFingerprintVerification(pendingUri, false);
- pendingUri = null;
- }
- updatePortLayout();
- updateSaveButton();
- invalidateOptionsMenu();
- }
-
- private String getUserModeDomain() {
- if (mAccount != null && mAccount.getJid().getDomain() != null) {
- return mAccount.getJid().getDomain();
- } else {
- return Config.DOMAIN_LOCK;
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (MenuDoubleTabUtil.shouldIgnoreTap()) {
- return false;
- }
- switch (item.getItemId()) {
- case R.id.action_show_block_list:
- final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
- showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
- startActivity(showBlocklistIntent);
- break;
- case R.id.action_server_info_show_more:
- changeMoreTableVisibility(!item.isChecked());
- break;
- case R.id.action_share_barcode:
- shareBarcode();
- break;
- case R.id.action_share_http:
- shareLink(true);
- break;
- case R.id.action_share_uri:
- shareLink(false);
- break;
- case R.id.action_change_password_on_server:
- gotoChangePassword(null);
- break;
- case R.id.action_mam_prefs:
- editMamPrefs();
- break;
- case R.id.action_renew_certificate:
- renewCertificate();
- break;
- case R.id.action_change_presence:
- changePresence();
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void shareBarcode() {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setType("image/png");
- startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
- }
-
- private void changeMoreTableVisibility(boolean visible) {
- binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
- private void gotoChangePassword(String newPassword) {
- final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
- changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
- if (newPassword != null) {
- changePasswordIntent.putExtra("password", newPassword);
- }
- startActivity(changePasswordIntent);
- }
-
- private void renewCertificate() {
- KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
- }
-
- private void changePresence() {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean manualStatus = sharedPreferences.getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
- String current = mAccount.getPresenceStatusMessage();
- if (current != null && !current.trim().isEmpty()) {
- binding.statusMessage.append(current);
- }
- setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
- binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
- List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
- PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.simple_list_item, templates);
- binding.statusMessage.setAdapter(presenceTemplateAdapter);
- binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> {
- PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position);
- setAvailabilityRadioButton(template.getStatus(), binding);
- });
- builder.setTitle(R.string.edit_status_message_title);
- builder.setView(binding.getRoot());
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
- PresenceTemplate template = new PresenceTemplate(getAvailabilityRadioButton(binding), binding.statusMessage.getText().toString().trim());
- if (mAccount.getPgpId() != 0 && hasPgp()) {
- generateSignature(null, template);
- } else {
- xmppConnectionService.changeStatus(mAccount, template, null);
- }
- });
- builder.create().show();
- }
-
- private void generateSignature(Intent intent, PresenceTemplate template) {
- xmppConnectionService.getPgpEngine().generateSignature(intent, mAccount, template.getStatusMessage(), new UiCallback<String>() {
- @Override
- public void success(String signature) {
- xmppConnectionService.changeStatus(mAccount, template, signature);
- }
-
- @Override
- public void error(int errorCode, String object) {
-
- }
-
- @Override
- public void userInputRequried(PendingIntent pi, String object) {
- mPendingPresenceTemplate.push(template);
- try {
- startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0);
- } catch (final IntentSender.SendIntentException ignored) {
- }
- }
- });
- }
-
- private static void setAvailabilityRadioButton(Presence.Status status, DialogPresenceBinding binding) {
- if (status == null) {
- binding.online.setChecked(true);
- return;
- }
- switch (status) {
- case DND:
- binding.dnd.setChecked(true);
- break;
- case XA:
- binding.xa.setChecked(true);
- break;
- case AWAY:
- binding.xa.setChecked(true);
- break;
- default:
- binding.online.setChecked(true);
- }
- }
-
- private static Presence.Status getAvailabilityRadioButton(DialogPresenceBinding binding) {
- if (binding.dnd.isChecked()) {
- return Presence.Status.DND;
- } else if (binding.xa.isChecked()) {
- return Presence.Status.XA;
- } else if (binding.away.isChecked()) {
- return Presence.Status.AWAY;
- } else {
- return Presence.Status.ONLINE;
- }
- }
-
- @Override
- public void alias(String alias) {
- if (alias != null) {
- xmppConnectionService.updateKeyInAccount(mAccount, alias);
- }
- }
-
- private void updateAccountInformation(boolean init) {
- if (init) {
- this.binding.accountJid.getEditableText().clear();
- if (mUsernameMode) {
- this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal());
- } else {
- this.binding.accountJid.getEditableText().append(this.mAccount.getJid().asBareJid().toString());
- }
- this.binding.accountPassword.getEditableText().clear();
- this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
- this.binding.hostname.setText("");
- this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
- this.binding.port.setText("");
- this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
- this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
-
- }
-
- final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) && QuickConversationsService.isConversations();
- this.binding.accountJid.setEnabled(editable);
- this.binding.accountJid.setFocusable(editable);
- this.binding.accountJid.setFocusableInTouchMode(editable);
-
-
- if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
- this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(true);
- } else {
- this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(false);
- }
-
- if (!mInitMode) {
- this.binding.avater.setVisibility(View.VISIBLE);
- this.binding.avater.setImageBitmap(avatarService().get(this.mAccount, (int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
- } else {
- this.binding.avater.setVisibility(View.GONE);
- }
- this.binding.accountRegisterNew.setChecked(this.mAccount.isOptionSet(Account.OPTION_REGISTER));
- if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
- if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
- ActionBar actionBar = getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(R.string.create_account);
- }
- }
- this.binding.accountRegisterNew.setVisibility(View.GONE);
- } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
- this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
- } else {
- this.binding.accountRegisterNew.setVisibility(View.GONE);
- }
- if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
- Features features = this.mAccount.getXmppConnection().getFeatures();
- this.binding.stats.setVisibility(View.VISIBLE);
- boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
- boolean showDataSaverWarning = isAffectedByDataSaver();
- showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
- this.binding.sessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
- .getLastSessionEstablished()));
- if (features.rosterVersioning()) {
- this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
- }
- if (features.carbons()) {
- this.binding.serverInfoCarbons.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
- }
- if (features.mam()) {
- this.binding.serverInfoMam.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
- }
- if (features.csi()) {
- this.binding.serverInfoCsi.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
- }
- if (features.blocking()) {
- this.binding.serverInfoBlocking.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
- }
- if (features.sm()) {
- this.binding.serverInfoSm.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
- }
- if (features.pep()) {
- AxolotlService axolotlService = this.mAccount.getAxolotlService();
- if (axolotlService != null && axolotlService.isPepBroken()) {
- this.binding.serverInfoPep.setText(R.string.server_info_broken);
- } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
- this.binding.serverInfoPep.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoPep.setText(R.string.server_info_partial);
- }
- } else {
- this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
- }
- if (features.httpUpload(0)) {
- this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
- } else if (features.p1S3FileTransfer()) {
- this.binding.serverInfoHttpUploadDescription.setText(R.string.p1_s3_filetransfer);
- this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
- }
-
- this.binding.pushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
-
- if (xmppConnectionService.getPushManagementService().available(mAccount)) {
- this.binding.serverInfoPush.setText(R.string.server_info_available);
- } else {
- this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
- }
- final long pgpKeyId = this.mAccount.getPgpId();
- if (pgpKeyId != 0 && Config.supportOpenPgp()) {
- OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
- OnClickListener delete = view -> showDeletePgpDialog();
- this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
- this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
- this.binding.pgpFingerprint.setOnClickListener(openPgp);
- if ("pgp".equals(messageFingerprint)) {
- this.binding.pgpFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
- }
- this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
- this.binding.actionDeletePgp.setOnClickListener(delete);
- } else {
- this.binding.pgpFingerprintBox.setVisibility(View.GONE);
- }
- final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
- if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
- this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
- if (ownAxolotlFingerprint.equals(messageFingerprint)) {
- this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
- this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message);
- } else {
- this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption);
- this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
- }
- this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
- this.binding.actionCopyAxolotlToClipboard.setVisibility(View.VISIBLE);
- this.binding.actionCopyAxolotlToClipboard.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint));
- } else {
- this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
- }
- boolean hasKeys = false;
- binding.otherDeviceKeys.removeAllViews();
- for (XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
- if (!session.getTrust().isCompromised()) {
- boolean highlight = session.getFingerprint().equals(messageFingerprint);
- addFingerprintRow(binding.otherDeviceKeys, session, highlight);
- hasKeys = true;
- }
- }
- if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices
- this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
- Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
- if (otherDevices == null || otherDevices.isEmpty()) {
- binding.clearDevices.setVisibility(View.GONE);
- } else {
- binding.clearDevices.setVisibility(View.VISIBLE);
- }
- } else {
- this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
- }
- } else {
- final TextInputLayout errorLayout;
- if (this.mAccount.errorStatus()) {
- if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
- errorLayout = this.binding.accountPasswordLayout;
- } else if (mShowOptions
- && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
- && this.binding.hostname.getText().length() > 0) {
- errorLayout = this.binding.hostnameLayout;
- } else {
- errorLayout = this.binding.accountJidLayout;
- }
- errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
- if (init || !accountInfoEdited()) {
- errorLayout.requestFocus();
- }
- } else {
- errorLayout = null;
- }
- removeErrorsOnAllBut(errorLayout);
- this.binding.stats.setVisibility(View.GONE);
- this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
- }
- }
-
- private void removeErrorsOnAllBut(TextInputLayout exception) {
- if (this.binding.accountJidLayout != exception) {
- this.binding.accountJidLayout.setErrorEnabled(false);
- this.binding.accountJidLayout.setError(null);
- }
- if (this.binding.accountPasswordLayout != exception) {
- this.binding.accountPasswordLayout.setErrorEnabled(false);
- this.binding.accountPasswordLayout.setError(null);
- }
- if (this.binding.hostnameLayout != exception) {
- this.binding.hostnameLayout.setErrorEnabled(false);
- this.binding.hostnameLayout.setError(null);
- }
- if (this.binding.portLayout != exception) {
- this.binding.portLayout.setErrorEnabled(false);
- this.binding.portLayout.setError(null);
- }
- }
-
- private void showDeletePgpDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.unpublish_pgp);
- builder.setMessage(R.string.unpublish_pgp_message);
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> {
- mAccount.setPgpSignId(0);
- mAccount.unsetPgpSignature();
- xmppConnectionService.databaseBackend.updateAccount(mAccount);
- xmppConnectionService.sendPresence(mAccount);
- refreshUiReal();
- });
- builder.create().show();
- }
-
- private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
- this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
- if (showDataSaverWarning) {
- this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
- this.binding.osOptimizationBody.setText(R.string.data_saver_enabled_explained);
- this.binding.osOptimizationDisable.setText(R.string.allow);
- this.binding.osOptimizationDisable.setOnClickListener(v -> {
- Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
- Uri uri = Uri.parse("package:" + getPackageName());
- intent.setData(uri);
- try {
- startActivityForResult(intent, REQUEST_DATA_SAVER);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
- }
- });
- } else if (showBatteryWarning) {
- this.binding.osOptimizationDisable.setText(R.string.disable);
- this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
- this.binding.osOptimizationBody.setText(R.string.battery_optimizations_enabled_explained);
- this.binding.osOptimizationDisable.setOnClickListener(v -> {
- Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
- Uri uri = Uri.parse("package:" + getPackageName());
- intent.setData(uri);
- try {
- startActivityForResult(intent, REQUEST_BATTERY_OP);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
- }
- });
- }
- }
-
- public void showWipePepDialog() {
- Builder builder = new Builder(this);
- builder.setTitle(getString(R.string.clear_other_devices));
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getString(R.string.clear_other_devices_desc));
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.accept),
- (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
- builder.create().show();
- }
-
- private void editMamPrefs() {
- this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
- this.mFetchingMamPrefsToast.show();
- xmppConnectionService.fetchMamPreferences(mAccount, this);
- }
-
- @Override
- public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
- refreshUi();
- }
-
- @Override
- public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
- runOnUiThread(() -> {
- if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
- mCaptchaDialog.dismiss();
- }
- final Builder builder = new Builder(EditAccountActivity.this);
- final View view = getLayoutInflater().inflate(R.layout.captcha, null);
- final ImageView imageView = view.findViewById(R.id.captcha);
- final EditText input = view.findViewById(R.id.input);
- imageView.setImageBitmap(captcha);
-
- builder.setTitle(getString(R.string.captcha_required));
- builder.setView(view);
-
- builder.setPositiveButton(getString(R.string.ok),
- (dialog, which) -> {
- String rc = input.getText().toString();
- data.put("username", account.getUsername());
- data.put("password", account.getPassword());
- data.put("ocr", rc);
- data.submit();
-
- if (xmppConnectionServiceBound) {
- xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, id, data);
- }
- });
- builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
- if (xmppConnectionService != null) {
- xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
- }
- });
-
- builder.setOnCancelListener(dialog -> {
- if (xmppConnectionService != null) {
- xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
- }
- });
- mCaptchaDialog = builder.create();
- mCaptchaDialog.show();
- input.requestFocus();
- });
- }
-
- public void onShowErrorToast(final int resId) {
- runOnUiThread(() -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
- }
-
- @Override
- public void onPreferencesFetched(final Element prefs) {
- runOnUiThread(() -> {
- if (mFetchingMamPrefsToast != null) {
- mFetchingMamPrefsToast.cancel();
- }
- Builder builder = new Builder(EditAccountActivity.this);
- builder.setTitle(R.string.server_side_mam_prefs);
- String defaultAttr = prefs.getAttribute("default");
- final List<String> defaults = Arrays.asList("never", "roster", "always");
- final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
- builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.ok, (dialog, which) -> {
- prefs.setAttribute("default", defaults.get(choice.get()));
- xmppConnectionService.pushMamPreferences(mAccount, prefs);
- });
- builder.create().show();
- });
- }
-
- @Override
- public void onPreferencesFetchFailed() {
- runOnUiThread(() -> {
- if (mFetchingMamPrefsToast != null) {
- mFetchingMamPrefsToast.cancel();
- }
- Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show();
- });
- }
-
- @Override
- public void OnUpdateBlocklist(Status status) {
- refreshUi();
- }
+ OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
+
+ public static final String EXTRA_OPENED_FROM_NOTIFICATION = "opened_from_notification";
+
+ private static final int REQUEST_DATA_SAVER = 0xf244;
+ private static final int REQUEST_CHANGE_STATUS = 0xee11;
+ private final PendingItem<PresenceTemplate> mPendingPresenceTemplate = new PendingItem<>();
+ private AlertDialog mCaptchaDialog = null;
+ private Jid jidToEdit;
+ private boolean mInitMode = false;
+ private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
+ private boolean mShowOptions = false;
+ private Account mAccount;
+ private final OnClickListener mCancelButtonClickListener = v -> {
+ deleteAccountAndReturnIfNecessary();
+ finish();
+ };
+ private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
+
+ @Override
+ public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+
+ @Override
+ public void success(final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+
+ @Override
+ public void error(final int errorCode, final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+ };
+ private final OnClickListener mAvatarClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ if (mAccount != null) {
+ final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
+ startActivity(intent);
+ }
+ }
+ };
+ private String messageFingerprint;
+ private boolean mFetchingAvatar = false;
+ private Toast mFetchingMamPrefsToast;
+ private String mSavedInstanceAccount;
+ private boolean mSavedInstanceInit = false;
+ private XmppUri pendingUri = null;
+ private boolean mUseTor;
+ private ActivityEditAccountBinding binding;
+ private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ final String password = binding.accountPassword.getText().toString();
+ final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED;
+ final boolean accountInfoEdited = accountInfoEdited();
+
+ if (!mInitMode && passwordChangedInMagicCreateMode()) {
+ gotoChangePassword(password);
+ return;
+ }
+ if (mInitMode && mAccount != null) {
+ mAccount.setOption(Account.OPTION_DISABLED, false);
+ }
+ if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) {
+ mAccount.setOption(Account.OPTION_DISABLED, false);
+ if (!xmppConnectionService.updateAccount(mAccount)) {
+ Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
+ }
+ return;
+ }
+ final boolean registerNewAccount = binding.accountRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
+ if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) {
+ binding.accountJidLayout.setError(getString(R.string.invalid_username));
+ removeErrorsOnAllBut(binding.accountJidLayout);
+ binding.accountJid.requestFocus();
+ return;
+ }
+
+ XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
+ boolean openRegistrationUrl = registerNewAccount && !accountInfoEdited && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB;
+ boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
+ final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
+ URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null;
+ if (url != null && !wasDisabled) {
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
+ return;
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(EditAccountActivity.this, R.string.application_found_to_open_website, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+
+ final Jid jid;
+ try {
+ if (mUsernameMode) {
+ jid = Jid.of(binding.accountJid.getText().toString(), getUserModeDomain(), null);
+ } else {
+ jid = Jid.of(binding.accountJid.getText().toString());
+ }
+ } catch (final NullPointerException | IllegalArgumentException e) {
+ if (mUsernameMode) {
+ binding.accountJidLayout.setError(getString(R.string.invalid_username));
+ } else {
+ binding.accountJidLayout.setError(getString(R.string.invalid_jid));
+ }
+ binding.accountJid.requestFocus();
+ removeErrorsOnAllBut(binding.accountJidLayout);
+ return;
+ }
+ String hostname = null;
+ int numericPort = 5222;
+ if (mShowOptions) {
+ hostname = binding.hostname.getText().toString().replaceAll("\\s", "");
+ final String port = binding.port.getText().toString().replaceAll("\\s", "");
+ if (hostname.contains(" ")) {
+ binding.hostnameLayout.setError(getString(R.string.not_valid_hostname));
+ binding.hostname.requestFocus();
+ removeErrorsOnAllBut(binding.hostnameLayout);
+ return;
+ }
+ try {
+ numericPort = Integer.parseInt(port);
+ if (numericPort < 0 || numericPort > 65535) {
+ binding.portLayout.setError(getString(R.string.not_a_valid_port));
+ removeErrorsOnAllBut(binding.portLayout);
+ binding.port.requestFocus();
+ return;
+ }
+
+ } catch (NumberFormatException e) {
+ binding.portLayout.setError(getString(R.string.not_a_valid_port));
+ removeErrorsOnAllBut(binding.portLayout);
+ binding.port.requestFocus();
+ return;
+ }
+ }
+
+ if (jid.getLocal() == null) {
+ if (mUsernameMode) {
+ binding.accountJidLayout.setError(getString(R.string.invalid_username));
+ } else {
+ binding.accountJidLayout.setError(getString(R.string.invalid_jid));
+ }
+ removeErrorsOnAllBut(binding.accountJidLayout);
+ binding.accountJid.requestFocus();
+ return;
+ }
+ if (mAccount != null) {
+ if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
+ mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
+ }
+ mAccount.setJid(jid);
+ mAccount.setPort(numericPort);
+ mAccount.setHostname(hostname);
+ binding.accountJidLayout.setError(null);
+ mAccount.setPassword(password);
+ mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+ if (!xmppConnectionService.updateAccount(mAccount)) {
+ Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ } else {
+ if (xmppConnectionService.findAccountByJid(jid) != null) {
+ binding.accountJidLayout.setError(getString(R.string.account_already_exists));
+ removeErrorsOnAllBut(binding.accountJidLayout);
+ binding.accountJid.requestFocus();
+ return;
+ }
+ mAccount = new Account(jid.asBareJid(), password);
+ mAccount.setPort(numericPort);
+ mAccount.setHostname(hostname);
+ mAccount.setOption(Account.OPTION_USETLS, true);
+ mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
+ mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+ xmppConnectionService.createAccount(mAccount);
+ }
+ binding.hostnameLayout.setError(null);
+ binding.portLayout.setError(null);
+ if (mAccount.isEnabled()
+ && !registerNewAccount
+ && !mInitMode) {
+ finish();
+ } else {
+ updateSaveButton();
+ updateAccountInformation(true);
+ }
+
+ }
+ };
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
+ updatePortLayout();
+ updateSaveButton();
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+
+ }
+ };
+ private View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean b) {
+ EditText et = (EditText) view;
+ if (b) {
+ int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id;
+ if (view.getId() == R.id.hostname) {
+ resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
+ }
+ final int res = resId;
+ new Handler().postDelayed(() -> et.setHint(res), 200);
+ } else {
+ et.setHint(null);
+ }
+ }
+ };
+
+ private static void setAvailabilityRadioButton(Presence.Status status, DialogPresenceBinding binding) {
+ if (status == null) {
+ binding.online.setChecked(true);
+ return;
+ }
+ switch (status) {
+ case DND:
+ binding.dnd.setChecked(true);
+ break;
+ case XA:
+ binding.xa.setChecked(true);
+ break;
+ case AWAY:
+ binding.xa.setChecked(true);
+ break;
+ default:
+ binding.online.setChecked(true);
+ }
+ }
+
+ private static Presence.Status getAvailabilityRadioButton(DialogPresenceBinding binding) {
+ if (binding.dnd.isChecked()) {
+ return Presence.Status.DND;
+ } else if (binding.xa.isChecked()) {
+ return Presence.Status.XA;
+ } else if (binding.away.isChecked()) {
+ return Presence.Status.AWAY;
+ } else {
+ return Presence.Status.ONLINE;
+ }
+ }
+
+ public void refreshUiReal() {
+ invalidateOptionsMenu();
+ if (mAccount != null
+ && mAccount.getStatus() != Account.State.ONLINE
+ && mFetchingAvatar) {
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ startActivity(intent);
+ finish();
+ } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
+ if (!mFetchingAvatar) {
+ mFetchingAvatar = true;
+ xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
+ }
+ }
+ if (mAccount != null) {
+ updateAccountInformation(false);
+ }
+ updateSaveButton();
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ deleteAccountAndReturnIfNecessary();
+ return super.onNavigateUp();
+ }
+
+ @Override
+ public void onBackPressed() {
+ deleteAccountAndReturnIfNecessary();
+ super.onBackPressed();
+ }
+
+ private void deleteAccountAndReturnIfNecessary() {
+ if (mInitMode && mAccount != null && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
+ xmppConnectionService.deleteAccount(mAccount);
+ }
+
+ if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
+ Intent intent = SignupUtils.getSignUpIntent(this);
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ protected void finishInitialSetup(final Avatar avatar) {
+ runOnUiThread(() -> {
+ SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this);
+ final Intent intent;
+ final XmppConnection connection = mAccount.getXmppConnection();
+ final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
+ if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
+ intent = new Intent(getApplicationContext(), StartConversationActivity.class);
+ if (wasFirstAccount) {
+ intent.putExtra("init", true);
+ }
+ } else {
+ intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
+ intent.putExtra("setup", true);
+ }
+ if (wasFirstAccount) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ startActivity(intent);
+ finish();
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
+ updateAccountInformation(mAccount == null);
+ }
+ if (requestCode == REQUEST_CHANGE_STATUS) {
+ PresenceTemplate template = mPendingPresenceTemplate.pop();
+ if (template != null && resultCode == Activity.RESULT_OK) {
+ generateSignature(data, template);
+ } else {
+ Log.d(Config.LOGTAG, "pgp result not ok");
+ }
+ }
+ }
+
+ @Override
+ protected void processFingerprintVerification(XmppUri uri) {
+ processFingerprintVerification(uri, true);
+ }
+
+ protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
+ if (mAccount != null && mAccount.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
+ if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
+ Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
+ updateAccountInformation(false);
+ }
+ } else if (showWarningToast) {
+ Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void updatePortLayout() {
+ String hostname = this.binding.hostname.getText().toString();
+ this.binding.portLayout.setEnabled(!TextUtils.isEmpty(hostname));
+ }
+
+ protected void updateSaveButton() {
+ boolean accountInfoEdited = accountInfoEdited();
+
+ if (!mInitMode && passwordChangedInMagicCreateMode()) {
+ this.binding.saveButton.setText(R.string.change_password);
+ this.binding.saveButton.setEnabled(true);
+ } else if (accountInfoEdited && !mInitMode) {
+ this.binding.saveButton.setText(R.string.save);
+ this.binding.saveButton.setEnabled(true);
+ } else if (mAccount != null
+ && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) {
+ this.binding.saveButton.setEnabled(false);
+ this.binding.saveButton.setText(R.string.account_status_connecting);
+ } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
+ this.binding.saveButton.setEnabled(true);
+ this.binding.saveButton.setText(R.string.enable);
+ } else {
+ this.binding.saveButton.setEnabled(true);
+ if (!mInitMode) {
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ this.binding.saveButton.setText(R.string.save);
+ if (!accountInfoEdited) {
+ this.binding.saveButton.setEnabled(false);
+ }
+ } else {
+ XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
+ URL url = connection != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED ? connection.getRedirectionUrl() : null;
+ if (url != null) {
+ this.binding.saveButton.setText(R.string.open_website);
+ } else {
+ this.binding.saveButton.setText(R.string.connect);
+ }
+ }
+ } else {
+ XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
+ URL url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null;
+ if (url != null && this.binding.accountRegisterNew.isChecked() && !accountInfoEdited) {
+ this.binding.saveButton.setText(R.string.open_website);
+ } else {
+ this.binding.saveButton.setText(R.string.next);
+ }
+ }
+ }
+ }
+
+ protected boolean accountInfoEdited() {
+ if (this.mAccount == null) {
+ return false;
+ }
+ return jidEdited() ||
+ !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString()) ||
+ !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString()) ||
+ !String.valueOf(this.mAccount.getPort()).equals(this.binding.port.getText().toString());
+ }
+
+ protected boolean jidEdited() {
+ final String unmodified;
+ if (mUsernameMode) {
+ unmodified = this.mAccount.getJid().getLocal();
+ } else {
+ unmodified = this.mAccount.getJid().asBareJid().toString();
+ }
+ return !unmodified.equals(this.binding.accountJid.getText().toString());
+ }
+
+ protected boolean passwordChangedInMagicCreateMode() {
+ return mAccount != null
+ && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
+ && !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString())
+ && !this.jidEdited()
+ && mAccount.isOnlineAndConnected();
+ }
+
+ @Override
+ protected String getShareableUri(boolean http) {
+ if (mAccount != null) {
+ return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ this.mSavedInstanceAccount = savedInstanceState.getString("account");
+ this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
+ }
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
+ setSupportActionBar((Toolbar) binding.toolbar);
+ binding.accountJid.addTextChangedListener(this.mTextWatcher);
+ binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
+ this.binding.accountPassword.addTextChangedListener(this.mTextWatcher);
+
+ this.binding.avater.setOnClickListener(this.mAvatarClickListener);
+ this.binding.hostname.addTextChangedListener(mTextWatcher);
+ this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
+ this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
+ this.binding.port.setText("5222");
+ this.binding.port.addTextChangedListener(mTextWatcher);
+ this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
+ this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
+ if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
+ changeMoreTableVisibility(true);
+ }
+ final OnCheckedChangeListener OnCheckedShowConfirmPassword = (buttonView, isChecked) -> updateSaveButton();
+ this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
+ if (Config.DISALLOW_REGISTRATION_IN_UI) {
+ this.binding.accountRegisterNew.setVisibility(View.GONE);
+ }
+ this.binding.actionEditYourName.setOnClickListener(this::onEditYourNameClicked);
+ }
+
+ private void onEditYourNameClicked(View view) {
+ quickEdit(mAccount.getDisplayName(), R.string.your_name, value -> {
+ final String displayName = value.trim();
+ updateDisplayName(displayName);
+ mAccount.setDisplayName(displayName);
+ xmppConnectionService.publishDisplayName(mAccount);
+ return null;
+ }, true);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.editaccount, menu);
+ final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
+ final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
+ final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
+ final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
+ final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
+ final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
+ final MenuItem share = menu.findItem(R.id.action_share);
+ renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
+
+ share.setVisible(mAccount != null && !mInitMode);
+
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ if (!mAccount.getXmppConnection().getFeatures().blocking()) {
+ showBlocklist.setVisible(false);
+ }
+
+ if (!mAccount.getXmppConnection().getFeatures().register()) {
+ changePassword.setVisible(false);
+ }
+ mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
+ changePresence.setVisible(!mInitMode);
+ } else {
+ showBlocklist.setVisible(false);
+ showMoreInfo.setVisible(false);
+ changePassword.setVisible(false);
+ mamPrefs.setVisible(false);
+ changePresence.setVisible(false);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
+ if (showMoreInfo.isVisible()) {
+ showMoreInfo.setChecked(binding.serverInfoMore.getVisibility() == View.VISIBLE);
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final Intent intent = getIntent();
+ final int theme = findTheme();
+ if (this.mTheme != theme) {
+ recreate();
+ } else if (intent != null) {
+ try {
+ this.jidToEdit = Jid.of(intent.getStringExtra("jid"));
+ } catch (final IllegalArgumentException | NullPointerException ignored) {
+ this.jidToEdit = null;
+ }
+ if (jidToEdit != null && intent.getData() != null && intent.getBooleanExtra("scanned", false)) {
+ final XmppUri uri = new XmppUri(intent.getData());
+ if (xmppConnectionServiceBound) {
+ processFingerprintVerification(uri, false);
+ } else {
+ this.pendingUri = uri;
+ }
+ }
+ boolean init = intent.getBooleanExtra("init", false);
+ boolean openedFromNotification = intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false);
+ this.mInitMode = init || this.jidToEdit == null;
+ this.messageFingerprint = intent.getStringExtra("fingerprint");
+ if (!mInitMode) {
+ this.binding.accountRegisterNew.setVisibility(View.GONE);
+ setTitle(getString(R.string.account_details));
+ configureActionBar(getSupportActionBar(), !openedFromNotification);
+ } else {
+ this.binding.avater.setVisibility(View.GONE);
+ configureActionBar(getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null));
+ setTitle(R.string.action_add_account);
+ }
+ }
+ SharedPreferences preferences = getPreferences();
+ mUseTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor));
+ this.mShowOptions = mUseTor || (QuickConversationsService.isConversations() && preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options)));
+ this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ if (intent != null && intent.getData() != null) {
+ final XmppUri uri = new XmppUri(intent.getData());
+ if (xmppConnectionServiceBound) {
+ processFingerprintVerification(uri, false);
+ } else {
+ this.pendingUri = uri;
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle savedInstanceState) {
+ if (mAccount != null) {
+ savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString());
+ savedInstanceState.putBoolean("initMode", mInitMode);
+ savedInstanceState.putBoolean("showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE);
+ }
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ protected void onBackendConnected() {
+ boolean init = true;
+ if (mSavedInstanceAccount != null) {
+ try {
+ this.mAccount = xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount));
+ this.mInitMode = mSavedInstanceInit;
+ init = false;
+ } catch (IllegalArgumentException e) {
+ this.mAccount = null;
+ }
+
+ } else if (this.jidToEdit != null) {
+ this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
+ }
+
+ if (mAccount != null) {
+ this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
+ this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
+ if (this.mAccount.getPrivateKeyAlias() != null) {
+ this.binding.accountPassword.setHint(R.string.authenticate_with_certificate);
+ if (this.mInitMode) {
+ this.binding.accountPassword.requestFocus();
+ }
+ }
+ if (mPendingFingerprintVerificationUri != null) {
+ processFingerprintVerification(mPendingFingerprintVerificationUri, false);
+ mPendingFingerprintVerificationUri = null;
+ }
+ updateAccountInformation(init);
+ }
+
+
+ if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
+ this.binding.cancelButton.setEnabled(false);
+ }
+ if (mUsernameMode) {
+ this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
+ this.binding.accountJid.setHint(R.string.username_hint);
+ } else {
+ final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
+ R.layout.simple_list_item,
+ xmppConnectionService.getKnownHosts());
+ this.binding.accountJid.setAdapter(mKnownHostsAdapter);
+ }
+
+ if (pendingUri != null) {
+ processFingerprintVerification(pendingUri, false);
+ pendingUri = null;
+ }
+ updatePortLayout();
+ updateSaveButton();
+ invalidateOptionsMenu();
+ }
+
+ private String getUserModeDomain() {
+ if (mAccount != null && mAccount.getJid().getDomain() != null) {
+ return mAccount.getJid().getDomain();
+ } else {
+ return Config.DOMAIN_LOCK;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (MenuDoubleTabUtil.shouldIgnoreTap()) {
+ return false;
+ }
+ switch (item.getItemId()) {
+ case R.id.action_show_block_list:
+ final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
+ showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
+ startActivity(showBlocklistIntent);
+ break;
+ case R.id.action_server_info_show_more:
+ changeMoreTableVisibility(!item.isChecked());
+ break;
+ case R.id.action_share_barcode:
+ shareBarcode();
+ break;
+ case R.id.action_share_http:
+ shareLink(true);
+ break;
+ case R.id.action_share_uri:
+ shareLink(false);
+ break;
+ case R.id.action_change_password_on_server:
+ gotoChangePassword(null);
+ break;
+ case R.id.action_mam_prefs:
+ editMamPrefs();
+ break;
+ case R.id.action_renew_certificate:
+ renewCertificate();
+ break;
+ case R.id.action_change_presence:
+ changePresence();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void shareBarcode() {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType("image/png");
+ startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
+ }
+
+ private void changeMoreTableVisibility(boolean visible) {
+ binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ private void gotoChangePassword(String newPassword) {
+ final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
+ changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
+ if (newPassword != null) {
+ changePasswordIntent.putExtra("password", newPassword);
+ }
+ startActivity(changePasswordIntent);
+ }
+
+ private void renewCertificate() {
+ KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+ }
+
+ private void changePresence() {
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean manualStatus = sharedPreferences.getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
+ String current = mAccount.getPresenceStatusMessage();
+ if (current != null && !current.trim().isEmpty()) {
+ binding.statusMessage.append(current);
+ }
+ setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
+ binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
+ List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
+ PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.simple_list_item, templates);
+ binding.statusMessage.setAdapter(presenceTemplateAdapter);
+ binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> {
+ PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position);
+ setAvailabilityRadioButton(template.getStatus(), binding);
+ });
+ builder.setTitle(R.string.edit_status_message_title);
+ builder.setView(binding.getRoot());
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
+ PresenceTemplate template = new PresenceTemplate(getAvailabilityRadioButton(binding), binding.statusMessage.getText().toString().trim());
+ if (mAccount.getPgpId() != 0 && hasPgp()) {
+ generateSignature(null, template);
+ } else {
+ xmppConnectionService.changeStatus(mAccount, template, null);
+ }
+ });
+ builder.create().show();
+ }
+
+ private void generateSignature(Intent intent, PresenceTemplate template) {
+ xmppConnectionService.getPgpEngine().generateSignature(intent, mAccount, template.getStatusMessage(), new UiCallback<String>() {
+ @Override
+ public void success(String signature) {
+ xmppConnectionService.changeStatus(mAccount, template, signature);
+ }
+
+ @Override
+ public void error(int errorCode, String object) {
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, String object) {
+ mPendingPresenceTemplate.push(template);
+ try {
+ startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0);
+ } catch (final IntentSender.SendIntentException ignored) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void alias(String alias) {
+ if (alias != null) {
+ xmppConnectionService.updateKeyInAccount(mAccount, alias);
+ }
+ }
+
+ private void updateAccountInformation(boolean init) {
+ if (init) {
+ this.binding.accountJid.getEditableText().clear();
+ if (mUsernameMode) {
+ this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal());
+ } else {
+ this.binding.accountJid.getEditableText().append(this.mAccount.getJid().asBareJid().toString());
+ }
+ this.binding.accountPassword.getEditableText().clear();
+ this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
+ this.binding.hostname.setText("");
+ this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
+ this.binding.port.setText("");
+ this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
+ this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
+
+ }
+
+ final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) && QuickConversationsService.isConversations();
+ this.binding.accountJid.setEnabled(editable);
+ this.binding.accountJid.setFocusable(editable);
+ this.binding.accountJid.setFocusableInTouchMode(editable);
+
+
+ final String displayName = mAccount.getDisplayName();
+ updateDisplayName(displayName);
+
+
+ if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
+ this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(true);
+ } else {
+ this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(false);
+ }
+
+ if (!mInitMode) {
+ this.binding.avater.setVisibility(View.VISIBLE);
+ this.binding.avater.setImageBitmap(avatarService().get(this.mAccount, (int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
+ } else {
+ this.binding.avater.setVisibility(View.GONE);
+ }
+ this.binding.accountRegisterNew.setChecked(this.mAccount.isOptionSet(Account.OPTION_REGISTER));
+ if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
+ if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.create_account);
+ }
+ }
+ this.binding.accountRegisterNew.setVisibility(View.GONE);
+ } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
+ this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
+ } else {
+ this.binding.accountRegisterNew.setVisibility(View.GONE);
+ }
+ if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
+ Features features = this.mAccount.getXmppConnection().getFeatures();
+ this.binding.stats.setVisibility(View.VISIBLE);
+ boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
+ boolean showDataSaverWarning = isAffectedByDataSaver();
+ showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
+ this.binding.sessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
+ .getLastSessionEstablished()));
+ if (features.rosterVersioning()) {
+ this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
+ }
+ if (features.carbons()) {
+ this.binding.serverInfoCarbons.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
+ }
+ if (features.mam()) {
+ this.binding.serverInfoMam.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
+ }
+ if (features.csi()) {
+ this.binding.serverInfoCsi.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
+ }
+ if (features.blocking()) {
+ this.binding.serverInfoBlocking.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
+ }
+ if (features.sm()) {
+ this.binding.serverInfoSm.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
+ }
+ if (features.pep()) {
+ AxolotlService axolotlService = this.mAccount.getAxolotlService();
+ if (axolotlService != null && axolotlService.isPepBroken()) {
+ this.binding.serverInfoPep.setText(R.string.server_info_broken);
+ } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
+ this.binding.serverInfoPep.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoPep.setText(R.string.server_info_partial);
+ }
+ } else {
+ this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
+ }
+ if (features.httpUpload(0)) {
+ this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
+ } else if (features.p1S3FileTransfer()) {
+ this.binding.serverInfoHttpUploadDescription.setText(R.string.p1_s3_filetransfer);
+ this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
+ }
+
+ this.binding.pushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
+
+ if (xmppConnectionService.getPushManagementService().available(mAccount)) {
+ this.binding.serverInfoPush.setText(R.string.server_info_available);
+ } else {
+ this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
+ }
+ final long pgpKeyId = this.mAccount.getPgpId();
+ if (pgpKeyId != 0 && Config.supportOpenPgp()) {
+ OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
+ OnClickListener delete = view -> showDeletePgpDialog();
+ this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
+ this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
+ this.binding.pgpFingerprint.setOnClickListener(openPgp);
+ if ("pgp".equals(messageFingerprint)) {
+ this.binding.pgpFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
+ }
+ this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
+ this.binding.actionDeletePgp.setOnClickListener(delete);
+ } else {
+ this.binding.pgpFingerprintBox.setVisibility(View.GONE);
+ }
+ final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
+ if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
+ this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
+ if (ownAxolotlFingerprint.equals(messageFingerprint)) {
+ this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
+ this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message);
+ } else {
+ this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption);
+ this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
+ }
+ this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
+ this.binding.actionCopyAxolotlToClipboard.setVisibility(View.VISIBLE);
+ this.binding.actionCopyAxolotlToClipboard.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint));
+ } else {
+ this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
+ }
+ boolean hasKeys = false;
+ binding.otherDeviceKeys.removeAllViews();
+ for (XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
+ if (!session.getTrust().isCompromised()) {
+ boolean highlight = session.getFingerprint().equals(messageFingerprint);
+ addFingerprintRow(binding.otherDeviceKeys, session, highlight);
+ hasKeys = true;
+ }
+ }
+ if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices
+ this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
+ Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
+ if (otherDevices == null || otherDevices.isEmpty()) {
+ binding.clearDevices.setVisibility(View.GONE);
+ } else {
+ binding.clearDevices.setVisibility(View.VISIBLE);
+ }
+ } else {
+ this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
+ }
+ } else {
+ final TextInputLayout errorLayout;
+ if (this.mAccount.errorStatus()) {
+ if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
+ errorLayout = this.binding.accountPasswordLayout;
+ } else if (mShowOptions
+ && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
+ && this.binding.hostname.getText().length() > 0) {
+ errorLayout = this.binding.hostnameLayout;
+ } else {
+ errorLayout = this.binding.accountJidLayout;
+ }
+ errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
+ if (init || !accountInfoEdited()) {
+ errorLayout.requestFocus();
+ }
+ } else {
+ errorLayout = null;
+ }
+ removeErrorsOnAllBut(errorLayout);
+ this.binding.stats.setVisibility(View.GONE);
+ this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
+ }
+ }
+
+ private void updateDisplayName(String displayName) {
+ if (TextUtils.isEmpty(displayName)) {
+ this.binding.yourName.setText(R.string.no_name_set_instructions);
+ this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1_Tertiary);
+ } else {
+ this.binding.yourName.setText(displayName);
+ this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1);
+ }
+ }
+
+ private void removeErrorsOnAllBut(TextInputLayout exception) {
+ if (this.binding.accountJidLayout != exception) {
+ this.binding.accountJidLayout.setErrorEnabled(false);
+ this.binding.accountJidLayout.setError(null);
+ }
+ if (this.binding.accountPasswordLayout != exception) {
+ this.binding.accountPasswordLayout.setErrorEnabled(false);
+ this.binding.accountPasswordLayout.setError(null);
+ }
+ if (this.binding.hostnameLayout != exception) {
+ this.binding.hostnameLayout.setErrorEnabled(false);
+ this.binding.hostnameLayout.setError(null);
+ }
+ if (this.binding.portLayout != exception) {
+ this.binding.portLayout.setErrorEnabled(false);
+ this.binding.portLayout.setError(null);
+ }
+ }
+
+ private void showDeletePgpDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.unpublish_pgp);
+ builder.setMessage(R.string.unpublish_pgp_message);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> {
+ mAccount.setPgpSignId(0);
+ mAccount.unsetPgpSignature();
+ xmppConnectionService.databaseBackend.updateAccount(mAccount);
+ xmppConnectionService.sendPresence(mAccount);
+ refreshUiReal();
+ });
+ builder.create().show();
+ }
+
+ private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
+ this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
+ if (showDataSaverWarning) {
+ this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
+ this.binding.osOptimizationBody.setText(R.string.data_saver_enabled_explained);
+ this.binding.osOptimizationDisable.setText(R.string.allow);
+ this.binding.osOptimizationDisable.setOnClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
+ Uri uri = Uri.parse("package:" + getPackageName());
+ intent.setData(uri);
+ try {
+ startActivityForResult(intent, REQUEST_DATA_SAVER);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
+ }
+ });
+ } else if (showBatteryWarning) {
+ this.binding.osOptimizationDisable.setText(R.string.disable);
+ this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
+ this.binding.osOptimizationBody.setText(R.string.battery_optimizations_enabled_explained);
+ this.binding.osOptimizationDisable.setOnClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ Uri uri = Uri.parse("package:" + getPackageName());
+ intent.setData(uri);
+ try {
+ startActivityForResult(intent, REQUEST_BATTERY_OP);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+
+ public void showWipePepDialog() {
+ Builder builder = new Builder(this);
+ builder.setTitle(getString(R.string.clear_other_devices));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getString(R.string.clear_other_devices_desc));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.accept),
+ (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
+ builder.create().show();
+ }
+
+ private void editMamPrefs() {
+ this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
+ this.mFetchingMamPrefsToast.show();
+ xmppConnectionService.fetchMamPreferences(mAccount, this);
+ }
+
+ @Override
+ public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
+ refreshUi();
+ }
+
+ @Override
+ public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
+ runOnUiThread(() -> {
+ if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
+ mCaptchaDialog.dismiss();
+ }
+ final Builder builder = new Builder(EditAccountActivity.this);
+ final View view = getLayoutInflater().inflate(R.layout.captcha, null);
+ final ImageView imageView = view.findViewById(R.id.captcha);
+ final EditText input = view.findViewById(R.id.input);
+ imageView.setImageBitmap(captcha);
+
+ builder.setTitle(getString(R.string.captcha_required));
+ builder.setView(view);
+
+ builder.setPositiveButton(getString(R.string.ok),
+ (dialog, which) -> {
+ String rc = input.getText().toString();
+ data.put("username", account.getUsername());
+ data.put("password", account.getPassword());
+ data.put("ocr", rc);
+ data.submit();
+
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, id, data);
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ });
+
+ builder.setOnCancelListener(dialog -> {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ });
+ mCaptchaDialog = builder.create();
+ mCaptchaDialog.show();
+ input.requestFocus();
+ });
+ }
+
+ public void onShowErrorToast(final int resId) {
+ runOnUiThread(() -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void onPreferencesFetched(final Element prefs) {
+ runOnUiThread(() -> {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ Builder builder = new Builder(EditAccountActivity.this);
+ builder.setTitle(R.string.server_side_mam_prefs);
+ String defaultAttr = prefs.getAttribute("default");
+ final List<String> defaults = Arrays.asList("never", "roster", "always");
+ final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
+ builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, (dialog, which) -> {
+ prefs.setAttribute("default", defaults.get(choice.get()));
+ xmppConnectionService.pushMamPreferences(mAccount, prefs);
+ });
+ builder.create().show();
+ });
+ }
+
+ @Override
+ public void onPreferencesFetchFailed() {
+ runOnUiThread(() -> {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show();
+ });
+ }
+
+ @Override
+ public void OnUpdateBlocklist(Status status) {
+ refreshUi();
+ }
}
@@ -16,6 +16,7 @@ public final class Namespace {
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB+"#publish-options";
public static final String PUBSUB_ERROR = PUBSUB+"#errors";
+ public static final String PUBSUB_OWNER = PUBSUB+"#owner";
public static final String NICK = "http://jabber.org/protocol/nick";
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
@@ -470,11 +470,53 @@
</TableLayout>
<RelativeLayout
- android:id="@+id/pgp_fingerprint_box"
+ android:id="@+id/your_name_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="32dp">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@+id/action_edit_your_name"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/your_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/no_name_set_instructions"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1.Tertiary"/>
+
+ <TextView
+ android:id="@+id/your_name_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/your_name"
+ android:textAppearance="@style/TextAppearance.Conversations.Caption"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/action_edit_your_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:alpha="?attr/icon_alpha"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:padding="@dimen/image_button_padding"
+ android:src="?attr/icon_edit_body"
+ android:visibility="visible"/>
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:id="@+id/pgp_fingerprint_box"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginTop="16dp">
+
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -514,7 +556,7 @@
android:id="@+id/axolotl_fingerprint_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginTop="24dp">
+ android:layout_marginTop="16dp">
<LinearLayout
android:layout_width="wrap_content"
@@ -515,6 +515,9 @@
<string name="share_uri_with">Share URI withโฆ</string>
<string name="welcome_header" translatable="false">Join the Conversation</string>
<string name="welcome_text">Jabber is a provider independent instant messaging network. You can use this client with what ever Jabber server you choose.\nHowever for your convenience we made it easy to create an account on conversations.imยน; a provider specially suited for the use with Conversations.</string>
+ <string name="welcome_header_quicksy" translatable="false">Have some Quick Conversations</string>
+ <string name="welcome_text_quicksy"><![CDATA[Quicksy is a spin off of the popular Jabber/XMPP client Conversations with automatic contact discovery.<br><br>You sign up with your phone number and Quicksy will automaticallyโbased on the phone numbers in your address bookโsuggest possible contacts to you.<br><br>By signing up you agree to our <a href="https://quicksy.im/#privacy">privacy policy</a>.]]></string>
+ <string name="agree_and_continue">Agree & continue</string>
<string name="magic_create_text">We will guide you through the process of creating an account on conversations.im.ยน\nWhen picking conversations.im as a provider you will be able to communicate with users of other providers by giving them your full Jabber ID.</string>
<string name="your_full_jid_will_be">Your full Jabber ID will be: %s</string>
<string name="create_account">Create Account</string>
@@ -789,4 +792,9 @@
<string name="too_many_attempts">Too many attempts</string>
<string name="the_app_is_out_of_date">You are using an out of date version of this app.</string>
<string name="update">Update</string>
+ <string name="logged_in_with_another_device">This phone number is currently logged in with another device.</string>
+ <string name="enter_your_name_instructions">Please enter your name to let people, who donโt have you in their address books, know who you are.</string>
+ <string name="your_name">Your name</string>
+ <string name="enter_your_name">Enter your name</string>
+ <string name="no_name_set_instructions">Use the edit button to set your name.</string>
</resources>
@@ -80,6 +80,10 @@
<item name="android:textColor">?android:textColorSecondary</item>
</style>
+ <style name="TextAppearance.Conversations.Body1.Tertiary" parent="TextAppearance.Conversations.Body1">
+ <item name="android:textColor">?android:textColorTertiary</item>
+ </style>
+
<style name="TextAppearance.Conversations.Fingerprint" parent="TextAppearance.Conversations.Body1">
<item name="android:fontFamily" tools:targetApi="jelly_bean">monospace</item>
<item name="android:typeface">monospace</item>
@@ -19,5 +19,15 @@
android:launchMode="singleTask"
android:label="@string/verify_your_phone_number"/>
+ <activity
+ android:name=".ui.TosActivity"
+ android:launchMode="singleTask"
+ android:label="@string/app_name"/>
+
+ <activity
+ android:name=".ui.EnterNameActivity"
+ android:launchMode="singleTask"
+ android:label="@string/enter_your_name"/>
+
</application>
</manifest>
@@ -0,0 +1,54 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityEnterNameBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.AccountUtils;
+
+public class EnterNameActivity extends XmppActivity {
+
+ private ActivityEnterNameBinding binding;
+
+ private Account account;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_enter_name);
+ setSupportActionBar((Toolbar) this.binding.toolbar);
+ this.binding.next.setOnClickListener(this::next);
+ }
+
+ private void next(View view) {
+ if (account != null) {
+
+ String name = this.binding.name.getText().toString().trim();
+
+ account.setDisplayName(name);
+
+ xmppConnectionService.publishDisplayName(account);
+
+ Intent intent = new Intent(this, PublishProfilePictureActivity.class);
+ intent.putExtra(PublishProfilePictureActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+ intent.putExtra("setup", true);
+ startActivity(intent);
+ }
+ finish();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+ this.account = AccountUtils.getFirst(xmppConnectionService);
+ }
+}
@@ -0,0 +1,80 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.XmppUri;
+
+public class TosActivity extends XmppActivity {
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final int theme = findTheme();
+ if (this.mTheme != theme) {
+ recreate();
+ }
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ if (intent != null) {
+ setIntent(intent);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (getResources().getBoolean(R.bool.portrait_only)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_tos);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ final ActionBar ab = getSupportActionBar();
+ if (ab != null) {
+ ab.setDisplayShowHomeEnabled(false);
+ ab.setDisplayHomeAsUpEnabled(false);
+ }
+ final Button agreeButton = findViewById(R.id.agree);
+ final TextView welcomeText = findViewById(R.id.welcome_text);
+ agreeButton.setOnClickListener(v -> {
+ final Intent intent = new Intent(this, EnterPhoneNumberActivity.class);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ preferences.edit().putBoolean("tos", true).apply();
+ addInviteUri(intent);
+ startActivity(intent);
+ finish();
+ });
+ welcomeText.setText(Html.fromHtml(getString(R.string.welcome_text_quicksy)));
+ welcomeText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ }
+
+ public void addInviteUri(Intent intent) {
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ }
+}
@@ -269,9 +269,7 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
private void performPostVerificationRedirect() {
if (redirectInProgress.compareAndSet(false, true)) {
- Intent intent = new Intent(this, PublishProfilePictureActivity.class);
- intent.putExtra(PublishProfilePictureActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
- intent.putExtra("setup", true);
+ Intent intent = new Intent(this, EnterNameActivity.class);
startActivity(intent);
finish();
}
@@ -39,6 +39,9 @@ public class ApiDialogHelper {
case 403:
res = R.string.the_app_is_out_of_date;
break;
+ case 409:
+ res = R.string.logged_in_with_another_device;
+ break;
case 500:
res = R.string.something_went_wrong_processing_your_request;
break;
@@ -2,6 +2,8 @@ package eu.siacs.conversations.utils;
import android.app.Activity;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.util.Log;
import eu.siacs.conversations.Config;
@@ -9,6 +11,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.EnterPhoneNumberActivity;
import eu.siacs.conversations.ui.StartConversationActivity;
+import eu.siacs.conversations.ui.TosActivity;
import eu.siacs.conversations.ui.VerifyActivity;
public class SignupUtils {
@@ -28,7 +31,12 @@ public class SignupUtils {
intent = new Intent(activity, StartConversationActivity.class);
}
} else {
- intent = getSignUpIntent(activity);
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ if (preferences.getBoolean("tos",false)) {
+ intent = getSignUpIntent(activity);
+ } else {
+ intent = new Intent(activity, TosActivity.class);
+ }
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <include android:id="@+id/toolbar" layout="@layout/toolbar" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/instructions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:gravity="center_horizontal"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1"
+ android:text="@string/enter_your_name_instructions"/>
+
+ <LinearLayout
+ android:id="@+id/name_box"
+ android:layout_width="256dp"
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/next"
+ android:layout_below="@+id/instructions"
+ android:layout_centerHorizontal="true"
+ android:orientation="vertical">
+
+ <EditText
+ android:imeOptions="flagNoExtractUi"
+ android:id="@+id/name"
+ style="@style/Widget.Conversations.EditText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/your_name"
+ android:longClickable="false" />
+
+ </LinearLayout>
+ <Button
+ android:id="@+id/next"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ style="@style/Widget.Conversations.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/next"
+ android:textColor="?colorAccent"/>
+ </RelativeLayout>
+ </ScrollView>
+ </LinearLayout>
+</layout>
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <include layout="@layout/toolbar" />
+
+ <ScrollView android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/color_background_primary">
+
+ <LinearLayout
+ android:id="@+id/linearLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:minHeight="256dp"
+ android:orientation="vertical">
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="16dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/welcome_header_quicksy"
+ android:textAppearance="@style/TextAppearance.Conversations.Title"/>
+ <TextView
+ android:id="@+id/welcome_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="@string/welcome_text_quicksy"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
+ </LinearLayout>
+ <Button
+ android:id="@+id/agree"
+ style="@style/Widget.Conversations.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:text="@string/agree_and_continue"
+ android:textColor="?colorAccent"/>
+ </LinearLayout>
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/linearLayout"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:padding="8dp"
+ android:src="@drawable/main_logo"/>
+ </RelativeLayout>
+ </RelativeLayout>
+ </ScrollView>
+</LinearLayout>