diff options
Diffstat (limited to '')
69 files changed, 1146 insertions, 712 deletions
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java index 51da8ccb0..02062eba1 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java @@ -1256,7 +1256,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return verified; } - public boolean hasPendingKeyFetches(Account account, List<Jid> jids) { + public boolean hasPendingKeyFetches(List<Jid> jids) { SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0); if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) { return true; diff --git a/src/main/java/de/pixart/messenger/entities/Bookmark.java b/src/main/java/de/pixart/messenger/entities/Bookmark.java index 079b283c0..fc7725bb5 100644 --- a/src/main/java/de/pixart/messenger/entities/Bookmark.java +++ b/src/main/java/de/pixart/messenger/entities/Bookmark.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import de.pixart.messenger.utils.StringUtils; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.InvalidJid; @@ -40,6 +41,14 @@ public class Bookmark extends Element implements ListItem { return bookmark; } + public static boolean printableValue(@Nullable String value, boolean permitNone) { + return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); + } + + public static boolean printableValue(@Nullable String value) { + return printableValue(value, true); + } + public void setAutojoin(boolean autojoin) { if (autojoin) { this.setAttribute("autojoin", "true"); @@ -68,14 +77,6 @@ public class Bookmark extends Element implements ListItem { } } - public static boolean printableValue(@Nullable String value, boolean permitNone) { - return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value)); - } - - public static boolean printableValue(@Nullable String value) { - return printableValue(value, true); - } - @Override public int getOffline() { return 0; @@ -168,11 +169,11 @@ public class Bookmark extends Element implements ListItem { public boolean setBookmarkName(String name) { String before = getBookmarkName(); - if (name != null && !name.equals(before)) { + if (name != null) { this.setAttribute("name", name); - return true; } else { - return false; + this.removeAttribute("name"); } + return StringUtils.changed(before, name); } } diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java index 18a20532a..fe89009d3 100644 --- a/src/main/java/de/pixart/messenger/entities/Conversation.java +++ b/src/main/java/de/pixart/messenger/entities/Conversation.java @@ -134,6 +134,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl this.messagesLeftOnServer = value; } + public void deleteMessage(Message message) { + this.messages.remove(message); + } + public Message getFirstUnreadMessage() { Message first = null; synchronized (this.messages) { diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java index 2ef23882f..774620707 100644 --- a/src/main/java/de/pixart/messenger/entities/MucOptions.java +++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java @@ -2,18 +2,19 @@ package de.pixart.messenger.entities; import android.annotation.SuppressLint; import android.support.annotation.NonNull; -import android.util.Log; +import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import de.pixart.messenger.Config; import de.pixart.messenger.R; +import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.utils.JidHelper; -import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.chatstate.ChatState; import de.pixart.messenger.xmpp.forms.Data; @@ -30,8 +31,11 @@ public class MucOptions { return this.conversation.getAccount(); } - public void setSelf(User user) { + public boolean setSelf(User user) { this.self = user; + final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString()); + final boolean affiliationChanged = this.conversation.setAttribute("affiliation", user.affiliation.toString()); + return roleChanged || affiliationChanged; } public void changeAffiliation(Jid jid, Affiliation affiliation) { @@ -67,20 +71,22 @@ public class MucOptions { } } + public boolean mamSupport() { + return MessageArchiveService.Version.has(getFeatures()); + } + public enum Affiliation { - OWNER("owner", 4, R.string.owner), - ADMIN("admin", 3, R.string.admin), - MEMBER("member", 2, R.string.member), - OUTCAST("outcast", 0, R.string.outcast), - NONE("none", 1, R.string.no_affiliation); - - Affiliation(String string, int rank, int resId) { - this.string = string; + OWNER(4, R.string.owner), + ADMIN(3, R.string.admin), + MEMBER(2, R.string.member), + OUTCAST(0, R.string.outcast), + NONE(1, R.string.no_affiliation); + + Affiliation(int rank, int resId) { this.resId = resId; this.rank = rank; } - private String string; private int resId; private int rank; @@ -90,7 +96,7 @@ public class MucOptions { @Override public String toString() { - return this.string; + return name().toLowerCase(Locale.US); } public boolean outranks(Affiliation affiliation) { @@ -100,21 +106,30 @@ public class MucOptions { public boolean ranks(Affiliation affiliation) { return rank >= affiliation.rank; } + + public static Affiliation of(@Nullable String value) { + if (value == null) { + return NONE; + } + try { + return Affiliation.valueOf(value.toUpperCase(Locale.US)); + } catch (IllegalArgumentException e) { + return NONE; + } + } } public enum Role { - MODERATOR("moderator", R.string.moderator, 3), - VISITOR("visitor", R.string.visitor, 1), - PARTICIPANT("participant", R.string.participant, 2), - NONE("none", R.string.no_role, 0); + MODERATOR(R.string.moderator, 3), + VISITOR(R.string.visitor, 1), + PARTICIPANT(R.string.participant, 2), + NONE(R.string.no_role, 0); - Role(String string, int resId, int rank) { - this.string = string; + Role(int resId, int rank) { this.resId = resId; this.rank = rank; } - private String string; private int resId; private int rank; @@ -124,12 +139,23 @@ public class MucOptions { @Override public String toString() { - return this.string; + return name().toLowerCase(Locale.US); } public boolean ranks(Role role) { return rank >= role.rank; } + + public static Role of(@Nullable String value) { + if (value == null) { + return NONE; + } + try { + return Role.valueOf(value.toUpperCase(Locale.US)); + } catch (IllegalArgumentException e) { + return NONE; + } + } } public enum Error { @@ -140,8 +166,10 @@ public class MucOptions { PASSWORD_REQUIRED, BANNED, MEMBERS_ONLY, + RESOURCE_CONSTRAINT, KICKED, SHUTDOWN, + DESTROYED, INVALID_NICK, UNKNOWN } @@ -193,25 +221,7 @@ public class MucOptions { } public void setRole(String role) { - if (role == null) { - this.role = Role.NONE; - return; - } - role = role.toLowerCase(); - switch (role) { - case "moderator": - this.role = Role.MODERATOR; - break; - case "participant": - this.role = Role.PARTICIPANT; - break; - case "visitor": - this.role = Role.VISITOR; - break; - default: - this.role = Role.NONE; - break; - } + this.role = Role.of(role); } public Affiliation getAffiliation() { @@ -219,27 +229,7 @@ public class MucOptions { } public void setAffiliation(String affiliation) { - if (affiliation == null) { - this.affiliation = Affiliation.NONE; - return; - } - affiliation = affiliation.toLowerCase(); - switch (affiliation) { - case "admin": - this.affiliation = Affiliation.ADMIN; - break; - case "owner": - this.affiliation = Affiliation.OWNER; - break; - case "member": - this.affiliation = Affiliation.MEMBER; - break; - case "outcast": - this.affiliation = Affiliation.OUTCAST; - break; - default: - this.affiliation = Affiliation.NONE; - } + this.affiliation = Affiliation.of(affiliation); } public void setPgpKeyId(long id) { @@ -377,6 +367,8 @@ public class MucOptions { this.account = conversation.getAccount(); this.conversation = conversation; this.self = new User(this, createJoinJid(getProposedNick())); + this.self.affiliation = Affiliation.of(conversation.getAttribute("affiliation")); + this.self.role = Role.of(conversation.getAttribute("role")); } public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) { @@ -384,12 +376,12 @@ public class MucOptions { String name; Field roomConfigName = getRoomInfoForm().getFieldByName("muc#roomconfig_roomname"); if (roomConfigName != null) { - Log.d(Config.LOGTAG, "value of room config name " + roomConfigName.getValue()); name = roomConfigName.getValue(); } else { List<ServiceDiscoveryResult.Identity> identities = serviceDiscoveryResult.getIdentities(); String identityName = identities.size() > 0 ? identities.get(0).getName() : null; - if (!conversation.getJid().getEscapedLocal().equals(identityName)) { + final Jid jid = conversation.getJid(); + if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) { name = identityName; } else { name = null; @@ -446,21 +438,15 @@ public class MucOptions { } public boolean participating() { - return !online() - || self.getRole().ranks(Role.PARTICIPANT) - || hasFeature("muc_unmoderated"); + return self.getRole().ranks(Role.PARTICIPANT) || !moderated(); } public boolean membersOnly() { return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, false); } - public boolean mamSupport() { - return hasFeature(Namespace.MAM) || hasFeature(Namespace.MAM_LEGACY); - } - - public boolean mamLegacy() { - return hasFeature(Namespace.MAM_LEGACY) && !hasFeature(Namespace.MAM); + public List<String> getFeatures() { + return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList(); } public boolean nonanonymous() { @@ -714,8 +700,7 @@ public class MucOptions { } public String getName() { - String mucName = this.conversation.getAttribute("muc_name"); - return conversation.getJid().getEscapedLocal().equals(mucName) ? null : mucName; + return this.conversation.getAttribute("muc_name"); } private List<User> getFallbackUsersFromCryptoTargets() { diff --git a/src/main/java/de/pixart/messenger/entities/ReadByMarker.java b/src/main/java/de/pixart/messenger/entities/ReadByMarker.java index ec2d1aa73..86057c680 100644 --- a/src/main/java/de/pixart/messenger/entities/ReadByMarker.java +++ b/src/main/java/de/pixart/messenger/entities/ReadByMarker.java @@ -162,4 +162,10 @@ public class ReadByMarker { return true; } + public static boolean allUsersRepresented(Collection<MucOptions.User> users, Set<ReadByMarker> markers, ReadByMarker marker) { + HashSet<ReadByMarker> markersCopy = new HashSet<>(markers); + markersCopy.add(marker); + return allUsersRepresented(users, markersCopy); + } + } diff --git a/src/main/java/de/pixart/messenger/entities/Roster.java b/src/main/java/de/pixart/messenger/entities/Roster.java index 97ad41114..2db0c796e 100644 --- a/src/main/java/de/pixart/messenger/entities/Roster.java +++ b/src/main/java/de/pixart/messenger/entities/Roster.java @@ -76,7 +76,6 @@ public class Roster { return; } contact.setAccount(account); - contact.setOption(Contact.Options.IN_ROSTER); synchronized (this.contacts) { contacts.put(contact.getJid().asBareJid(), contact); } diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java index eab087975..918c95e6f 100644 --- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java @@ -248,10 +248,10 @@ public class IqGenerator extends AbstractGenerator { public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - final Element query = packet.query(mam.isLegacy() ? Namespace.MAM_LEGACY : Namespace.MAM); + final Element query = packet.query(mam.version.namespace); query.setAttribute("queryid", mam.getQueryId()); final Data data = new Data(); - data.setFormType(mam.isLegacy() ? Namespace.MAM_LEGACY : Namespace.MAM); + data.setFormType(mam.version.namespace); if (mam.muc()) { packet.setTo(mam.getWith()); } else if (mam.getWith() != null) { @@ -325,7 +325,7 @@ public class IqGenerator extends AbstractGenerator { Element query = packet.query("http://jabber.org/protocol/muc#admin"); for (Jid jid : jids) { Element item = query.addChild("item"); - item.setAttribute("jid", jid.toString()); + item.setAttribute("jid", jid.toEscapedString()); item.setAttribute("affiliation", affiliation); } return packet; @@ -455,8 +455,9 @@ public class IqGenerator extends AbstractGenerator { options.putString("muc#roomconfig_membersonly", "1"); options.putString("muc#roomconfig_publicroom", "0"); options.putString("muc#roomconfig_whois", "anyone"); - options.putString("muc#roomconfig_enablearchiving", "1"); - options.putString("mam", "1"); + options.putString("muc#roomconfig_enablearchiving", "1"); //prosody + options.putString("mam", "1"); //ejabberd community + options.putString("muc#roomconfig_mam", "1"); //ejabberd saas return options; } diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java index b35e0aa83..efdfdc61e 100644 --- a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java @@ -52,7 +52,7 @@ public class HttpDownloadConnection implements Transferable { private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - public HttpDownloadConnection(HttpConnectionManager manager) { + HttpDownloadConnection(HttpConnectionManager manager) { this.mHttpConnectionManager = manager; this.mXmppConnectionService = manager.getXmppConnectionService(); this.mUseTor = mXmppConnectionService.useTorToConnect(); @@ -64,7 +64,7 @@ public class HttpDownloadConnection implements Transferable { if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { checkFileSize(true); } else { - new Thread(new FileDownloader(true)).start(); + download(true); } return true; } else { @@ -113,12 +113,22 @@ public class HttpDownloadConnection implements Transferable { this.message.setEncryption(Message.ENCRYPTION_NONE); } method = mUrl.getProtocol().equalsIgnoreCase(P1S3UrlStreamHandler.PROTOCOL_NAME) ? Method.P1_S3 : Method.HTTP_UPLOAD; - checkFileSize(interactive); + long knownFileSize = message.getFileParams().size; + if (knownFileSize > 0 && interactive && method != Method.P1_S3) { + this.file.setExpectedSize(knownFileSize); + download(true); + } else { + checkFileSize(interactive); + } } catch (MalformedURLException e) { this.cancel(); } } + private void download(boolean interactive) { + new Thread(new FileDownloader(interactive)).start(); + } + private void checkFileSize(boolean interactive) { new Thread(new FileSizeChecker(interactive)).start(); } @@ -257,7 +267,7 @@ public class HttpDownloadConnection implements Transferable { && size <= mHttpConnectionManager.getAutoAcceptFileSize() && mXmppConnectionService.isDataSaverDisabled()) { HttpDownloadConnection.this.acceptedAutomatically = true; - new Thread(new FileDownloader(interactive)).start(); + download(interactive); } else { changeStatus(STATUS_OFFER); HttpDownloadConnection.this.acceptedAutomatically = false; diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java index 090e7ad07..eee8b3c01 100644 --- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java @@ -230,7 +230,7 @@ public class HttpUploadConnection implements Transferable { Log.d(Config.LOGTAG, "http upload failed because response code was " + code); fail("http upload failed because response code was " + code); } - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); Log.d(Config.LOGTAG, "http upload failed " + e.getMessage()); fail(e.getMessage()); diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index 7fc2e2d50..e86758fd7 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -26,7 +26,6 @@ import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.crypto.axolotl.NotEncryptedForThisDeviceException; import de.pixart.messenger.crypto.axolotl.XmppAxolotlMessage; import de.pixart.messenger.entities.Account; -import de.pixart.messenger.entities.Bookmark; import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Message; @@ -276,10 +275,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece mXmppConnectionService.updateAccountUi(); } else { Contact contact = account.getRoster().getContact(from); - contact.setAvatar(avatar); - mXmppConnectionService.getAvatarService().clear(contact); - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); + if (contact.setAvatar(avatar)) { + mXmppConnectionService.syncRoster(account); + mXmppConnectionService.getAvatarService().clear(contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + } } } else if (mXmppConnectionService.isDataSaverDisabled()) { mXmppConnectionService.fetchAvatar(account, avatar); @@ -337,16 +338,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final boolean isForwarded; boolean isCarbon = false; String serverMsgId = null; - final Element fin = original.findChild("fin", Namespace.MAM_LEGACY); + final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace); if (fin != null) { mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom()); return; } - final boolean mamLegacy = original.hasChild("result", Namespace.MAM_LEGACY); - final Element result = original.findChild("result", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM); + 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", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM); + Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace); if (f == null) { return; } @@ -759,12 +759,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (conversation.getMucOptions().setSubject(subject)) { mXmppConnectionService.updateConversation(conversation); } - final Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.getBookmarkName() == null) { - if (bookmark.setBookmarkName(subject)) { - mXmppConnectionService.pushBookmarks(account); - } - } mXmppConnectionService.updateConversationUi(); return; } diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java index 1da8553dc..6602e8aef 100644 --- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java +++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java @@ -18,6 +18,7 @@ import de.pixart.messenger.generator.IqGenerator; import de.pixart.messenger.generator.PresenceGenerator; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.utils.Namespace; +import de.pixart.messenger.utils.XmppUri; import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.InvalidJid; import de.pixart.messenger.xmpp.OnPresencePacketReceived; @@ -71,7 +72,10 @@ public class PresenceParser extends AbstractParser implements if (mucOptions.setOnline()) { mXmppConnectionService.getAvatarService().clear(mucOptions); } - mucOptions.setSelf(user); + if (mucOptions.setSelf(user)) { + Log.d(Config.LOGTAG, "role or affiliation changed"); + mXmppConnectionService.databaseBackend.updateConversation(conversation); + } mXmppConnectionService.persistSelfNick(user); invokeRenameListener(mucOptions, true); } @@ -118,7 +122,15 @@ public class PresenceParser extends AbstractParser implements } } } else if (type.equals("unavailable")) { - if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN) && from.equals(mucOptions.getSelf().getFullJid())) { + final boolean fullJidMatches = from.equals(mucOptions.getSelf().getFullJid()); + if (x.hasChild("destroy") && fullJidMatches) { + Element destroy = x.findChild("destroy"); + final Jid alternate = destroy == null ? null : InvalidJid.getNullForInvalid(destroy.getAttributeAsJid("jid")); + mucOptions.setError(MucOptions.Error.DESTROYED); + if (alternate != null) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": muc destroyed. alternate location " + alternate); + } + } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN) && fullJidMatches) { mucOptions.setError(MucOptions.Error.SHUTDOWN); } else if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE)) { if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { @@ -162,6 +174,25 @@ public class PresenceParser extends AbstractParser implements mucOptions.setError(MucOptions.Error.BANNED); } else if (error.hasChild("registration-required")) { mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } else if (error.hasChild("resource-constraint")) { + mucOptions.setError(MucOptions.Error.RESOURCE_CONSTRAINT); + } else if (error.hasChild("gone")) { + final String gone = error.findChildContent("gone"); + final Jid alternate; + if (gone != null) { + final XmppUri xmppUri = new XmppUri(gone); + if (xmppUri.isJidValid()) { + alternate = xmppUri.getJid(); + } else { + alternate = null; + } + } else { + alternate = null; + } + mucOptions.setError(MucOptions.Error.DESTROYED); + if (alternate != null) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": muc destroyed. alternate location " + alternate); + } } else { final String text = error.findChildContent("text"); if (text != null && text.contains("attribute 'to'")) { @@ -229,6 +260,7 @@ public class PresenceParser extends AbstractParser implements mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateAccountUi(); } else if (contact.setAvatar(avatar)) { + mXmppConnectionService.syncRoster(account); mXmppConnectionService.getAvatarService().clear(contact); mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateRosterUi(); diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index b2f4fe9d5..94710b86e 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -901,7 +901,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); for (Contact contact : roster.getContacts()) { - if (contact.getOption(Contact.Options.IN_ROSTER)) { + if (contact.getOption(Contact.Options.IN_ROSTER) || contact.getAvatar() != null) { db.insert(Contact.TABLENAME, null, contact.getContentValues()); } else { String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; @@ -917,6 +917,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persisted roster in " + duration + "ms"); } + public void deleteMessageInConversation(Message message) { + long start = SystemClock.elapsedRealtime(); + final SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); + String[] args = {message.getUuid()}; + db.delete("messages_index", "uuid =?", args); + db.setTransactionSuccessful(); + db.endTransaction(); + Log.d(Config.LOGTAG, "deleted single message in " + (SystemClock.elapsedRealtime() - start) + "ms"); + } + public void deleteMessagesInConversation(Conversation conversation) { long start = SystemClock.elapsedRealtime(); final SQLiteDatabase db = this.getWritableDatabase(); diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java index 06b0e1f5f..bf415515b 100644 --- a/src/main/java/de/pixart/messenger/services/AvatarService.java +++ b/src/main/java/de/pixart/messenger/services/AvatarService.java @@ -54,7 +54,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { protected XmppConnectionService mXmppConnectionService = null; - public AvatarService(XmppConnectionService service) { + AvatarService(XmppConnectionService service) { this.mXmppConnectionService = service; } @@ -231,7 +231,13 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { if (bookmark.getConversation() != null) { return get(bookmark.getConversation(), size, cachedOnly); } else { - String seed = bookmark.getJid() != null ? bookmark.getJid().asBareJid().toString() : null; + Jid jid = bookmark.getJid(); + Account account = bookmark.getAccount(); + Contact contact = jid == null ? null : account.getRoster().getContact(jid); + if (contact != null && contact.getAvatar() != null) { + return get(contact, size, cachedOnly); + } + String seed = jid != null ? jid.asBareJid().toString() : null; return get(bookmark.getDisplayName(), seed, size, cachedOnly); } } else { diff --git a/src/main/java/de/pixart/messenger/services/MessageArchiveService.java b/src/main/java/de/pixart/messenger/services/MessageArchiveService.java index df376ab73..d9394d4dc 100644 --- a/src/main/java/de/pixart/messenger/services/MessageArchiveService.java +++ b/src/main/java/de/pixart/messenger/services/MessageArchiveService.java @@ -15,11 +15,11 @@ import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Conversational; import de.pixart.messenger.entities.ReceiptRequest; import de.pixart.messenger.generator.AbstractGenerator; -import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded; import de.pixart.messenger.xmpp.mam.MamReference; import de.pixart.messenger.xmpp.stanzas.IqPacket; +import de.pixart.messenger.xmpp.stanzas.MessagePacket; import rocks.xmpp.addr.Jid; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -29,6 +29,68 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private final HashSet<Query> queries = new HashSet<>(); private final ArrayList<Query> pendingQueries = new ArrayList<>(); + public enum Version { + MAM_0("urn:xmpp:mam:0", true), + MAM_1("urn:xmpp:mam:1", false), + MAM_2("urn:xmpp:mam:2", false); + + public final boolean legacy; + public final String namespace; + + Version(String namespace, boolean legacy) { + this.namespace = namespace; + this.legacy = legacy; + } + + public static Version get(Account account) { + return get(account, null); + } + + public static Version get(Account account, Conversation conversation) { + if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { + return get(account.getXmppConnection().getFeatures().getAccountFeatures()); + } else { + return get(conversation.getMucOptions().getFeatures()); + } + } + + private static Version get(List<String> features) { + final Version[] values = values(); + for (int i = values.length - 1; i >= 0; --i) { + for (String feature : features) { + if (values[i].namespace.equals(feature)) { + return values[i]; + } + } + } + return MAM_0; + } + + public static boolean has(List<String> features) { + for (String feature : features) { + for (Version version : values()) { + if (version.namespace.equals(feature)) { + return true; + } + } + } + return false; + } + + public static Element findResult(MessagePacket packet) { + for (Version version : values()) { + Element result = packet.findChild("result", version.namespace); + if (result != null) { + return result; + } + } + return null; + } + + } + + ; + MessageArchiveService(final XmppConnectionService service) { this.mXmppConnectionService = service; } @@ -168,7 +230,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { - Element fin = p.findChild("fin", Namespace.MAM); + Element fin = p.findChild("fin", query.version.namespace); if (p.getType() == IqPacket.TYPE.TIMEOUT) { synchronized (MessageArchiveService.this.queries) { MessageArchiveService.this.queries.remove(query); @@ -389,16 +451,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private PagingOrder pagingOrder = PagingOrder.NORMAL; private XmppConnectionService.OnMoreMessagesLoaded callback = null; private boolean catchup = true; + public final Version version; Query(Conversation conversation, MamReference start, long end, boolean catchup) { - this(conversation.getAccount(), catchup ? start : start.timeOnly(), end); + this(conversation.getAccount(), Version.get(conversation.getAccount(), conversation), catchup ? start : start.timeOnly(), end); this.conversation = conversation; this.pagingOrder = catchup ? PagingOrder.NORMAL : PagingOrder.REVERSE; this.catchup = catchup; } Query(Account account, MamReference start, long end) { + this(account, Version.get(account), start, end); + } + + Query(Account account, Version version, MamReference start, long end) { this.account = account; if (start.getReference() != null) { this.reference = start.getReference(); @@ -407,10 +474,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } this.end = end; this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + this.version = version; } private Query page(String reference) { - Query query = new Query(this.account, new MamReference(this.start, reference), this.end); + Query query = new Query(this.account, this.version, new MamReference(this.start, reference), this.end); query.conversation = conversation; query.totalCount = totalCount; query.actualCount = actualCount; @@ -432,11 +500,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public boolean isLegacy() { - if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { - return account.getXmppConnection().getFeatures().mamLegacy(); - } else { - return conversation.getMucOptions().mamLegacy(); - } + return version.legacy; } public boolean safeToExtractTrueCounterpart() { @@ -569,6 +633,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { builder.append(this.reference); } builder.append(", catchup=").append(Boolean.toString(catchup)); + builder.append(", ns=").append(version.namespace); return builder.toString(); } diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index 9f8438de3..7fa091478 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -181,7 +181,7 @@ public class NotificationService { public void push(final Message message) { synchronized (CATCHUP_LOCK) { final XmppConnection connection = message.getConversation().getAccount().getXmppConnection(); - if (connection.isWaitingForSmCatchup()) { + if (connection != null && connection.isWaitingForSmCatchup()) { connection.incrementSmCatchupMessageCounter(); pushFromBacklog(message); } else { diff --git a/src/main/java/de/pixart/messenger/services/UpdateService.java b/src/main/java/de/pixart/messenger/services/UpdateService.java index c08dab683..7e636b8e6 100644 --- a/src/main/java/de/pixart/messenger/services/UpdateService.java +++ b/src/main/java/de/pixart/messenger/services/UpdateService.java @@ -26,25 +26,20 @@ import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.ui.UpdaterActivity; +import static de.pixart.messenger.http.HttpConnectionManager.getProxy; import static de.pixart.messenger.services.NotificationService.UPDATE_NOTIFICATION_ID; public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapper> { - public UpdateService (){ - } - + private boolean mUseTor; private Context context; private boolean playstore; + public UpdateService() { + } - public UpdateService(Context context, boolean PlayStore) { + public UpdateService(Context context, boolean PlayStore, XmppConnectionService mXmppConnectionService) { this.context = context; this.playstore = PlayStore; - } - - public class Wrapper - { - public boolean UpdateAvailable = false; - public boolean NoUpdate = false; - public boolean isError = false; + this.mUseTor = mXmppConnectionService.useTorToConnect(); } @Override @@ -60,9 +55,13 @@ public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapp HttpsURLConnection connection = null; - try { + try { URL url = new URL(Config.UPDATE_URL); - connection = (HttpsURLConnection)url.openConnection(); + if (mUseTor) { + connection = (HttpsURLConnection) url.openConnection(getProxy()); + } else { + connection = (HttpsURLConnection) url.openConnection(); + } connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000); connection.setRequestProperty("User-Agent", context.getString(R.string.app_name)); @@ -128,18 +127,14 @@ public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapp return; } Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - - @Override - public void run() { - String ToastMessage = ""; - if (error) { - ToastMessage = context.getString(R.string.failed); - } else { - ToastMessage = context.getString(R.string.no_update_available); - } - Toast.makeText(context, ToastMessage, Toast.LENGTH_LONG).show(); + handler.post(() -> { + String ToastMessage = ""; + if (error) { + ToastMessage = context.getString(R.string.failed); + } else { + ToastMessage = context.getString(R.string.no_update_available); } + Toast.makeText(context, ToastMessage, Toast.LENGTH_LONG).show(); }); } @@ -211,4 +206,10 @@ public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapp } return 0; } + + public class Wrapper { + public boolean UpdateAvailable = false; + public boolean NoUpdate = false; + public boolean isError = false; + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 51f4793d1..7f7d319aa 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -31,6 +31,8 @@ import android.security.KeyChain; import android.support.annotation.BoolRes; import android.support.annotation.IntegerRes; import android.support.v4.app.RemoteInput; +import android.support.v4.content.ContextCompat; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.LruCache; @@ -46,7 +48,6 @@ import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; -import java.math.BigInteger; import java.net.URL; import java.security.SecureRandom; import java.security.cert.CertificateException; @@ -66,6 +67,7 @@ import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -125,6 +127,7 @@ import de.pixart.messenger.utils.ReplacingSerialSingleThreadExecutor; import de.pixart.messenger.utils.ReplacingTaskManager; import de.pixart.messenger.utils.Resolver; import de.pixart.messenger.utils.SerialSingleThreadExecutor; +import de.pixart.messenger.utils.StringUtils; import de.pixart.messenger.utils.WakeLockHelper; import de.pixart.messenger.utils.XmppUri; import de.pixart.messenger.xml.Element; @@ -259,29 +262,35 @@ public class XmppConnectionService extends Service { private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { @Override - public void onMessageAcknowledged(Account account, String uuid) { + public boolean onMessageAcknowledged(Account account, String uuid) { for (final Conversation conversation : getConversations()) { if (conversation.getAccount() == account) { Message message = conversation.findUnsentMessageWithUuid(uuid); if (message != null) { - markMessage(message, Message.STATUS_SEND); + message.setStatus(Message.STATUS_SEND); + message.setErrorMessage(null); + databaseBackend.updateMessage(message, false); + return true; } } } + return false; } }; private int unreadCount = -1; //Ui callback listeners - private final List<OnConversationUpdate> mOnConversationUpdates = new ArrayList<>(); - private final List<OnShowErrorToast> mOnShowErrorToasts = new ArrayList<>(); - private final List<OnAccountUpdate> mOnAccountUpdates = new ArrayList<>(); - private final List<OnCaptchaRequested> mOnCaptchaRequested = new ArrayList<>(); - private final List<OnRosterUpdate> mOnRosterUpdates = new ArrayList<>(); - private final List<OnUpdateBlocklist> mOnUpdateBlocklist = new ArrayList<>(); - private final List<OnMucRosterUpdate> mOnMucRosterUpdate = new ArrayList<>(); - private final List<OnKeyStatusUpdated> mOnKeyStatusUpdated = new ArrayList<>(); + private final Set<OnConversationUpdate> mOnConversationUpdates = Collections.newSetFromMap(new WeakHashMap<OnConversationUpdate, Boolean>()); + private final Set<OnShowErrorToast> mOnShowErrorToasts = Collections.newSetFromMap(new WeakHashMap<OnShowErrorToast, Boolean>()); + private final Set<OnAccountUpdate> mOnAccountUpdates = Collections.newSetFromMap(new WeakHashMap<OnAccountUpdate, Boolean>()); + private final Set<OnCaptchaRequested> mOnCaptchaRequested = Collections.newSetFromMap(new WeakHashMap<OnCaptchaRequested, Boolean>()); + private final Set<OnRosterUpdate> mOnRosterUpdates = Collections.newSetFromMap(new WeakHashMap<OnRosterUpdate, Boolean>()); + private final Set<OnUpdateBlocklist> mOnUpdateBlocklist = Collections.newSetFromMap(new WeakHashMap<OnUpdateBlocklist, Boolean>()); + private final Set<OnMucRosterUpdate> mOnMucRosterUpdate = Collections.newSetFromMap(new WeakHashMap<OnMucRosterUpdate, Boolean>()); + private final Set<OnKeyStatusUpdated> mOnKeyStatusUpdated = Collections.newSetFromMap(new WeakHashMap<OnKeyStatusUpdated, Boolean>()); + + private final Object LISTENER_LOCK = new Object(); private final OnBindListener mOnBindListener = new OnBindListener() { @@ -1101,7 +1110,10 @@ public class XmppConnectionService extends Service { restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); - new Thread(fileObserver::startWatching).start(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + Log.d(Config.LOGTAG, "starting file observer"); + new Thread(fileObserver::startWatching).start(); + } if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @Override @@ -1160,6 +1172,11 @@ public class XmppConnectionService extends Service { CancelAutomaticExport(true); } + public void restartFileObserver() { + Log.d(Config.LOGTAG, "restarting file observer"); + new Thread(fileObserver::restartWatching).start(); + } + public void toggleScreenEventReceiver() { if (awayWhenScreenOff() && !manuallyChangePresence()) { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); @@ -1214,10 +1231,13 @@ public class XmppConnectionService extends Service { public void scheduleWakeUpCall(int seconds, int requestCode) { final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000; final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(this, EventReceiver.class); + if (alarmManager == null) { + return; + } + final Intent intent = new Intent(this, EventReceiver.class); intent.setAction("ping"); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent, 0); try { + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent, 0); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent); } catch (RuntimeException e) { Log.e(Config.LOGTAG, "unable to schedule alarm for ping", e); @@ -1230,10 +1250,13 @@ public class XmppConnectionService extends Service { private void scheduleNextIdlePing() { final long timeToWake = SystemClock.elapsedRealtime() + (Config.IDLE_PING_INTERVAL * 1000); final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(this, EventReceiver.class); + if (alarmManager == null) { + return; + } + final Intent intent = new Intent(this, EventReceiver.class); intent.setAction(ACTION_IDLE_PING); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); try { + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent); } catch (RuntimeException e) { Log.d(Config.LOGTAG, "unable to schedule alarm for idle ping", e); @@ -1709,7 +1732,7 @@ public class XmppConnectionService extends Service { } else { for (Conversation conversation : getConversations()) { if (conversation.getMode() == Conversation.MODE_SINGLE - || conversation.getAccount().httpUploadAvailable()) { + || (conversation.getAccount().httpUploadAvailable() && conversation.getMucOptions().participating())) { list.add(conversation); } } @@ -2123,18 +2146,19 @@ public class XmppConnectionService extends Service { } public void setOnConversationListChangedListener(OnConversationUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnConversationUpdates.add(listener); - Log.d(Config.LOGTAG, "XmppConnectionService setOnConversationListChangedListener(): setIsInForeground = " + (this.mOnConversationUpdates.size() > 0)); + if (!this.mOnConversationUpdates.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as ConversationListChangedListener"); + } this.mNotificationService.setIsInForeground(this.mOnConversationUpdates.size() > 0); } } public void removeOnConversationListChangedListener(OnConversationUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnConversationUpdates.remove(listener); Log.d(Config.LOGTAG, "XmppConnectionService removeOnConversationListChangedListener(): setIsInForeground = " + (this.mOnConversationUpdates.size() > 0)); this.mNotificationService.setIsInForeground(this.mOnConversationUpdates.size() > 0); @@ -2144,17 +2168,19 @@ public class XmppConnectionService extends Service { } } - public void setOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) { - synchronized (this) { + public void setOnShowErrorToastListener(OnShowErrorToast listener) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnShowErrorToasts.add(onShowErrorToast); + if (!this.mOnShowErrorToasts.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnShowErrorToastListener"); + } } } public void removeOnShowErrorToastListener(OnShowErrorToast onShowErrorToast) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnShowErrorToasts.remove(onShowErrorToast); if (checkListeners()) { switchToBackground(); @@ -2163,16 +2189,18 @@ public class XmppConnectionService extends Service { } public void setOnAccountListChangedListener(OnAccountUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnAccountUpdates.add(listener); + if (!this.mOnAccountUpdates.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnAccountListChangedtListener"); + } } } public void removeOnAccountListChangedListener(OnAccountUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnAccountUpdates.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2181,16 +2209,18 @@ public class XmppConnectionService extends Service { } public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnCaptchaRequested.add(listener); + if (!this.mOnCaptchaRequested.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnCaptchaRequestListener"); + } } } public void removeOnCaptchaRequestedListener(OnCaptchaRequested listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnCaptchaRequested.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2199,16 +2229,18 @@ public class XmppConnectionService extends Service { } public void setOnRosterUpdateListener(final OnRosterUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnRosterUpdates.add(listener); + if (!this.mOnRosterUpdates.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnRosterUpdateListener"); + } } } public void removeOnRosterUpdateListener(final OnRosterUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnRosterUpdates.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2217,16 +2249,18 @@ public class XmppConnectionService extends Service { } public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnUpdateBlocklist.add(listener); + if (!this.mOnUpdateBlocklist.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnUpdateBlocklistListener"); + } } } public void removeOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnUpdateBlocklist.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2235,16 +2269,18 @@ public class XmppConnectionService extends Service { } public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnKeyStatusUpdated.add(listener); + if (!this.mOnKeyStatusUpdated.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnKeyStatusUpdateListener"); + } } } public void removeOnNewKeysAvailableListener(final OnKeyStatusUpdated listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnKeyStatusUpdated.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2253,16 +2289,18 @@ public class XmppConnectionService extends Service { } public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { if (checkListeners()) { switchToForeground(); } - this.mOnMucRosterUpdate.add(listener); + if (!this.mOnMucRosterUpdate.add(listener)) { + Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnMucRosterListener"); + } } } public void removeOnMucRosterUpdateListener(final OnMucRosterUpdate listener) { - synchronized (this) { + synchronized (LISTENER_LOCK) { this.mOnMucRosterUpdate.remove(listener); if (checkListeners()) { switchToBackground(); @@ -2275,6 +2313,7 @@ public class XmppConnectionService extends Service { && this.mOnConversationUpdates.size() == 0 && this.mOnRosterUpdates.size() == 0 && this.mOnCaptchaRequested.size() == 0 + && this.mOnMucRosterUpdate.size() == 0 && this.mOnUpdateBlocklist.size() == 0 && this.mOnShowErrorToasts.size() == 0 && this.mOnKeyStatusUpdated.size() == 0); @@ -2625,7 +2664,7 @@ public class XmppConnectionService extends Service { } public boolean createAdhocConference(final Account account, - final String subject, + final String name, final Iterable<Jid> jids, final UiCallback<Conversation> callback) { Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": creating adhoc conference with " + jids.toString()); @@ -2638,24 +2677,25 @@ public class XmppConnectionService extends Service { } return false; } - final Jid jid = Jid.of(new BigInteger(64, getRNG()).toString(Character.MAX_RADIX), server, null); + final Jid jid = Jid.of(CryptoHelper.pronounceable(getRNG()), server, null); final Conversation conversation = findOrCreateConversation(account, jid, true, false, true); joinMuc(conversation, new OnConferenceJoined() { @Override public void onConferenceJoined(final Conversation conversation) { - pushConferenceConfiguration(conversation, IqGenerator.defaultRoomConfiguration(), new OnConfigurationPushed() { + final Bundle configuration = IqGenerator.defaultRoomConfiguration(); + if (!TextUtils.isEmpty(name)) { + configuration.putString("muc#roomconfig_roomname", name); + } + pushConferenceConfiguration(conversation, configuration, new OnConfigurationPushed() { @Override public void onPushSucceeded() { - if (subject != null && !subject.trim().isEmpty()) { - pushSubjectToConference(conversation, subject.trim()); - } for (Jid invite : jids) { invite(conversation, invite); } if (account.countPresences() > 1) { directInvite(conversation, account.getJid().asBareJid()); } - saveConversationAsBookmark(conversation, subject); + saveConversationAsBookmark(conversation, name); if (callback != null) { callback.success(conversation); } @@ -2698,13 +2738,25 @@ public class XmppConnectionService extends Service { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { - if (conversation.getMucOptions().updateConfiguration(new ServiceDiscoveryResult(packet))) { + final MucOptions mucOptions = conversation.getMucOptions(); + final Bookmark bookmark = conversation.getBookmark(); + final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName()); + + if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(packet))) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid()); updateConversation(conversation); } + + if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) { + if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) { + pushBookmarks(account); + } + } + if (callback != null) { callback.onConferenceConfigurationFetched(conversation); } + updateConversationUi(); } else if (packet.getType() == IqPacket.TYPE.ERROR) { if (callback != null) { @@ -2786,7 +2838,7 @@ public class XmppConnectionService extends Service { } public void pushSubjectToConference(final Conversation conference, final String subject) { - MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, subject); + MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject)); this.sendMessagePacket(conference.getAccount(), packet); } @@ -3200,10 +3252,12 @@ public class XmppConnectionService extends Service { updateAccountUi(); } else { Contact contact = a.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar); - getAvatarService().clear(contact); - updateConversationUi(); - updateRosterUi(); + if (contact.setAvatar(avatar)) { + syncRoster(account); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } } if (callback != null) { callback.success(avatar); @@ -3255,9 +3309,11 @@ public class XmppConnectionService extends Service { updateAccountUi(); } else { Contact contact = account.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar); - getAvatarService().clear(contact); - updateRosterUi(); + if (contact.setAvatar(avatar)) { + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); + } } updateConversationUi(); } else { @@ -3525,26 +3581,32 @@ public class XmppConnectionService extends Service { vibrator.vibrate(100); } + private <T> List<T> threadSafeList(Set<T> set) { + synchronized (LISTENER_LOCK) { + return set.size() == 0 ? Collections.emptyList() : new ArrayList<>(set); + } + } + public void showErrorToastInUi(int resId) { - for(OnShowErrorToast listener : this.mOnShowErrorToasts) { + for (OnShowErrorToast listener : threadSafeList(this.mOnShowErrorToasts)) { listener.onShowErrorToast(resId); } } public void updateConversationUi() { - for(OnConversationUpdate listener : this.mOnConversationUpdates) { + for (OnConversationUpdate listener : threadSafeList(this.mOnConversationUpdates)) { listener.onConversationUpdate(); } } public void updateAccountUi() { - for(OnAccountUpdate listener : this.mOnAccountUpdates) { + for (OnAccountUpdate listener : threadSafeList(this.mOnAccountUpdates)) { listener.onAccountUpdate(); } } public void updateRosterUi() { - for(OnRosterUpdate listener : this.mOnRosterUpdates) { + for (OnRosterUpdate listener : threadSafeList(this.mOnRosterUpdates)) { listener.onRosterUpdate(); } } @@ -3554,8 +3616,7 @@ public class XmppConnectionService extends Service { DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics(); Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity), (int) (captcha.getHeight() * metrics.scaledDensity), false); - - for(OnCaptchaRequested listener : this.mOnCaptchaRequested) { + for (OnCaptchaRequested listener : threadSafeList(this.mOnCaptchaRequested)) { listener.onCaptchaRequested(account, id, data, scaled); } return true; @@ -3564,19 +3625,19 @@ public class XmppConnectionService extends Service { } public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { - for(OnUpdateBlocklist listener : this.mOnUpdateBlocklist) { + for (OnUpdateBlocklist listener : threadSafeList(this.mOnUpdateBlocklist)) { listener.OnUpdateBlocklist(status); } } public void updateMucRosterUi() { - for(OnMucRosterUpdate listener : this.mOnMucRosterUpdate) { + for (OnMucRosterUpdate listener : threadSafeList(this.mOnMucRosterUpdate)) { listener.onMucRosterUpdate(); } } public void keyStatusUpdated(AxolotlService.FetchStatus report) { - for(OnKeyStatusUpdated listener : this.mOnKeyStatusUpdated) { + for (OnKeyStatusUpdated listener : threadSafeList(this.mOnKeyStatusUpdated)) { listener.onKeyStatusUpdated(report); } } @@ -3777,6 +3838,8 @@ public class XmppConnectionService extends Service { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); + } else if (callback != null) { + callback.onIqPacketReceived(account, new IqPacket(IqPacket.TYPE.TIMEOUT)); } } @@ -3908,6 +3971,16 @@ public class XmppConnectionService extends Service { updateConversationUi(); } + public void deleteMessage(final Conversation conversation, Message message) { + conversation.deleteMessage(message); + Runnable runnable = () -> { + databaseBackend.deleteMessageInConversation(message); + databaseBackend.updateConversation(conversation); + + }; + mDatabaseWriterExecutor.execute(runnable); + } + public void clearConversationHistory(final Conversation conversation) { final long clearDate; final String reference; @@ -4060,11 +4133,11 @@ public class XmppConnectionService extends Service { } public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { - final boolean legacy = account.getXmppConnection().getFeatures().mamLegacy(); + final MessageArchiveService.Version version = MessageArchiveService.Version.get(account); IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.addChild("prefs",legacy ? Namespace.MAM_LEGACY : Namespace.MAM); + request.addChild("prefs", version.namespace); sendIqPacket(account, request, (account1, packet) -> { - Element prefs = packet.findChild("prefs",legacy ? Namespace.MAM_LEGACY : Namespace.MAM); + Element prefs = packet.findChild("prefs", version.namespace); if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { callback.onPreferencesFetched(prefs); } else { @@ -4116,8 +4189,8 @@ public class XmppConnectionService extends Service { if (!conversation.getJid().isBareJid()) { bookmark.setNick(conversation.getJid().getResource()); } - if (name != null && !name.trim().isEmpty()) { - bookmark.setBookmarkName(name.trim()); + if (!TextUtils.isEmpty(name)) { + bookmark.setBookmarkName(name); } bookmark.setAutojoin(getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))); account.getBookmarks().add(bookmark); diff --git a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java index e79be5c4a..a760a563b 100644 --- a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java @@ -11,7 +11,6 @@ import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; import android.view.ActionMode; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -38,12 +37,19 @@ import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.utils.XmppUri; import rocks.xmpp.addr.Jid; -public class ChooseContactActivity extends AbstractSearchableListItemActivity { +public class ChooseContactActivity extends AbstractSearchableListItemActivity implements MultiChoiceModeListener { public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id"; + public static final String EXTRA_GROUP_CHAT_NAME = "extra_group_chat_name"; + public static final String EXTRA_SELECT_MULTIPLE = "extra_select_multiple"; + public static final String EXTRA_SHOW_ENTER_JID = "extra_show_enter_jid"; + public static final String EXTRA_CONVERSATION = "extra_conversation"; + private static final String EXTRA_FILTERED_CONTACTS = "extra_filtered_contacts"; private List<String> mActivatedAccounts = new ArrayList<>(); private Set<String> selected = new HashSet<>(); private Set<String> filterContacts; + private boolean showEnterJid = false; + private PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>(); public static Intent create(Activity activity, Conversation conversation) { @@ -59,14 +65,31 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { } else { contacts.add(conversation.getJid().asBareJid().toString()); } - intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()])); - intent.putExtra("conversation", conversation.getUuid()); - intent.putExtra("multiple", true); - intent.putExtra("show_enter_jid", true); + intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[contacts.size()])); + intent.putExtra(EXTRA_CONVERSATION, conversation.getUuid()); + intent.putExtra(EXTRA_SELECT_MULTIPLE, true); + intent.putExtra(EXTRA_SHOW_ENTER_JID, true); intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); return intent; } + public static List<Jid> extractJabberIds(Intent result) { + List<Jid> jabberIds = new ArrayList<>(); + try { + if (result.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false)) { + String[] toAdd = result.getStringArrayExtra("contacts"); + for (String item : toAdd) { + jabberIds.add(Jid.of(item)); + } + } else { + jabberIds.add(Jid.of(result.getStringExtra("contact"))); + } + return jabberIds; + } catch (IllegalArgumentException e) { + return jabberIds; + } + } + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,81 +101,26 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { selected.addAll(Arrays.asList(selectedContacts)); } } - String[] contacts = getIntent().getStringArrayExtra("filter_contacts"); + + String[] contacts = getIntent().getStringArrayExtra(EXTRA_FILTERED_CONTACTS); if (contacts != null) { Collections.addAll(filterContacts, contacts); } - if (getIntent().getBooleanExtra("multiple", false)) { - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - binding.fab.setVisibility(View.GONE); - final View view = getSearchEditText(); - final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (view != null && imm != null) { - imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); - } - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.select_multiple, menu); - MenuItem selectButton = menu.findItem(R.id.selection_submit); - String buttonText = getResources().getQuantityString(R.plurals.select_contact, selected.size(), selected.size()); - selectButton.setTitle(buttonText); - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - binding.fab.setVisibility(View.VISIBLE); - selected.clear(); - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch (item.getItemId()) { - case R.id.selection_submit: - final Intent request = getIntent(); - final Intent data = new Intent(); - data.putExtra("conversation", - request.getStringExtra("conversation")); - String[] selection = getSelectedContactJids(); - data.putExtra("contacts", selection); - data.putExtra("multiple", true); - data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT)); - data.putExtra("subject", request.getStringExtra("subject")); - setResult(RESULT_OK, data); - finish(); - return true; - } - return false; - } + Intent intent = getIntent(); - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - Contact item = (Contact) getListItems().get(position); - if (checked) { - selected.add(item.getJid().toString()); - } else { - selected.remove(item.getJid().toString()); - } - int numSelected = selected.size(); - MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit); - String buttonText = getResources().getQuantityString(R.plurals.select_contact, - numSelected, numSelected); - selectButton.setTitle(buttonText); - } - }); + final boolean multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false); + if (multiple) { + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + getListView().setMultiChoiceModeListener(this); } - getListView().setOnItemClickListener((parent, view, position, id) -> { + if (multiple) { + startActionMode(this); + getListView().setItemChecked(position, true); + return; + } final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); final Intent request = getIntent(); @@ -164,42 +132,118 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { account = ((Contact) mListItem).getAccount().getJid().asBareJid().toString(); } data.putExtra(EXTRA_ACCOUNT, account); - data.putExtra("conversation", request.getStringExtra("conversation")); - data.putExtra("multiple", false); - data.putExtra("subject", request.getStringExtra("subject")); + data.putExtra(EXTRA_SELECT_MULTIPLE, false); + copy(request, data); setResult(RESULT_OK, data); finish(); }); final Intent i = getIntent(); - boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); - if (showEnterJid) { - this.binding.fab.setOnClickListener((v) -> showEnterJidDialog(null)); + this.showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false); + this.binding.fab.setOnClickListener(this::onFabClicked); + if (this.showEnterJid) { + this.binding.fab.setVisibility(View.VISIBLE); + } else { + this.binding.fab.setVisibility(View.GONE); + } + } + + private void onFabClicked(View v) { + if (selected.size() == 0) { + showEnterJidDialog(null); + } else { + submitSelection(); + } + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.setTitle(getTitleFromIntent()); + binding.fab.setImageResource(R.drawable.ic_forward_white_24dp); + binding.fab.setVisibility(View.VISIBLE); + final View view = getSearchEditText(); + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (view != null && imm != null) { + imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); + } + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + this.binding.fab.setImageResource(R.drawable.ic_person_add_white_24dp); + if (this.showEnterJid) { + this.binding.fab.setVisibility(View.VISIBLE); } else { this.binding.fab.setVisibility(View.GONE); } + selected.clear(); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + private void submitSelection() { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contacts", getSelectedContactJids()); + data.putExtra(EXTRA_SELECT_MULTIPLE, true); + data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT)); + copy(request, data); + setResult(RESULT_OK, data); + finish(); + } + + private static void copy(Intent from, Intent to) { + to.putExtra(EXTRA_CONVERSATION, from.getStringExtra(EXTRA_CONVERSATION)); + to.putExtra(EXTRA_GROUP_CHAT_NAME, from.getStringExtra(EXTRA_GROUP_CHAT_NAME)); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + if (selected.size() != 0) { + getListView().playSoundEffect(0); + } + Contact item = (Contact) getListItems().get(position); + if (checked) { + selected.add(item.getJid().toString()); + } else { + selected.remove(item.getJid().toString()); + } } @Override public void onStart() { super.onStart(); - Intent intent = getIntent(); - @StringRes - int res = intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, R.string.title_activity_choose_contact) : R.string.title_activity_choose_contact; ActionBar bar = getSupportActionBar(); if (bar != null) { try { - bar.setTitle(res); + bar.setTitle(getTitleFromIntent()); } catch (Exception e) { bar.setTitle(R.string.title_activity_choose_contact); } } } + public @StringRes + int getTitleFromIntent() { + final Intent intent = getIntent(); + boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false); + @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact; + return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback; + } + @Override public boolean onCreateOptionsMenu(final Menu menu) { super.onCreateOptionsMenu(menu); final Intent i = getIntent(); - boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); + boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false); menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid); return true; } @@ -264,7 +308,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { jid == null ? null : jid.asBareJid().toString(), getIntent().getStringExtra(EXTRA_ACCOUNT), true, - xmppConnectionService.multipleAccounts() + true ); dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> { @@ -272,10 +316,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { final Intent data = new Intent(); data.putExtra("contact", contactJid.toString()); data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); - data.putExtra("conversation", - request.getStringExtra("conversation")); - data.putExtra("multiple", false); - data.putExtra("subject", request.getStringExtra("subject")); + data.putExtra(EXTRA_SELECT_MULTIPLE, false); + copy(request, data); setResult(RESULT_OK, data); finish(); @@ -309,7 +351,6 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { @Override void onBackendConnected() { filterContacts(); - this.mActivatedAccounts.clear(); for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { @@ -334,4 +375,4 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { ScanActivity.onRequestPermissionResult(this, requestCode, grantResults); } -} +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 8e8178609..db70c8147 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -53,6 +53,7 @@ import de.pixart.messenger.ui.util.MyLinkify; import de.pixart.messenger.ui.util.SoftKeyboardUtils; import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.MenuDoubleTabUtil; +import de.pixart.messenger.utils.StringUtils; import de.pixart.messenger.utils.StylingHelper; import de.pixart.messenger.utils.TimeframeUtils; import de.pixart.messenger.utils.UIHelper; @@ -60,6 +61,7 @@ import de.pixart.messenger.utils.XmppUri; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.entities.Bookmark.printableValue; +import static de.pixart.messenger.utils.StringUtils.changed; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher { public static final String ACTION_VIEW_MUC = "view_muc"; @@ -405,19 +407,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) && changed(mucOptions.getName(), name)) { Bundle options = new Bundle(); options.putString("muc#roomconfig_persistentroom", "1"); - options.putString("muc#roomconfig_roomname", name); + options.putString("muc#roomconfig_roomname", StringUtils.nullOnEmpty(name)); xmppConnectionService.pushConferenceConfiguration(mConversation, options, this); } } - private static String blankOnNull(String input) { - return input == null ? "" : input; - } - - private static boolean changed(String one, String two) { - return !blankOnNull(one).equals(blankOnNull(two)); - } - @Override protected String getShareableUri(boolean http) { if (mConversation != null) { @@ -595,8 +589,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } protected void saveAsBookmark() { - xmppConnectionService.saveConversationAsBookmark(mConversation, - mConversation.getMucOptions().getSubject()); + xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName()); updateView(); } @@ -666,7 +659,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers String subject = mucOptions.getSubject(); final boolean hasTitle; if (printableValue(roomName)) { - this.binding.mucTitle.setText(roomName); + this.binding.mucTitle.setText(EmojiWrapper.transform(roomName)); this.binding.mucTitle.setVisibility(View.VISIBLE); hasTitle = true; } else if (!printableValue(subject)) { diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index fa2ba5327..8ee0448ef 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -82,6 +82,7 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Blockable; import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Conversational; import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.MucOptions; @@ -566,6 +567,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return true; } + private static boolean writeGranted(int[] grantResults, String[] permission) { + for (int i = 0; i < grantResults.length; ++i) { + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) { + return grantResults[i] == PackageManager.PERMISSION_GRANTED; + } + } + return false; + } + private static String getFirstDenied(int[] grantResults, String[] permissions) { for (int i = 0; i < grantResults.length; ++i) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) { @@ -951,7 +961,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(conversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); - if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { + boolean downloadInProgress = axolotlService.hasPendingKeyFetches(targets); + if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted || downloadInProgress) { axolotlService.createSessionsIfNeeded(conversation); Intent intent = new Intent(getActivity(), TrustKeysActivity.class); String[] contacts = new String[targets.size()]; @@ -1125,7 +1136,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final MenuItem menuNeedHelp = menu.findItem(R.id.action_create_issue); final MenuItem menuSearchUpdates = menu.findItem(R.id.action_check_updates); final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat); - final MenuItem menuEditProfiles = menu.findItem(R.id.action_accounts); if (conversation != null) { if (conversation.getMode() == Conversation.MODE_MULTI) { @@ -1135,7 +1145,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke menuInviteContact.setVisible(false); menuArchiveChat.setTitle(R.string.action_end_conversation); } - menuEditProfiles.setVisible(false); menuNeedHelp.setVisible(true); menuSearchUpdates.setVisible(false); ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu); @@ -1288,6 +1297,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke MenuItem quoteMessage = menu.findItem(R.id.quote_message); MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); MenuItem correctMessage = menu.findItem(R.id.correct_message); + MenuItem deleteMessage = menu.findItem(R.id.delete_message); MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); @@ -1298,6 +1308,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (!m.isFileOrImage() && !encrypted && !m.isGeoUri() && !m.treatAsDownloadable()) { copyMessage.setVisible(true); quoteMessage.setVisible(MessageUtils.prepareQuote(m).length() > 0); + deleteMessage.setVisible(true); String body = m.getMergedBody().toString(); if (ShareUtil.containsXmppUri(body)) { copyLink.setTitle(R.string.copy_jabber_id); @@ -1386,6 +1397,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case R.id.retry_decryption: retryDecryption(selectedMessage); return true; + case R.id.delete_message: + deleteMessage(selectedMessage); + return true; case R.id.delete_file: deleteFile(selectedMessage); return true; @@ -1618,6 +1632,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke Toast.makeText(getActivity(), res, Toast.LENGTH_SHORT).show(); } } + if (writeGranted(grantResults, permissions)) { + if (activity != null && activity.xmppConnectionService != null) { + activity.xmppConnectionService.restartFileObserver(); + } + } } public void startDownloadable(Message message) { @@ -1831,6 +1850,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke builder.create().show(); } + private void deleteMessage(Message message) { + final Conversation conversation = (Conversation) message.getConversation(); + activity.xmppConnectionService.deleteMessage(conversation, message); + activity.onConversationsListItemUpdated(); + refresh(); + } + private void deleteFile(Message message) { if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); @@ -2035,7 +2061,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } if (this.conversation != null) { final String msg = this.binding.textinput.getText().toString(); - if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && this.conversation.setNextMessage(msg)) { + final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); + if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) { this.activity.xmppConnectionService.updateConversation(this.conversation); } updateChatState(this.conversation, msg); @@ -2059,7 +2086,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } Log.d(Config.LOGTAG, "ConversationFragment.saveMessageDraftStopAudioPlayer()"); final String msg = this.binding.textinput.getText().toString(); - if (previousConversation.setNextMessage(msg)) { + final boolean participating = previousConversation.getMode() == Conversational.MODE_SINGLE || previousConversation.getMucOptions().participating(); + if (participating && previousConversation.setNextMessage(msg)) { activity.xmppConnectionService.updateConversation(previousConversation); } updateChatState(this.conversation, msg); @@ -2115,7 +2143,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textSendButton.setContentDescription(activity.getString(R.string.send_message_to_x, conversation.getName())); this.binding.textinput.setKeyboardListener(null); this.binding.textinput.setText(""); - this.binding.textinput.append(this.conversation.getNextMessage()); + final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); + if (participating) { + this.binding.textinput.append(this.conversation.getNextMessage()); + } this.binding.textinput.setKeyboardListener(this); messageListAdapter.updatePreferences(); refresh(false); @@ -2315,17 +2346,23 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case MEMBERS_ONLY: showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); break; + case RESOURCE_CONSTRAINT: + showSnackbar(R.string.conference_resource_constraint, R.string.try_again, joinMuc); + break; case KICKED: showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); break; case UNKNOWN: - showSnackbar(R.string.conference_unknown_error, R.string.leave, leaveMuc); + showSnackbar(R.string.conference_unknown_error, R.string.leave, joinMuc); break; case INVALID_NICK: showSnackbar(R.string.invalid_muc_nick, R.string.edit, clickToMuc); case SHUTDOWN: showSnackbar(R.string.conference_shutdown, R.string.try_again, joinMuc); break; + case DESTROYED: + showSnackbar(R.string.conference_destroyed, R.string.leave, leaveMuc); + break; default: hideSnackbar(); break; @@ -2411,6 +2448,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } updateSendButton(); updateEditablity(); + activity.invalidateOptionsMenu(); } } } @@ -2422,8 +2460,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.append(conversation.getDraftMessage()); conversation.setDraftMessage(null); } - if (conversation.setNextMessage(this.binding.textinput.getText().toString())) { - activity.xmppConnectionService.updateConversation(conversation); + final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); + if (participating && conversation.setNextMessage(this.binding.textinput.getText().toString())) { + activity.xmppConnectionService.databaseBackend.updateConversation(conversation); } updateChatMsgHint(); SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); @@ -2451,6 +2490,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.setFocusableInTouchMode(canWrite); this.binding.textSendButton.setEnabled(canWrite); this.binding.textinput.setCursorVisible(canWrite); + this.binding.textinput.setEnabled(canWrite); } public void updateSendButton() { @@ -2508,6 +2548,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final String body; if (size <= 4) { body = getString(R.string.contacts_have_read_up_to_this_point, UIHelper.concatNames(shownMarkers)); + } else if (ReadByMarker.allUsersRepresented(allUsers, markersForMessage, markerForSender)) { + body = getString(R.string.everyone_has_read_up_to_this_point); } else { body = getString(R.string.contacts_and_n_more_have_read_up_to_this_point, UIHelper.concatNames(shownMarkers, 3), size - 3); } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java index 3882fd732..67618dc65 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java @@ -85,6 +85,7 @@ import de.pixart.messenger.ui.interfaces.OnConversationsListItemUpdated; import de.pixart.messenger.ui.util.ActivityResult; import de.pixart.messenger.ui.util.ConversationMenuConfigurator; import de.pixart.messenger.ui.util.PendingItem; +import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.ExceptionHelper; import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.UIHelper; @@ -545,7 +546,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio case android.R.id.home: FragmentManager fm = getFragmentManager(); if (fm.getBackStackEntryCount() > 0) { - fm.popBackStack(); + try { + fm.popBackStack(); + } catch (IllegalArgumentException e) { + Log.w(Config.LOGTAG, "Unable to pop back stack after pressing home button"); + } return true; } break; @@ -557,7 +562,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (!installFromUnknownSourceAllowed() && !xmppConnectionService.installedFromPlayStore()) { openInstallFromUnknownSourcesDialogIfNeeded(); } else { - UpdateService task = new UpdateService(this, xmppConnectionService.installedFromPlayStore()); + UpdateService task = new UpdateService(this, xmppConnectionService.installedFromPlayStore(), xmppConnectionService); task.executeOnExecutor(UpdateService.THREAD_POOL_EXECUTOR, "true"); Log.d(Config.LOGTAG, "AppUpdater started"); } @@ -688,7 +693,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio actionBar.setDisplayShowCustomEnabled(true); TextView abtitle = findViewById(android.R.id.text1); TextView absubtitle = findViewById(android.R.id.text2); - abtitle.setText(conversation.getName()); + abtitle.setText(EmojiWrapper.transform(conversation.getName())); abtitle.setOnClickListener(view1 -> { if (conversation.getMode() == Conversation.MODE_SINGLE) { switchToContactDetails(conversation.getContact()); @@ -766,7 +771,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (userWithChatStates.size() > 0) { if (userWithChatStates.size() == 1) { MucOptions.User user = userWithChatStates.get(0); - absubtitle.setText(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user))); + absubtitle.setText(EmojiWrapper.transform(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user)))); } else { StringBuilder builder = new StringBuilder(); for (MucOptions.User user : userWithChatStates) { @@ -775,7 +780,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } builder.append(UIHelper.getDisplayName(user)); } - absubtitle.setText(getString(R.string.contacts_are_typing, builder.toString())); + absubtitle.setText(EmojiWrapper.transform(getString(R.string.contacts_are_typing, builder.toString()))); } } } else { @@ -952,7 +957,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (!installFromUnknownSourceAllowed() && !PlayStore) { openInstallFromUnknownSourcesDialogIfNeeded(); } else { - UpdateService task = new UpdateService(this, PlayStore); + UpdateService task = new UpdateService(this, PlayStore, xmppConnectionService); task.executeOnExecutor(UpdateService.THREAD_POOL_EXECUTOR, "false"); Log.d(Config.LOGTAG, "AppUpdater started"); } diff --git a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java index da0fe37de..900f4a42d 100644 --- a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java +++ b/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java @@ -16,6 +16,7 @@ import java.util.List; import de.pixart.messenger.R; import de.pixart.messenger.databinding.CreateConferenceDialogBinding; import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.util.DelayedHintHelper; public class CreateConferenceDialog extends DialogFragment { @@ -45,10 +46,6 @@ public class CreateConferenceDialog extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.create_conference); - //final View dialogView = getActivity().getLayoutInflater().inflate(R.layout.create_conference_dialog, null); - //final TextView yourAccount = dialogView.findViewById(R.id.your_account); - //final Spinner spinner = dialogView.findViewById(R.id.account); - //final EditText subject = dialogView.findViewById(R.id.subject); CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false); if (getArguments().getBoolean(MULTIPLE_ACCOUNTS)) { binding.yourAccount.setVisibility(View.VISIBLE); @@ -60,8 +57,9 @@ public class CreateConferenceDialog extends DialogFragment { ArrayList<String> mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.subject.getText().toString())); + builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); builder.setNegativeButton(R.string.cancel, null); + DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); return builder.create(); } diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index 9333b13b4..e33de0eb4 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -431,7 +431,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } protected void updateInfoButtons() { - if (this.binding.accountRegisterNew.isChecked() && this.binding.accountJid.getText().length() > 0) { + if (this.binding.accountRegisterNew.isChecked() && this.binding.accountJid.getText().length() > 0 && !this.binding.accountJid.getText().toString().contains("@")) { if (!mUsernameMode && Jid.of(this.binding.accountJid.getText()).getDomain().toLowerCase().equals("pix-art.de")) { this.binding.showPrivacyPolicy.setVisibility(View.VISIBLE); this.binding.showTermsOfUse.setVisibility(View.VISIBLE); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index ddb5a74f8..a58c2e0c0 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -660,26 +660,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne this.mPostponedActivityResult = null; if (requestCode == REQUEST_CREATE_CONFERENCE) { Account account = extractAccount(intent); - final String subject = intent.getStringExtra("subject"); - List<Jid> jids = new ArrayList<>(); - if (intent.getBooleanExtra("multiple", false)) { - String[] toAdd = intent.getStringArrayExtra("contacts"); - for (String item : toAdd) { - try { - jids.add(Jid.of(item)); - } catch (IllegalArgumentException e) { - //ignored - } - } - } else { - try { - jids.add(Jid.of(intent.getStringExtra("contact"))); - } catch (Exception e) { - //ignored - } - } + final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME); + final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent); if (account != null && jids.size() > 0) { - if (xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback)) { + if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) { mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); mToast.show(); } @@ -959,7 +943,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void onCreateDialogPositiveClick(Spinner spinner, String subject) { + public void onCreateDialogPositiveClick(Spinner spinner, String name) { if (!xmppConnectionServiceBound) { return; } @@ -968,10 +952,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne return; } Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); - intent.putExtra("multiple", true); - intent.putExtra("show_enter_jid", true); - intent.putExtra("subject", subject); - intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); + intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false); + intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true); + intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim()); + intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants); startActivityForResult(intent, REQUEST_CREATE_CONFERENCE); overridePendingTransition(R.animator.fade_in, R.animator.fade_out); @@ -1054,8 +1038,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } @Override - public void onCreateContextMenu(final ContextMenu menu, final View v, - final ContextMenuInfo menuInfo) { + public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); final StartConversationActivity activity = (StartConversationActivity) getActivity(); if (activity == null) { diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index 06fab28c4..e8e17e287 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -331,9 +331,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat processFingerprintVerification(this.mPendingFingerprintVerificationUri); this.mPendingFingerprintVerificationUri = null; } else { - reloadFingerprints(); - populateView(); - invalidateOptionsMenu(); + final boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + populateView(); + invalidateOptionsMenu(); + } else { + finishOk(false); + } } } } @@ -347,7 +351,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private boolean hasPendingKeyFetches() { - return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids); + return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(contactJids); } diff --git a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java index f98872b7c..4832dc466 100644 --- a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java +++ b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java @@ -33,24 +33,29 @@ import java.util.List; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.utils.WakeLockHelper; +import static de.pixart.messenger.http.HttpConnectionManager.getProxy; + public class UpdaterActivity extends XmppActivity { static final private String FileName = "update.apk"; - String appURI = ""; String changelog = ""; Integer filesize = 0; boolean playstore = false; ProgressDialog mProgressDialog; DownloadTask downloadTask; + XmppConnectionService mXmppConnectionService; + private boolean mUseTor; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //set activity setContentView(R.layout.activity_updater); - + this.mUseTor = mXmppConnectionService.useTorToConnect(); this.mTheme = findTheme(); setTheme(this.mTheme); @@ -65,7 +70,7 @@ public class UpdaterActivity extends XmppActivity { } }; mProgressDialog.setMessage(getString(R.string.download_started)); - mProgressDialog.setProgressNumberFormat (null); + mProgressDialog.setProgressNumberFormat(null); mProgressDialog.setIndeterminate(true); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setCancelable(false); @@ -140,7 +145,8 @@ public class UpdaterActivity extends XmppActivity { } } else { Toast.makeText(getApplicationContext(), getText(R.string.download_started), Toast.LENGTH_LONG).show(); - downloadTask = new DownloadTask(UpdaterActivity.this) {}; + downloadTask = new DownloadTask(UpdaterActivity.this) { + }; downloadTask.execute(appURI); } } else { @@ -188,14 +194,90 @@ public class UpdaterActivity extends XmppActivity { super.onRestoreInstanceState(savedInstanceState); } + //check for internet connection + private boolean isNetworkAvailable(Context context) { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + NetworkInfo[] info = connectivity.getAllNetworkInfo(); + if (info != null) { + for (int i = 0; i < info.length; i++) { + if (info[i].getState() == NetworkInfo.State.CONNECTED) { + return true; + } + } + } + } + return false; + } + + public boolean isStoragePermissionGranted() { + if (Build.VERSION.SDK_INT >= 23) { + if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + return false; + } + } else { //permission is automatically granted on sdk<23 upon installation + return true; + } + } + + //show warning on back pressed + @Override + public void onBackPressed() { + showCancelDialog(); + } + + private void showCancelDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.cancel_update) + .setCancelable(false) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { + downloadTask.cancel(true); + } + if (mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); + } + UpdaterActivity.this.finish(); + } + }) + .setNegativeButton(R.string.no, (dialog, id) -> dialog.cancel()); + AlertDialog alert = builder.create(); + alert.show(); + } + + @Override + public void onPause() { + super.onPause(); + if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { + downloadTask.cancel(true); + } + UpdaterActivity.this.finish(); + } + + @Override + protected void onStop() { + super.onStop(); + if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { + downloadTask.cancel(true); + } + UpdaterActivity.this.finish(); + } + private class DownloadTask extends AsyncTask<String, Integer, String> { + File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getDirectoryName("Update", false)); + File file = new File(dir, FileName); private Context context; - private PowerManager.WakeLock mWakeLock; private long startTime = 0; - File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getDirectoryName("Update", false)); - File file = new File(dir, FileName); + public DownloadTask(Context context) { this.context = context; } @@ -233,7 +315,12 @@ public class UpdaterActivity extends XmppActivity { Log.d(Config.LOGTAG, "AppUpdater: download update from url: " + sUrl[0] + " to file name: " + file.toString()); URL url = new URL(sUrl[0]); - connection = (HttpURLConnection) url.openConnection(); + + if (mUseTor) { + connection = (HttpURLConnection) url.openConnection(getProxy()); + } else { + connection = (HttpURLConnection) url.openConnection(); + } connection.connect(); // expect HTTP 200 OK, so we don't mistakenly save error report @@ -309,79 +396,4 @@ public class UpdaterActivity extends XmppActivity { } } - //check for internet connection - private boolean isNetworkAvailable(Context context) { - ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity != null) { - NetworkInfo[] info = connectivity.getAllNetworkInfo(); - if (info != null) { - for (int i = 0; i < info.length; i++) { - if (info[i].getState() == NetworkInfo.State.CONNECTED) { - return true; - } - } - } - } - return false; - } - - public boolean isStoragePermissionGranted() { - if (Build.VERSION.SDK_INT >= 23) { - if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - return true; - } else { - - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); - return false; - } - } else { //permission is automatically granted on sdk<23 upon installation - return true; - } - } - - //show warning on back pressed - @Override - public void onBackPressed() { - showCancelDialog(); - } - - private void showCancelDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.cancel_update) - .setCancelable(false) - .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - if (!downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { - downloadTask.cancel(true); - } - if (mProgressDialog.isShowing()) { - mProgressDialog.dismiss(); - } - UpdaterActivity.this.finish(); - } - }) - .setNegativeButton(R.string.no, (dialog, id) -> dialog.cancel()); - AlertDialog alert = builder.create(); - alert.show(); - } - - @Override - public void onPause() { - super.onPause(); - if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { - downloadTask.cancel(true); - } - UpdaterActivity.this.finish(); - } - - @Override - protected void onStop() { - super.onStop(); - if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) { - downloadTask.cancel(true); - } - UpdaterActivity.this.finish(); - } } diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java index 7906c7f40..3c03542ee 100644 --- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java +++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java @@ -245,7 +245,9 @@ public class WelcomeActivity extends XmppActivity { if (checkDB != null) { checkDB.close(); } - Log.d(Config.LOGTAG, "checkDB = " + checkDB.toString() + ", Backup DB = " + Backup_DB_Version + ", DB = " + DB_Version); + if (checkDB != null) { + Log.d(Config.LOGTAG, "checkDB = " + checkDB.toString() + ", Backup DB = " + Backup_DB_Version + ", DB = " + DB_Version); + } if (checkDB != null && Backup_DB_Version != 0 && Backup_DB_Version <= DB_Version) { try { ImportDatabase(); diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index cd78879ac..f52b2d72c 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -61,7 +61,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import de.pixart.messenger.Config; @@ -99,7 +98,6 @@ public abstract class XmppActivity extends ActionBarActivity { public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; - protected final AtomicBoolean registeredListeners = new AtomicBoolean(false); protected int mColorRed; protected int mColorWarningButton; @@ -122,9 +120,7 @@ public abstract class XmppActivity extends ActionBarActivity { XmppConnectionBinder binder = (XmppConnectionBinder) service; xmppConnectionService = binder.getService(); xmppConnectionServiceBound = true; - if (registeredListeners.compareAndSet(false, true)) { - registerListeners(); - } + registerListeners(); invalidateOptionsMenu(); onBackendConnected(); } @@ -228,9 +224,7 @@ public abstract class XmppActivity extends ActionBarActivity { connectToBackend(); } } else { - if (registeredListeners.compareAndSet(false, true)) { - this.registerListeners(); - } + this.registerListeners(); this.onBackendConnected(); } } @@ -246,9 +240,7 @@ public abstract class XmppActivity extends ActionBarActivity { protected void onStop() { super.onStop(); if (xmppConnectionServiceBound) { - if (registeredListeners.compareAndSet(true, false)) { - this.unregisterListeners(); - } + this.unregisterListeners(); unbindService(mConnection); xmppConnectionServiceBound = false; } @@ -1131,22 +1123,11 @@ public abstract class XmppActivity extends ActionBarActivity { public static ConferenceInvite parse(Intent data) { ConferenceInvite invite = new ConferenceInvite(); - invite.uuid = data.getStringExtra("conversation"); + invite.uuid = data.getStringExtra(ChooseContactActivity.EXTRA_CONVERSATION); if (invite.uuid == null) { return null; } - try { - if (data.getBooleanExtra("multiple", false)) { - String[] toAdd = data.getStringArrayExtra("contacts"); - for (String item : toAdd) { - invite.jids.add(Jid.of(item)); - } - } else { - invite.jids.add(Jid.of(data.getStringExtra("contact"))); - } - } catch (final IllegalArgumentException ignored) { - return null; - } + invite.jids.addAll(ChooseContactActivity.extractJabberIds(data)); return invite; } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index 11c129522..5294ba7cd 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -96,7 +96,11 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte viewHolder.name.setText(EmojiWrapper.transform(name)); } - viewHolder.frame.setBackgroundColor(Color.get(activity, conversation == ConversationFragment.getConversation(activity) ? R.attr.color_background_secondary : R.attr.color_background_primary)); + if (conversation == ConversationFragment.getConversation(activity)) { + viewHolder.frame.setBackgroundColor(Color.get(activity, R.attr.color_background_tertiary)); + } else { + viewHolder.frame.setBackgroundColor(Color.get(activity,R.attr.color_background_primary)); + } Message message = conversation.getLatestMessage(); final int failedCount = conversation.failedCount(); diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java index eb7217b95..03f7638e3 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java +++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java @@ -40,6 +40,7 @@ import de.pixart.messenger.R; import de.pixart.messenger.crypto.OmemoSetting; import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.entities.Conversational; import de.pixart.messenger.entities.Message; public class ConversationMenuConfigurator { @@ -71,6 +72,11 @@ public class ConversationMenuConfigurator { public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu) { final MenuItem menuSecure = menu.findItem(R.id.action_security); + final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); + if (!participating) { + menuSecure.setVisible(false); + return; + } final MenuItem none = menu.findItem(R.id.encryption_choice_none); final MenuItem otr = menu.findItem(R.id.encryption_choice_otr); final MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp); diff --git a/src/main/java/de/pixart/messenger/ui/util/EditMessageActionModeCallback.java b/src/main/java/de/pixart/messenger/ui/util/EditMessageActionModeCallback.java index d7e2ca347..cb50e4164 100644 --- a/src/main/java/de/pixart/messenger/ui/util/EditMessageActionModeCallback.java +++ b/src/main/java/de/pixart/messenger/ui/util/EditMessageActionModeCallback.java @@ -55,7 +55,10 @@ public class EditMessageActionModeCallback implements ActionMode.Callback { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.edit_message_actions, menu); MenuItem pasteAsQuote = menu.findItem(R.id.paste_as_quote); - pasteAsQuote.setVisible(clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClipDescription().hasMimeType("text/plain")); + ClipData primaryClip = clipboardManager.getPrimaryClip(); + if (primaryClip != null && primaryClip.getItemCount() >= 0) { + pasteAsQuote.setVisible(primaryClip.getDescription().getMimeType(0).startsWith("text/") && primaryClip.getItemAt(0).getText() != null); + } return true; } @@ -67,8 +70,8 @@ public class EditMessageActionModeCallback implements ActionMode.Callback { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { if (item.getItemId() == R.id.paste_as_quote) { - ClipData primaryClip = clipboardManager.getPrimaryClip(); - if (primaryClip.getItemCount() >= 1) { + final ClipData primaryClip = clipboardManager.getPrimaryClip(); + if (primaryClip != null && primaryClip.getItemCount() >= 1) { editMessage.insertAsQuote(primaryClip.getItemAt(0).getText().toString()); return true; } diff --git a/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java b/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java index 8ab582189..d95a4705f 100644 --- a/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java +++ b/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java @@ -6,7 +6,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; -import android.support.text.emoji.widget.EmojiAppCompatEditText; import android.support.v13.view.inputmethod.EditorInfoCompat; import android.support.v13.view.inputmethod.InputConnectionCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat; @@ -22,7 +21,7 @@ import android.view.inputmethod.InputConnection; import de.pixart.messenger.Config; import de.pixart.messenger.R; -public class EditMessage extends EmojiAppCompatEditText { +public class EditMessage extends EmojiWrapperEditText { private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source; protected Handler mTypingHandler = new Handler(); @@ -148,7 +147,7 @@ public class EditMessage extends EmojiAppCompatEditText { public InputConnection onCreateInputConnection(EditorInfo editorInfo) { final InputConnection ic = super.onCreateInputConnection(editorInfo); - if (mimeTypes != null && mCommitContentListener != null) { + if (mimeTypes != null && mCommitContentListener != null && ic != null) { EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes); return InputConnectionCompat.createWrapper(ic, editorInfo, (inputContentInfo, flags, opts) -> EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes)); } else { diff --git a/src/main/java/de/pixart/messenger/ui/widget/EmojiWrapperEditText.java b/src/main/java/de/pixart/messenger/ui/widget/EmojiWrapperEditText.java new file mode 100644 index 000000000..38ce53299 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/EmojiWrapperEditText.java @@ -0,0 +1,17 @@ +package de.pixart.messenger.ui.widget; + +import android.content.Context; +import android.support.text.emoji.widget.EmojiAppCompatEditText; +import android.util.AttributeSet; + +public class EmojiWrapperEditText extends EmojiAppCompatEditText { + + public EmojiWrapperEditText(Context context) { + super(context); + } + + public EmojiWrapperEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java index 72a01a13c..4e275df3f 100644 --- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java +++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java @@ -80,6 +80,11 @@ public abstract class ConversationsFileObserver { abstract public void onEvent(int event, String path); + public void restartWatching() { + stopWatching(); + startWatching(); + } + private class SingleFileObserver extends FileObserver { private final String path; diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java index 8e70e7bb7..d34823d86 100644 --- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java +++ b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java @@ -34,6 +34,10 @@ import de.pixart.messenger.http.AesGcmURLStreamHandler; import rocks.xmpp.addr.Jid; public final class CryptoHelper { + + private static final char[] VOWELS = "aeiou".toCharArray(); + private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray(); + public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); @@ -50,6 +54,16 @@ public final class CryptoHelper { return new String(hexChars); } + public static String pronounceable(SecureRandom random) { + char[] output = new char[random.nextInt(4) * 2 + 5]; + boolean vowel = random.nextBoolean(); + for (int i = 0; i < output.length; ++i) { + output[i] = vowel ? VOWELS[random.nextInt(VOWELS.length)] : CONSONANTS[random.nextInt(CONSONANTS.length)]; + vowel = !vowel; + } + return String.valueOf(output); + } + public static byte[] hexToBytes(String hexString) { int len = hexString.length(); byte[] array = new byte[len / 2]; diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java index c3ff29697..d76e4fede 100644 --- a/src/main/java/de/pixart/messenger/utils/Namespace.java +++ b/src/main/java/de/pixart/messenger/utils/Namespace.java @@ -8,8 +8,6 @@ public final class Namespace { public static final String HTTP_UPLOAD = "urn:xmpp:http:upload:0"; public static final String HTTP_UPLOAD_LEGACY = "urn:xmpp:http:upload"; public static final String STANZA_IDS = "urn:xmpp:sid:0"; - public static final String MAM = "urn:xmpp:mam:2"; - public static final String MAM_LEGACY = "urn:xmpp:mam:0"; public static final String IDLE = "urn:xmpp:idle:1"; public static final String DATA = "jabber:x:data"; public static final String OOB = "jabber:x:oob"; diff --git a/src/main/java/de/pixart/messenger/utils/StringUtils.java b/src/main/java/de/pixart/messenger/utils/StringUtils.java new file mode 100644 index 000000000..8d0988a0d --- /dev/null +++ b/src/main/java/de/pixart/messenger/utils/StringUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.utils; + +public class StringUtils { + + private static String blankOnNull(String input) { + return input == null ? "" : input; + } + + public static boolean equals(String one, String two) { + return blankOnNull(one).equals(blankOnNull(two)); + } + + public static boolean changed(String one, String two) { + return !equals(one, two); + } + + public static String nullOnEmpty(String input) { + return input == null || input.trim().isEmpty() ? null : input; + } + +} diff --git a/src/main/java/de/pixart/messenger/utils/XmppUri.java b/src/main/java/de/pixart/messenger/utils/XmppUri.java index 23ec4efa4..db0002a29 100644 --- a/src/main/java/de/pixart/messenger/utils/XmppUri.java +++ b/src/main/java/de/pixart/messenger/utils/XmppUri.java @@ -57,7 +57,10 @@ public class XmppUri { return safeSource; } - protected void parse(Uri uri) { + protected void parse(final Uri uri) { + if (uri == null) { + return; + } this.uri = uri; String scheme = uri.getScheme(); String host = uri.getHost(); diff --git a/src/main/java/de/pixart/messenger/xml/Element.java b/src/main/java/de/pixart/messenger/xml/Element.java index f90efa555..d7cace655 100644 --- a/src/main/java/de/pixart/messenger/xml/Element.java +++ b/src/main/java/de/pixart/messenger/xml/Element.java @@ -73,7 +73,7 @@ public class Element { return findInternationalizedChildContent(name, Locale.getDefault().getLanguage()); } - public String findInternationalizedChildContent(String name, @NonNull String language) { + private String findInternationalizedChildContent(String name, @NonNull String language) { HashMap<String, String> contents = new HashMap<>(); for (Element child : this.children) { if (name.equals(child.getName())) { @@ -88,8 +88,11 @@ public class Element { } } } - - return contents.get(null); + String value = contents.get(null); + if (value != null) { + return value; + } + return contents.size() > 0 ? contents.values().iterator().next() : null; } public Element findChild(String name, String xmlns) { @@ -134,6 +137,11 @@ public class Element { return this; } + public Element removeAttribute(String name) { + this.attributes.remove(name); + return this; + } + public Element setAttributes(Hashtable<String, String> attributes) { this.attributes = attributes; return this; diff --git a/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java b/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java index 7ff5658a8..9c123af7c 100644 --- a/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java +++ b/src/main/java/de/pixart/messenger/xmpp/OnMessageAcknowledged.java @@ -3,5 +3,5 @@ package de.pixart.messenger.xmpp; import de.pixart.messenger.entities.Account; public interface OnMessageAcknowledged { - public void onMessageAcknowledged(Account account, String id); + boolean onMessageAcknowledged(Account account, String id); } diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index a279308e0..a6fe3756a 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -31,6 +31,7 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -71,6 +72,7 @@ import de.pixart.messenger.entities.ServiceDiscoveryResult; import de.pixart.messenger.generator.IqGenerator; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.MemorizingTrustManager; +import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.NotificationService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.EditAccountActivity; @@ -211,8 +213,7 @@ public class XmppConnection implements Runnable { protected void changeStatus(final Account.State nextStatus) { synchronized (this) { - this.mThread = Thread.currentThread(); - if (this.mThread.isInterrupted()) { + if (Thread.currentThread().isInterrupted()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": not changing status to " + nextStatus + " because thread was interrupted"); return; } @@ -503,7 +504,8 @@ public class XmppConnection implements Runnable { @Override public void run() { synchronized (this) { - if (Thread.currentThread().isInterrupted()) { + this.mThread = Thread.currentThread(); + if (this.mThread.isInterrupted()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": aborting connect because thread was interrupted"); return; } @@ -601,6 +603,7 @@ public class XmppConnection implements Runnable { final String h = resumed.getAttribute("h"); try { ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>(); + final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { final int serverCount = Integer.parseInt(h); if (serverCount < stanzasSent) { @@ -610,12 +613,15 @@ public class XmppConnection implements Runnable { } else { Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": session resumed"); } - acknowledgeStanzaUpTo(serverCount); + acknowledgedMessages = acknowledgeStanzaUpTo(serverCount); for (int i = 0; i < this.mStanzaQueue.size(); ++i) { failedStanzas.add(mStanzaQueue.valueAt(i)); } mStanzaQueue.clear(); } + if (acknowledgedMessages) { + mXmppConnectionService.updateConversationUi(); + } Log.d(Config.LOGTAG, "resending " + failedStanzas.size() + " stanzas"); for (AbstractAcknowledgeableStanza packet : failedStanzas) { if (packet instanceof MessagePacket) { @@ -656,9 +662,13 @@ public class XmppConnection implements Runnable { final Element ack = tagReader.readElement(nextTag); lastPacketReceived = SystemClock.elapsedRealtime(); try { + final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - acknowledgeStanzaUpTo(serverSequence); + acknowledgedMessages = acknowledgeStanzaUpTo(serverSequence); + } + if (acknowledgedMessages) { + mXmppConnectionService.updateConversationUi(); } } catch (NumberFormatException | NullPointerException e) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server send ack without sequence number"); @@ -668,8 +678,12 @@ public class XmppConnection implements Runnable { try { final int serverCount = Integer.parseInt(failed.getAttribute("h")); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": resumption failed but server acknowledged stanza #" + serverCount); + final boolean acknowledgedMessages; synchronized (this.mStanzaQueue) { - acknowledgeStanzaUpTo(serverCount); + acknowledgedMessages = acknowledgeStanzaUpTo(serverCount); + } + if (acknowledgedMessages) { + mXmppConnectionService.updateConversationUi(); } } catch (NumberFormatException | NullPointerException e) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": resumption failed"); @@ -690,10 +704,11 @@ public class XmppConnection implements Runnable { } } - private void acknowledgeStanzaUpTo(int serverCount) { + private boolean acknowledgeStanzaUpTo(int serverCount) { if (serverCount > stanzasSent) { Log.e(Config.LOGTAG, "server acknowledged more stanzas than we sent. serverCount=" + serverCount + ", ourCount=" + stanzasSent); } + boolean acknowledgedMessages = false; for (int i = 0; i < mStanzaQueue.size(); ++i) { if (serverCount >= mStanzaQueue.keyAt(i)) { if (Config.EXTENDED_SM_LOGGING) { @@ -702,12 +717,13 @@ public class XmppConnection implements Runnable { AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); if (stanza instanceof MessagePacket && acknowledgedListener != null) { MessagePacket packet = (MessagePacket) stanza; - acknowledgedListener.onMessageAcknowledged(account, packet.getId()); + acknowledgedMessages |= acknowledgedListener.onMessageAcknowledged(account, packet.getId()); } mStanzaQueue.removeAt(i); i--; } } + return acknowledgedMessages; } private @NonNull @@ -1574,6 +1590,7 @@ public class XmppConnection implements Runnable { for (final Entry<Jid, ServiceDiscoveryResult> cursor : disco.entrySet()) { final ServiceDiscoveryResult value = cursor.getValue(); if (value.getFeatures().contains("http://jabber.org/protocol/muc") + && value.hasIdentity("conference", "text") && !value.getFeatures().contains("jabber:iq:gateway") && !value.hasIdentity("conference", "irc")) { servers.add(cursor.getKey().toString()); @@ -1820,13 +1837,12 @@ public class XmppConnection implements Runnable { } public boolean mam() { - return hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM) - || hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM_LEGACY); + return MessageArchiveService.Version.has(getAccountFeatures()); } - public boolean mamLegacy() { - return !hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM) - && hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM_LEGACY); + public List<String> getAccountFeatures() { + ServiceDiscoveryResult result = connection.disco.get(account.getJid().asBareJid()); + return result == null ? Collections.emptyList() : result.getFeatures(); } public boolean push() { diff --git a/src/main/res/drawable-hdpi/ic_forward_white_24dp.png b/src/main/res/drawable-hdpi/ic_forward_white_24dp.png Binary files differnew file mode 100644 index 000000000..b40c6b780 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_forward_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_forward_white_24dp.png b/src/main/res/drawable-mdpi/ic_forward_white_24dp.png Binary files differnew file mode 100644 index 000000000..ec881edda --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_forward_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_forward_white_24dp.png b/src/main/res/drawable-xhdpi/ic_forward_white_24dp.png Binary files differnew file mode 100644 index 000000000..551baf53b --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_forward_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_forward_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_forward_white_24dp.png Binary files differnew file mode 100644 index 000000000..6eadc2bbf --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_forward_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_forward_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_forward_white_24dp.png Binary files differnew file mode 100644 index 000000000..fae07bf4c --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_forward_white_24dp.png diff --git a/src/main/res/drawable/list_item_background_dark.xml b/src/main/res/drawable/list_item_background_dark.xml new file mode 100644 index 000000000..eca985306 --- /dev/null +++ b/src/main/res/drawable/list_item_background_dark.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (c) 2018, Daniel Gultsch All rights reserved. + ~ + ~ Redistribution and use in source and binary forms, with or without modification, + ~ are permitted provided that the following conditions are met: + ~ + ~ 1. Redistributions of source code must retain the above copyright notice, this + ~ list of conditions and the following disclaimer. + ~ + ~ 2. Redistributions in binary form must reproduce the above copyright notice, + ~ this list of conditions and the following disclaimer in the documentation and/or + ~ other materials provided with the distribution. + ~ + ~ 3. Neither the name of the copyright holder nor the names of its contributors + ~ may be used to endorse or promote products derived from this software without + ~ specific prior written permission. + ~ + ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ~ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + ~ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ~ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + ~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + ~ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ~ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ~ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ~ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@color/grey700" android:state_activated="true" /> +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/list_item_background_light.xml b/src/main/res/drawable/list_item_background_light.xml new file mode 100644 index 000000000..f064403da --- /dev/null +++ b/src/main/res/drawable/list_item_background_light.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (c) 2018, Daniel Gultsch All rights reserved. + ~ + ~ Redistribution and use in source and binary forms, with or without modification, + ~ are permitted provided that the following conditions are met: + ~ + ~ 1. Redistributions of source code must retain the above copyright notice, this + ~ list of conditions and the following disclaimer. + ~ + ~ 2. Redistributions in binary form must reproduce the above copyright notice, + ~ this list of conditions and the following disclaimer in the documentation and/or + ~ other materials provided with the distribution. + ~ + ~ 3. Neither the name of the copyright holder nor the names of its contributors + ~ may be used to endorse or promote products derived from this software without + ~ specific prior written permission. + ~ + ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ~ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + ~ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ~ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + ~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + ~ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ~ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ~ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ~ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@color/grey300" android:state_activated="true" /> +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/message_border.xml b/src/main/res/drawable/message_border.xml deleted file mode 100644 index cee834e98..000000000 --- a/src/main/res/drawable/message_border.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - - <corners android:radius="5dp" /> - - <padding - android:bottom="1.5dp" - android:left="1.5dp" - android:right="1.5dp" - android:top="1.5dp" /> - - <solid android:color="@color/grey800" /> - -</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_border_dark.xml b/src/main/res/drawable/message_border_dark.xml deleted file mode 100644 index 83eef8eb8..000000000 --- a/src/main/res/drawable/message_border_dark.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - - <corners android:radius="5dp" /> - - <padding - android:bottom="1.5dp" - android:left="1.5dp" - android:right="1.5dp" - android:top="1.5dp" /> - - <solid android:color="@color/grey500" /> - -</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_received_light.xml b/src/main/res/drawable/message_bubble_received_light.xml index 6e8e95e1d..32e146460 100644 --- a/src/main/res/drawable/message_bubble_received_light.xml +++ b/src/main/res/drawable/message_bubble_received_light.xml @@ -13,7 +13,7 @@ android:top="2dp" /> <stroke android:width="1dp" - android:color="@color/grey800"> + android:color="@color/grey500"> </stroke> <solid android:color="@color/lightwhite"> diff --git a/src/main/res/drawable/message_bubble_received_light_dark.xml b/src/main/res/drawable/message_bubble_received_light_dark.xml index edd1e62ce..8f9132566 100644 --- a/src/main/res/drawable/message_bubble_received_light_dark.xml +++ b/src/main/res/drawable/message_bubble_received_light_dark.xml @@ -13,7 +13,7 @@ android:top="2dp" /> <stroke android:width="1dp" - android:color="@color/grey500"> + android:color="@color/grey700"> </stroke> <solid android:color="@color/darkwhite"> diff --git a/src/main/res/drawable/message_bubble_received_warning.xml b/src/main/res/drawable/message_bubble_received_warning.xml index c3d553a7d..b3bf6d451 100644 --- a/src/main/res/drawable/message_bubble_received_warning.xml +++ b/src/main/res/drawable/message_bubble_received_warning.xml @@ -13,7 +13,7 @@ android:top="4dp" /> <stroke android:width="1dp" - android:color="@color/grey800"> + android:color="@color/grey500"> </stroke> <solid android:color="@color/lightred"> diff --git a/src/main/res/drawable/message_bubble_received_warning_dark.xml b/src/main/res/drawable/message_bubble_received_warning_dark.xml index 5d8a270ab..5867f67ce 100644 --- a/src/main/res/drawable/message_bubble_received_warning_dark.xml +++ b/src/main/res/drawable/message_bubble_received_warning_dark.xml @@ -13,7 +13,7 @@ android:top="4dp" /> <stroke android:width="1dp" - android:color="@color/grey500"> + android:color="@color/grey700"> </stroke> <solid android:color="@color/darkred"> diff --git a/src/main/res/drawable/message_bubble_sent_blue.xml b/src/main/res/drawable/message_bubble_sent_blue.xml index 0f1424d21..1ee3ad0c8 100644 --- a/src/main/res/drawable/message_bubble_sent_blue.xml +++ b/src/main/res/drawable/message_bubble_sent_blue.xml @@ -13,7 +13,7 @@ android:top="4dp" /> <stroke android:width="1dp" - android:color="@color/grey800"> + android:color="@color/grey500"> </stroke> <solid android:color="@color/lightblue"> diff --git a/src/main/res/drawable/message_bubble_sent_blue_dark.xml b/src/main/res/drawable/message_bubble_sent_blue_dark.xml index 3d1b72042..d9853bf74 100644 --- a/src/main/res/drawable/message_bubble_sent_blue_dark.xml +++ b/src/main/res/drawable/message_bubble_sent_blue_dark.xml @@ -13,7 +13,7 @@ android:top="4dp" /> <stroke android:width="1dp" - android:color="@color/grey500"> + android:color="@color/grey700"> </stroke> <solid android:color="@color/darkblue"> diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml index f6a2b1069..dd3ff2c31 100644 --- a/src/main/res/layout/activity_contact_details.xml +++ b/src/main/res/layout/activity_contact_details.xml @@ -33,143 +33,162 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_horizontal" - android:orientation="vertical" - android:padding="@dimen/card_padding_regular"> - - <android.support.text.emoji.widget.EmojiTextView - android:id="@+id/contact_display_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:paddingBottom="5dp" - android:text="@string/contact" - android:textAlignment="center" - android:textAppearance="@style/TextAppearance.Conversations.Title" - android:textIsSelectable="false" - android:textStyle="bold" /> - - <QuickContactBadge - android:id="@+id/details_contact_badge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@+id/contact_display_name" - android:layout_centerHorizontal="true" - android:adjustViewBounds="true" - android:background="?attr/message_border" - android:maxHeight="384dp" - android:maxWidth="384dp" - android:padding="1dp" - android:scaleType="centerCrop" /> + android:orientation="vertical"> - <LinearLayout - android:id="@+id/details_jidbox" + <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentLeft="true" - android:layout_alignParentRight="true" - android:layout_alignParentStart="true" - android:layout_below="@+id/details_contact_badge" - android:layout_marginTop="16dp" - android:orientation="vertical"> - - <TextView - android:id="@+id/details_contactjid" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:text="@string/account_settings_example_jabber_id" - android:textAppearance="@style/TextAppearance.Conversations.Title" - android:textIsSelectable="true" - android:visibility="gone" /> - - <com.wefika.flowlayout.FlowLayout - android:id="@+id/tags" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" - android:layout_marginLeft="-2dp" - android:layout_marginTop="4dp" - android:orientation="horizontal"></com.wefika.flowlayout.FlowLayout> - - <TextView - android:id="@+id/details_lastseen" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" - android:layout_marginTop="4dp" - android:textAppearance="@style/TextAppearance.Conversations.Subhead" /> + android:gravity="center_horizontal" + android:orientation="vertical" + android:padding="@dimen/card_padding_regular"> <android.support.text.emoji.widget.EmojiTextView - android:id="@+id/status_message" + android:id="@+id/contact_display_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" - android:layout_marginTop="4dp" - android:gravity="center_horizontal" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:textAppearance="@style/TextAppearance.Conversations.Title" /> + </RelativeLayout> - <android.support.text.emoji.widget.EmojiTextView - android:id="@+id/resource" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" - android:layout_marginTop="4dp" - android:gravity="center_horizontal" - android:textSize="?attr/TextSizeBody" - android:textStyle="italic" /> + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content"> - <Button - android:id="@+id/add_contact_button" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_centerHorizontal="true" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" - android:layout_marginTop="4dp" - android:text="@string/add_contact" /> - - <CheckBox - android:id="@+id/details_send_presence" - style="@style/Widget.Conversations.CheckBox" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_below="@+id/add_contact_button" - android:layout_marginTop="4dp" - android:text="@string/send_presence_updates" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> - - <CheckBox - android:id="@+id/details_receive_presence" - style="@style/Widget.Conversations.CheckBox" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_below="@+id/details_send_presence" - android:text="@string/receive_presence_updates" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + android:gravity="center_horizontal" + android:orientation="vertical" + android:padding="@dimen/card_padding_regular"> - </LinearLayout> + <QuickContactBadge + android:id="@+id/details_contact_badge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/contact_display_name" + android:layout_centerHorizontal="true" + android:adjustViewBounds="true" + android:background="?attr/color_border" + android:maxHeight="384dp" + android:maxWidth="384dp" + android:padding="1dp" + android:scaleType="centerCrop" /> - <TextView - android:id="@+id/details_account" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@+id/details_jidbox" - android:layout_marginTop="32dp" - android:text="@string/using_account" - android:textAppearance="@style/TextAppearance.Conversations.Caption" - android:visibility="gone" /> + <LinearLayout + android:id="@+id/details_jidbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:layout_alignParentStart="true" + android:layout_below="@+id/details_contact_badge" + android:layout_marginTop="16dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/details_contactjid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/account_settings_example_jabber_id" + android:textAppearance="@style/TextAppearance.Conversations.Title" + android:textIsSelectable="true" + android:visibility="gone" /> + + <com.wefika.flowlayout.FlowLayout + android:id="@+id/tags" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" + android:layout_marginLeft="-2dp" + android:layout_marginTop="4dp" + android:orientation="horizontal"> + </com.wefika.flowlayout.FlowLayout> + + <TextView + android:id="@+id/details_lastseen" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:textAppearance="@style/TextAppearance.Conversations.Subhead" /> + + <android.support.text.emoji.widget.EmojiTextView + android:id="@+id/status_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:gravity="center_horizontal" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <android.support.text.emoji.widget.EmojiTextView + android:id="@+id/resource" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:gravity="center_horizontal" + android:textSize="?attr/TextSizeBody" + android:textStyle="italic" /> + + <Button + android:id="@+id/add_contact_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" + android:layout_marginTop="4dp" + android:text="@string/add_contact" /> + + <CheckBox + android:id="@+id/details_send_presence" + style="@style/Widget.Conversations.CheckBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@+id/add_contact_button" + android:layout_marginTop="4dp" + android:text="@string/send_presence_updates" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <CheckBox + android:id="@+id/details_receive_presence" + style="@style/Widget.Conversations.CheckBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@+id/details_send_presence" + android:text="@string/receive_presence_updates" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/card_padding_list"> + + <TextView + android:id="@+id/details_account" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:layout_marginTop="24dp" + android:text="@string/using_account" + android:textAppearance="@style/TextAppearance.Conversations.Caption" + android:visibility="visible" /> + </LinearLayout> + </LinearLayout> + </RelativeLayout> </LinearLayout> </android.support.v7.widget.CardView> diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index 2c1a0e682..2a450f964 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -51,7 +51,9 @@ android:id="@+id/muc_display" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentLeft="true" android:layout_alignParentStart="true" + android:layout_toLeftOf="@+id/edit_muc_name_button" android:layout_toStartOf="@+id/edit_muc_name_button" android:orientation="vertical"> @@ -85,9 +87,10 @@ android:id="@+id/muc_editor" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentLeft="true" android:layout_alignParentStart="true" + android:layout_toLeftOf="@+id/edit_muc_name_button" android:layout_toStartOf="@+id/edit_muc_name_button" - android:orientation="vertical" android:visibility="gone"> @@ -97,7 +100,7 @@ app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> - <android.support.design.widget.TextInputEditText + <de.pixart.messenger.ui.widget.EmojiWrapperEditText android:id="@+id/muc_edit_title" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -111,7 +114,7 @@ app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> - <android.support.design.widget.TextInputEditText + <de.pixart.messenger.ui.widget.EmojiWrapperEditText android:id="@+id/muc_edit_subject" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -126,6 +129,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:alpha="?attr/icon_alpha" android:background="?attr/selectableItemBackgroundBorderless" @@ -145,6 +149,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/avatar_item_distance" + android:layout_marginRight="@dimen/avatar_item_distance" android:adjustViewBounds="true" android:maxHeight="384dp" android:maxWidth="384dp" @@ -267,7 +272,7 @@ android:layout_marginTop="24dp" android:text="@string/using_account" android:textAppearance="@style/TextAppearance.Conversations.Caption" - android:visibility="visible" /> + android:visibility="gone" /> </LinearLayout> </LinearLayout> </android.support.v7.widget.CardView> diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml index c2e374356..0218e299b 100644 --- a/src/main/res/layout/contact.xml +++ b/src/main/res/layout/contact.xml @@ -5,7 +5,7 @@ <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?android:attr/activatedBackgroundIndicator" + android:background="?attr/list_item_background" android:padding="@dimen/list_padding"> <com.makeramen.roundedimageview.RoundedImageView diff --git a/src/main/res/layout/create_conference_dialog.xml b/src/main/res/layout/create_conference_dialog.xml index ca26e618b..384baa7ca 100644 --- a/src/main/res/layout/create_conference_dialog.xml +++ b/src/main/res/layout/create_conference_dialog.xml @@ -25,26 +25,18 @@ android:layout_height="wrap_content" android:visibility="gone" /> - <View - android:layout_width="0dp" - android:layout_height="0dp" - android:focusable="true" - android:focusableInTouchMode="true" /> - <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> - <android.support.design.widget.TextInputEditText - android:id="@+id/subject" + <de.pixart.messenger.ui.widget.EmojiWrapperEditText + android:id="@+id/group_chat_name" style="@style/Widget.Conversations.EditText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/topic" - android:nextFocusDown="@+id/subject" - android:nextFocusUp="@+id/subject" /> + android:hint="@string/create_dialog_group_chat_name"/> </android.support.design.widget.TextInputLayout> </LinearLayout> </layout>
\ No newline at end of file diff --git a/src/main/res/layout/dialog_quickedit.xml b/src/main/res/layout/dialog_quickedit.xml index 273bcf1e9..9ab308af8 100644 --- a/src/main/res/layout/dialog_quickedit.xml +++ b/src/main/res/layout/dialog_quickedit.xml @@ -18,7 +18,7 @@ app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error" app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"> - <android.support.design.widget.TextInputEditText + <de.pixart.messenger.ui.widget.EmojiWrapperEditText android:id="@+id/input_edit_text" style="@style/Widget.Conversations.EditText" android:layout_width="match_parent" @@ -27,7 +27,7 @@ android:inputType="textPersonName"> <requestFocus /> - </android.support.design.widget.TextInputEditText> + </de.pixart.messenger.ui.widget.EmojiWrapperEditText> </android.support.design.widget.TextInputLayout> </LinearLayout> diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index 84ed0fb9f..4112dfb8c 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -26,6 +26,10 @@ android:title="@string/correct_message" android:visible="false" /> <item + android:id="@+id/delete_message" + android:title="@string/delete_message" + android:visible="false" /> + <item android:id="@+id/copy_url" android:title="@string/copy_original_url" android:visible="false" /> diff --git a/src/main/res/menu/select_multiple.xml b/src/main/res/menu/select_multiple.xml deleted file mode 100644 index 51f05e21a..000000000 --- a/src/main/res/menu/select_multiple.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - - <item - android:id="@+id/selection_submit" - android:title="@string/invite_contact" - app:showAsAction="always" /> - -</menu> diff --git a/src/main/res/values-v21/themes.xml b/src/main/res/values-v21/themes.xml new file mode 100644 index 000000000..47f419248 --- /dev/null +++ b/src/main/res/values-v21/themes.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="ConversationsTheme.Dark" parent="ConversationsTheme.Dark.Base"> + <item name="android:navigationBarColor">@color/realblack</item> + </style> +</resources>
\ No newline at end of file diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 10690b682..b261ea28a 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -17,6 +17,7 @@ <attr name="text_Color_Main" format="reference|color" /> + <attr name="color_background_tertiary" format="reference|color" /> <attr name="color_background_secondary" format="reference|color" /> <attr name="color_background_primary" format="reference|color" /> <attr name="color_warning" format="reference|color" /> @@ -25,6 +26,8 @@ <attr name="activity_background_search" format="reference" /> <attr name="activity_background_no_results" format="reference" /> + <attr name="list_item_background" format="reference" /> + <attr name="TextColorOnline" format="reference|color" /> <attr name="TextColorError" format="reference|color" /> @@ -96,8 +99,6 @@ <attr name="color_border" format="reference|color" /> - <attr name="message_border" format="reference" /> - <!-- settings --> <attr name="ic_settings_ui" format="reference" /> <attr name="ic_settings_notifications" format="reference" /> diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 56444212f..6ea10548f 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -22,6 +22,7 @@ <color name="grey200">#ffeeeeee</color> <color name="grey300">#ffe0e0e0</color> <color name="grey500">#ff9e9e9e</color> + <color name="grey700">#ff616161</color> <color name="grey800">#ff424242</color> <color name="grey900">#ff282828</color> <color name="red800">#ffc62828</color> diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index 9e269d271..49c1a849a 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -225,6 +225,8 @@ <item>chat.hipchat.com</item> <item>googlemail.com</item> <item>nsa.li</item> + <item>blabber.im</item> + <item>buzon.uy</item> </string-array> </resources> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 57edf9ba6..89cecf15f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -21,6 +21,7 @@ <string name="title_activity_sharewith">Share with Conversation</string> <string name="title_activity_start_conversation">Start Conversation</string> <string name="title_activity_choose_contact">Choose contact</string> + <string name="title_activity_choose_contacts">Choose Contacts</string> <string name="title_activity_block_list">Block list</string> <string name="just_now">just now</string> <string name="minute_ago">1 min ago</string> @@ -426,10 +427,6 @@ <item quantity="one">%d certificate deleted</item> <item quantity="other">%d certificates deleted</item> </plurals> - <plurals name="select_contact"> - <item quantity="one">Select %d contact</item> - <item quantity="other">Select %d contacts</item> - </plurals> <string name="pref_quick_action_summary">Replace send button with quick action</string> <string name="pref_quick_action">Quick Action</string> <string name="none">None</string> @@ -802,4 +799,10 @@ <string name="contact_name">Contact name</string> <string name="nickname">Nickname for this group chat</string> <string name="group_chat_name">Name</string> + <string name="providing_a_name_is_optional">Providing a name is optional</string> + <string name="create_dialog_group_chat_name">Group chat name</string> + <string name="everyone_has_read_up_to_this_point">Everyone has read up to this point</string> + <string name="conference_resource_constraint">Resource constraint</string> + <string name="delete_message">Delete message</string> + <string name="conference_destroyed">This group chat has been destroyed</string> </resources> diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index 6eef29072..f1b66319f 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -12,12 +12,14 @@ <item name="color_background_primary">@color/grey50</item> <item name="color_background_secondary">@color/grey200</item> + <item name="color_background_tertiary">@color/grey300</item> <item name="color_warning">@color/red_a700</item> <item name="TextColorOnline">@color/green500</item> <item name="TextColorError">@color/red800</item> <item name="activity_background_search">@drawable/search_background_light</item> <item name="activity_background_no_results">@drawable/no_results_background_light</item> + <item name="list_item_background">@drawable/list_item_background_light</item> <item name="EmojiColor">@color/realblack</item> @@ -26,8 +28,7 @@ <item name="color_bubble_date">@color/lightgreen</item> <item name="color_bubble_warning">@color/lightred</item> - <item name="color_border">@color/grey800</item> - <item name="message_border">@drawable/message_border</item> + <item name="color_border">@color/grey500</item> <item name="windowActionModeOverlay">true</item> <item name="android:actionModeBackground">@color/accent</item> @@ -135,7 +136,9 @@ <item name="ic_settings_about" type="reference">@drawable/ic_help_black_24dp</item> </style> - <style name="ConversationsTheme.Dark" parent="Theme.AppCompat.NoActionBar"> + <style name="ConversationsTheme.Dark" parent="ConversationsTheme.Dark.Base" /> + + <style name="ConversationsTheme.Dark.Base" parent="Theme.AppCompat.NoActionBar"> <item name="colorPrimary">@color/primary</item> <item name="colorPrimaryDark">@color/primary_dark</item> <item name="colorAccent">@color/accent</item> @@ -146,12 +149,14 @@ <item name="color_background_primary">@color/grey800</item> <item name="color_background_secondary">@color/grey900</item> + <item name="color_background_tertiary">@color/grey700</item> <item name="color_warning">@color/red_a700</item> <item name="TextColorOnline">@color/green500</item> <item name="TextColorError">@color/red500</item> <item name="activity_background_search">@drawable/search_background_dark</item> <item name="activity_background_no_results">@drawable/no_results_background_dark</item> + <item name="list_item_background">@drawable/list_item_background_dark</item> <item name="EmojiColor">@color/realwhite</item> @@ -160,8 +165,7 @@ <item name="color_bubble_date">@color/darkgreen</item> <item name="color_bubble_warning">@color/darkred</item> - <item name="color_border">@color/grey500</item> - <item name="message_border">@drawable/message_border_dark</item> + <item name="color_border">@color/grey700</item> <item name="windowActionModeOverlay">true</item> <item name="android:actionModeBackground">@color/accent</item> |