diff options
author | lookshe <github@lookshe.org> | 2015-01-03 23:19:05 +0100 |
---|---|---|
committer | lookshe <github@lookshe.org> | 2015-01-03 23:19:05 +0100 |
commit | 95e2a539517c27b3235acd582f17968c8e301e81 (patch) | |
tree | 213bbeed798751e949376d85f4d7d0bd30c5fbfa | |
parent | 48717dd7d37c066ab626fc626a2ced626ef21d42 (diff) | |
parent | 4f4eff2353f4e359b5582c8e808a4e88631c3e74 (diff) |
Merge branch 'master' of ssh://git.fucktheforce.de/conversations
Conflicts:
src/main/java/eu/siacs/conversations/Config.java
src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
src/main/java/eu/siacs/conversations/utils/UIHelper.java
Diffstat (limited to '')
99 files changed, 4722 insertions, 2659 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e44fbe..84eb5ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ ###Changelog +####Version 0.10 +* Support for Message Archive Management +* Dynamically load message history +* Ability to block contacts +* New UI to verify fingerprints +* Ability to change password on server +* removed stream compression +* quiet hours +* fixed connection issues on ipv6 servers + ####Version 0.9.3 * bug fixes @@ -40,7 +40,6 @@ run your own XMPP server for you and your friends. These XEP's are: * XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer files if both parties are behind a firewall (NAT). -* XEP-0138: Stream Compression saves bandwidth * XEP-0163: Personal Eventing Protocol for avatars * XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection. @@ -48,9 +47,14 @@ run your own XMPP server for you and your friends. These XEP's are: your desktop client and thus allows you to switch seamlessly from your mobile client to your desktop client and back within one conversation. * XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections -* XEP-0352: Client State Indication let the server know whether or not +* XEP-0313: Message Archive Management synchronize message history with the + server. Catch up with messages that were sent while Conversations was + offline. +* XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages. +* XEP-0191: Blocking command lets you blacklist spammers or block contacts + without removing them from your roster. ## Team diff --git a/build.gradle b/build.gradle index 48dac0c7..29cb3840 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.12.2' + classpath 'com.android.tools.build:gradle:1.0.0' } } @@ -46,8 +46,8 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 19 - versionCode 40 - versionName "0.9.3" + versionCode 41 + versionName "0.10" } compileOptions { @@ -81,18 +81,15 @@ android { buildTypes.release.signingConfig = null } - buildTypes { - applicationVariants.all { variant -> - def fileName = variant.packageApplication.outputFile.name.replace(".apk", - "-" + defaultConfig.versionName + ".apk") - variant.packageApplication.outputFile = new - File(variant.packageApplication.outputFile.parent, fileName) - if (variant.zipAlign) { - if (variant.name.equals('release')) { - variant.outputFile = new File(variant.outputFile.parent, - rootProject.name + "-" + defaultConfig.versionName + ".apk") - } + applicationVariants.all { variant -> + if (variant.name.equals('release')) { + variant.outputs.each { output -> + if (output.zipAlign != null) { + output.zipAlign.outputFile = new File(output.outputFile.parent, rootProject.name + "-${variant.versionName}.apk") } + output.packageApplication.outputFile = new File(output.outputFile.parent, output.packageApplication.outputFile.name + .replace(".apk", "-${variant.versionName}.apk")) + } } } diff --git a/docs/MISSION.md b/docs/MISSION.md index 74399e74..5e867194 100644 --- a/docs/MISSION.md +++ b/docs/MISSION.md @@ -1,25 +1,25 @@ Conversations is a messenger for the next decade. Based on already established -internet standards that have been around for over ten years Coversations isn’t +internet standards that have been around for over ten years Conversations isn’t trying to replace current commercial messengers. It will simply outlive them. -Commercial, closed source products are coming and going. 15 years ago we had -ICQ which was replaced by Skype. MySpace was replaced by Facebook. WhatsApp and -Hangouts will disapear soon. Internet standards however stick around. People are -still using IRC and e-mail even though these protocols have been around for +Commercial, closed source products are coming and going. 15 years ago we had ICQ +which was replaced by Skype. MySpace was replaced by Facebook. WhatsApp and +Hangouts will disappear soon. Internet standards however stick around. People +are still using IRC and e-mail even though these protocols have been around for decades. Utilizing proven standards doesn’t mean one can not evolve. GMail has revolutionized the way we look at e-mail. Firefox and Chrome have changed the way we use the Web. Conversations will change the way we look at instant -messaging. Being less obstrusive than a telephone call instant messaging has -always played an importent role in modern society. Conversations will show that -instant messaging can be fast, relialbe and private. Conversations will not -force its security and privacey aspects upon the user. For those willing to use encryption -Conversations will make it as uncomplicated as possible. However Conversations -is aware that end-to-end encryption by the very principle isn’t trivial. Instead -of trying the impossible and making encryption easier than comparing a -fingerprint Conversations will try to educate the willing user and explain the -necessary steps and the reasons behind them. Those unwilling to learn about -encryption will still be protected by the design principals of Conversations. -Conversations will simply not share or generate certain information for example -by encouraging the use of federated servers. Conversations will always -utilize the best available standards for encryption and media encoding instead -of reinventing the wheel. However it isn’t afraid to break with behavior patterns -that have been proven ineffctive. +messaging. Being less obtrusive than a telephone call instant messaging has +always played an important role in modern society. Conversations will show that +instant messaging can be fast, reliable and private. Conversations will not +force its security and privacy aspects upon the user. For those willing to use +encryption Conversations will make it as uncomplicated as possible. However +Conversations is aware that end-to-end encryption by the very principle isn’t +trivial. Instead of trying the impossible and making encryption easier than +comparing a fingerprint Conversations will try to educate the willing user and +explain the necessary steps and the reasons behind them. Those unwilling to +learn about encryption will still be protected by the design principals of +Conversations. Conversations will simply not share or generate certain +information for example by encouraging the use of federated servers. +Conversations will always utilize the best available standards for encryption +and media encoding instead of reinventing the wheel. However it isn’t afraid to +break with behavior patterns that have been proven ineffective. diff --git a/docs/XEPs.md b/docs/XEPs.md index 0dd6a1d7..35c7de45 100644 --- a/docs/XEPs.md +++ b/docs/XEPs.md @@ -3,7 +3,6 @@ * XEP-0045: Multi-User Chat * XEP-0048: Bookmarks * XEP-0115: Entity Capabilities -* XEP-0138: Stream Compression * XEP-0163: Personal Eventing Protocol (avatars and nicks) * XEP-0166: Jingle (only used for file transfer) * XEP-0184: Message Delivery Receipts (reply only) @@ -14,5 +13,7 @@ * XEP-0260: Jingle SOCKS5 Bytestreams Transport Method * XEP-0261: Jingle In-Band Bytestreams Transport Method * XEP-0280: Message Carbons +* XEP-0313: Message Archive Management * XEP-0333: Chat Markers * XEP-0352: Client State Indication +* XEP-0191: Blocking command diff --git a/docs/obeservations.md b/docs/observations.md index f4e4bf17..71502424 100644 --- a/docs/obeservations.md +++ b/docs/observations.md @@ -2,7 +2,7 @@ Observations on implementing XMPP ================================= After spending the last two and a half month basically writing my own XMPP library from scratch I decided to share some of the observations I made in the -process.. In part this article can be seen as a response to a blog post made by +process. In part this article can be seen as a response to a blog post made by Dr. Ing. Georg Lukas. The blog post introduces a couple of XEP (XMPP Extensions) which make the life on mobile devices a lot easier but states that they are currently very few implementations of those XEPs. So I went ahead and @@ -19,27 +19,27 @@ and straight forward. But then came the XEPs. ###Multi-User Chat The first one was XEP-0045 Multi-User Chat. This is the one XEP of the XEPs I’m going to mention in my article which is actually wildly adopted. Most clients -and servers I know of support MUC. However the level of completeness varies. +and servers I know of support MUC. However the level of completeness varies. MUC actually introduces access and permission roles which are far more complex than what some of us are used to from IRC but a lot of clients just don’t implement them. I’m not implementing them myself (at least for now) because I -somewhat doubt that someone would actually use them. (How ever this might be -some sort of chicken or egg problem.) I did find some strange bugs though which -might be interesting for other library developers. In theory a MUC server +somewhat doubt that someone would actually use them (however this might be some +sort of chicken or egg problem). I did find some strange bugs though which might +be interesting for other library developers. In theory a MUC server implementation can allow a single user (same jid) to join a conference room multiple times with the same nick from different clients. This means if someone wants to participate in a conference from two different devices (mobile and -desktop for example) one wouldn’t have to name oneself userDesktop and -userMobile but just user. Both ejabberd and prosody support this but with -strange side effects. prosody for example doesn’t allow a user to change its +desktop for example) one wouldn’t have to name oneself `userDesktop` and +`userMobile` but just `user`. Both ejabberd and prosody support this but with +strange side effects. Prosody for example doesn’t allow a user to change its name once two clients are “merged” by having the same nick. ###Carbons and Stream Management -Two of the other XEPs Lukas’ mentions - Carbons (XEP-0280) and Stream Management -(XEP-0198) - were actually fairly easy to implement. The only challenges were to -find a server to support them (I ended up running my own prosody server) and a -desktop client to test them with. For carbons there is a patched mcabber version -and gajim. After implementing stream management I had very good results on my +Two of the other XEPs Lukas mentions — Carbons (XEP-0280) and Stream Management +(XEP-0198) — were actually fairly easy to implement. The only challenges were to +find a server to support them (I ended up running my own Prosody server) and a +desktop client to test them with. For carbons there is a patched Mcabber version +and Gajim. After implementing stream management I had very good results on my mobile device. I had sessions running for up to 24 hours with a walking outside, loosing mobile coverage for a few minutes and so on. The only limitation was that I had to keep on developing and reinstalling my app. @@ -52,14 +52,14 @@ come to some sort of consent among XMPP developers to ultimately increase the interoperability. OTR has some down sides which make it difficult or at times even dangerous to implement within XMPP. First of all it is a synchronous protocol which is tunneled through a different protocol (XMPP). Synchronous -means - among other things - auto replies. (An OTR session begins with “hi I’m -speaking otr give me your key” “ok cool here is my key”) And auto replies - we -know that since the first time an out of office auto responder went postal - are +means — among other things — auto replies. (An OTR session begins with “hi I’m +speaking otr give me your key” “ok cool here is my key”) And auto replies — we +know that since the first time an out of office auto responder went postal — are dangerous. Things really start to get messy when you use one of the best -features of XMPP - multiple clients. The way XMPP works is that clients are +features of XMPP — multiple clients. The way XMPP works is that clients are encouraged to send their messages to the raw jid and let the server decide what full jid the messages are routed to. If in doubt even all of them. So what -happens when Alice sends a start-otr-message to Bobs raw jid? Bob receives the +happens when Alice sends a start-otr-message to Bobs raw jid? Bob receives the message on his notebook as well as his cell phone. Both of them answer. Alice gets two different replies. Shit explodes. Even if Alice sends the message to bob/notebook chances are that Bob has carbon messages enabled and still receives @@ -67,11 +67,11 @@ the messages on both devices. Now assuming that Bobs client is clever enough not to auto reply to carbonated messages Bob/cellphone will still end up with a lot of garbage messages. (Essentially the entire conversation between Alice and Bob/notebook but unreadable of course) Therefor it should be good practice to -tag OTR messages as both private and no-copy. (private is part of the carbons -XEP, no-copy is a general hint. I found that prosody for some reasons doesn’t +tag OTR messages as both private and no-copy (private is part of the carbons +XEP, no-copy is a general hint). I found that prosody for some reasons doesn’t honor the private tag on outgoing messages. While this is easily fixed I presume that having both the private and the no-copy tag will make it more compatible -with servers or clients I don’t know about yet) +with servers or clients I don’t know about yet. ####Rules to follow when implementing OTR To summarize my observations on implementing OTR in XMPP let me make the @@ -81,17 +81,17 @@ following three statements. and have the receiving server or user decide how they should be routed OTR messages must be send to a specific resource. To make this work the user should be given the option to select the presence (which can be assisted with some -educated guessing by the client based on previous messages). -Furthermore a client should encourage a user to choose meaningful presences -instead of the clients name or even random ones. Something like /mobile, -/notebook, /desktop is a greater assist to any one who wants to start an otr -session then /Gajim, /mcabber or /pidgin +educated guessing by the client based on previous messages). Furthermore a +client should encourage a user to choose meaningful presences instead of the +clients name or even random ones. Something like `/mobile`, `/notebook`, +`/desktop` is a greater assist to any one who wants to start an otr session then +`/Gajim`, `/mcabber` or `/pidgin`. 2. Messages should be tagged private and no-copy to avoid unnecessary traffic or otr error loops with faulty clients. This tagging should be done even if your own client doesn’t support carbons. -3. When dealing with “legacy clients” - meaning clients which don’t follow my -advise a client should be extra careful not to create message loops. This means -to not respond with otr errors if a client is not 100% sure it is the only +3. When dealing with “legacy clients” — meaning clients which don’t follow my +advise — a client should be extra careful not to create message loops. This +means to not respond with otr errors if a client is not 100% sure it is the only client which received the message diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e61d1fd..81cfe7fe 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Sat Nov 22 17:47:57 CET 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/libs/minidns/build.gradle b/libs/minidns/build.gradle index 5941beaf..c9b93467 100644 --- a/libs/minidns/build.gradle +++ b/libs/minidns/build.gradle @@ -19,7 +19,10 @@ group = 'de.measite.minidns' description = "A minimal DNS client library with support for A, AAAA, NS and SRV records" sourceCompatibility = 1.7 version = 'git tag --points-at HEAD'.execute().text.trim() -isSNAPSHOT = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim() == 'master' + +ext { + isSNAPSHOT = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim() == 'master' +} if (isSNAPSHOT) { version = version + '-SNAPSHOT' @@ -74,4 +77,4 @@ modifyPom { } dependencies { -}
\ No newline at end of file +} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 937b6813..f3d58d57 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -77,6 +77,12 @@ android:name=".ui.ChooseContactActivity" android:label="@string/title_activity_choose_contact" /> <activity + android:name=".ui.BlocklistActivity" + android:label="@string/title_activity_block_list" /> + <activity + android:name=".ui.ChangePasswordActivity" + android:label="@string/change_password_on_server" /> + <activity android:name=".ui.ManageAccountActivity" android:configChanges="orientation|screenSize" android:label="@string/title_activity_manage_accounts" /> @@ -114,7 +120,7 @@ <category android:name="android.intent.category.DEFAULT" /> - <data android:mimeType="image/*" /> + <data android:mimeType="*/*" /> </intent-filter> </activity> <activity diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 8b46335a..ad874660 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -20,10 +20,18 @@ public final class Config { public static final boolean PARSE_EMOTICONS = false; public static final boolean UTF8_EMOTICONS = false; + + public static final int PAGE_SIZE = 50; + public static final int MAX_NUM_PAGES = 3; + public static final int PROGRESS_UI_UPDATE_INTERVAL = 750; public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; + public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; + public static final int MAM_MAX_MESSAGES = 500; + private Config() { } diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java index 642d0ed0..7f7b350c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java @@ -34,7 +34,7 @@ import net.java.otr4j.crypto.OtrCryptoException; import net.java.otr4j.session.InstanceTag; import net.java.otr4j.session.SessionID; -public class OtrEngine implements OtrEngineHost { +public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost { private Account account; private OtrPolicy otrPolicy; @@ -126,7 +126,7 @@ public class OtrEngine implements OtrEngineHost { @Override public byte[] getLocalFingerprintRaw(SessionID arg0) { try { - return new OtrCryptoEngineImpl().getFingerprintRaw(getPublicKey()); + return getFingerprintRaw(getPublicKey()); } catch (OtrCryptoException e) { return null; } @@ -180,6 +180,7 @@ public class OtrEngine implements OtrEngineHost { packet.setBody(body); packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-store", "urn:xmpp:hints"); packet.setType(MessagePacket.TYPE_CHAT); account.getXmppConnection().sendMessagePacket(packet); } @@ -257,10 +258,10 @@ public class OtrEngine implements OtrEngineHost { Conversation conversation = this.mXmppConnectionService.find(this.account,jid); if (conversation!=null) { if (approved) { - conversation.getContact().addOtrFingerprint(CryptoHelper.prettifyFingerprint(fingerprint)); + conversation.getContact().addOtrFingerprint(fingerprint); } conversation.smp().hint = null; - conversation.smp().status = Conversation.Smp.STATUS_FINISHED; + conversation.smp().status = Conversation.Smp.STATUS_VERIFIED; mXmppConnectionService.updateConversationUi(); mXmppConnectionService.syncRosterToDisk(conversation.getAccount()); } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java b/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java index c7dedc5e..40a55151 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java @@ -14,7 +14,7 @@ public class Plain extends SaslMechanism { @Override public int getPriority() { - return 0; + return 10; } @Override diff --git a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java index 92b8a729..957b0a14 100644 --- a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java +++ b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java @@ -17,5 +17,4 @@ public abstract class AbstractEntity { public boolean equals(AbstractEntity entity) { return this.getUuid().equals(entity.getUuid()); } - } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 538d0ec2..b0cde62c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -10,9 +10,12 @@ import net.java.otr4j.crypto.OtrCryptoException; import org.json.JSONException; import org.json.JSONObject; +import java.security.PublicKey; import java.security.interfaces.DSAPublicKey; +import java.util.Collection; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -56,7 +59,7 @@ public class Account extends AbstractEntity { SECURITY_ERROR(true), INCOMPATIBLE_SERVER(true); - private boolean isError; + private final boolean isError; public boolean isError() { return this.isError; @@ -116,11 +119,11 @@ public class Account extends AbstractEntity { protected boolean online = false; private OtrEngine otrEngine = null; private XmppConnection xmppConnection = null; - private Presences presences = new Presences(); private long mEndGracePeriod = 0L; private String otrFingerprint; - private Roster roster = null; + private final Roster roster = new Roster(this); private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); + private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); public Account() { this.uuid = "0"; @@ -150,7 +153,7 @@ public class Account extends AbstractEntity { this.avatar = avatar; } - public static Account fromCursor(Cursor cursor) { + public static Account fromCursor(final Cursor cursor) { Jid jid = null; try { jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)), @@ -166,11 +169,11 @@ public class Account extends AbstractEntity { cursor.getString(cursor.getColumnIndex(AVATAR))); } - public boolean isOptionSet(int option) { + public boolean isOptionSet(final int option) { return ((options & (1 << option)) != 0); } - public void setOption(int option, boolean value) { + public void setOption(final int option, final boolean value) { if (value) { this.options |= 1 << option; } else { @@ -241,34 +244,18 @@ public class Account extends AbstractEntity { return keys; } - public String getSSLFingerprint() { - if (keys.has("ssl_cert")) { - try { - return keys.getString("ssl_cert"); - } catch (JSONException e) { - return null; - } - } else { - return null; - } - } - - public void setSSLCertFingerprint(String fingerprint) { - this.setKey("ssl_cert", fingerprint); - } - - public boolean setKey(String keyName, String keyValue) { + public boolean setKey(final String keyName, final String keyValue) { try { this.keys.put(keyName, keyValue); return true; - } catch (JSONException e) { + } catch (final JSONException e) { return false; } } @Override public ContentValues getContentValues() { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put(UUID, uuid); values.put(USERNAME, jid.getLocalpart()); values.put(SERVER, jid.getDomainpart()); @@ -280,7 +267,7 @@ public class Account extends AbstractEntity { return values; } - public void initOtrEngine(XmppConnectionService context) { + public void initOtrEngine(final XmppConnectionService context) { this.otrEngine = new OtrEngine(context, this); } @@ -292,7 +279,7 @@ public class Account extends AbstractEntity { return this.xmppConnection; } - public void setXmppConnection(XmppConnection connection) { + public void setXmppConnection(final XmppConnection connection) { this.xmppConnection = connection; } @@ -302,8 +289,8 @@ public class Account extends AbstractEntity { if (this.otrEngine == null) { return null; } - DSAPublicKey publicKey = (DSAPublicKey) this.otrEngine.getPublicKey(); - if (publicKey == null) { + final PublicKey publicKey = this.otrEngine.getPublicKey(); + if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { return null; } this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey); @@ -324,31 +311,19 @@ public class Account extends AbstractEntity { } } - public void setRosterVersion(String version) { + public void setRosterVersion(final String version) { this.rosterVersion = version; } - public void updatePresence(String resource, int status) { - this.presences.updatePresence(resource, status); - } - - public void removePresence(String resource) { - this.presences.removePresence(resource); - } - - public void clearPresences() { - this.presences = new Presences(); - } - public int countPresences() { - return this.presences.size(); + return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size(); } public String getPgpSignature() { if (keys.has("pgp_signature")) { try { return keys.getString("pgp_signature"); - } catch (JSONException e) { + } catch (final JSONException e) { return null; } } else { @@ -357,9 +332,6 @@ public class Account extends AbstractEntity { } public Roster getRoster() { - if (this.roster == null) { - this.roster = new Roster(this); - } return this.roster; } @@ -367,12 +339,12 @@ public class Account extends AbstractEntity { return this.bookmarks; } - public void setBookmarks(List<Bookmark> bookmarks) { + public void setBookmarks(final List<Bookmark> bookmarks) { this.bookmarks = bookmarks; } public boolean hasBookmarkFor(final Jid conferenceJid) { - for (Bookmark bookmark : this.bookmarks) { + for (final Bookmark bookmark : this.bookmarks) { final Jid jid = bookmark.getJid(); if (jid != null && jid.equals(conferenceJid.toBareJid())) { return true; @@ -381,7 +353,7 @@ public class Account extends AbstractEntity { return false; } - public boolean setAvatar(String filename) { + public boolean setAvatar(final String filename) { if (this.avatar != null && this.avatar.equals(filename)) { return false; } else { @@ -408,11 +380,32 @@ public class Account extends AbstractEntity { } public String getShareableUri() { - String fingerprint = this.getOtrFingerprint(); + final String fingerprint = this.getOtrFingerprint(); if (fingerprint != null) { return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint; } else { return "xmpp:" + this.getJid().toBareJid().toString(); } } + + public boolean isBlocked(final ListItem contact) { + final Jid jid = contact.getJid(); + return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid())); + } + + public boolean isBlocked(final Jid jid) { + return jid != null && blocklist.contains(jid.toBareJid()); + } + + public Collection<Jid> getBlocklist() { + return this.blocklist; + } + + public void clearBlocklist() { + getBlocklist().clear(); + } + + public boolean isOnlineAndConnected() { + return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Blockable.java b/src/main/java/eu/siacs/conversations/entities/Blockable.java new file mode 100644 index 00000000..dbcd55c4 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/entities/Blockable.java @@ -0,0 +1,11 @@ +package eu.siacs.conversations.entities; + +import eu.siacs.conversations.xmpp.jid.Jid; + +public interface Blockable { + public boolean isBlocked(); + public boolean isDomainBlocked(); + public Jid getBlockedJid(); + public Jid getJid(); + public Account getAccount(); +} diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 559e2f2d..f81f1a87 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -6,7 +6,6 @@ import java.util.Locale; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public class Bookmark extends Element implements ListItem { @@ -60,16 +59,7 @@ public class Bookmark extends Element implements ListItem { @Override public Jid getJid() { - final String jid = this.getAttribute("jid"); - if (jid != null) { - try { - return Jid.fromString(jid); - } catch (final InvalidJidException e) { - return null; - } - } else { - return null; - } + return this.getAttributeAsJid("jid"); } @Override @@ -102,9 +92,7 @@ public class Bookmark extends Element implements ListItem { } public boolean autojoin() { - String autojoin = this.getAttribute("autojoin"); - return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin - .equalsIgnoreCase("1"))); + return this.getAttributeAsBoolean("autojoin"); } public String getPassword() { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 6a6b41d6..698e0322 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -16,7 +16,7 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class Contact implements ListItem { +public class Contact implements ListItem, Blockable { public static final String TABLENAME = "contacts"; public static final String SYSTEMNAME = "systemname"; @@ -47,8 +47,8 @@ public class Contact implements ListItem { protected Account account; public Contact(final String account, final String systemName, final String serverName, - final Jid jid, final int subscription, final String photoUri, - final String systemAccount, final String keys, final String avatar, final Lastseen lastseen, final String groups) { + final Jid jid, final int subscription, final String photoUri, + final String systemAccount, final String keys, final String avatar, final Lastseen lastseen, final String groups) { this.accountUuid = account; this.systemName = systemName; this.serverName = serverName; @@ -122,11 +122,10 @@ public class Contact implements ListItem { @Override public List<Tag> getTags() { - ArrayList<Tag> tags = new ArrayList<Tag>(); - for (String group : getGroups()) { + final ArrayList<Tag> tags = new ArrayList<>(); + for (final String group : getGroups()) { tags.add(new Tag(group, UIHelper.getColorForName(group))); } - int status = getMostAvailableStatus(); switch (getMostAvailableStatus()) { case Presences.CHAT: case Presences.ONLINE: @@ -142,6 +141,9 @@ public class Contact implements ListItem { tags.add(new Tag("dnd", 0xffe51c23)); break; } + if (isBlocked()) { + tags.add(new Tag("blocked", 0xff2e2f3b)); + } return tags; } @@ -176,7 +178,7 @@ public class Contact implements ListItem { } public ContentValues getContentValues() { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put(ACCOUNT, accountUuid); values.put(SYSTEMNAME, systemName); values.put(SERVERNAME, serverName); @@ -213,11 +215,11 @@ public class Contact implements ListItem { this.presences = pres; } - public void updatePresence(String resource, int status) { + public void updatePresence(final String resource, final int status) { this.presences.updatePresence(resource, status); } - public void removePresence(String resource) { + public void removePresence(final String resource) { this.presences.removePresence(resource); } @@ -266,13 +268,15 @@ public class Contact implements ListItem { } public ArrayList<String> getOtrFingerprints() { - ArrayList<String> fingerprints = new ArrayList<String>(); + final ArrayList<String> fingerprints = new ArrayList<String>(); try { if (this.keys.has("otr_fingerprints")) { - JSONArray prints = this.keys - .getJSONArray("otr_fingerprints"); + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); for (int i = 0; i < prints.length(); ++i) { - fingerprints.add(prints.getString(i)); + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i)); + } } } } catch (final JSONException ignored) { @@ -335,8 +339,8 @@ public class Contact implements ListItem { public boolean showInRoster() { return (this.getOption(Contact.Options.IN_ROSTER) && (!this - .getOption(Contact.Options.DIRTY_DELETE))) - || (this.getOption(Contact.Options.DIRTY_PUSH)); + .getOption(Contact.Options.DIRTY_DELETE))) + || (this.getOption(Contact.Options.DIRTY_PUSH)); } public void parseSubscriptionFromElement(Element item) { @@ -426,7 +430,7 @@ public class Contact implements ListItem { if (this.keys.has("otr_fingerprints")) { JSONArray newPrints = new JSONArray(); JSONArray oldPrints = this.keys - .getJSONArray("otr_fingerprints"); + .getJSONArray("otr_fingerprints"); for (int i = 0; i < oldPrints.length(); ++i) { if (!oldPrints.getString(i).equals(fingerprint)) { newPrints.put(oldPrints.getString(i)); @@ -449,28 +453,46 @@ public class Contact implements ListItem { public String getShareableUri() { if (getOtrFingerprints().size() >= 1) { String otr = getOtrFingerprints().get(0); - return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr.replace(" ", ""); + return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr; } else { return "xmpp:" + getJid().toBareJid().toString(); } } + @Override + public boolean isBlocked() { + return getAccount().isBlocked(this); + } + + @Override + public boolean isDomainBlocked() { + return getAccount().isBlocked(this.getJid().toDomainJid()); + } + + @Override + public Jid getBlockedJid() { + if (isDomainBlocked()) { + return getJid().toDomainJid(); + } else { + return getJid(); + } + } + public static class Lastseen { public long time; public String presence; public Lastseen() { - time = 0; - presence = null; + this(null, 0); } public Lastseen(final String presence, final long time) { - this.time = time; this.presence = presence; + this.time = time; } } - public class Options { + public final class Options { public static final int TO = 0; public static final int FROM = 1; public static final int ASKING = 2; diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index a7da0bc2..470bd290 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -16,12 +16,15 @@ import org.json.JSONObject; import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class Conversation extends AbstractEntity { +public class Conversation extends AbstractEntity implements Blockable { public static final String TABLENAME = "conversations"; public static final int STATUS_AVAILABLE = 0; @@ -43,6 +46,7 @@ public class Conversation extends AbstractEntity { public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; + public static final String ATTRIBUTE_LAST_MESSAGE_TRANSMITTED = "last_message_transmitted"; private String name; private String contactUuid; @@ -72,6 +76,140 @@ public class Conversation extends AbstractEntity { private Bookmark bookmark; + private boolean messagesLeftOnServer = true; + + public boolean hasMessagesLeftOnServer() { + return messagesLeftOnServer; + } + + public void setHasMessagesLeftOnServer(boolean value) { + this.messagesLeftOnServer = value; + } + + public Message findUnsentMessageWithUuid(String uuid) { + synchronized(this.messages) { + for (final Message message : this.messages) { + final int s = message.getStatus(); + if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { + return message; + } + } + } + return null; + } + + public void findWaitingMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for(Message message : this.messages) { + if (message.getStatus() == Message.STATUS_WAITING) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public void findMessagesWithFiles(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) + && message.getEncryption() != Message.ENCRYPTION_PGP) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public Message findMessageWithFileAndUuid(String uuid) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (message.getType() == Message.TYPE_IMAGE + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getUuid().equals(uuid)) { + return message; + } + } + } + return null; + } + + public void clearMessages() { + synchronized (this.messages) { + this.messages.clear(); + } + } + + public void trim() { + synchronized (this.messages) { + final int size = messages.size(); + final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES; + if (size > maxsize) { + this.messages.subList(0, size - maxsize).clear(); + } + } + } + + public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) + && (message.getEncryption() == Message.ENCRYPTION_OTR)) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public void findUnsentTextMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (message.getType() != Message.TYPE_IMAGE + && message.getStatus() == Message.STATUS_UNSEND) { + onMessageFound.onMessageFound(message); + } + } + } + } + + public Message findSentMessageWithUuid(String uuid) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (uuid.equals(message.getUuid()) + || (message.getStatus() >= Message.STATUS_SEND && uuid + .equals(message.getRemoteMsgId()))) { + return message; + } + } + } + return null; + } + + public void populateWithMessages(final List<Message> messages) { + synchronized (this.messages) { + messages.clear(); + messages.addAll(this.messages); + } + } + + @Override + public boolean isBlocked() { + return getContact().isBlocked(); + } + + @Override + public boolean isDomainBlocked() { + return getContact().isDomainBlocked(); + } + + @Override + public Jid getBlockedJid() { + return getContact().getBlockedJid(); + } + + + public interface OnMessageFound { + public void onMessageFound(final Message message); + } + public Conversation(final String name, final Account account, final Jid contactJid, final int mode) { this(java.util.UUID.randomUUID().toString(), name, null, account @@ -98,18 +236,11 @@ public class Conversation extends AbstractEntity { } } - public List<Message> getMessages() { - return messages; - } - public boolean isRead() { - return (this.messages == null) || (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); - } + return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); + } public void markRead() { - if (this.messages == null) { - return; - } for (int i = this.messages.size() - 1; i >= 0; --i) { if (messages.get(i).isRead()) { break; @@ -119,9 +250,6 @@ public class Conversation extends AbstractEntity { } public Message getLatestMarkableMessage() { - if (this.messages == null) { - return null; - } for (int i = this.messages.size() - 1; i >= 0; --i) { if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED && this.messages.get(i).markable) { @@ -130,13 +258,13 @@ public class Conversation extends AbstractEntity { } else { return this.messages.get(i); } - } + } } return null; } public Message getLatestMessage() { - if ((this.messages == null) || (this.messages.size() == 0)) { + if (this.messages.size() == 0) { Message message = new Message(this, "", Message.ENCRYPTION_NONE); message.setTime(getCreated()); return message; @@ -158,7 +286,7 @@ public class Conversation extends AbstractEntity { if (generatedName != null) { return generatedName; } else { - return getContactJid().getLocalpart(); + return getJid().getLocalpart(); } } } else { @@ -166,10 +294,6 @@ public class Conversation extends AbstractEntity { } } - public String getProfilePhotoString() { - return this.getContact().getProfilePhoto(); - } - public String getAccountUuid() { return this.accountUuid; } @@ -182,11 +306,12 @@ public class Conversation extends AbstractEntity { return this.account.getRoster().getContact(this.contactJid); } - public void setAccount(Account account) { + public void setAccount(final Account account) { this.account = account; } - public Jid getContactJid() { + @Override + public Jid getJid() { return this.contactJid; } @@ -213,14 +338,14 @@ public class Conversation extends AbstractEntity { } public static Conversation fromCursor(Cursor cursor) { - Jid jid; - try { - jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID))); - } catch (final InvalidJidException e) { - // Borked DB.. - jid = null; - } - return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), + Jid jid; + try { + jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID))); + } catch (final InvalidJidException e) { + // Borked DB.. + jid = null; + } + return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)), cursor.getString(cursor.getColumnIndex(NAME)), cursor.getString(cursor.getColumnIndex(CONTACT)), cursor.getString(cursor.getColumnIndex(ACCOUNT)), @@ -247,9 +372,9 @@ public class Conversation extends AbstractEntity { if (this.otrSession != null) { return this.otrSession; } else { - final SessionID sessionId = new SessionID(this.getContactJid().toBareJid().toString(), - presence, - "xmpp"); + final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(), + presence, + "xmpp"); this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine()); try { if (sendStart) { @@ -288,7 +413,7 @@ public class Conversation extends AbstractEntity { } catch (OtrException e) { this.resetOtrSession(); } - } + } } public boolean endOtrIfNeeded() { @@ -315,30 +440,29 @@ public class Conversation extends AbstractEntity { return this.otrSession != null; } - public String getOtrFingerprint() { + public synchronized String getOtrFingerprint() { if (this.otrFingerprint == null) { try { - if (getOtrSession() == null) { - return ""; + if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + return null; } - DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession() - .getRemotePublicKey(); - StringBuilder builder = new StringBuilder( - new OtrCryptoEngineImpl().getFingerprint(remotePubKey)); - builder.insert(8, " "); - builder.insert(17, " "); - builder.insert(26, " "); - builder.insert(35, " "); - this.otrFingerprint = builder.toString(); - } catch (final OtrCryptoException ignored) { - + DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey(); + this.otrFingerprint = getAccount().getOtrEngine().getFingerprint(remotePubKey); + } catch (final OtrCryptoException | UnsupportedOperationException ignored) { + return null; } } return this.otrFingerprint; } - public void verifyOtrFingerprint() { - getContact().addOtrFingerprint(getOtrFingerprint()); + public boolean verifyOtrFingerprint() { + final String fingerprint = getOtrFingerprint(); + if (fingerprint != null) { + getContact().addOtrFingerprint(fingerprint); + return true; + } else { + return false; + } } public boolean isOtrFingerprintVerified() { @@ -450,9 +574,11 @@ public class Conversation extends AbstractEntity { } public boolean hasDuplicateMessage(Message message) { - for (int i = this.getMessages().size() - 1; i >= 0; --i) { - if (this.messages.get(i).equals(message)) { - return true; + synchronized (this.messages) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).equals(message)) { + return true; + } } } return false; @@ -460,7 +586,7 @@ public class Conversation extends AbstractEntity { public Message findSentMessageWithBody(String body) { synchronized (this.messages) { - for (int i = this.getMessages().size() - 1; i >= 0; --i) { + for (int i = this.messages.size() - 1; i >= 0; --i) { Message message = this.messages.get(i); if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) && message.getBody() != null && message.getBody().equals(body)) { return message; @@ -470,6 +596,31 @@ public class Conversation extends AbstractEntity { } } + public boolean setLastMessageTransmitted(long value) { + long before = getLastMessageTransmitted(); + if (value - before > 1000) { + this.setAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED, String.valueOf(value)); + return true; + } else { + return false; + } + } + + public long getLastMessageTransmitted() { + long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED,0); + if (timestamp == 0) { + synchronized (this.messages) { + for(int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_RECEIVED) { + return message.getTimeSent(); + } + } + } + } + return timestamp; + } + public void setMutedTill(long value) { this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value)); } @@ -535,12 +686,32 @@ public class Conversation extends AbstractEntity { } } + public void sort() { + synchronized (this.messages) { + Collections.sort(this.messages, new Comparator<Message>() { + @Override + public int compare(Message left, Message right) { + if (left.getTimeSent() < right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() > right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + }); + for(Message message : this.messages) { + message.untie(); + } + } + } + public class Smp { public static final int STATUS_NONE = 0; public static final int STATUS_CONTACT_REQUESTED = 1; public static final int STATUS_WE_REQUESTED = 2; public static final int STATUS_FAILED = 3; - public static final int STATUS_FINISHED = 4; + public static final int STATUS_VERIFIED = 4; public String secret = null; public String hint = null; diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index 25f33907..7c8f95d1 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -56,12 +56,16 @@ public class DownloadableFile extends File { public String getMimeType() { String path = this.getAbsolutePath(); - String mime = URLConnection.guessContentTypeFromName(path); - if (mime != null) { - return mime; - } else if (mime == null && path.endsWith(".webp")) { - return "image/webp"; - } else { + try { + String mime = URLConnection.guessContentTypeFromName(path.replace("#","")); + if (mime != null) { + return mime; + } else if (mime == null && path.endsWith(".webp")) { + return "image/webp"; + } else { + return ""; + } + } catch (final StringIndexOutOfBoundsException e) { return ""; } } diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java index db9fbc37..efc1c2b9 100644 --- a/src/main/java/eu/siacs/conversations/entities/ListItem.java +++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java @@ -12,10 +12,10 @@ public interface ListItem extends Comparable<ListItem> { public List<Tag> getTags(); public final class Tag { - private String name; - private int color; + private final String name; + private final int color; - public Tag(String name, int color) { + public Tag(final String name, final int color) { this.name = name; this.color = color; } @@ -28,4 +28,6 @@ public interface ListItem extends Comparable<ListItem> { return this.name; } } + + public boolean match(final String needle); } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 47861d06..b5a1897d 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -45,6 +45,7 @@ public class Message extends AbstractEntity { public static String STATUS = "status"; public static String TYPE = "type"; public static String REMOTE_MSG_ID = "remoteMsgId"; + public static String SERVER_MSG_ID = "serverMsgId"; public static String RELATIVE_FILE_PATH = "relativeFilePath"; public boolean markable = false; protected String conversationUuid; @@ -59,6 +60,7 @@ public class Message extends AbstractEntity { protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; + protected String serverMsgId = null; protected Conversation conversation = null; protected Downloadable downloadable = null; private Message mNextMessage = null; @@ -75,7 +77,7 @@ public class Message extends AbstractEntity { public Message(Conversation conversation, String body, int encryption, int status) { this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), - conversation.getContactJid() == null ? null : conversation.getContactJid().toBareJid(), + conversation.getJid() == null ? null : conversation.getJid().toBareJid(), null, body, System.currentTimeMillis(), @@ -83,13 +85,15 @@ public class Message extends AbstractEntity { status, TYPE_TEXT, null, + null, null); this.conversation = conversation; } private Message(final String uuid, final String conversationUUid, final Jid counterpart, - final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final String remoteMsgId, final String relativeFilePath) { + final Jid trueCounterpart, final String body, final long timeSent, + final int encryption, final int status, final int type, final String remoteMsgId, + final String relativeFilePath, final String serverMsgId) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -101,6 +105,7 @@ public class Message extends AbstractEntity { this.type = type; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; + this.serverMsgId = serverMsgId; } public static Message fromCursor(Cursor cursor) { @@ -136,7 +141,8 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), - cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH))); + cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), + cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID))); } public static Message createStatusMessage(Conversation conversation) { @@ -168,6 +174,7 @@ public class Message extends AbstractEntity { values.put(TYPE, type); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); + values.put(SERVER_MSG_ID,serverMsgId); return values; } @@ -199,7 +206,7 @@ public class Message extends AbstractEntity { return null; } else { return this.conversation.getAccount().getRoster() - .getContactFromRoster(this.trueCounterpart); + .getContactFromRoster(this.trueCounterpart); } } } @@ -248,6 +255,14 @@ public class Message extends AbstractEntity { this.remoteMsgId = id; } + public String getServerMsgId() { + return this.serverMsgId; + } + + public void setServerMsgId(String id) { + this.serverMsgId = id; + } + public boolean isRead() { return this.read; } @@ -293,38 +308,43 @@ public class Message extends AbstractEntity { } public boolean equals(Message message) { - return (this.remoteMsgId != null) && (this.body != null) && (this.counterpart != null) && this.remoteMsgId.equals(message.getRemoteMsgId()) && this.body.equals(message.getBody()) && this.counterpart.equals(message.getCounterpart()); + if (this.serverMsgId != null && message.getServerMsgId() != null) { + return this.serverMsgId.equals(message.getServerMsgId()); + } else { + return this.body != null + && this.counterpart != null + && ((this.remoteMsgId != null && this.remoteMsgId.equals(message.getRemoteMsgId())) + || this.uuid.equals(message.getRemoteMsgId())) && this.body.equals(message.getBody()) + && this.counterpart.equals(message.getCounterpart()); + } } public Message next() { - if (this.mNextMessage == null) { - synchronized (this.conversation.messages) { + synchronized (this.conversation.messages) { + if (this.mNextMessage == null) { int index = this.conversation.messages.indexOf(this); - if (index < 0 - || index >= this.conversation.getMessages().size() - 1) { + if (index < 0 || index >= this.conversation.messages.size() - 1) { this.mNextMessage = null; } else { - this.mNextMessage = this.conversation.messages - .get(index + 1); + this.mNextMessage = this.conversation.messages.get(index + 1); } } + return this.mNextMessage; } - return this.mNextMessage; } public Message prev() { - if (this.mPreviousMessage == null) { - synchronized (this.conversation.messages) { + synchronized (this.conversation.messages) { + if (this.mPreviousMessage == null) { int index = this.conversation.messages.indexOf(this); if (index <= 0 || index > this.conversation.messages.size()) { this.mPreviousMessage = null; } else { - this.mPreviousMessage = this.conversation.messages - .get(index - 1); + this.mPreviousMessage = this.conversation.messages.get(index - 1); } } + return this.mPreviousMessage; } - return this.mPreviousMessage; } public boolean mergeable(final Message message) { @@ -368,7 +388,7 @@ public class Message extends AbstractEntity { if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { return false; - } + } if (url.getPath() == null) { return false; } @@ -382,14 +402,14 @@ public class Message extends AbstractEntity { String[] extensionParts = filename.split("\\."); if (extensionParts.length == 2 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 1])) { + extensionParts[extensionParts.length - 1])) { return true; } else if (extensionParts.length == 3 && Arrays .asList(Downloadable.VALID_CRYPTO_EXTENSIONS) .contains(extensionParts[extensionParts.length - 1]) && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains( - extensionParts[extensionParts.length - 2])) { + extensionParts[extensionParts.length - 2])) { return true; } else { return false; @@ -493,6 +513,15 @@ public class Message extends AbstractEntity { } } + public void untie() { + this.mNextMessage = null; + this.mPreviousMessage = null; + } + + public boolean isFileOrImage() { + return type == TYPE_FILE || type == TYPE_IMAGE; + } + public class ImageParams { public URL url; public long size = 0; diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index c8706fc9..97a63532 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -80,20 +80,20 @@ public class MucOptions { public void setRole(String role) { 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; - } + 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; + } } public int getAffiliation() { @@ -164,7 +164,7 @@ public class MucOptions { } public void processPacket(PresencePacket packet, PgpEngine pgp) { - final Jid from = packet.getFrom(); + final Jid from = packet.getFrom(); if (!from.isBareJid()) { final String name = from.getResourcepart(); final String type = packet.getAttribute("type"); @@ -179,7 +179,7 @@ public class MucOptions { user.setAffiliation(item.getAttribute("affiliation")); user.setRole(item.getAttribute("role")); user.setJid(item.getAttributeAsJid("jid")); - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getContactJid())) { + if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) { this.isOnline = true; this.error = ERROR_NO_ERROR; self = user; @@ -204,14 +204,14 @@ public class MucOptions { msg = ""; } user.setPgpKeyId(pgp.fetchKeyId(account, msg, - signed.getContent())); + signed.getContent())); } } } } } else if (type.equals("unavailable")) { if (codes.contains(STATUS_CODE_SELF_PRESENCE) || - packet.getFrom().equals(this.conversation.getContactJid())) { + packet.getFrom().equals(this.conversation.getJid())) { if (codes.contains(STATUS_CODE_CHANGED_NICK)) { this.mNickChangingInProgress = true; } else if (codes.contains(STATUS_CODE_KICKED)) { @@ -282,8 +282,8 @@ public class MucOptions { && conversation.getBookmark().getNick() != null && !conversation.getBookmark().getNick().isEmpty()) { return conversation.getBookmark().getNick(); - } else if (!conversation.getContactJid().isBareJid()) { - return conversation.getContactJid().getResourcepart(); + } else if (!conversation.getJid().isBareJid()) { + return conversation.getJid().getResourcepart(); } else { return account.getUsername(); } @@ -334,14 +334,14 @@ public class MucOptions { public String createNameFromParticipants() { if (users.size() >= 2) { List<String> names = new ArrayList<String>(); - for (User user : users) { - Contact contact = user.getContact(); - if (contact != null && !contact.getDisplayName().isEmpty()) { - names.add(contact.getDisplayName().split("\\s+")[0]); - } else { - names.add(user.getName()); - } + for (User user : users) { + Contact contact = user.getContact(); + if (contact != null && !contact.getDisplayName().isEmpty()) { + names.add(contact.getDisplayName().split("\\s+")[0]); + } else { + names.add(user.getName()); } + } StringBuilder builder = new StringBuilder(); for (int i = 0; i < names.size(); ++i) { builder.append(names.get(i)); @@ -388,12 +388,12 @@ public class MucOptions { } public Jid createJoinJid(String nick) { - try { - return Jid.fromString(this.conversation.getContactJid().toBareJid().toString() + "/"+nick); - } catch (final InvalidJidException e) { - return null; - } - } + try { + return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/"+nick); + } catch (final InvalidJidException e) { + return null; + } + } public Jid getTrueCounterpart(String counterpart) { for (User user : this.getUsers()) { diff --git a/src/main/java/eu/siacs/conversations/entities/Roster.java b/src/main/java/eu/siacs/conversations/entities/Roster.java index 12a89cec..1a81a419 100644 --- a/src/main/java/eu/siacs/conversations/entities/Roster.java +++ b/src/main/java/eu/siacs/conversations/entities/Roster.java @@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentHashMap; import eu.siacs.conversations.xmpp.jid.Jid; public class Roster { - Account account; + final Account account; final ConcurrentHashMap<String, Contact> contacts = new ConcurrentHashMap<>(); private String version = null; @@ -19,7 +19,7 @@ public class Roster { if (jid == null) { return null; } - Contact contact = contacts.get(jid.toBareJid().toString()); + final Contact contact = contacts.get(jid.toBareJid().toString()); if (contact != null && contact.showInRoster()) { return contact; } else { @@ -32,7 +32,7 @@ public class Roster { if (contacts.containsKey(bareJid.toString())) { return contacts.get(bareJid.toString()); } else { - Contact contact = new Contact(bareJid); + final Contact contact = new Contact(bareJid); contact.setAccount(account); contacts.put(bareJid.toString(), contact); return contact; @@ -46,13 +46,13 @@ public class Roster { } public void markAllAsNotInRoster() { - for (Contact contact : getContacts()) { + for (final Contact contact : getContacts()) { contact.resetOption(Contact.Options.IN_ROSTER); } } public void clearSystemAccounts() { - for (Contact contact : getContacts()) { + for (final Contact contact : getContacts()) { contact.setPhotoUri(null); contact.setSystemName(null); contact.setSystemAccount(null); diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index f46e7ba4..870ee757 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -4,9 +4,12 @@ import android.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import eu.siacs.conversations.services.XmppConnectionService; @@ -20,9 +23,11 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/disco#info", "urn:xmpp:avatar:metadata+notify", "urn:xmpp:ping"}; - public final String IDENTITY_NAME = "Conversations 0.9.3"; + public final String IDENTITY_NAME = "Conversations 0.10"; public final String IDENTITY_TYPE = "phone"; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + protected XmppConnectionService mXmppConnectionService; protected AbstractGenerator(XmppConnectionService service) { @@ -46,4 +51,9 @@ public abstract class AbstractGenerator { byte[] sha1 = md.digest(s.toString().getBytes()); return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } + + public static String getTimestamp(long time) { + DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + return DATE_FORMAT.format(time); + } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 5d674748..f94dc5d7 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -4,52 +4,56 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { - public IqGenerator(XmppConnectionService service) { + public IqGenerator(final XmppConnectionService service) { super(service); } - public IqPacket discoResponse(IqPacket request) { - IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT); + public IqPacket discoResponse(final IqPacket request) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT); packet.setId(request.getId()); - packet.setTo(request.getFrom()); - Element query = packet.addChild("query", + packet.setTo(request.getFrom()); + final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info"); query.setAttribute("node", request.query().getAttribute("node")); - Element identity = query.addChild("identity"); + final Element identity = query.addChild("identity"); identity.setAttribute("category", "client"); identity.setAttribute("type", this.IDENTITY_TYPE); identity.setAttribute("name", IDENTITY_NAME); - List<String> features = Arrays.asList(FEATURES); + final List<String> features = Arrays.asList(FEATURES); Collections.sort(features); - for (String feature : features) { + for (final String feature : features) { query.addChild("feature").setAttribute("var", feature); } return packet; } - protected IqPacket publish(String node, Element item) { - IqPacket packet = new IqPacket(IqPacket.TYPE_SET); - Element pubsub = packet.addChild("pubsub", + protected IqPacket publish(final String node, final Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + final Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); - Element publish = pubsub.addChild("publish"); + final Element publish = pubsub.addChild("publish"); publish.setAttribute("node", node); publish.addChild(item); return packet; } protected IqPacket retrieve(String node, Element item) { - IqPacket packet = new IqPacket(IqPacket.TYPE_GET); - Element pubsub = packet.addChild("pubsub", + final IqPacket packet = new IqPacket(IqPacket.TYPE_GET); + final Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub"); - Element items = pubsub.addChild("items"); + final Element items = pubsub.addChild("items"); items.setAttribute("node", node); if (item != null) { items.addChild(item); @@ -58,19 +62,19 @@ public class IqGenerator extends AbstractGenerator { } public IqPacket publishAvatar(Avatar avatar) { - Element item = new Element("item"); + final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - Element data = item.addChild("data", "urn:xmpp:avatar:data"); + final Element data = item.addChild("data", "urn:xmpp:avatar:data"); data.setContent(avatar.image); return publish("urn:xmpp:avatar:data", item); } - public IqPacket publishAvatarMetadata(Avatar avatar) { - Element item = new Element("item"); + public IqPacket publishAvatarMetadata(final Avatar avatar) { + final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - Element metadata = item - .addChild("metadata", "urn:xmpp:avatar:metadata"); - Element info = metadata.addChild("info"); + final Element metadata = item + .addChild("metadata", "urn:xmpp:avatar:metadata"); + final Element info = metadata.addChild("info"); info.setAttribute("bytes", avatar.size); info.setAttribute("id", avatar.sha1sum); info.setAttribute("height", avatar.height); @@ -79,10 +83,10 @@ public class IqGenerator extends AbstractGenerator { return publish("urn:xmpp:avatar:metadata", item); } - public IqPacket retrieveAvatar(Avatar avatar) { - Element item = new Element("item"); + public IqPacket retrieveAvatar(final Avatar avatar) { + final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); - IqPacket packet = retrieve("urn:xmpp:avatar:data", item); + final IqPacket packet = retrieve("urn:xmpp:avatar:data", item); packet.setTo(avatar.owner); return packet; } @@ -94,4 +98,54 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + final Element query = packet.query("urn:xmpp:mam:0"); + query.setAttribute("queryid",mam.getQueryId()); + final Data data = new Data(); + data.setFormType("urn:xmpp:mam:0"); + if (mam.getWith()!=null) { + data.put("with", mam.getWith().toString()); + } + data.put("start",getTimestamp(mam.getStart())); + data.put("end",getTimestamp(mam.getEnd())); + query.addChild(data); + if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { + query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference()); + } else if (mam.getReference() != null) { + query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getReference()); + } + return packet; + } + public IqPacket generateGetBlockList() { + final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + iq.addChild("blocklist", Xmlns.BLOCKING); + + return iq; + } + + public IqPacket generateSetBlockRequest(final Jid jid) { + final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final Element block = iq.addChild("block", Xmlns.BLOCKING); + block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + return iq; + } + + public IqPacket generateSetUnblockRequest(final Jid jid) { + final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final Element block = iq.addChild("unblock", Xmlns.BLOCKING); + block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); + return iq; + } + + public IqPacket generateSetPassword(final Account account, final String newPassword) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + packet.setTo(account.getServer()); + final Element query = packet.addChild("query", Xmlns.REGISTER); + final Jid jid = account.getJid(); + query.addChild("username").setContent(jid.getLocalpart()); + query.addChild("password").setContent(newPassword); + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index ca5417e0..8e99888b 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -114,7 +114,7 @@ public class MessageGenerator extends AbstractGenerator { private MessagePacket generateError(MessagePacket origin) { MessagePacket packet = new MessagePacket(); packet.setId(origin.getId()); - packet.setTo(origin.getFrom()); + packet.setTo(origin.getFrom()); packet.setBody(origin.getBody()); packet.setType(MessagePacket.TYPE_ERROR); return packet; @@ -135,7 +135,7 @@ public class MessageGenerator extends AbstractGenerator { String subject) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_GROUPCHAT); - packet.setTo(conversation.getContactJid().toBareJid()); + packet.setTo(conversation.getJid().toBareJid()); Element subjectChild = new Element("subject"); subjectChild.setContent(subject); packet.addChild(subjectChild); @@ -149,13 +149,13 @@ public class MessageGenerator extends AbstractGenerator { packet.setTo(contact); packet.setFrom(conversation.getAccount().getJid()); Element x = packet.addChild("x", "jabber:x:conference"); - x.setAttribute("jid", conversation.getContactJid().toBareJid().toString()); + x.setAttribute("jid", conversation.getJid().toBareJid().toString()); return packet; } public MessagePacket invite(Conversation conversation, Jid contact) { MessagePacket packet = new MessagePacket(); - packet.setTo(conversation.getContactJid().toBareJid()); + packet.setTo(conversation.getJid().toBareJid()); packet.setFrom(conversation.getAccount().getJid()); Element x = new Element("x"); x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); @@ -170,7 +170,7 @@ public class MessageGenerator extends AbstractGenerator { MessagePacket originalMessage, String namespace) { MessagePacket receivedPacket = new MessagePacket(); receivedPacket.setType(MessagePacket.TYPE_NORMAL); - receivedPacket.setTo(originalMessage.getFrom()); + receivedPacket.setTo(originalMessage.getFrom()); receivedPacket.setFrom(account.getJid()); Element received = receivedPacket.addChild("received", namespace); received.setAttribute("id", originalMessage.getId()); diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index eedfca16..08070c08 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -2,8 +2,6 @@ package eu.siacs.conversations.parser; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.Locale; @@ -11,7 +9,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public abstract class AbstractParser { @@ -24,54 +21,39 @@ public abstract class AbstractParser { protected long getTimestamp(Element packet) { long now = System.currentTimeMillis(); - ArrayList<String> stamps = new ArrayList<>(); - for (Element child : packet.getChildren()) { - if (child.getName().equals("delay")) { - stamps.add(child.getAttribute("stamp").replace("Z", "+0000")); - } + Element delay = packet.findChild("delay"); + if (delay == null) { + return now; } - Collections.sort(stamps); - if (stamps.size() >= 1) { - try { - String stamp = stamps.get(stamps.size() - 1); - if (stamp.contains(".")) { - Date date = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) - .parse(stamp); - if (now < date.getTime()) { - return now; - } else { - return date.getTime(); - } - } else { - Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", - Locale.US).parse(stamp); - if (now < date.getTime()) { - return now; - } else { - return date.getTime(); - } - } - } catch (ParseException e) { - return now; - } - } else { + String stamp = delay.getAttribute("stamp"); + if (stamp == null) { + return now; + } + try { + long time = parseTimestamp(stamp).getTime(); + return now < time ? now : time; + } catch (ParseException e) { return now; } } + public static Date parseTimestamp(String timestamp) throws ParseException { + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + if (timestamp.contains(".")) { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + } else { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); + } + return dateFormat.parse(timestamp); + } + protected void updateLastseen(final Element packet, final Account account, final boolean presenceOverwrite) { - Jid from; - try { - from = Jid.fromString(packet.getAttribute("from")).toBareJid(); - } catch (final InvalidJidException e) { - // TODO: Handle this? - from = null; - } - String presence = from == null || from.isBareJid() ? "" : from.getResourcepart(); - Contact contact = account.getRoster().getContact(from); - long timestamp = getTimestamp(packet); + final Jid from = packet.getAttributeAsJid("from"); + final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart(); + final Contact contact = account.getRoster().getContact(from); + final long timestamp = getTimestamp(packet); if (timestamp >= contact.lastseen.time) { contact.lastseen.time = timestamp; if (!presence.isEmpty() && presenceOverwrite) { diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index aeec56d0..e84545fc 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -2,36 +2,40 @@ package eu.siacs.conversations.parser; import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; + import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class IqParser extends AbstractParser implements OnIqPacketReceived { - public IqParser(XmppConnectionService service) { + public IqParser(final XmppConnectionService service) { super(service); } - public void rosterItems(Account account, Element query) { - String version = query.getAttribute("ver"); + private void rosterItems(final Account account, final Element query) { + final String version = query.getAttribute("ver"); if (version != null) { account.getRoster().setVersion(version); } - for (Element item : query.getChildren()) { + for (final Element item : query.getChildren()) { if (item.getName().equals("item")) { final Jid jid = item.getAttributeAsJid("jid"); if (jid == null) { - break; + continue; } - String name = item.getAttribute("name"); - String subscription = item.getAttribute("subscription"); - Contact contact = account.getRoster().getContact(jid); + final String name = item.getAttribute("name"); + final String subscription = item.getAttribute("subscription"); + final Contact contact = account.getRoster().getContact(jid); if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { contact.setServerName(name); contact.parseGroupsFromElement(item); @@ -54,53 +58,104 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.updateRosterUi(); } - public String avatarData(IqPacket packet) { - Element pubsub = packet.findChild("pubsub", + public String avatarData(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); if (pubsub == null) { return null; } - Element items = pubsub.findChild("items"); + final Element items = pubsub.findChild("items"); if (items == null) { return null; } return super.avatarData(items); } + public static boolean fromServer(final Account account, final IqPacket packet) { + return packet.getFrom() == null + || packet.getFrom().equals(account.getServer()) + || packet.getFrom().equals(account.getJid().toBareJid()) + || packet.getFrom().equals(account.getJid()); + } + @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.hasChild("query", "jabber:iq:roster")) { - final Jid from = packet.getFrom(); - if ((from == null) || (from.equals(account.getJid().toBareJid()))) { - Element query = packet.findChild("query"); - this.rosterItems(account, query); + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.hasChild("query", Xmlns.ROSTER) && fromServer(account, packet)) { + final Element query = packet.findChild("query"); + // If this is in response to a query for the whole roster: + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.getRoster().markAllAsNotInRoster(); } - } else { - if (packet.getFrom() == null) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": received iq with invalid from "+packet.toString()); - return; - } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") - || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager() - .deliverIbbPacket(account, packet); - } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - IqPacket response = mXmppConnectionService.getIqGenerator() - .discoResponse(packet); - account.getXmppConnection().sendIqPacket(response, null); - } else if (packet.hasChild("ping", "urn:xmpp:ping")) { - IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); + this.rosterItems(account, query); + } else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) && + fromServer(account, packet)) { + // Block list or block push. + Log.d(Config.LOGTAG, "Received blocklist update from server"); + final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING); + final Element block = packet.findChild("block", Xmlns.BLOCKING); + final Collection<Element> items = blocklist != null ? blocklist.getChildren() : + (block != null ? block.getChildren() : null); + // If this is a response to a blocklist query, clear the block list and replace with the new one. + // Otherwise, just update the existing blocklist. + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.clearBlocklist(); + } + if (items != null) { + final Collection<Jid> jids = new ArrayList<>(items.size()); + // Create a collection of Jids from the packet + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = item.getAttributeAsJid("jid"); + if (jid != null) { + jids.add(jid); + } + } + } + account.getBlocklist().addAll(jids); + } + // Update the UI + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + } else if (packet.hasChild("unblock", Xmlns.BLOCKING) && + fromServer(account, packet) && packet.getType() == IqPacket.TYPE_SET) { + Log.d(Config.LOGTAG, "Received unblock update from server"); + final Collection<Element> items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren(); + if (items.size() == 0) { + // No children to unblock == unblock all + account.getBlocklist().clear(); } else { - if ((packet.getType() == IqPacket.TYPE_GET) - || (packet.getType() == IqPacket.TYPE_SET)) { - IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR); - Element error = response.addChild("error"); - error.setAttribute("type", "cancel"); - error.addChild("feature-not-implemented", - "urn:ietf:params:xml:ns:xmpp-stanzas"); - account.getXmppConnection().sendIqPacket(response, null); + final Collection<Jid> jids = new ArrayList<>(items.size()); + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = item.getAttributeAsJid("jid"); + if (jid != null) { + jids.add(jid); + } + } } + account.getBlocklist().removeAll(jids); } + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") + || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { + mXmppConnectionService.getJingleConnectionManager() + .deliverIbbPacket(account, packet); + } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { + final IqPacket response = mXmppConnectionService.getIqGenerator() + .discoResponse(packet); + account.getXmppConnection().sendIqPacket(response, null); + } else if (packet.hasChild("ping", "urn:xmpp:ping")) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE_RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } else { + if ((packet.getType() == IqPacket.TYPE_GET) + || (packet.getType() == IqPacket.TYPE_SET)) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE_ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("feature-not-implemented", + "urn:ietf:params:xml:ns:xmpp-stanzas"); + account.getXmppConnection().sendIqPacket(response, null); + } } } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 782675da..44cda261 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -10,11 +10,11 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -60,12 +60,12 @@ public class MessageParser extends AbstractParser implements } private Message parseOtrChat(MessagePacket packet, Account account) { - boolean properlyAddressed = (!packet.getTo().isBareJid()) - || (account.countPresences() == 1); - final Jid from = packet.getFrom(); - if (from == null) { + final Jid to = packet.getTo(); + final Jid from = packet.getFrom(); + if (to == null || from == null) { return null; } + boolean properlyAddressed = !to.isBareJid() || account.countPresences() == 1; Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, from.toBareJid(), false); String presence; @@ -124,10 +124,6 @@ public class MessageParser extends AbstractParser implements finishedMessage.setCounterpart(from); return finishedMessage; } catch (Exception e) { - String receivedId = packet.getId(); - if (receivedId != null) { - mXmppConnectionService.replyWithNotAcceptable(account, packet); - } conversation.resetOtrSession(); return null; } @@ -230,7 +226,7 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.getConversations(), account, to.toBareJid()); if (conversation != null) { - mXmppConnectionService.markRead(conversation, false); + mXmppConnectionService.markRead(conversation); } } } @@ -277,6 +273,69 @@ public class MessageParser extends AbstractParser implements return finishedMessage; } + private Message parseMamMessage(MessagePacket packet, final Account account) { + final Element result = packet.findChild("result","urn:xmpp:mam:0"); + if (result == null ) { + return null; + } + final MessageArchiveService.Query query = this.mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid")); + if (query!=null) { + query.incrementTotalCount(); + } + final Element forwarded = result.findChild("forwarded","urn:xmpp:forward:0"); + if (forwarded == null) { + return null; + } + final Element message = forwarded.findChild("message"); + if (message == null) { + return null; + } + final Element body = message.findChild("body"); + if (body == null || message.hasChild("private","urn:xmpp:carbons:2") || message.hasChild("no-copy","urn:xmpp:hints")) { + return null; + } + int encryption; + String content = getPgpBody(message); + if (content != null) { + encryption = Message.ENCRYPTION_PGP; + } else { + encryption = Message.ENCRYPTION_NONE; + content = body.getContent(); + } + if (content == null) { + return null; + } + final long timestamp = getTimestamp(forwarded); + final Jid to = message.getAttributeAsJid("to"); + final Jid from = message.getAttributeAsJid("from"); + Jid counterpart; + int status; + Conversation conversation; + if (from!=null && to != null && from.toBareJid().equals(account.getJid().toBareJid())) { + status = Message.STATUS_SEND; + conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false,query); + counterpart = to; + } else if (from !=null && to != null) { + status = Message.STATUS_RECEIVED; + conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false,query); + counterpart = from; + } else { + return null; + } + Message finishedMessage = new Message(conversation,content,encryption,status); + finishedMessage.setTime(timestamp); + finishedMessage.setCounterpart(counterpart); + finishedMessage.setRemoteMsgId(message.getAttribute("id")); + finishedMessage.setServerMsgId(result.getAttribute("id")); + if (conversation.hasDuplicateMessage(finishedMessage)) { + return null; + } + if (query!=null) { + query.incrementMessageCount(); + } + return finishedMessage; + } + private void parseError(final MessagePacket packet, final Account account) { final Jid from = packet.getFrom(); mXmppConnectionService.markMessage(account, from.toBareJid(), @@ -444,12 +503,22 @@ public class MessageParser extends AbstractParser implements if (message != null) { if (message.getStatus() == Message.STATUS_SEND) { account.activateGracePeriod(); - mXmppConnectionService.markRead( - message.getConversation(), false); + mXmppConnectionService.markRead(message.getConversation()); } else { message.markUnread(); } } + } else if (packet.hasChild("result","urn:xmpp:mam:0")) { + message = parseMamMessage(packet, account); + if (message != null) { + Conversation conversation = message.getConversation(); + conversation.add(message); + mXmppConnectionService.databaseBackend.createMessage(message); + } + return; + } else if (packet.hasChild("fin","urn:xmpp:mam:0")) { + Element fin = packet.findChild("fin","urn:xmpp:mam:0"); + mXmppConnectionService.getMessageArchiveService().processFin(fin); } else { parseNonMessage(packet, account); } @@ -459,8 +528,7 @@ public class MessageParser extends AbstractParser implements if (message.getStatus() == Message.STATUS_RECEIVED) { message.markUnread(); } else { - mXmppConnectionService.markRead(message.getConversation(), - false); + mXmppConnectionService.markRead(message.getConversation()); account.activateGracePeriod(); } } @@ -491,12 +559,16 @@ public class MessageParser extends AbstractParser implements } Conversation conversation = message.getConversation(); conversation.add(message); + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { + mXmppConnectionService.updateConversation(conversation); + } + } if (message.getStatus() == Message.STATUS_RECEIVED && conversation.getOtrSession() != null && !conversation.getOtrSession().getSessionID().getUserID() .equals(message.getCounterpart().getResourcepart())) { - Log.d(Config.LOGTAG, "ending because of reasons"); conversation.endOtrIfNeeded(); } @@ -509,7 +581,7 @@ public class MessageParser extends AbstractParser implements if (message.trusted() && message.bodyContainsDownloadable()) { this.mXmppConnectionService.getHttpConnectionManager() .createNewConnection(message); - } else { + } else if (!message.isRead()) { mXmppConnectionService.getNotificationService().push(message); } mXmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 43c8fa8d..accb56ea 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -45,69 +45,56 @@ public class PresenceParser extends AbstractParser implements } final Jid from = packet.getFrom(); String type = packet.getAttribute("type"); - if (from.toBareJid().equals(account.getJid().toBareJid())) { + Contact contact = account.getRoster().getContact(packet.getFrom()); + if (type == null) { + String presence; if (!from.isBareJid()) { - if (type == null) { - account.updatePresence(from.getResourcepart(), - Presences.parseShow(packet.findChild("show"))); - } else if (type.equals("unavailable")) { - account.removePresence(from.getResourcepart()); - account.deactivateGracePeriod(); - } + presence = from.getResourcepart(); + } else { + presence = ""; } - } else { - Contact contact = account.getRoster().getContact(packet.getFrom()); - if (type == null) { - String presence; - if (!from.isBareJid()) { - presence = from.getResourcepart(); - } else { - presence = ""; - } - int sizeBefore = contact.getPresences().size(); - contact.updatePresence(presence, - Presences.parseShow(packet.findChild("show"))); - PgpEngine pgp = mXmppConnectionService.getPgpEngine(); - if (pgp != null) { - Element x = packet.findChild("x", "jabber:x:signed"); - if (x != null) { - Element status = packet.findChild("status"); - String msg; - if (status != null) { - msg = status.getContent(); - } else { - msg = ""; - } - contact.setPgpKeyId(pgp.fetchKeyId(account, msg, - x.getContent())); + int sizeBefore = contact.getPresences().size(); + contact.updatePresence(presence, + Presences.parseShow(packet.findChild("show"))); + PgpEngine pgp = mXmppConnectionService.getPgpEngine(); + if (pgp != null) { + Element x = packet.findChild("x", "jabber:x:signed"); + if (x != null) { + Element status = packet.findChild("status"); + String msg; + if (status != null) { + msg = status.getContent(); + } else { + msg = ""; } + contact.setPgpKeyId(pgp.fetchKeyId(account, msg, + x.getContent())); } - boolean online = sizeBefore < contact.getPresences().size(); - updateLastseen(packet, account, true); - mXmppConnectionService.onContactStatusChanged - .onContactStatusChanged(contact, online); - } else if (type.equals("unavailable")) { - if (from.isBareJid()) { - contact.clearPresences(); - } else { - contact.removePresence(from.getResourcepart()); - } - mXmppConnectionService.onContactStatusChanged - .onContactStatusChanged(contact, false); - } else if (type.equals("subscribe")) { - if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - mXmppConnectionService.sendPresencePacket(account, - mPresenceGenerator.sendPresenceUpdatesTo(contact)); - } else { - contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); - } } - Element nick = packet.findChild("nick", - "http://jabber.org/protocol/nick"); - if (nick != null) { - contact.setPresenceName(nick.getContent()); + boolean online = sizeBefore < contact.getPresences().size(); + updateLastseen(packet, account, false); + mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online); + } else if (type.equals("unavailable")) { + if (from.isBareJid()) { + contact.clearPresences(); + } else { + contact.removePresence(from.getResourcepart()); + } + mXmppConnectionService.onContactStatusChanged + .onContactStatusChanged(contact, false); + } else if (type.equals("subscribe")) { + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { + mXmppConnectionService.sendPresencePacket(account, + mPresenceGenerator.sendPresenceUpdatesTo(contact)); + } else { + contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); } } + Element nick = packet.findChild("nick", + "http://jabber.org/protocol/nick"); + if (nick != null) { + contact.setPresenceName(nick.getContent()); + } mXmppConnectionService.updateRosterUi(); } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 55fcff2e..5fa61491 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -22,7 +22,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 11; + private static final int DATABASE_VERSION = 13; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -65,6 +65,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, " + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + Message.RELATIVE_FILE_PATH + " TEXT, " + + Message.SERVER_MSG_ID + " TEXT, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -121,6 +122,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("delete from "+Contact.TABLENAME); db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); } + if (oldVersion < 12 && newVersion >= 12) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.SERVER_MSG_ID + " TEXT"); + } + if (oldVersion < 13 && newVersion >= 13) { + db.execSQL("delete from "+Contact.TABLENAME); + db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -223,9 +232,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { return conversation; } - public void updateConversation(Conversation conversation) { - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { conversation.getUuid() }; + public void updateConversation(final Conversation conversation) { + final SQLiteDatabase db = this.getWritableDatabase(); + final String[] args = { conversation.getUuid() }; db.update(Conversation.TABLENAME, conversation.getContentValues(), Conversation.UUID + "=?", args); } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java new file mode 100644 index 00000000..82111243 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -0,0 +1,366 @@ +package eu.siacs.conversations.services; + +import android.util.Log; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.generator.AbstractGenerator; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { + + private final XmppConnectionService mXmppConnectionService; + + private final HashSet<Query> queries = new HashSet<Query>(); + private final ArrayList<Query> pendingQueries = new ArrayList<Query>(); + + public enum PagingOrder { + NORMAL, + REVERSE + }; + + public MessageArchiveService(final XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public void catchup(final Account account) { + long startCatchup = getLastMessageTransmitted(account); + long endCatchup = account.getXmppConnection().getLastSessionEstablished(); + if (startCatchup == 0) { + return; + } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { + startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; + List<Conversation> conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { + this.query(conversation,startCatchup); + } + } + } + final Query query = new Query(account, startCatchup, endCatchup); + this.queries.add(query); + this.execute(query); + } + + private long getLastMessageTransmitted(final Account account) { + long timestamp = 0; + for(final Conversation conversation : mXmppConnectionService.getConversations()) { + if (conversation.getAccount() == account) { + long tmp = conversation.getLastMessageTransmitted(); + if (tmp > timestamp) { + timestamp = tmp; + } + } + } + return timestamp; + } + + public Query query(final Conversation conversation) { + return query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } + + public Query query(final Conversation conversation, long end) { + return this.query(conversation,conversation.getLastMessageTransmitted(),end); + } + + public Query query(Conversation conversation, long start, long end) { + synchronized (this.queries) { + if (start > end) { + return null; + } + final Query query = new Query(conversation, start, end,PagingOrder.REVERSE); + this.queries.add(query); + this.execute(query); + return query; + } + } + + public void executePendingQueries(final Account account) { + List<Query> pending = new ArrayList<>(); + synchronized(this.pendingQueries) { + for(Iterator<Query> iterator = this.pendingQueries.iterator(); iterator.hasNext();) { + Query query = iterator.next(); + if (query.getAccount() == account) { + pending.add(query); + iterator.remove(); + } + } + } + for(Query query : pending) { + this.execute(query); + } + } + + private void execute(final Query query) { + final Account account= query.getAccount(); + if (account.getStatus() == Account.State.ONLINE) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_ERROR) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); + finalizeQuery(query); + } + } + }); + } else { + synchronized (this.pendingQueries) { + this.pendingQueries.add(query); + } + } + } + + private void finalizeQuery(Query query) { + synchronized (this.queries) { + this.queries.remove(query); + } + final Conversation conversation = query.getConversation(); + if (conversation != null) { + conversation.sort(); + if (conversation.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(conversation); + } + conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); + if (query.hasCallback()) { + query.callback(); + } else { + this.mXmppConnectionService.updateConversationUi(); + } + } else { + for(Conversation tmp : this.mXmppConnectionService.getConversations()) { + if (tmp.getAccount() == query.getAccount()) { + tmp.sort(); + if (tmp.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(tmp); + } + } + } + } + } + + public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { + synchronized (this.queries) { + for(Query query : queries) { + if (query.conversation == conversation) { + if (!query.hasCallback() && callback != null) { + query.setCallback(callback); + } + return true; + } + } + return false; + } + } + + public void processFin(Element fin) { + if (fin == null) { + return; + } + Query query = findQuery(fin.getAttribute("queryid")); + if (query == null) { + return; + } + boolean complete = fin.getAttributeAsBoolean("complete"); + Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); + Element last = set == null ? null : set.findChild("last"); + Element first = set == null ? null : set.findChild("first"); + Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; + boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + if (complete || relevant == null || abort) { + this.finalizeQuery(query); + Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": finished mam after "+query.getTotalCount()+" messages"); + } else { + final Query nextQuery; + if (query.getPagingOrder() == PagingOrder.NORMAL) { + nextQuery = query.next(last == null ? null : last.getContent()); + } else { + nextQuery = query.prev(first == null ? null : first.getContent()); + } + this.execute(nextQuery); + this.finalizeQuery(query); + synchronized (this.queries) { + this.queries.remove(query); + this.queries.add(nextQuery); + } + } + } + + public Query findQuery(String id) { + if (id == null) { + return null; + } + synchronized (this.queries) { + for(Query query : this.queries) { + if (query.getQueryId().equals(id)) { + return query; + } + } + return null; + } + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + this.catchup(account); + } + } + + public class Query { + private int totalCount = 0; + private int messageCount = 0; + private long start; + private long end; + private Jid with = null; + private String queryId; + private String reference = null; + private Account account; + private Conversation conversation; + private PagingOrder pagingOrder = PagingOrder.NORMAL; + private XmppConnectionService.OnMoreMessagesLoaded callback = null; + + + public Query(Conversation conversation, long start, long end) { + this(conversation.getAccount(), start, end); + this.conversation = conversation; + this.with = conversation.getJid().toBareJid(); + } + + public Query(Conversation conversation, long start, long end, PagingOrder order) { + this(conversation,start,end); + this.pagingOrder = order; + } + + public Query(Account account, long start, long end) { + this.account = account; + this.start = start; + this.end = end; + this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + } + + private Query page(String reference) { + Query query = new Query(this.account,this.start,this.end); + query.reference = reference; + query.conversation = conversation; + query.with = with; + query.totalCount = totalCount; + query.callback = callback; + return query; + } + + public Query next(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.NORMAL; + return query; + } + + public Query prev(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.REVERSE; + return query; + } + + public String getReference() { + return reference; + } + + public PagingOrder getPagingOrder() { + return this.pagingOrder; + } + + public String getQueryId() { + return queryId; + } + + public Jid getWith() { + return with; + } + + public long getStart() { + return start; + } + + public void setCallback(XmppConnectionService.OnMoreMessagesLoaded callback) { + this.callback = callback; + } + + public void callback() { + if (this.callback != null) { + this.callback.onMoreMessagesLoaded(messageCount,conversation); + if (messageCount == 0) { + this.callback.informUser(R.string.no_more_history_on_server); + } + } + } + + public long getEnd() { + return end; + } + + public Conversation getConversation() { + return conversation; + } + + public Account getAccount() { + return this.account; + } + + public void incrementTotalCount() { + this.totalCount++; + } + + public void incrementMessageCount() { + this.messageCount++; + } + + public int getTotalCount() { + return this.totalCount; + } + + public int getMessageCount() { + return this.messageCount; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("with="); + if (this.with==null) { + builder.append("*"); + } else { + builder.append(with.toString()); + } + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + builder.append(", end="); + builder.append(AbstractGenerator.getTimestamp(this.end)); + if (this.reference!=null) { + if (this.pagingOrder == PagingOrder.NORMAL) { + builder.append(", after="); + } else { + builder.append(", before="); + } + builder.append(this.reference); + } + return builder.toString(); + } + + public boolean hasCallback() { + return this.callback != null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index f649f9d4..a30cf2f1 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -16,9 +16,11 @@ import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.TaskStackBuilder; import android.text.Html; import android.util.DisplayMetrics; +import android.util.Log; import java.io.FileNotFoundException; import java.util.ArrayList; +import java.util.Calendar; import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Matcher; @@ -33,16 +35,17 @@ import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.ui.TimePreference; public class NotificationService { private XmppConnectionService mXmppConnectionService; - private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>(); + private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>(); - public static int NOTIFICATION_ID = 0x2342; - public static int FOREGROUND_NOTIFICATION_ID = 0x8899; - public static int ERROR_NOTIFICATION_ID = 0x5678; + public static final int NOTIFICATION_ID = 0x2342; + public static final int FOREGROUND_NOTIFICATION_ID = 0x8899; + public static final int ERROR_NOTIFICATION_ID = 0x5678; private Conversation mOpenConversation; private boolean mIsInForeground; @@ -52,46 +55,61 @@ public class NotificationService { this.mXmppConnectionService = service; } - public boolean notify(Message message) { + public boolean notify(final Message message) { return (message.getStatus() == Message.STATUS_RECEIVED) - && notificationsEnabled() - && !message.getConversation().isMuted() - && (message.getConversation().getMode() == Conversation.MODE_SINGLE + && notificationsEnabled() + && !message.getConversation().isMuted() + && (message.getConversation().getMode() == Conversation.MODE_SINGLE || conferenceNotificationsEnabled() || wasHighlightedOrPrivate(message) - ); + ); } public boolean notificationsEnabled() { return mXmppConnectionService.getPreferences().getBoolean("show_notification", true); } + public boolean isQuietHours() { + if (!mXmppConnectionService.getPreferences().getBoolean("enable_quiet_hours", false)) { + return false; + } + final long startTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; + final long endTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; + final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY; + + if (endTime < startTime) { + return nowTime > startTime || nowTime < endTime; + } else { + return nowTime > startTime && nowTime < endTime; + } + } + public boolean conferenceNotificationsEnabled() { return mXmppConnectionService.getPreferences().getBoolean("always_notify_in_conference", false); } - public void push(Message message) { + public void push(final Message message) { if (!notify(message)) { return; } - PowerManager pm = (PowerManager) mXmppConnectionService - .getSystemService(Context.POWER_SERVICE); - boolean isScreenOn = pm.isScreenOn(); + final PowerManager pm = (PowerManager) mXmppConnectionService + .getSystemService(Context.POWER_SERVICE); + final boolean isScreenOn = pm.isScreenOn(); if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; - } + } synchronized (notifications) { - String conversationUuid = message.getConversationUuid(); + final String conversationUuid = message.getConversationUuid(); if (notifications.containsKey(conversationUuid)) { notifications.get(conversationUuid).add(message); } else { - ArrayList<Message> mList = new ArrayList<Message>(); + final ArrayList<Message> mList = new ArrayList<>(); mList.add(message); notifications.put(conversationUuid, mList); } - Account account = message.getConversation().getAccount(); + final Account account = message.getConversation().getAccount(); updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) && !account.inGracePeriod() && !this.inMiniGracePeriod(account)); @@ -106,21 +124,20 @@ public class NotificationService { } } - public void clear(Conversation conversation) { + public void clear(final Conversation conversation) { synchronized (notifications) { notifications.remove(conversation.getUuid()); updateNotification(false); } } - private void updateNotification(boolean notify) { - NotificationManager notificationManager = (NotificationManager) mXmppConnectionService - .getSystemService(Context.NOTIFICATION_SERVICE); - SharedPreferences preferences = mXmppConnectionService.getPreferences(); + private void updateNotification(final boolean notify) { + final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService + .getSystemService(Context.NOTIFICATION_SERVICE); + final SharedPreferences preferences = mXmppConnectionService.getPreferences(); - String ringtone = preferences.getString("notification_ringtone", null); - boolean vibrate = preferences.getBoolean("vibrate_on_notification", - true); + final String ringtone = preferences.getString("notification_ringtone", null); + final boolean vibrate = preferences.getBoolean("vibrate_on_notification", true); if (notifications.size() == 0) { notificationManager.cancel(NOTIFICATION_ID); @@ -128,16 +145,16 @@ public class NotificationService { if (notify) { this.markLastNotification(); } - Builder mBuilder; + final Builder mBuilder; if (notifications.size() == 1) { mBuilder = buildSingleConversations(notify); } else { mBuilder = buildMultipleConversation(); } - if (notify) { + if (notify && !isQuietHours()) { if (vibrate) { - int dat = 70; - long[] pattern = {0, 3 * dat, dat, dat}; + final int dat = 70; + final long[] pattern = {0, 3 * dat, dat, dat}; mBuilder.setVibrate(pattern); } if (ringtone != null) { @@ -147,27 +164,27 @@ public class NotificationService { mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); mBuilder.setLights(0xffffffff, 2000, 4000); - Notification notification = mBuilder.build(); + final Notification notification = mBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notification); } } private Builder buildMultipleConversation() { - Builder mBuilder = new NotificationCompat.Builder( + final Builder mBuilder = new NotificationCompat.Builder( mXmppConnectionService); NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); style.setBigContentTitle(notifications.size() + " " + mXmppConnectionService .getString(R.string.unread_conversations)); - StringBuilder names = new StringBuilder(); + final StringBuilder names = new StringBuilder(); Conversation conversation = null; for (ArrayList<Message> messages : notifications.values()) { if (messages.size() > 0) { conversation = messages.get(0).getConversation(); String name = conversation.getName(); style.addLine(Html.fromHtml("<b>" + name + "</b> " - + getReadableBody(messages.get(0)))); + + getReadableBody(messages.get(0)))); names.append(name); names.append(", "); } @@ -183,46 +200,45 @@ public class NotificationService { mBuilder.setStyle(style); if (conversation != null) { mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); + .getUuid())); } return mBuilder; } - private Builder buildSingleConversations(boolean notify) { - Builder mBuilder = new NotificationCompat.Builder( + private Builder buildSingleConversations(final boolean notify) { + final Builder mBuilder = new NotificationCompat.Builder( mXmppConnectionService); - ArrayList<Message> messages = notifications.values().iterator().next(); + final ArrayList<Message> messages = notifications.values().iterator().next(); if (messages.size() >= 1) { - Conversation conversation = messages.get(0).getConversation(); + final Conversation conversation = messages.get(0).getConversation(); mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService() .get(conversation, getPixel(64))); mBuilder.setContentTitle(conversation.getName()); - Message message; + final Message message; if ((message = getImage(messages)) != null) { modifyForImage(mBuilder, message, messages, notify); } else { modifyForTextOnly(mBuilder, messages, notify); } mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); + .getUuid())); } return mBuilder; - } - private void modifyForImage(Builder builder, Message message, - ArrayList<Message> messages, boolean notify) { + private void modifyForImage(final Builder builder, final Message message, + final ArrayList<Message> messages, final boolean notify) { try { - Bitmap bitmap = mXmppConnectionService.getFileBackend() - .getThumbnail(message, getPixel(288), false); - ArrayList<Message> tmp = new ArrayList<Message>(); - for (Message msg : messages) { + final Bitmap bitmap = mXmppConnectionService.getFileBackend() + .getThumbnail(message, getPixel(288), false); + final ArrayList<Message> tmp = new ArrayList<>(); + for (final Message msg : messages) { if (msg.getType() == Message.TYPE_TEXT && msg.getDownloadable() == null) { tmp.add(msg); - } + } } - BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); + final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); bigPictureStyle.bigPicture(bitmap); if (tmp.size() > 0) { bigPictureStyle.setSummaryText(getMergedBodies(tmp)); @@ -231,13 +247,13 @@ public class NotificationService { builder.setContentText(mXmppConnectionService.getString(R.string.image_file)); } builder.setStyle(bigPictureStyle); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { modifyForTextOnly(builder, messages, notify); } } - private void modifyForTextOnly(Builder builder, - ArrayList<Message> messages, boolean notify) { + private void modifyForTextOnly(final Builder builder, + final ArrayList<Message> messages, final boolean notify) { builder.setStyle(new NotificationCompat.BigTextStyle() .bigText(getMergedBodies(messages))); builder.setContentText(getReadableBody(messages.get(0))); @@ -246,19 +262,19 @@ public class NotificationService { } } - private Message getImage(ArrayList<Message> messages) { - for (Message message : messages) { + private Message getImage(final ArrayList<Message> messages) { + for (final Message message : messages) { if (message.getType() == Message.TYPE_IMAGE && message.getDownloadable() == null && message.getEncryption() != Message.ENCRYPTION_PGP) { return message; - } + } } return null; } - private String getMergedBodies(ArrayList<Message> messages) { - StringBuilder text = new StringBuilder(); + private String getMergedBodies(final ArrayList<Message> messages) { + final StringBuilder text = new StringBuilder(); for (int i = 0; i < messages.size(); ++i) { text.append(getReadableBody(messages.get(i))); if (i != messages.size() - 1) { @@ -268,10 +284,10 @@ public class NotificationService { return text.toString(); } - private String getReadableBody(Message message) { + private String getReadableBody(final Message message) { if (message.getDownloadable() != null && (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message - .getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) { + .getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) { if (message.getType() == Message.TYPE_FILE) { return mXmppConnectionService.getString(R.string.file_offered_for_download); } else { @@ -283,27 +299,27 @@ public class NotificationService { R.string.encrypted_message_received).toString(); } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { return mXmppConnectionService.getText(R.string.decryption_failed) - .toString(); + .toString(); } else if (message.getType() == Message.TYPE_FILE) { DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message); return mXmppConnectionService.getString(R.string.file,file.getMimeType()); } else if (message.getType() == Message.TYPE_IMAGE) { return mXmppConnectionService.getText(R.string.image_file) - .toString(); + .toString(); } else { return message.getBody().trim(); } } - private PendingIntent createContentIntent(String conversationUuid) { - TaskStackBuilder stackBuilder = TaskStackBuilder - .create(mXmppConnectionService); + private PendingIntent createContentIntent(final String conversationUuid) { + final TaskStackBuilder stackBuilder = TaskStackBuilder + .create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); - Intent viewConversationIntent = new Intent(mXmppConnectionService, + final Intent viewConversationIntent = new Intent(mXmppConnectionService, ConversationActivity.class); viewConversationIntent.setAction(Intent.ACTION_VIEW); - if (conversationUuid!=null) { + if (conversationUuid != null) { viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, conversationUuid); viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); @@ -311,36 +327,34 @@ public class NotificationService { stackBuilder.addNextIntent(viewConversationIntent); - PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, - PendingIntent.FLAG_UPDATE_CURRENT); - return resultPendingIntent; + return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); } private PendingIntent createDeleteIntent() { - Intent intent = new Intent(mXmppConnectionService, + final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_CLEAR_NOTIFICATION); return PendingIntent.getService(mXmppConnectionService, 0, intent, 0); } private PendingIntent createDisableForeground() { - Intent intent = new Intent(mXmppConnectionService, + final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_DISABLE_FOREGROUND); return PendingIntent.getService(mXmppConnectionService, 0, intent, 0); } - private boolean wasHighlightedOrPrivate(Message message) { - String nick = message.getConversation().getMucOptions().getActualNick(); - Pattern highlight = generateNickHighlightPattern(nick); + private boolean wasHighlightedOrPrivate(final Message message) { + final String nick = message.getConversation().getMucOptions().getActualNick(); + final Pattern highlight = generateNickHighlightPattern(nick); if (message.getBody() == null || nick == null) { return false; } - Matcher m = highlight.matcher(message.getBody()); + final Matcher m = highlight.matcher(message.getBody()); return (m.find() || message.getType() == Message.TYPE_PRIVATE); } - private static Pattern generateNickHighlightPattern(String nick) { + private static Pattern generateNickHighlightPattern(final String nick) { // We expect a word boundary, i.e. space or start of string, followed by // the // nick (matched in case-insensitive manner), followed by optional @@ -350,17 +364,20 @@ public class NotificationService { Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); } - public void setOpenConversation(Conversation conversation) { + public void setOpenConversation(final Conversation conversation) { this.mOpenConversation = conversation; } - public void setIsInForeground(boolean foreground) { + public void setIsInForeground(final boolean foreground) { + if (foreground != this.mIsInForeground) { + Log.d(Config.LOGTAG,"setIsInForeground("+Boolean.toString(foreground)+")"); + } this.mIsInForeground = foreground; } - private int getPixel(int dp) { - DisplayMetrics metrics = mXmppConnectionService.getResources() - .getDisplayMetrics(); + private int getPixel(final int dp) { + final DisplayMetrics metrics = mXmppConnectionService.getResources() + .getDisplayMetrics(); return ((int) (dp * metrics.density)); } @@ -368,14 +385,14 @@ public class NotificationService { this.mLastNotification = SystemClock.elapsedRealtime(); } - private boolean inMiniGracePeriod(Account account) { - int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD - : Config.MINI_GRACE_PERIOD * 2; + private boolean inMiniGracePeriod(final Account account) { + final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD + : Config.MINI_GRACE_PERIOD * 2; return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace); } public Notification createForegroundNotification() { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); + final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export); mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service)); mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_disable)); @@ -386,14 +403,14 @@ public class NotificationService { } public void updateErrorNotification() { - NotificationManager mNotificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); - List<Account> errors = new ArrayList<>(); - for (Account account : mXmppConnectionService.getAccounts()) { + final NotificationManager mNotificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); + final List<Account> errors = new ArrayList<>(); + for (final Account account : mXmppConnectionService.getAccounts()) { if (account.hasErrorStatus()) { errors.add(account); } } - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); + final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); if (errors.size() == 0) { mNotificationManager.cancel(ERROR_NOTIFICATION_ID); return; @@ -410,13 +427,12 @@ public class NotificationService { TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); - Intent manageAccountsIntent = new Intent(mXmppConnectionService,ManageAccountActivity.class); + final Intent manageAccountsIntent = new Intent(mXmppConnectionService,ManageAccountActivity.class); stackBuilder.addNextIntent(manageAccountsIntent); - PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); + final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); - Notification notification = mBuilder.build(); - mNotificationManager.notify(ERROR_NOTIFICATION_ID, notification); + mNotificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 41a40224..6bdc55a1 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -32,20 +32,16 @@ import net.java.otr4j.session.SessionStatus; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; -import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Locale; -import java.util.TimeZone; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import de.duenndns.ssl.MemorizingTrustManager; @@ -53,11 +49,11 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Downloadable; -import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadablePlaceholder; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; @@ -78,12 +74,17 @@ import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnMessageAcknowledged; +import eu.siacs.conversations.xmpp.OnMessagePacketReceived; +import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.PacketReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; @@ -97,11 +98,12 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -public class XmppConnectionService extends Service { +public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener { + + public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; - public static String ACTION_CLEAR_NOTIFICATION = "clear_notification"; - private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; - public static String ACTION_DISABLE_FOREGROUND = "disable_foreground"; private ContentObserver contactObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { @@ -126,7 +128,7 @@ public class XmppConnectionService extends Service { conversation.resetOtrSession(); } if (online && (contact.getPresences().size() == 1)) { - sendUnsendMessages(conversation); + sendUnsentMessages(conversation); } } } @@ -135,18 +137,19 @@ public class XmppConnectionService extends Service { private MemorizingTrustManager mMemorizingTrustManager; private NotificationService mNotificationService = new NotificationService( this); - private MessageParser mMessageParser = new MessageParser(this); - private PresenceParser mPresenceParser = new PresenceParser(this); + private OnMessagePacketReceived mMessageParser = new MessageParser(this); + private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); private IqParser mIqParser = new IqParser(this); private MessageGenerator mMessageGenerator = new MessageGenerator(this); private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); private List<Account> accounts; - private final CopyOnWriteArrayList<Conversation> conversations = new CopyOnWriteArrayList<Conversation>(); + private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( this); private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private OnConversationUpdate mOnConversationUpdate = null; private Integer convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; @@ -165,12 +168,13 @@ public class XmppConnectionService extends Service { for (Conversation conversation : account.pendingConferenceJoins) { joinMuc(conversation); } + mMessageArchiveService.executePendingQueries(account); mJingleConnectionManager.cancelInTransmission(); List<Conversation> conversations = getConversations(); for (Conversation conversation : conversations) { if (conversation.getAccount() == account) { conversation.startOtrIfNeeded(); - sendUnsendMessages(conversation); + sendUnsentMessages(conversation); } } if (connection != null && connection.getFeatures().csi()) { @@ -209,13 +213,16 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); } }; + private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; + private OnUpdateBlocklist mOnUpdateBlocklist = null; + private int updateBlocklistListenerCount = 0; private int rosterChangedListenerCount = 0; private OnMucRosterUpdate mOnMucRosterUpdate = null; private int mucRosterChangedListenerCount = 0; private SecureRandom mRandom; - private FileObserver fileObserver = new FileObserver( + private final FileObserver fileObserver = new FileObserver( FileBackend.getConversationsImageDirectory()) { @Override @@ -225,7 +232,7 @@ public class XmppConnectionService extends Service { } } }; - private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { @Override public void onJinglePacketReceived(Account account, JinglePacket packet) { @@ -239,43 +246,41 @@ public class XmppConnectionService extends Service { private PendingIntent pendingPingIntent = null; private WakeLock wakeLock; private PowerManager pm; - private OnBindListener mOnBindListener = new OnBindListener() { + private final OnBindListener mOnBindListener = new OnBindListener() { @Override public void onBind(final Account account) { account.getRoster().clearPresences(); - account.clearPresences(); // self presences account.pendingConferenceJoins.clear(); account.pendingConferenceLeaves.clear(); fetchRosterFromServer(account); fetchBookmarks(account); - sendPresencePacket(account, - mPresenceGenerator.sendPresence(account)); + sendPresencePacket(account,mPresenceGenerator.sendPresence(account)); connectMultiModeConversations(account); updateConversationUi(); } }; - private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { @Override public void onMessageAcknowledged(Account account, String uuid) { - for (Conversation conversation : getConversations()) { + for (final Conversation conversation : getConversations()) { if (conversation.getAccount() == account) { - for (Message message : conversation.getMessages()) { - if ((message.getStatus() == Message.STATUS_UNSEND || message - .getStatus() == Message.STATUS_WAITING) - && message.getUuid().equals(uuid)) { - markMessage(message, Message.STATUS_SEND); - return; - } + Message message = conversation.findUnsentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, Message.STATUS_SEND); + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { + databaseBackend.updateConversation(conversation); + } } } } } }; private LruCache<String, Bitmap> mBitmapCache; - private IqGenerator mIqGenerator = new IqGenerator(this); + private final IqGenerator mIqGenerator = new IqGenerator(this); + private Thread mPhoneContactMergerThread; public PgpEngine getPgpEngine() { if (pgpServiceConnection.isBound()) { @@ -299,7 +304,9 @@ public class XmppConnectionService extends Service { return this.mAvatarService; } - public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback<Message> callback) { + public void attachFileToConversation(final Conversation conversation, + final Uri uri, + final UiCallback<Message> callback) { final Message message; if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", @@ -359,13 +366,13 @@ public class XmppConnectionService extends Service { @Override public void run() { try { - DownloadableFile file = getFileBackend().copyImageToPrivateStorage(message, uri); + getFileBackend().copyImageToPrivateStorage(message, uri); if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { getPgpEngine().encrypt(message, callback); } else { callback.success(message); } - } catch (FileBackend.FileCopyException e) { + } catch (final FileBackend.FileCopyException e) { callback.error(e.getResId(), message); } } @@ -384,7 +391,7 @@ public class XmppConnectionService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getAction() != null) { if (intent.getAction().equals(ACTION_MERGE_PHONE_CONTACTS)) { - mergePhoneContactsWithRoster(); + PhoneHelper.loadPhoneContacts(getApplicationContext(), new ArrayList<Bundle>(), this); return START_STICKY; } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { logoutAndSave(); @@ -479,11 +486,11 @@ public class XmppConnectionService extends Service { this.mMemorizingTrustManager = new MemorizingTrustManager( getApplicationContext()); - int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); - int cacheSize = maxMemory / 8; + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) { @Override - protected int sizeOf(String key, Bitmap bitmap) { + protected int sizeOf(final String key, final Bitmap bitmap) { return bitmap.getByteCount() / 1024; } }; @@ -491,12 +498,12 @@ public class XmppConnectionService extends Service { this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); - for (Account account : this.accounts) { + for (final Account account : this.accounts) { account.initOtrEngine(this); this.databaseBackend.readRoster(account.getRoster()); } initConversations(); - this.mergePhoneContactsWithRoster(); + PhoneHelper.loadPhoneContacts(getApplicationContext(),new ArrayList<Bundle>(), this); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); this.fileObserver.startWatching(); @@ -517,7 +524,7 @@ public class XmppConnectionService extends Service { } @Override - public void onTaskRemoved(Intent rootIntent) { + public void onTaskRemoved(final Intent rootIntent) { super.onTaskRemoved(rootIntent); if (!getPreferences().getBoolean("keep_foreground_service",false)) { this.logoutAndSave(); @@ -525,7 +532,7 @@ public class XmppConnectionService extends Service { } private void logoutAndSave() { - for (Account account : accounts) { + for (final Account account : accounts) { databaseBackend.writeRoster(account.getRoster()); if (account.getXmppConnection() != null) { disconnect(account, false); @@ -578,26 +585,26 @@ public class XmppConnectionService extends Service { } - public XmppConnection createConnection(Account account) { - SharedPreferences sharedPref = getPreferences(); + public XmppConnection createConnection(final Account account) { + final SharedPreferences sharedPref = getPreferences(); account.setResource(sharedPref.getString("resource", "mobile") .toLowerCase(Locale.getDefault())); - XmppConnection connection = new XmppConnection(account, this); + final XmppConnection connection = new XmppConnection(account, this); connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); connection.setOnPresencePacketReceivedListener(this.mPresenceParser); connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); - connection - .setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); return connection; } - public void sendMessage(Message message) { - Account account = message.getConversation().getAccount(); + public void sendMessage(final Message message) { + final Account account = message.getConversation().getAccount(); account.deactivateGracePeriod(); - Conversation conv = message.getConversation(); + final Conversation conv = message.getConversation(); MessagePacket packet = null; boolean saveInDb = true; boolean send = false; @@ -641,12 +648,22 @@ public class XmppConnectionService extends Service { } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { message.getConversation().endOtrIfNeeded(); - failWaitingOtrMessages(message.getConversation()); + message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message,Message.STATUS_SEND_FAILED); + } + }); packet = mMessageGenerator.generatePgpChat(message); send = true; } else { message.getConversation().endOtrIfNeeded(); - failWaitingOtrMessages(message.getConversation()); + message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message,Message.STATUS_SEND_FAILED); + } + }); packet = mMessageGenerator.generateChat(message); send = true; } @@ -689,16 +706,17 @@ public class XmppConnectionService extends Service { updateConversationUi(); } - private void sendUnsendMessages(Conversation conversation) { - for (int i = 0; i < conversation.getMessages().size(); ++i) { - int status = conversation.getMessages().get(i).getStatus(); - if (status == Message.STATUS_WAITING) { - resendMessage(conversation.getMessages().get(i)); + private void sendUnsentMessages(final Conversation conversation) { + conversation.findWaitingMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { + resendMessage(message); } - } + }); } - private void resendMessage(Message message) { + private void resendMessage(final Message message) { Account account = message.getConversation().getAccount(); MessagePacket packet = null; if (message.getEncryption() == Message.ENCRYPTION_OTR) { @@ -725,7 +743,7 @@ public class XmppConnectionService extends Service { } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { mJingleConnectionManager.createNewConnection(message); } - } catch (InvalidJidException e) { + } catch (final InvalidJidException ignored) { } } @@ -768,47 +786,35 @@ public class XmppConnectionService extends Service { } } - public void fetchRosterFromServer(Account account) { - IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); + public void fetchRosterFromServer(final Account account) { + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); if (!"".equals(account.getRosterVersion())) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster version " + account.getRosterVersion()); } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); } - iqPacket.query("jabber:iq:roster").setAttribute("ver", + iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion()); - account.getXmppConnection().sendIqPacket(iqPacket, - new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, - IqPacket packet) { - Element query = packet.findChild("query"); - if (query != null) { - account.getRoster().markAllAsNotInRoster(); - mIqParser.rosterItems(account, query); - } - } - }); + account.getXmppConnection().sendIqPacket(iqPacket, mIqParser); } - public void fetchBookmarks(Account account) { - IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); - Element query = iqPacket.query("jabber:iq:private"); + public void fetchBookmarks(final Account account) { + final IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); + final Element query = iqPacket.query("jabber:iq:private"); query.addChild("storage", "storage:bookmarks"); - OnIqPacketReceived callback = new OnIqPacketReceived() { + final PacketReceived callback = new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element query = packet.query(); - List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); - Element storage = query.findChild("storage", + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final Element query = packet.query(); + final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); + final Element storage = query.findChild("storage", "storage:bookmarks"); if (storage != null) { - for (Element item : storage.getChildren()) { + for (final Element item : storage.getChildren()) { if (item.getName().equals("conference")) { - Bookmark bookmark = Bookmark.parse(item, account); + final Bookmark bookmark = Bookmark.parse(item, account); bookmarks.add(bookmark); Conversation conversation = find(bookmark); if (conversation != null) { @@ -826,7 +832,6 @@ public class XmppConnectionService extends Service { } }; sendIqPacket(account, iqPacket, callback); - } public void pushBookmarks(Account account) { @@ -839,53 +844,56 @@ public class XmppConnectionService extends Service { sendIqPacket(account, iqPacket, null); } - private void mergePhoneContactsWithRoster() { - PhoneHelper.loadPhoneContacts(getApplicationContext(), - new OnPhoneContactsLoadedListener() { - @Override - public void onPhoneContactsLoaded(List<Bundle> phoneContacts) { - for (Account account : accounts) { - account.getRoster().clearSystemAccounts(); + public void onPhoneContactsLoaded(final List<Bundle> phoneContacts) { + if (mPhoneContactMergerThread != null) { + mPhoneContactMergerThread.interrupt(); + } + mPhoneContactMergerThread = new Thread(new Runnable() { + @Override + public void run() { + Log.d(Config.LOGTAG,"start merging phone contacts with roster"); + for (Account account : accounts) { + account.getRoster().clearSystemAccounts(); + for (Bundle phoneContact : phoneContacts) { + if (Thread.interrupted()) { + Log.d(Config.LOGTAG,"interrupted merging phone contacts"); + return; } - for (Bundle phoneContact : phoneContacts) { - for (Account account : accounts) { - Jid jid; - try { - jid = Jid.fromString(phoneContact.getString("jid")); - } catch (final InvalidJidException e) { - // TODO: Warn if contact import fails here? - break; - } - final Contact contact = account.getRoster() - .getContact(jid); - String systemAccount = phoneContact - .getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); - contact.setSystemAccount(systemAccount); - contact.setPhotoUri(phoneContact - .getString("photouri")); - contact.setSystemName(phoneContact - .getString("displayname")); - getAvatarService().clear(contact); - } + Jid jid; + try { + jid = Jid.fromString(phoneContact.getString("jid")); + } catch (final InvalidJidException e) { + continue; } + final Contact contact = account.getRoster().getContact(jid); + String systemAccount = phoneContact.getInt("phoneid") + + "#" + + phoneContact.getString("lookup"); + contact.setSystemAccount(systemAccount); + contact.setPhotoUri(phoneContact.getString("photouri")); + getAvatarService().clear(contact); + contact.setSystemName(phoneContact.getString("displayname")); } - }); + } + Log.d(Config.LOGTAG,"finished merging phone contacts"); + updateAccountUi(); + } + }); + mPhoneContactMergerThread.start(); } private void initConversations() { synchronized (this.conversations) { - Hashtable<String, Account> accountLookupTable = new Hashtable<>(); + final Map<String, Account> accountLookupTable = new Hashtable<>(); for (Account account : this.accounts) { accountLookupTable.put(account.getUuid(), account); } this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE)); - for (Conversation conv : this.conversations) { - Account account = accountLookupTable.get(conv.getAccountUuid()); - conv.setAccount(account); - conv.addAll(0, databaseBackend.getMessages(conv, 50)); - checkDeletedFiles(conv); + for (Conversation conversation : this.conversations) { + Account account = accountLookupTable.get(conversation.getAccountUuid()); + conversation.setAccount(account); + conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); + checkDeletedFiles(conversation); } } } @@ -895,28 +903,26 @@ public class XmppConnectionService extends Service { } private void checkDeletedFiles(Conversation conversation) { - for (Message message : conversation.getMessages()) { - if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) - && message.getEncryption() != Message.ENCRYPTION_PGP) { + conversation.findMessagesWithFiles(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { if (!getFileBackend().isFileAvailable(message)) { message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); } - } - } + } + }); } private void markFileDeleted(String uuid) { for (Conversation conversation : getConversations()) { - for (Message message : conversation.getMessages()) { - if (message.getType() == Message.TYPE_IMAGE - && message.getEncryption() != Message.ENCRYPTION_PGP - && message.getUuid().equals(uuid)) { - if (!getFileBackend().isFileAvailable(message)) { - message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); - updateConversationUi(); - } - return; - } + Message message = conversation.findMessageWithFileAndUuid(uuid); + if (message != null) { + if (!getFileBackend().isFileAvailable(message)) { + message.setDownloadable(new DownloadablePlaceholder(Downloadable.STATUS_DELETED)); + updateConversationUi(); + } + return; } } } @@ -952,22 +958,43 @@ public class XmppConnectionService extends Service { }); } - public int loadMoreMessages(Conversation conversation, long timestamp) { - List<Message> messages = databaseBackend.getMessages(conversation, 50, - timestamp); - for (Message message : messages) { - message.setConversation(conversation); + public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { + Log.d(Config.LOGTAG,"load more messages for "+conversation.getName() + " prior to "+MessageGenerator.getTimestamp(timestamp)); + if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) { + return; } - conversation.addAll(0, messages); - return messages.size(); + new Thread(new Runnable() { + @Override + public void run() { + final Account account = conversation.getAccount(); + List<Message> messages = databaseBackend.getMessages(conversation, 50,timestamp); + if (messages.size() > 0) { + conversation.addAll(0, messages); + callback.onMoreMessagesLoaded(messages.size(), conversation); + } else if (conversation.hasMessagesLeftOnServer() + && account.isOnlineAndConnected() + && account.getXmppConnection().getFeatures().mam()) { + MessageArchiveService.Query query = getMessageArchiveService().query(conversation,0,timestamp - 1); + if (query != null) { + query.setCallback(callback); + } + callback.informUser(R.string.fetching_history_from_server); + } + } + }).start(); + } + + public interface OnMoreMessagesLoaded { + public void onMoreMessagesLoaded(int count,Conversation conversation); + public void informUser(int r); } public List<Account> getAccounts() { return this.accounts; } - public Conversation find(List<Conversation> haystack, Contact contact) { - for (Conversation conversation : haystack) { + public Conversation find(final Iterable<Conversation> haystack, final Contact contact) { + for (final Conversation conversation : haystack) { if (conversation.getContact() == contact) { return conversation; } @@ -975,23 +1002,24 @@ public class XmppConnectionService extends Service { return null; } - public Conversation find(final List<Conversation> haystack, - final Account account, - final Jid jid) { + public Conversation find(final Iterable<Conversation> haystack, final Account account, final Jid jid) { if (jid == null ) { return null; } - for (Conversation conversation : haystack) { + for (final Conversation conversation : haystack) { if ((account == null || conversation.getAccount() == account) - && (conversation.getContactJid().toBareJid().equals(jid.toBareJid()))) { + && (conversation.getJid().toBareJid().equals(jid.toBareJid()))) { return conversation; } } return null; } - public Conversation findOrCreateConversation(final Account account, final Jid jid, - final boolean muc) { + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc) { + return this.findOrCreateConversation(account,jid,muc,null); + } + + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc, final MessageArchiveService.Query query) { synchronized (this.conversations) { Conversation conversation = find(account, jid); if (conversation != null) { @@ -1006,7 +1034,7 @@ public class XmppConnectionService extends Service { } else { conversation.setMode(Conversation.MODE_SINGLE); } - conversation.addAll(0, databaseBackend.getMessages(conversation, 50)); + conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); this.databaseBackend.updateConversation(conversation); } else { String conversationName; @@ -1025,6 +1053,13 @@ public class XmppConnectionService extends Service { } this.databaseBackend.createConversation(conversation); } + if (query == null) { + this.mMessageArchiveService.query(conversation); + } else { + if (query.getConversation() == null) { + this.mMessageArchiveService.query(conversation,query.getStart()); + } + } this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1051,13 +1086,7 @@ public class XmppConnectionService extends Service { } } - public void clearConversationHistory(Conversation conversation) { - this.databaseBackend.deleteMessagesInConversation(conversation); - conversation.getMessages().clear(); - updateConversationUi(); - } - - public void createAccount(Account account) { + public void createAccount(final Account account) { account.initOtrEngine(this); databaseBackend.createAccount(account); this.accounts.add(account); @@ -1065,7 +1094,7 @@ public class XmppConnectionService extends Service { updateAccountUi(); } - public void updateAccount(Account account) { + public void updateAccount(final Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); reconnectAccount(account, false); @@ -1073,9 +1102,30 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); } - public void deleteAccount(Account account) { + public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) { + final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); + sendIqPacket(account, iq, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.setPassword(newPassword); + databaseBackend.updateAccount(account); + callback.onPasswordChangeSucceeded(); + } else { + callback.onPasswordChangeFailed(); + } + } + }); + } + + public interface OnAccountPasswordChanged { + public void onPasswordChangeSucceeded(); + public void onPasswordChangeFailed(); + } + + public void deleteAccount(final Account account) { synchronized (this.conversations) { - for (Conversation conversation : conversations) { + for (final Conversation conversation : conversations) { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation); @@ -1147,7 +1197,7 @@ public class XmppConnectionService extends Service { } } - public void setOnRosterUpdateListener(OnRosterUpdate listener) { + public void setOnRosterUpdateListener(final OnRosterUpdate listener) { synchronized (this) { if (checkListeners()) { switchToForeground(); @@ -1172,6 +1222,31 @@ public class XmppConnectionService extends Service { } } + public void setOnUpdateBlocklistListener(final OnUpdateBlocklist listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnUpdateBlocklist = listener; + if (this.updateBlocklistListenerCount < 2) { + this.updateBlocklistListenerCount++; + } + } + } + + public void removeOnUpdateBlocklistListener() { + synchronized (this) { + this.updateBlocklistListenerCount--; + if (this.updateBlocklistListenerCount <= 0) { + this.updateBlocklistListenerCount = 0; + this.mOnUpdateBlocklist = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1199,7 +1274,9 @@ public class XmppConnectionService extends Service { private boolean checkListeners() { return (this.mOnAccountUpdate == null - && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null); + && this.mOnConversationUpdate == null + && this.mOnRosterUpdate == null + && this.mOnUpdateBlocklist == null); } private void switchToForeground() { @@ -1227,11 +1304,12 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, "app switched into background"); } - public void connectMultiModeConversations(Account account) { + private void connectMultiModeConversations(Account account) { List<Conversation> conversations = getConversations(); for (Conversation conversation : conversations) { if ((conversation.getMode() == Conversation.MODE_MULTI) && (conversation.getAccount() == account)) { + conversation.resetMucOptions(); joinMuc(conversation); } } @@ -1251,29 +1329,18 @@ public class XmppConnectionService extends Service { PresencePacket packet = new PresencePacket(); packet.setFrom(conversation.getAccount().getJid()); packet.setTo(joinJid); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + Element x = packet.addChild("x","http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { - Element password = x.addChild("password"); - password.setContent(conversation.getMucOptions().getPassword()); + x.addChild("password").setContent(conversation.getMucOptions().getPassword()); } + x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } - if (conversation.getMessages().size() != 0) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = new Date(conversation.getLatestMessage() - .getTimeSent() + 1000); - x.addChild("history").setAttribute("since", - mDateFormat.format(date)); - } - packet.addChild(x); sendPresencePacket(account, packet); - if (!joinJid.equals(conversation.getContactJid())) { + if (!joinJid.equals(conversation.getJid())) { conversation.setContactJid(joinJid); databaseBackend.updateConversation(conversation); } @@ -1315,7 +1382,7 @@ public class XmppConnectionService extends Service { @Override public void onFailure() { - callback.error(R.string.nick_in_use,conversation); + callback.error(R.string.nick_in_use, conversation); } }); @@ -1349,14 +1416,14 @@ public class XmppConnectionService extends Service { account.pendingConferenceLeaves.remove(conversation); if (account.getStatus() == Account.State.ONLINE) { PresencePacket packet = new PresencePacket(); - packet.setTo(conversation.getContactJid()); + packet.setTo(conversation.getJid()); packet.setFrom(conversation.getAccount().getJid()); packet.setAttribute("type", "unavailable"); sendPresencePacket(conversation.getAccount(), packet); conversation.getMucOptions().setOffline(); conversation.deregisterWithBookmark(); Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() - + ": leaving muc " + conversation.getContactJid()); + + ": leaving muc " + conversation.getJid()); } else { account.pendingConferenceLeaves.add(conversation); } @@ -1381,8 +1448,8 @@ public class XmppConnectionService extends Service { return null; } - public void createAdhocConference(final Account account, final List<Jid> jids, final UiCallback<Conversation> callback) { - Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": creating adhoc conference with "+ jids.toString()); + public void createAdhocConference(final Account account, final Iterable<Jid> jids, final UiCallback<Conversation> callback) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString()); if (account.getStatus() == Account.State.ONLINE) { try { String server = findConferenceServer(account); @@ -1434,7 +1501,7 @@ public class XmppConnectionService extends Service { public void pushConferenceConfiguration(final Conversation conversation,final Bundle options, final OnConferenceOptionsPushed callback) { IqPacket request = new IqPacket(IqPacket.TYPE_GET); - request.setTo(conversation.getContactJid().toBareJid()); + request.setTo(conversation.getJid().toBareJid()); request.query("http://jabber.org/protocol/muc#owner"); sendIqPacket(conversation.getAccount(),request,new OnIqPacketReceived() { @Override @@ -1448,7 +1515,7 @@ public class XmppConnectionService extends Service { } data.submit(); IqPacket set = new IqPacket(IqPacket.TYPE_SET); - set.setTo(conversation.getContactJid().toBareJid()); + set.setTo(conversation.getJid().toBareJid()); set.query("http://jabber.org/protocol/muc#owner").addChild(data); sendIqPacket(account, set, new OnIqPacketReceived() { @Override @@ -1486,7 +1553,7 @@ public class XmppConnectionService extends Service { if (conversation.endOtrIfNeeded()) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ended otr session with " - + conversation.getContactJid()); + + conversation.getJid()); } } } @@ -1528,36 +1595,35 @@ public class XmppConnectionService extends Service { } public void onOtrSessionEstablished(Conversation conversation) { - Account account = conversation.getAccount(); - List<Message> messages = conversation.getMessages(); - Session otrSession = conversation.getOtrSession(); + final Account account = conversation.getAccount(); + final Session otrSession = conversation.getOtrSession(); Log.d(Config.LOGTAG, account.getJid().toBareJid() + " otr session established with " - + conversation.getContactJid() + "/" + + conversation.getJid() + "/" + otrSession.getSessionID().getUserID()); - for (Message msg : messages) { - if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING) - && (msg.getEncryption() == Message.ENCRYPTION_OTR)) { + conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { SessionID id = otrSession.getSessionID(); try { - msg.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID())); + message.setCounterpart(Jid.fromString(id.getAccountID() + "/" + id.getUserID())); } catch (InvalidJidException e) { - break; + return; } - if (msg.getType() == Message.TYPE_TEXT) { - MessagePacket outPacket = mMessageGenerator - .generateOtrChat(msg, true); + if (message.getType() == Message.TYPE_TEXT) { + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true); if (outPacket != null) { - msg.setStatus(Message.STATUS_SEND); - databaseBackend.updateMessage(msg); + message.setStatus(Message.STATUS_SEND); + databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); } - } else if (msg.getType() == Message.TYPE_IMAGE || msg.getType() == Message.TYPE_FILE) { - mJingleConnectionManager.createNewConnection(msg); + } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { + mJingleConnectionManager.createNewConnection(message); } - } - } - updateConversationUi(); + updateConversationUi(); + } + }); } public boolean renewSymmetricKey(Conversation conversation) { @@ -1587,17 +1653,17 @@ public class XmppConnectionService extends Service { return false; } - public void pushContactToServer(Contact contact) { + public void pushContactToServer(final Contact contact) { contact.resetOption(Contact.Options.DIRTY_DELETE); contact.setOption(Contact.Options.DIRTY_PUSH); - Account account = contact.getAccount(); + final Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { - boolean ask = contact.getOption(Contact.Options.ASKING); - boolean sendUpdates = contact + final boolean ask = contact.getOption(Contact.Options.ASKING); + final boolean sendUpdates = contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - iq.query("jabber:iq:roster").addChild(contact.asElement()); + final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + iq.query(Xmlns.ROSTER).addChild(contact.asElement()); account.getXmppConnection().sendIqPacket(iq, null); if (sendUpdates) { sendPresencePacket(account, @@ -1610,7 +1676,8 @@ public class XmppConnectionService extends Service { } } - public void publishAvatar(Account account, Uri image, + public void publishAvatar(final Account account, + final Uri image, final UiCallback<Avatar> callback) { final Bitmap.CompressFormat format = Config.AVATAR_FORMAT; final int size = Config.AVATAR_SIZE; @@ -1630,13 +1697,13 @@ public class XmppConnectionService extends Service { callback.error(R.string.error_saving_avatar, avatar); return; } - IqPacket packet = this.mIqGenerator.publishAvatar(avatar); + final IqPacket packet = this.mIqGenerator.publishAvatar(avatar); this.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket result) { if (result.getType() == IqPacket.TYPE_RESULT) { - IqPacket packet = XmppConnectionService.this.mIqGenerator + final IqPacket packet = XmppConnectionService.this.mIqGenerator .publishAvatarMetadata(avatar); sendIqPacket(account, packet, new OnIqPacketReceived() { @@ -1679,7 +1746,7 @@ public class XmppConnectionService extends Service { @Override public void onIqPacketReceived(Account account, IqPacket result) { final String ERROR = account.getJid().toBareJid() - + ": fetching avatar for " + avatar.owner + " failed "; + + ": fetching avatar for " + avatar.owner + " failed "; if (result.getType() == IqPacket.TYPE_RESULT) { avatar.image = mIqParser.avatarData(result); if (avatar.image != null) { @@ -1693,7 +1760,7 @@ public class XmppConnectionService extends Service { updateAccountUi(); } else { Contact contact = account.getRoster() - .getContact(avatar.owner); + .getContact(avatar.owner); contact.setAvatar(avatar.getFilename()); getAvatarService().clear(contact); updateConversationUi(); @@ -1769,7 +1836,7 @@ public class XmppConnectionService extends Service { Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { IqPacket iq = new IqPacket(IqPacket.TYPE_SET); - Element item = iq.query("jabber:iq:roster").addChild("item"); + Element item = iq.query(Xmlns.ROSTER).addChild("item"); item.setAttribute("jid", contact.getJid().toString()); item.setAttribute("subscription", "remove"); account.getXmppConnection().sendIqPacket(iq, null); @@ -1812,12 +1879,13 @@ public class XmppConnectionService extends Service { public void resetSendingToWaiting(Account account) { for (Conversation conversation : getConversations()) { if (conversation.getAccount() == account) { - for (Message message : conversation.getMessages()) { - if (message.getType() != Message.TYPE_IMAGE - && message.getStatus() == Message.STATUS_UNSEND) { + conversation.findUnsentTextMessages(new Conversation.OnMessageFound() { + + @Override + public void onMessageFound(Message message) { markMessage(message, Message.STATUS_WAITING); - } - } + } + }); } } } @@ -1828,7 +1896,7 @@ public class XmppConnectionService extends Service { return false; } else { for (Conversation conversation : getConversations()) { - if (conversation.getContactJid().equals(recipient) + if (conversation.getJid().equals(recipient) && conversation.getAccount().equals(account)) { return markMessage(conversation, uuid, status); } @@ -1842,15 +1910,13 @@ public class XmppConnectionService extends Service { if (uuid == null) { return false; } else { - for (Message message : conversation.getMessages()) { - if (uuid.equals(message.getUuid()) - || (message.getStatus() >= Message.STATUS_SEND && uuid - .equals(message.getRemoteMsgId()))) { - markMessage(message, status); - return true; - } + Message message = conversation.findSentMessageWithUuid(uuid); + if (message!=null) { + markMessage(message,status); + return true; + } else { + return false; } - return false; } } @@ -1904,6 +1970,12 @@ public class XmppConnectionService extends Service { } } + public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { + if (mOnUpdateBlocklist != null) { + mOnUpdateBlocklist.OnUpdateBlocklist(status); + } + } + public void updateMucRosterUi() { if (mOnMucRosterUpdate != null) { mOnMucRosterUpdate.onMucRosterUpdate(); @@ -1928,29 +2000,22 @@ public class XmppConnectionService extends Service { return null; } - public void markRead(Conversation conversation, boolean calledByUi) { + public void markRead(final Conversation conversation) { mNotificationService.clear(conversation); - final Message markable = conversation.getLatestMarkableMessage(); conversation.markRead(); - if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null && calledByUi) { + } + + public void sendReadMarker(final Conversation conversation) { + final Message markable = conversation.getLatestMarkableMessage(); + this.markRead(conversation); + if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()+ ": sending read marker to " + markable.getCounterpart().toString()); Account account = conversation.getAccount(); final Jid to = markable.getCounterpart(); MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId()); this.sendMessagePacket(conversation.getAccount(),packet); } - if (!calledByUi) { - updateConversationUi(); - } - } - - public void failWaitingOtrMessages(Conversation conversation) { - for (Message message : conversation.getMessages()) { - if (message.getEncryption() == Message.ENCRYPTION_OTR - && message.getStatus() == Message.STATUS_WAITING) { - markMessage(message, Message.STATUS_SEND_FAILED); - } - } + updateConversationUi(); } public SecureRandom getRNG() { @@ -1969,14 +2034,6 @@ public class XmppConnectionService extends Service { return this.mBitmapCache; } - public void replyWithNotAcceptable(Account account, MessagePacket packet) { - if (account.getStatus() == Account.State.ONLINE) { - MessagePacket error = this.mMessageGenerator - .generateNotAcceptable(packet); - sendMessagePacket(account, error); - } - } - public void syncRosterToDisk(final Account account) { new Thread(new Runnable() { @@ -1989,12 +2046,12 @@ public class XmppConnectionService extends Service { } public List<String> getKnownHosts() { - List<String> hosts = new ArrayList<>(); - for (Account account : getAccounts()) { + final List<String> hosts = new ArrayList<>(); + for (final Account account : getAccounts()) { if (!hosts.contains(account.getServer().toString())) { hosts.add(account.getServer().toString()); } - for (Contact contact : account.getRoster().getContacts()) { + for (final Contact contact : account.getRoster().getContacts()) { if (contact.showInRoster()) { final String server = contact.getServer().toString(); if (server != null && !hosts.contains(server)) { @@ -2007,10 +2064,10 @@ public class XmppConnectionService extends Service { } public List<String> getKnownConferenceHosts() { - ArrayList<String> mucServers = new ArrayList<>(); - for (Account account : accounts) { + final ArrayList<String> mucServers = new ArrayList<>(); + for (final Account account : accounts) { if (account.getXmppConnection() != null) { - String server = account.getXmppConnection().getMucServer(); + final String server = account.getXmppConnection().getMucServer(); if (server != null && !mucServers.contains(server)) { mucServers.add(server); } @@ -2033,9 +2090,8 @@ public class XmppConnectionService extends Service { } } - public void sendIqPacket(Account account, IqPacket packet, - OnIqPacketReceived callback) { - XmppConnection connection = account.getXmppConnection(); + public void sendIqPacket(final Account account, final IqPacket packet, final PacketReceived callback) { + final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); } @@ -2053,10 +2109,16 @@ public class XmppConnectionService extends Service { return this.mIqGenerator; } + public IqParser getIqParser() { return this.mIqParser; } + public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } + public MessageArchiveService getMessageArchiveService() { + return this.mMessageArchiveService; + } + public List<Contact> findContacts(Jid jid) { ArrayList<Contact> contacts = new ArrayList<>(); for (Account account : getAccounts()) { @@ -2078,8 +2140,8 @@ public class XmppConnectionService extends Service { return this.mHttpConnectionManager; } - public void resendFailedMessages(Message message) { - List<Message> messages = new ArrayList<>(); + public void resendFailedMessages(final Message message) { + final Collection<Message> messages = new ArrayList<>(); Message current = message; while (current.getStatus() == Message.STATUS_SEND_FAILED) { messages.add(current); @@ -2089,12 +2151,23 @@ public class XmppConnectionService extends Service { break; } } - for (Message msg : messages) { + for (final Message msg : messages) { markMessage(msg, Message.STATUS_WAITING); this.resendMessage(msg); } } + public void clearConversationHistory(final Conversation conversation) { + conversation.clearMessages(); + conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam + new Thread(new Runnable() { + @Override + public void run() { + databaseBackend.deleteMessagesInConversation(conversation); + } + }).start(); + } + public interface OnConversationUpdate { public void onConversationUpdate(); } @@ -2121,4 +2194,35 @@ public class XmppConnectionService extends Service { return XmppConnectionService.this; } } + + public void sendBlockRequest(final Blockable blockable) { + if (blockable != null && blockable.getBlockedJid() != null) { + final Jid jid = blockable.getBlockedJid(); + this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid), new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.getBlocklist().add(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + } + } + }); + } + } + + public void sendUnblockRequest(final Blockable blockable) { + if (blockable != null && blockable.getJid() != null) { + final Jid jid = blockable.getBlockedJid(); + this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.getBlocklist().remove(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + } + } + }); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java b/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java new file mode 100644 index 00000000..1a9fc95c --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java @@ -0,0 +1,124 @@ +package eu.siacs.conversations.ui; + +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.ui.adapter.ListItemAdapter; + +public abstract class AbstractSearchableListItemActivity extends XmppActivity { + private ListView mListView; + private final List<ListItem> listItems = new ArrayList<>(); + private ArrayAdapter<ListItem> mListItemsAdapter; + + private EditText mSearchEditText; + + private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { + + @Override + public boolean onMenuItemActionExpand(final MenuItem item) { + mSearchEditText.post(new Runnable() { + + @Override + public void run() { + mSearchEditText.requestFocus(); + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mSearchEditText, + InputMethodManager.SHOW_IMPLICIT); + } + }); + + return true; + } + + @Override + public boolean onMenuItemActionCollapse(final MenuItem item) { + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + mSearchEditText.setText(""); + filterContacts(); + return true; + } + }; + + private final TextWatcher mSearchTextWatcher = new TextWatcher() { + + @Override + public void afterTextChanged(final Editable editable) { + filterContacts(editable.toString()); + } + + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, + final int after) { + } + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, + final int count) { + } + }; + + public ListView getListView() { + return mListView; + } + + public List<ListItem> getListItems() { + return listItems; + } + + public EditText getSearchEditText() { + return mSearchEditText; + } + + public ArrayAdapter<ListItem> getListItemAdapter() { + return mListItemsAdapter; + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_choose_contact); + mListView = (ListView) findViewById(R.id.choose_contact_list); + mListView.setFastScrollEnabled(true); + mListItemsAdapter = new ListItemAdapter(this, listItems); + mListView.setAdapter(mListItemsAdapter); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.choose_contact, menu); + final MenuItem menuSearchView = menu.findItem(R.id.action_search); + final View mSearchView = menuSearchView.getActionView(); + mSearchEditText = (EditText) mSearchView + .findViewById(R.id.search_field); + mSearchEditText.addTextChangedListener(mSearchTextWatcher); + menuSearchView.setOnActionExpandListener(mOnActionExpandListener); + return true; + } + + protected void filterContacts() { + filterContacts(null); + } + + protected abstract void filterContacts(final String needle); + + @Override + void onBackendConnected() { + filterContacts(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java new file mode 100644 index 00000000..9cf7e9f8 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java @@ -0,0 +1,41 @@ +package eu.siacs.conversations.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Blockable; +import eu.siacs.conversations.services.XmppConnectionService; + +public final class BlockContactDialog { + public static void show(final Context context, + final XmppConnectionService xmppConnectionService, + final Blockable blockable) { + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final boolean isBlocked = blockable.isBlocked(); + builder.setNegativeButton(R.string.cancel, null); + + if (blockable.getJid().isDomainJid() || blockable.getAccount().isBlocked(blockable.getJid().toDomainJid())) { + builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); + builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, + blockable.getJid().toDomainJid())); + } else { + builder.setTitle(isBlocked ? R.string.action_unblock_contact : R.string.action_block_contact); + builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, + blockable.getJid().toBareJid())); + } + builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, new DialogInterface.OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (isBlocked) { + xmppConnectionService.sendUnblockRequest(blockable); + } else { + xmppConnectionService.sendBlockRequest(blockable); + } + } + }); + builder.create().show(); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java new file mode 100644 index 00000000..13d7f4fc --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java @@ -0,0 +1,75 @@ +package eu.siacs.conversations.ui; + +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.widget.AdapterView; + +import java.util.Collections; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class BlocklistActivity extends AbstractSearchableListItemActivity implements OnUpdateBlocklist { + + private Account account = null; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(final AdapterView<?> parent, + final View view, + final int position, + final long id) { + BlockContactDialog.show(parent.getContext(), xmppConnectionService,(Contact) getListItems().get(position)); + return true; + } + }); + } + + @Override + public void onBackendConnected() { + for (final Account account : xmppConnectionService.getAccounts()) { + if (account.getJid().toString().equals(getIntent().getStringExtra("account"))) { + this.account = account; + break; + } + } + filterContacts(); + } + + @Override + protected void filterContacts(final String needle) { + getListItems().clear(); + if (account != null) { + for (final Jid jid : account.getBlocklist()) { + final Contact contact = account.getRoster().getContact(jid); + if (contact.match(needle) && contact.isBlocked()) { + getListItems().add(contact); + } + } + Collections.sort(getListItems()); + } + runOnUiThread(new Runnable() { + @Override + public void run() { + getListItemAdapter().notifyDataSetChanged(); + } + }); + } + + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + final Editable editable = getSearchEditText().getText(); + if (editable != null) { + filterContacts(editable.toString()); + } else { + filterContacts(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java new file mode 100644 index 00000000..54c064c6 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java @@ -0,0 +1,107 @@ +package eu.siacs.conversations.ui; + +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged { + + private Button mChangePasswordButton; + private View.OnClickListener mOnChangePasswordButtonClicked = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mAccount != null) { + final String currentPassword = mCurrentPassword.getText().toString(); + final String newPassword = mNewPassword.getText().toString(); + final String newPasswordConfirm = mNewPasswordConfirm.getText().toString(); + if (!currentPassword.equals(mAccount.getPassword())) { + mCurrentPassword.requestFocus(); + mCurrentPassword.setError(getString(R.string.account_status_unauthorized)); + } else if (!newPassword.equals(newPasswordConfirm)) { + mNewPasswordConfirm.requestFocus(); + mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + } else if (newPassword.trim().isEmpty()) { + mNewPassword.requestFocus(); + mNewPassword.setError(getString(R.string.password_should_not_be_empty)); + } else { + mCurrentPassword.setError(null); + mNewPassword.setError(null); + mNewPasswordConfirm.setError(null); + xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this); + mChangePasswordButton.setEnabled(false); + mChangePasswordButton.setTextColor(getSecondaryTextColor()); + mChangePasswordButton.setText(R.string.updating); + } + } + } + }; + private EditText mCurrentPassword; + private EditText mNewPassword; + private EditText mNewPasswordConfirm; + private Account mAccount; + + @Override + void onBackendConnected() { + try { + final String jid = getIntent() == null ? null : getIntent().getStringExtra("account"); + if (jid != null) { + this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(jid)); + } + } catch (final InvalidJidException ignored) { + + } + + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_change_password); + Button mCancelButton = (Button) findViewById(R.id.left_button); + mCancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + this.mChangePasswordButton = (Button) findViewById(R.id.right_button); + this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked); + this.mCurrentPassword = (EditText) findViewById(R.id.current_password); + this.mNewPassword = (EditText) findViewById(R.id.new_password); + this.mNewPasswordConfirm = (EditText) findViewById(R.id.new_password_confirm); + } + + @Override + public void onPasswordChangeSucceeded() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ChangePasswordActivity.this,R.string.password_changed,Toast.LENGTH_LONG).show(); + finish(); + } + }); + } + + @Override + public void onPasswordChangeFailed() { + runOnUiThread(new Runnable() { + @Override + public void run() { + mNewPassword.setError(getString(R.string.could_not_change_password)); + mChangePasswordButton.setEnabled(true); + mChangePasswordButton.setTextColor(getPrimaryTextColor()); + mChangePasswordButton.setText(R.string.change_password); + } + }); + + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java index e7254933..70b353c6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -1,101 +1,33 @@ package eu.siacs.conversations.ui; -import java.util.ArrayList; -import java.util.Collections; - import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import eu.siacs.conversations.R; + +import java.util.Collections; + import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.ui.adapter.ListItemAdapter; - -public class ChooseContactActivity extends XmppActivity { - - private ListView mListView; - private ArrayList<ListItem> contacts = new ArrayList<>(); - private ArrayAdapter<ListItem> mContactsAdapter; - - private EditText mSearchEditText; - - private TextWatcher mSearchTextWatcher = new TextWatcher() { - - @Override - public void afterTextChanged(Editable editable) { - filterContacts(editable.toString()); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - } - }; - - private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { - - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - mSearchEditText.post(new Runnable() { - - @Override - public void run() { - mSearchEditText.requestFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mSearchEditText, - InputMethodManager.SHOW_IMPLICIT); - } - }); - - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY); - mSearchEditText.setText(""); - filterContacts(null); - return true; - } - }; +public class ChooseContactActivity extends AbstractSearchableListItemActivity { @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_choose_contact); - mListView = (ListView) findViewById(R.id.choose_contact_list); - mListView.setFastScrollEnabled(true); - mContactsAdapter = new ListItemAdapter(this, contacts); - mListView.setAdapter(mContactsAdapter); - mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), + public void onItemClick(final AdapterView<?> parent, final View view, + final int position, final long id) { + final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY); - Intent request = getIntent(); - Intent data = new Intent(); - ListItem mListItem = contacts.get(position); + final Intent request = getIntent(); + final Intent data = new Intent(); + final ListItem mListItem = getListItems().get(position); data.putExtra("contact", mListItem.getJid().toString()); String account = request.getStringExtra("account"); if (account == null && mListItem instanceof Contact) { @@ -108,38 +40,21 @@ public class ChooseContactActivity extends XmppActivity { finish(); } }); - } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.choose_contact, menu); - MenuItem menuSearchView = menu.findItem(R.id.action_search); - View mSearchView = menuSearchView.getActionView(); - mSearchEditText = (EditText) mSearchView - .findViewById(R.id.search_field); - mSearchEditText.addTextChangedListener(mSearchTextWatcher); - menuSearchView.setOnActionExpandListener(mOnActionExpandListener); - return true; } - @Override - void onBackendConnected() { - filterContacts(null); - } - - protected void filterContacts(String needle) { - this.contacts.clear(); - for (Account account : xmppConnectionService.getAccounts()) { + protected void filterContacts(final String needle) { + getListItems().clear(); + for (final Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { - for (Contact contact : account.getRoster().getContacts()) { + for (final Contact contact : account.getRoster().getContacts()) { if (contact.showInRoster() && contact.match(needle)) { - this.contacts.add(contact); + getListItems().add(contact); } } } } - Collections.sort(this.contacts); - mContactsAdapter.notifyDataSetChanged(); + Collections.sort(getListItems()); + getListItemAdapter().notifyDataSetChanged(); } - } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 2e36c545..eeb015f3 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -157,8 +157,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override public void onValueEdited(String value) { MessagePacket packet = xmppConnectionService - .getMessageGenerator().conferenceSubject( - mConversation, value); + .getMessageGenerator().conferenceSubject( + mConversation, value); xmppConnectionService.sendMessagePacket( mConversation.getAccount(), packet); } @@ -191,7 +191,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override protected String getShareableUri() { if (mConversation != null) { - return "xmpp:" + mConversation.getContactJid().toBareJid().toString() + "?join"; + return "xmpp:" + mConversation.getJid().toBareJid().toString() + "?join"; } else { return ""; } @@ -202,7 +202,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); Account account = mConversation.getAccount(); - if (account.hasBookmarkFor(mConversation.getContactJid().toBareJid())) { + if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) { menuItemSaveBookmark.setVisible(false); menuItemDeleteBookmark.setVisible(true); } else { @@ -263,9 +263,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers protected void saveAsBookmark() { Account account = mConversation.getAccount(); - Bookmark bookmark = new Bookmark(account, mConversation.getContactJid().toBareJid()); - if (!mConversation.getContactJid().isBareJid()) { - bookmark.setNick(mConversation.getContactJid().getResourcepart()); + Bookmark bookmark = new Bookmark(account, mConversation.getJid().toBareJid()); + if (!mConversation.getJid().isBareJid()) { + bookmark.setNick(mConversation.getJid().getResourcepart()); } bookmark.setAutojoin(true); account.getBookmarks().add(bookmark); @@ -288,7 +288,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } if (uuid != null) { this.mConversation = xmppConnectionService - .findConversationByUuid(uuid); + .findConversationByUuid(uuid); if (this.mConversation != null) { populateView(); } @@ -297,11 +297,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private void populateView() { mAccountJid.setText(getString(R.string.using_account, mConversation - .getAccount().getJid().toBareJid())); + .getAccount().getJid().toBareJid())); mYourPhoto.setImageBitmap(avatarService().get( - mConversation.getAccount(), getPixel(48))); + mConversation.getAccount(), getPixel(48))); setTitle(mConversation.getName()); - mFullJid.setText(mConversation.getContactJid().toBareJid().toString()); + mFullJid.setText(mConversation.getJid().toBareJid().toString()); mYourNick.setText(mConversation.getMucOptions().getActualNick()); mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); if (mConversation.getMucOptions().online()) { @@ -338,7 +338,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers registerForContextMenu(view); view.setTag(user); TextView name = (TextView) view - .findViewById(R.id.contact_display_name); + .findViewById(R.id.contact_display_name); TextView key = (TextView) view.findViewById(R.id.key); TextView role = (TextView) view.findViewById(R.id.contact_jid); if (user.getPgpKeyId() != 0) { diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 4259371a..657ae75b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -37,11 +37,13 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate { +public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; @@ -50,7 +52,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onClick(DialogInterface dialog, int which) { ContactDetailsActivity.this.xmppConnectionService - .deleteContactOnServer(contact); + .deleteContactOnServer(contact); ContactDetailsActivity.this.finish(); } }; @@ -58,14 +60,14 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { + boolean isChecked) { if (isChecked) { if (contact .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { xmppConnectionService.sendPresencePacket(contact - .getAccount(), + .getAccount(), xmppConnectionService.getPresenceGenerator() - .sendPresenceUpdatesTo(contact)); + .sendPresenceUpdatesTo(contact)); } else { contact.setOption(Contact.Options.PREEMPTIVE_GRANT); } @@ -73,7 +75,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator() - .stopPresenceUpdatesTo(contact)); + .stopPresenceUpdatesTo(contact)); } } }; @@ -81,15 +83,15 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { + boolean isChecked) { if (isChecked) { xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); + .requestPresenceUpdatesFrom(contact)); } else { xmppConnectionService.sendPresencePacket(contact.getAccount(), xmppConnectionService.getPresenceGenerator() - .stopPresenceUpdatesFrom(contact)); + .stopPresenceUpdatesFrom(contact)); } } }; @@ -127,7 +129,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd ContactDetailsActivity.this); builder.setTitle(getString(R.string.action_add_phone_book)); builder.setMessage(getString(R.string.add_phone_book_text, - contact.getJid())); + contact.getJid())); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.add), addToPhonebook); builder.create().show(); @@ -166,7 +168,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { try { @@ -188,15 +190,17 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd badge = (QuickContactBadge) findViewById(R.id.details_contact_badge); keys = (LinearLayout) findViewById(R.id.details_contact_keys); tags = (LinearLayout) findViewById(R.id.tags); - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false); } @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { + public boolean onOptionsItemSelected(final MenuItem menuItem) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setNegativeButton(getString(R.string.cancel), null); switch (menuItem.getItemId()) { @@ -205,11 +209,11 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd break; case R.id.action_delete_contact: builder.setTitle(getString(R.string.action_delete_contact)) - .setMessage( - getString(R.string.remove_contact_text, - contact.getJid())) - .setPositiveButton(getString(R.string.delete), - removeFromRoster).create().show(); + .setMessage( + getString(R.string.remove_contact_text, + contact.getJid())) + .setPositiveButton(getString(R.string.delete), + removeFromRoster).create().show(); break; case R.id.action_edit_contact: if (contact.getSystemAccount() == null) { @@ -219,7 +223,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd public void onValueEdited(String value) { contact.setServerName(value); ContactDetailsActivity.this.xmppConnectionService - .pushContactToServer(contact); + .pushContactToServer(contact); populateView(); } }); @@ -285,7 +289,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); lastseen.setText(UIHelper.lastseen(getApplicationContext(), - contact.lastseen.time)); + contact.lastseen.time)); if (contact.getPresences().size() > 1) { contactJidTv.setText(contact.getJid() + " (" @@ -294,7 +298,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd contactJidTv.setText(contact.getJid().toString()); } accountJidTv.setText(getString(R.string.using_account, contact - .getAccount().getJid().toBareJid())); + .getAccount().getJid().toBareJid())); prepareContactBadge(badge, contact); if (contact.getSystemAccount() == null) { badge.setOnClickListener(onBadgeClick); @@ -309,10 +313,10 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); ImageButton remove = (ImageButton) view - .findViewById(R.id.button_remove); + .findViewById(R.id.button_remove); remove.setVisibility(View.VISIBLE); keyType.setText("OTR Fingerprint"); - key.setText(otrFingerprint); + key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); keys.addView(view); remove.setOnClickListener(new OnClickListener() { @@ -334,7 +338,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd @Override public void onClick(View v) { PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService - .getPgpEngine(); + .getPgpEngine(); if (pgp != null) { PendingIntent intent = pgp.getIntentForKey(contact); if (intent != null) { @@ -363,8 +367,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } else { tags.setVisibility(View.VISIBLE); tags.removeAllViewsInLayout(); - for(ListItem.Tag tag : tagList) { - TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tags,false); + for(final ListItem.Tag tag : tagList) { + final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tags,false); tv.setText(tag.getName()); tv.setBackgroundColor(tag.getColor()); tags.addView(tv); @@ -393,8 +397,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd public void onClick(DialogInterface dialog, int which) { if (contact.deleteOtrFingerprint(fingerprint)) { populateView(); - xmppConnectionService.syncRosterToDisk(contact - .getAccount()); + xmppConnectionService.syncRosterToDisk(contact.getAccount()); } } @@ -406,7 +409,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd public void onBackendConnected() { if ((accountJid != null) && (contactJid != null)) { Account account = xmppConnectionService - .findAccountByJid(accountJid); + .findAccountByJid(accountJid); if (account == null) { return; } @@ -414,4 +417,15 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd populateView(); } } + + @Override + public void OnUpdateBlocklist(final Status status) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + populateView(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 5b0fa562..5cfc1116 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -9,13 +9,13 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.media.MediaActionSound; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.provider.MediaStore; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; -import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -28,10 +28,14 @@ import android.widget.PopupMenu; import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.Toast; +import net.java.otr4j.session.SessionStatus; + import java.util.ArrayList; import java.util.List; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; @@ -40,27 +44,23 @@ import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdat import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; -public class ConversationActivity extends XmppActivity implements - OnAccountUpdate, OnConversationUpdate, OnRosterUpdate { +public class ConversationActivity extends XmppActivity + implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist { public static final String VIEW_CONVERSATION = "viewConversation"; public static final String CONVERSATION = "conversationUuid"; public static final String TEXT = "text"; public static final String NICK = "nick"; - public static final String PRESENCE = "eu.siacs.conversations.presence"; public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; - private static final int REQUEST_ATTACH_IMAGE_DIALOG = 0x0203; - private static final int REQUEST_IMAGE_CAPTURE = 0x0204; - private static final int REQUEST_RECORD_AUDIO = 0x0205; - private static final int REQUEST_SEND_PGP_IMAGE = 0x0206; - private static final int REQUEST_ATTACH_FILE_DIALOG = 0x0208; private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; private static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; + private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_URI = "state_pending_uri"; @@ -81,6 +81,8 @@ public class ConversationActivity extends XmppActivity implements private Toast prepareFileToast; + private boolean mActivityPaused = false; + public List<Conversation> getConversationList() { return this.conversationList; @@ -94,10 +96,6 @@ public class ConversationActivity extends XmppActivity implements this.mSelectedConversation = conversation; } - public ListView getConversationListView() { - return this.listView; - } - public void showConversationsOverview() { if (mContentView instanceof SlidingPaneLayout) { SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; @@ -144,12 +142,12 @@ public class ConversationActivity extends XmppActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) {mOpenConverstaion = savedInstanceState.getString( - STATE_OPEN_CONVERSATION, null); - mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true); - String pending = savedInstanceState.getString(STATE_PENDING_URI, null); - if (pending != null) { - mPendingImageUri = Uri.parse(pending); - } + STATE_OPEN_CONVERSATION, null); + mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true); + String pending = savedInstanceState.getString(STATE_PENDING_URI, null); + if (pending != null) { + mPendingImageUri = Uri.parse(pending); + } } setContentView(R.layout.fragment_conversations_overview); @@ -172,7 +170,7 @@ public class ConversationActivity extends XmppActivity implements @Override public void onItemClick(AdapterView<?> arg0, View clickedView, - int position, long arg3) { + int position, long arg3) { if (getSelectedConversation() != conversationList.get(position)) { setSelectedConversation(conversationList.get(position)); ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); @@ -188,7 +186,7 @@ public class ConversationActivity extends XmppActivity implements SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; mSlidingPaneLayout.setParallaxDistance(150); mSlidingPaneLayout - .setShadowResource(R.drawable.es_slidingpane_shadow); + .setShadowResource(R.drawable.es_slidingpane_shadow); mSlidingPaneLayout.setSliderFadeColor(0); mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() { @@ -199,7 +197,7 @@ public class ConversationActivity extends XmppActivity implements hideKeyboard(); if (xmppConnectionServiceBound) { xmppConnectionService.getNotificationService() - .setOpenConversation(null); + .setOpenConversation(null); } closeContextMenu(); } @@ -244,7 +242,7 @@ public class ConversationActivity extends XmppActivity implements if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) { ab.setTitle(conversation.getName()); } else { - ab.setTitle(conversation.getContactJid().toBareJid().toString()); + ab.setTitle(conversation.getJid().toBareJid().toString()); } } else { ab.setDisplayHomeAsUpEnabled(false); @@ -258,28 +256,33 @@ public class ConversationActivity extends XmppActivity implements this.updateActionBarTitle(); this.invalidateOptionsMenu(); if (xmppConnectionServiceBound) { - xmppConnectionService.getNotificationService().setOpenConversation(getSelectedConversation()); - if (!getSelectedConversation().isRead()) { - xmppConnectionService.markRead(getSelectedConversation(), true); - listView.invalidateViews(); - } + final Conversation conversation = getSelectedConversation(); + xmppConnectionService.getNotificationService().setOpenConversation(conversation); + sendReadMarkerIfNecessary(conversation); + } + } + + public void sendReadMarkerIfNecessary(final Conversation conversation) { + if (!mActivityPaused && !conversation.isRead()) { + xmppConnectionService.sendReadMarker(conversation); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.conversations, menu); - MenuItem menuSecure = menu.findItem(R.id.action_security); - MenuItem menuArchive = menu.findItem(R.id.action_archive); - MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details); - MenuItem menuContactDetails = menu - .findItem(R.id.action_contact_details); - MenuItem menuAttach = menu.findItem(R.id.action_attach_file); - MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); - MenuItem menuAdd = menu.findItem(R.id.action_add); - MenuItem menuInviteContact = menu.findItem(R.id.action_invite); - MenuItem menuMute = menu.findItem(R.id.action_mute); - MenuItem menuUnmute = menu.findItem(R.id.action_unmute); + final MenuItem menuSecure = menu.findItem(R.id.action_security); + final MenuItem menuArchive = menu.findItem(R.id.action_archive); + final MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details); + final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details); + final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); + final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); + final MenuItem menuAdd = menu.findItem(R.id.action_add); + final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + final MenuItem menuMute = menu.findItem(R.id.action_mute); + final MenuItem menuUnmute = menu.findItem(R.id.action_unmute); + final MenuItem menuBlock = menu.findItem(R.id.action_block); + final MenuItem menuUnblock = menu.findItem(R.id.action_unblock); if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { @@ -292,19 +295,33 @@ public class ConversationActivity extends XmppActivity implements menuClearHistory.setVisible(false); menuMute.setVisible(false); menuUnmute.setVisible(false); + menuBlock.setVisible(false); + menuUnblock.setVisible(false); } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { if (this.getSelectedConversation().getLatestMessage() .getEncryption() != Message.ENCRYPTION_NONE) { menuSecure.setIcon(R.drawable.ic_action_secure); - } + } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); menuAttach.setVisible(false); + menuBlock.setVisible(false); + menuUnblock.setVisible(false); } else { menuMucDetails.setVisible(false); menuInviteContact.setTitle(R.string.conference_with); + if (this.getSelectedConversation().isBlocked()) { + menuBlock.setVisible(false); + } else { + menuUnblock.setVisible(false); + } + final Account account = this.getSelectedConversation().getAccount(); + if (!(account.isOnlineAndConnected() && account.getXmppConnection().getFeatures().blocking())) { + menuBlock.setVisible(false); + menuUnblock.setVisible(false); + } } if (this.getSelectedConversation().isMuted()) { menuMute.setVisible(false); @@ -321,33 +338,37 @@ public class ConversationActivity extends XmppActivity implements @Override public void onPresenceSelected() { - if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO) { - mPendingImageUri = xmppConnectionService.getFileBackend() - .getTakePhotoUri(); - Intent takePictureIntent = new Intent( - MediaStore.ACTION_IMAGE_CAPTURE); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, - mPendingImageUri); - if (takePictureIntent.resolveActivity(getPackageManager()) != null) { - startActivityForResult(takePictureIntent, - REQUEST_IMAGE_CAPTURE); + Intent intent = new Intent(); + boolean chooser = false; + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + chooser = true; + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri(); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT,mPendingImageUri); + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + chooser = true; + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); + break; + } + if (intent.resolveActivity(getPackageManager()) != null) { + if (chooser) { + startActivityForResult( + Intent.createChooser(intent,getString(R.string.perform_action_with)), + attachmentChoice); + } else { + startActivityForResult(intent, attachmentChoice); } - } else if (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { - Intent attachFileIntent = new Intent(); - attachFileIntent.setType("image/*"); - attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); - Intent chooser = Intent.createChooser(attachFileIntent, - getString(R.string.attach_file)); - startActivityForResult(chooser, REQUEST_ATTACH_IMAGE_DIALOG); - } else if (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_FILE) { - Intent attachFileIntent = new Intent(); - //attachFileIntent.setType("file/*"); - attachFileIntent.setType("*/*"); - attachFileIntent.addCategory(Intent.CATEGORY_OPENABLE); - attachFileIntent.setAction(Intent.ACTION_GET_CONTENT); - Intent chooser = Intent.createChooser(attachFileIntent, - getString(R.string.attach_file)); - startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG); } } }); @@ -364,7 +385,7 @@ public class ConversationActivity extends XmppActivity implements @Override public void userInputRequried(PendingIntent pi, - Contact contact) { + Contact contact) { ConversationActivity.this.runIntent(pi, attachmentChoice); } @@ -381,18 +402,18 @@ public class ConversationActivity extends XmppActivity implements }); } else { final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { fragment.showNoPGPKeyDialog(false, new OnClickListener() { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); selectPresenceToAttachFile(attachmentChoice); } }); @@ -402,7 +423,7 @@ public class ConversationActivity extends XmppActivity implements showInstallPgpDialog(); } } else if (getSelectedConversation().getNextEncryption( - forceEncryption()) == Message.ENCRYPTION_NONE) { + forceEncryption()) == Message.ENCRYPTION_NONE) { selectPresenceToAttachFile(attachmentChoice); } else { selectPresenceToAttachFile(attachmentChoice); @@ -410,7 +431,7 @@ public class ConversationActivity extends XmppActivity implements } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == android.R.id.home) { showConversationsOverview(); return true; @@ -455,6 +476,12 @@ public class ConversationActivity extends XmppActivity implements case R.id.action_unmute: unmuteConversation(getSelectedConversation()); break; + case R.id.action_block: + BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); + break; + case R.id.action_unblock: + BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation()); + break; default: break; } @@ -483,7 +510,7 @@ public class ConversationActivity extends XmppActivity implements View dialogView = getLayoutInflater().inflate( R.layout.dialog_clear_history, null); final CheckBox endConversationCheckBox = (CheckBox) dialogView - .findViewById(R.id.end_conversation_checkbox); + .findViewById(R.id.end_conversation_checkbox); builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.delete_messages), @@ -491,10 +518,12 @@ public class ConversationActivity extends XmppActivity implements @Override public void onClick(DialogInterface dialog, int which) { - ConversationActivity.this.xmppConnectionService - .clearConversationHistory(conversation); + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); if (endConversationCheckBox.isChecked()) { endConversation(conversation); + } else { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); } } }); @@ -508,28 +537,68 @@ public class ConversationActivity extends XmppActivity implements } PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); attachFilePopup.inflate(R.menu.attachment_choices); - attachFilePopup - .setOnMenuItemClickListener(new OnMenuItemClickListener() { + if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) { + attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false); + } + attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.attach_choose_picture: - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); - break; - case R.id.attach_record_voice: - attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); - break; - } - return false; + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case R.id.attach_choose_file: + attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; } - }); + return false; + } + }); attachFilePopup.show(); } + public void verifyOtrSessionDialog(final Conversation conversation, View view) { + if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); + return; + } + if (view == null) { + return; + } + PopupMenu popup = new PopupMenu(this, view); + popup.inflate(R.menu.verification_choices); + popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + switch (menuItem.getItemId()) { + case R.id.scan_fingerprint: + intent.putExtra("mode",VerifyOTRActivity.MODE_SCAN_FINGERPRINT); + break; + case R.id.ask_question: + intent.putExtra("mode",VerifyOTRActivity.MODE_ASK_QUESTION); + break; + case R.id.manual_verification: + intent.putExtra("mode",VerifyOTRActivity.MODE_MANUAL_VERIFICATION); + break; + } + startActivity(intent); + return true; + } + }); + popup.show(); + } + protected void selectEncryptionDialog(final Conversation conversation) { View menuItemView = findViewById(R.id.action_security); if (menuItemView == null) { @@ -537,7 +606,7 @@ public class ConversationActivity extends XmppActivity implements } PopupMenu popup = new PopupMenu(this, menuItemView); final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -557,7 +626,7 @@ public class ConversationActivity extends XmppActivity implements if (conversation.getAccount().getKeys() .has("pgp_signature")) { conversation - .setNextEncryption(Message.ENCRYPTION_PGP); + .setNextEncryption(Message.ENCRYPTION_PGP); item.setChecked(true); } else { announcePgp(conversation.getAccount(), @@ -572,7 +641,7 @@ public class ConversationActivity extends XmppActivity implements break; } xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); fragment.updateChatMsgHint(); return true; } @@ -597,11 +666,11 @@ public class ConversationActivity extends XmppActivity implements break; case Message.ENCRYPTION_PGP: popup.getMenu().findItem(R.id.encryption_choice_pgp) - .setChecked(true); + .setChecked(true); break; default: popup.getMenu().findItem(R.id.encryption_choice_none) - .setChecked(true); + .setChecked(true); break; } popup.show(); @@ -617,17 +686,17 @@ public class ConversationActivity extends XmppActivity implements new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { - long till; + public void onClick(final DialogInterface dialog, final int which) { + final long till; if (durations[which] == -1) { till = Long.MAX_VALUE; } else { till = SystemClock.elapsedRealtime() - + (durations[which] * 1000); + + (durations[which] * 1000); } conversation.setMutedTill(till); ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); invalidateOptionsMenu(); @@ -676,6 +745,28 @@ public class ConversationActivity extends XmppActivity implements } @Override + public void onPause() { + super.onPause(); + this.mActivityPaused = true; + if (this.xmppConnectionServiceBound) { + this.xmppConnectionService.getNotificationService().setIsInForeground(false); + } + } + + @Override + public void onResume() { + super.onResume(); + int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } + this.mActivityPaused = false; + if (this.xmppConnectionServiceBound) { + this.xmppConnectionService.getNotificationService().setIsInForeground(true); + } + } + + @Override public void onSaveInstanceState(final Bundle savedInstanceState) { Conversation conversation = getSelectedConversation(); if (conversation != null) { @@ -692,6 +783,7 @@ public class ConversationActivity extends XmppActivity implements @Override void onBackendConnected() { + this.xmppConnectionService.getNotificationService().setIsInForeground(true); updateConversationList(); if (xmppConnectionService.getAccounts().size() == 0) { startActivity(new Intent(this, EditAccountActivity.class)); @@ -752,11 +844,11 @@ public class ConversationActivity extends XmppActivity implements } private void selectConversationByUuid(String uuid) { - for (Conversation aConversationList : conversationList) { - if (aConversationList.getUuid().equals(uuid)) { - setSelectedConversation(aConversationList); - } - } + for (Conversation aConversationList : conversationList) { + if (aConversationList.getUuid().equals(uuid)) { + setSelectedConversation(aConversationList); + } + } } @Override @@ -767,50 +859,35 @@ public class ConversationActivity extends XmppActivity implements @Override protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_DECRYPT_PGP) { mConversationFragment.hideSnackbar(); mConversationFragment.updateMessages(); - } else if (requestCode == REQUEST_ATTACH_IMAGE_DIALOG) { + } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { mPendingImageUri = data.getData(); if (xmppConnectionServiceBound) { - attachImageToConversation(getSelectedConversation(), - mPendingImageUri); + attachImageToConversation(getSelectedConversation(),mPendingImageUri); mPendingImageUri = null; } - } else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) { + } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { mPendingFileUri = data.getData(); if (xmppConnectionServiceBound) { - attachFileToConversation(getSelectedConversation(), - mPendingFileUri); + attachFileToConversation(getSelectedConversation(),mPendingFileUri); mPendingFileUri = null; } - } else if (requestCode == REQUEST_SEND_PGP_IMAGE) { - - } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { - attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); - } else if (requestCode == REQUEST_ANNOUNCE_PGP) { - announcePgp(getSelectedConversation().getAccount(), - getSelectedConversation()); - } else if (requestCode == REQUEST_ENCRYPT_MESSAGE) { - // encryptTextMessage(); - } else if (requestCode == REQUEST_IMAGE_CAPTURE && mPendingImageUri != null) { + } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO && mPendingImageUri != null) { if (xmppConnectionServiceBound) { - attachImageToConversation(getSelectedConversation(), - mPendingImageUri); + attachImageToConversation(getSelectedConversation(),mPendingImageUri); mPendingImageUri = null; } - Intent intent = new Intent( - Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(mPendingImageUri); sendBroadcast(intent); } } else { - if (requestCode == REQUEST_IMAGE_CAPTURE) { + if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) { mPendingImageUri = null; } } @@ -848,10 +925,8 @@ public class ConversationActivity extends XmppActivity implements @Override public void userInputRequried(PendingIntent pi, - Message object) { + Message object) { hidePrepareFileToast(); - ConversationActivity.this.runIntent(pi, - ConversationActivity.REQUEST_SEND_PGP_IMAGE); } @Override @@ -881,7 +956,7 @@ public class ConversationActivity extends XmppActivity implements public void updateConversationList() { xmppConnectionService - .populateWithOrderedConversations(conversationList); + .populateWithOrderedConversations(conversationList); listAdapter.notifyDataSetChanged(); } @@ -899,7 +974,7 @@ public class ConversationActivity extends XmppActivity implements @Override public void userInputRequried(PendingIntent pi, - Message message) { + Message message) { ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_MESSAGE); } @@ -951,7 +1026,7 @@ public class ConversationActivity extends XmppActivity implements updateConversationList(); if (conversationList.size() == 0) { startActivity(new Intent(getApplicationContext(), - StartConversationActivity.class)); + StartConversationActivity.class)); finish(); } ConversationActivity.this.mConversationFragment.updateMessages(); @@ -964,12 +1039,31 @@ public class ConversationActivity extends XmppActivity implements public void onRosterUpdate() { runOnUiThread(new Runnable() { - @Override - public void run() { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - updateActionBarTitle(); - } - }); + @Override + public void run() { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + updateActionBarTitle(); + } + }); + } + + @Override + public void OnUpdateBlocklist(Status status) { + invalidateOptionsMenu(); + runOnUiThread(new Runnable() { + @Override + public void run() { + ConversationActivity.this.mConversationFragment.updateMessages(); + } + }); + } + + public void unblockConversation(final Blockable conversation) { + xmppConnectionService.sendUnblockRequest(conversation); + } + + public void blockConversation(final Blockable conversation) { + xmppConnectionService.sendBlockRequest(conversation); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 0edc6b6f..93a4cc7e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -103,7 +103,9 @@ public class ConversationFragment extends Fragment { private RelativeLayout snackbar; private TextView snackbarMessage; private TextView snackbarAction; - private boolean messagesLoaded = false; + private boolean messagesLoaded = true; + private Toast messageLoaderToast; + private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override @@ -114,20 +116,69 @@ public class ConversationFragment extends Fragment { @Override public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { + int visibleItemCount, int totalItemCount) { synchronized (ConversationFragment.this.messageList) { - if (firstVisibleItem == 0 && messagesLoaded) { + if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) { long timestamp = ConversationFragment.this.messageList.get(0).getTimeSent(); messagesLoaded = false; - int size = activity.xmppConnectionService.loadMoreMessages(conversation, timestamp); - ConversationFragment.this.messageList.clear(); - ConversationFragment.this.messageList.addAll(conversation.getMessages()); - updateStatusMessages(); - messageListAdapter.notifyDataSetChanged(); - if (size != 0) { - messagesLoaded = true; - } - messagesView.setSelectionFromTop(size + 1, 0); + activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() { + @Override + public void onMoreMessagesLoaded(final int count, Conversation conversation) { + if (ConversationFragment.this.conversation != conversation) { + return; + } + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final int oldPosition = messagesView.getFirstVisiblePosition(); + View v = messagesView.getChildAt(0); + final int pxOffset = (v == null) ? 0 : v.getTop(); + ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList); + updateStatusMessages(); + messageListAdapter.notifyDataSetChanged(); + if (count != 0) { + final int newPosition = oldPosition + count; + int offset = 0; + try { + Message tmpMessage = messageList.get(newPosition); + + while(tmpMessage.wasMergedIntoPrevious()) { + offset++; + tmpMessage = tmpMessage.prev(); + } + } catch (final IndexOutOfBoundsException ignored) { + + } + messagesView.setSelectionFromTop(newPosition - offset, pxOffset); + messagesLoaded = true; + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + } + } + }); + } + + @Override + public void informUser(final int resId) { + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + if (ConversationFragment.this.conversation != conversation) { + return; + } + messageLoaderToast = Toast.makeText(activity,resId,Toast.LENGTH_LONG); + messageLoaderToast.show(); + } + }); + + } + }); + } } } @@ -153,13 +204,7 @@ public class ConversationFragment extends Fragment { @Override public void onClick(View v) { - if (conversation.getOtrFingerprint() != null) { - Intent intent = new Intent(getActivity(), VerifyOTRActivity.class); - intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); - startActivity(intent); - } + activity.verifyOtrSessionDialog(conversation,v); } }; private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); @@ -170,7 +215,7 @@ public class ConversationFragment extends Fragment { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND) { InputMethodManager imm = (InputMethodManager) v.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); + .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); sendMessage(); return true; @@ -213,7 +258,7 @@ public class ConversationFragment extends Fragment { } Message message = new Message(conversation, mEditMessage.getText() .toString(), conversation.getNextEncryption(activity - .forceEncryption())); + .forceEncryption())); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); @@ -234,13 +279,13 @@ public class ConversationFragment extends Fragment { if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getNextCounterpart() != null) { this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); } else { switch (conversation.getNextEncryption(activity.forceEncryption())) { case Message.ENCRYPTION_NONE: mEditMessage - .setHint(getString(R.string.send_plain_text_message)); + .setHint(getString(R.string.send_plain_text_message)); break; case Message.ENCRYPTION_OTR: mEditMessage.setHint(getString(R.string.send_otr_message)); @@ -256,7 +301,7 @@ public class ConversationFragment extends Fragment { @Override public View onCreateView(final LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { + ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); mEditMessage = (EditMessage) view.findViewById(R.id.textinput); @@ -289,49 +334,51 @@ public class ConversationFragment extends Fragment { messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { - @Override - public void onContactPictureClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - if (!message.getCounterpart().isBareJid()) { - highlightInConference(message.getCounterpart().getResourcepart()); - } else { - highlightInConference(message.getCounterpart().toString()); - } - } + @Override + public void onContactPictureClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getCounterpart() != null) { + if (!message.getCounterpart().isBareJid()) { + highlightInConference(message.getCounterpart().getResourcepart()); } else { - Contact contact = message.getConversation() - .getContact(); - if (contact.showInRoster()) { - activity.switchToContactDetails(contact); - } else { - activity.showAddToRosterDialog(message - .getConversation()); - } + highlightInConference(message.getCounterpart().toString()); } + } + } else { + Contact contact = message.getConversation() + .getContact(); + if (contact.showInRoster()) { + activity.switchToContactDetails(contact); } else { - Account account = message.getConversation().getAccount(); - Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - startActivity(intent); + activity.showAddToRosterDialog(message + .getConversation()); } } - }); + } else { + Account account = message.getConversation().getAccount(); + Intent intent = new Intent(activity, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().toBareJid().toString()); + startActivity(intent); + } + } + }); messageListAdapter - .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { + .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { - @Override - public void onContactPictureLongClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - privateMessageWith(message.getCounterpart()); - } + @Override + public void onContactPictureLongClicked(Message message) { + if (message.getStatus() <= Message.STATUS_RECEIVED) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + if (message.getCounterpart() != null) { + privateMessageWith(message.getCounterpart()); } } + } else { + activity.showQrCode(); } - }); + } + }); messagesView.setAdapter(messageListAdapter); registerForContextMenu(messagesView); @@ -341,7 +388,7 @@ public class ConversationFragment extends Fragment { @Override public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + ContextMenuInfo menuInfo) { synchronized (this.messageList) { super.onCreateContextMenu(menu, v, menuInfo); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; @@ -351,7 +398,8 @@ public class ConversationFragment extends Fragment { } private void populateContextMenu(ContextMenu menu) { - if (this.selectedMessage.getType() != Message.TYPE_STATUS) { + final Message m = this.selectedMessage; + if (m.getType() != Message.TYPE_STATUS) { activity.getMenuInflater().inflate(R.menu.message_context, menu); menu.setHeaderTitle(R.string.message_options); MenuItem copyText = menu.findItem(R.id.copy_text); @@ -360,29 +408,26 @@ public class ConversationFragment extends Fragment { MenuItem copyUrl = menu.findItem(R.id.copy_url); MenuItem downloadImage = menu.findItem(R.id.download_image); MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); - if (this.selectedMessage.getType() != Message.TYPE_TEXT - || this.selectedMessage.getDownloadable() != null) { + if (m.getType() != Message.TYPE_TEXT || m.getDownloadable() != null) { copyText.setVisible(false); } - if (this.selectedMessage.getType() != Message.TYPE_IMAGE - || this.selectedMessage.getDownloadable() != null) { + if (m.getType() != Message.TYPE_IMAGE || m.getDownloadable() != null) { shareImage.setVisible(false); } - if (this.selectedMessage.getStatus() != Message.STATUS_SEND_FAILED) { + if (m.getStatus() != Message.STATUS_SEND_FAILED) { sendAgain.setVisible(false); } - if ((this.selectedMessage.getType() != Message.TYPE_IMAGE && this.selectedMessage - .getDownloadable() == null) - || this.selectedMessage.getImageParams().url == null) { + if ((m.getType() != Message.TYPE_IMAGE && m.getDownloadable() == null) + || m.getImageParams().url == null) { copyUrl.setVisible(false); } - if (this.selectedMessage.getType() != Message.TYPE_TEXT - || this.selectedMessage.getDownloadable() != null - || !this.selectedMessage.bodyContainsDownloadable()) { + if (m.getType() != Message.TYPE_TEXT + || m.getDownloadable() != null + || !m.bodyContainsDownloadable()) { downloadImage.setVisible(false); } - if (this.selectedMessage.getDownloadable() == null - || this.selectedMessage.getDownloadable() instanceof DownloadablePlaceholder) { + if (!((m.getDownloadable() != null && !(m.getDownloadable() instanceof DownloadablePlaceholder)) + || (m.isFileOrImage() && m.getStatus() == Message.STATUS_WAITING))) { cancelTransmission.setVisible(false); } } @@ -419,16 +464,16 @@ public class ConversationFragment extends Fragment { shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, activity.xmppConnectionService.getFileBackend() - .getJingleFileUri(message)); + .getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); shareIntent.setType("image/webp"); activity.startActivity(Intent.createChooser(shareIntent, - getText(R.string.share_with))); + getText(R.string.share_with))); } private void copyText(Message message) { if (activity.copyTextToClipboard(message.getMergedBody(), - R.string.message_text)) { + R.string.message_text)) { Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } @@ -448,21 +493,23 @@ public class ConversationFragment extends Fragment { private void copyUrl(Message message) { if (activity.copyTextToClipboard( - message.getImageParams().url.toString(), R.string.image_url)) { + message.getImageParams().url.toString(), R.string.image_url)) { Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } + } } private void downloadImage(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewConnection(message); + .createNewConnection(message); } private void cancelTransmission(Message message) { Downloadable downloadable = message.getDownloadable(); if (downloadable!=null) { downloadable.cancel(); + } else { + activity.xmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); } } @@ -478,9 +525,9 @@ public class ConversationFragment extends Fragment { mEditMessage.getText().insert(0, nick + ": "); } else { if (mEditMessage.getText().charAt( - mEditMessage.getSelectionStart() - 1) != ' ') { + mEditMessage.getSelectionStart() - 1) != ' ') { nick = " " + nick; - } + } mEditMessage.getText().insert(mEditMessage.getSelectionStart(), nick + " "); } @@ -501,6 +548,7 @@ public class ConversationFragment extends Fragment { } if (this.conversation != null) { this.conversation.setNextMessage(mEditMessage.getText().toString()); + this.conversation.trim(); } this.activity = (ConversationActivity) getActivity(); this.conversation = conversation; @@ -511,8 +559,13 @@ public class ConversationFragment extends Fragment { } this.mEditMessage.setText(""); this.mEditMessage.append(this.conversation.getNextMessage()); - this.messagesView.invalidate(); + this.messagesView.invalidateViews(); updateMessages(); + this.messagesLoaded = true; + int size = this.messageList.size(); + if (size > 0) { + messagesView.setSelection(size - 1); + } } public void updateMessages() { @@ -524,12 +577,30 @@ public class ConversationFragment extends Fragment { final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { final Contact contact = this.conversation.getContact(); - if (this.conversation.isMuted()) { + if (this.conversation.isBlocked()) { + showSnackbar(R.string.contact_blocked, R.string.unblock, + new OnClickListener() { + @Override + public void onClick(final View v) { + v.post(new Runnable() { + @Override + public void run() { + v.setVisibility(View.INVISIBLE); + } + }); + if (conversation.isDomainBlocked()) { + BlockContactDialog.show(getActivity(), ((ConversationActivity) getActivity()).xmppConnectionService, conversation); + } else { + ((ConversationActivity) getActivity()).unblockConversation(conversation); + } + } + }); + } else if (this.conversation.isMuted()) { showSnackbar(R.string.notifications_disabled, R.string.enable, new OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { activity.unmuteConversation(conversation); } }); @@ -542,7 +613,7 @@ public class ConversationFragment extends Fragment { @Override public void onClick(View v) { activity.xmppConnectionService - .createContact(contact); + .createContact(contact); activity.switchToContactDetails(contact); } }); @@ -579,31 +650,24 @@ public class ConversationFragment extends Fragment { default: break; } - } - this.messageList.clear(); - if (this.conversation.getMessages().size() == 0) { - messagesLoaded = false; - } else { - this.messageList.addAll(this.conversation.getMessages()); - messagesLoaded = true; - for (Message message : this.messageList) { - if (message.getEncryption() == Message.ENCRYPTION_PGP - && (message.getStatus() == Message.STATUS_RECEIVED || message + } + conversation.populateWithMessages(ConversationFragment.this.messageList); + for (Message message : this.messageList) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message .getStatus() >= Message.STATUS_SEND) - && message.getDownloadable() == null) { - if (!mEncryptedMessages.contains(message)) { - mEncryptedMessages.add(message); - } + && message.getDownloadable() == null) { + if (!mEncryptedMessages.contains(message)) { + mEncryptedMessages.add(message); } - } - decryptNext(); - updateStatusMessages(); + } } + decryptNext(); + updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); updateChatMsgHint(); if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { - activity.xmppConnectionService.markRead(conversation, true); - activity.updateConversationList(); + activity.sendReadMarkerIfNecessary(conversation); } this.updateSendButton(); } @@ -667,44 +731,44 @@ public class ConversationFragment extends Fragment { switch (c.getContact().getMostAvailableStatus()) { case Presences.CHAT: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); + .setImageResource(R.drawable.ic_action_send_now_online); break; case Presences.ONLINE: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); + .setImageResource(R.drawable.ic_action_send_now_online); break; case Presences.AWAY: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_away); + .setImageResource(R.drawable.ic_action_send_now_away); break; case Presences.XA: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_away); + .setImageResource(R.drawable.ic_action_send_now_away); break; case Presences.DND: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_dnd); + .setImageResource(R.drawable.ic_action_send_now_dnd); break; default: this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + .setImageResource(R.drawable.ic_action_send_now_offline); break; } } else if (c.getMode() == Conversation.MODE_MULTI) { if (c.getMucOptions().online()) { this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_online); + .setImageResource(R.drawable.ic_action_send_now_online); } else { this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + .setImageResource(R.drawable.ic_action_send_now_offline); } } else { this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + .setImageResource(R.drawable.ic_action_send_now_offline); } } else { this.mSendButton - .setImageResource(R.drawable.ic_action_send_now_offline); + .setImageResource(R.drawable.ic_action_send_now_offline); } } @@ -716,8 +780,7 @@ public class ConversationFragment extends Fragment { return; } else { if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) { - this.messageList.add(i + 1, - Message.createStatusMessage(conversation)); + this.messageList.add(i + 1,Message.createStatusMessage(conversation)); return; } } @@ -728,19 +791,30 @@ public class ConversationFragment extends Fragment { protected void makeFingerprintWarning() { if (conversation.smpRequested()) { - showSnackbar(R.string.smp_requested, R.string.verify, clickToVerify); + showSnackbar(R.string.smp_requested, R.string.verify, new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(activity, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("mode",VerifyOTRActivity.MODE_ANSWER_QUESTION); + startActivity(intent); + } + }); } else if (conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!conversation.isOtrFingerprintVerified())) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); - } + } } - protected void showSnackbar(int message, int action, - OnClickListener clickListener) { + protected void showSnackbar(final int message, final int action, + final OnClickListener clickListener) { snackbar.setVisibility(View.VISIBLE); snackbar.setOnClickListener(null); snackbarMessage.setText(message); snackbarMessage.setOnClickListener(null); + snackbarAction.setVisibility(View.VISIBLE); snackbarAction.setText(action); snackbarAction.setOnClickListener(clickListener); } @@ -767,7 +841,7 @@ public class ConversationFragment extends Fragment { @Override public void userInputRequried(PendingIntent pi, - Contact contact) { + Contact contact) { activity.runIntent( pi, ConversationActivity.REQUEST_ENCRYPT_MESSAGE); @@ -791,11 +865,11 @@ public class ConversationFragment extends Fragment { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); xmppService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.sendMessage(message); messageSent(); @@ -806,9 +880,9 @@ public class ConversationFragment extends Fragment { if (conversation.getMucOptions().pgpKeysInUse()) { if (!conversation.getMucOptions().everybodyHasKeys()) { Toast warning = Toast - .makeText(getActivity(), - R.string.missing_public_keys, - Toast.LENGTH_LONG); + .makeText(getActivity(), + R.string.missing_public_keys, + Toast.LENGTH_LONG); warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); warning.show(); } @@ -820,12 +894,12 @@ public class ConversationFragment extends Fragment { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); xmppService.sendMessage(message); messageSent(); } @@ -838,7 +912,7 @@ public class ConversationFragment extends Fragment { } public void showNoPGPKeyDialog(boolean plural, - DialogInterface.OnClickListener listener) { + DialogInterface.OnClickListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIconAttribute(android.R.attr.alertDialogIcon); if (plural) { diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 8fad66cf..ea45b75e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -19,6 +19,7 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; @@ -28,13 +29,12 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.utils.Validator; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; -public class EditAccountActivity extends XmppActivity implements OnAccountUpdate { +public class EditAccountActivity extends XmppActivity implements OnAccountUpdate{ private AutoCompleteTextView mAccountJid; private EditText mPassword; @@ -42,10 +42,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private CheckBox mRegisterNew; private Button mCancelButton; private Button mSaveButton; + private TableLayout mMoreTable; private LinearLayout mStats; private TextView mServerInfoSm; + private TextView mServerInfoRosterVersion; private TextView mServerInfoCarbons; + private TextView mServerInfoMam; + private TextView mServerInfoCSI; + private TextView mServerInfoBlocking; private TextView mServerInfoPep; private TextView mSessionEst; private TextView mOtrFingerprint; @@ -58,22 +63,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private boolean mFetchingAvatar = false; - private OnClickListener mSaveButtonClickListener = new OnClickListener() { + private final OnClickListener mSaveButtonClickListener = new OnClickListener() { @Override - public void onClick(View v) { - if (mAccount != null - && mAccount.getStatus() == Account.State.DISABLED) { + public void onClick(final View v) { + if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) { mAccount.setOption(Account.OPTION_DISABLED, false); xmppConnectionService.updateAccount(mAccount); return; } - if (!Validator.isValidJid(mAccountJid.getText().toString())) { - mAccountJid.setError(getString(R.string.invalid_jid)); - mAccountJid.requestFocus(); - return; - } - boolean registerNewAccount = mRegisterNew.isChecked(); + final boolean registerNewAccount = mRegisterNew.isChecked(); final Jid jid; try { jid = Jid.fromString(mAccountJid.getText().toString()); @@ -82,37 +81,41 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mAccountJid.requestFocus(); return; } - String password = mPassword.getText().toString(); - String passwordConfirm = mPasswordConfirm.getText().toString(); + if (jid.isDomainJid()) { + mAccountJid.setError(getString(R.string.invalid_jid)); + mAccountJid.requestFocus(); + return; + } + final String password = mPassword.getText().toString(); + final String passwordConfirm = mPasswordConfirm.getText().toString(); if (registerNewAccount) { if (!password.equals(passwordConfirm)) { - mPasswordConfirm - .setError(getString(R.string.passwords_do_not_match)); + mPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); mPasswordConfirm.requestFocus(); return; } } if (mAccount != null) { + try { + mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); + mAccount.setServer(jid.getDomainpart()); + } catch (final InvalidJidException ignored) { + return; + } mAccount.setPassword(password); - try { - mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); - mAccount.setServer(jid.getDomainpart()); - } catch (final InvalidJidException ignored) { - } mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.updateAccount(mAccount); } else { - try { - if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { - mAccountJid - .setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); - return; - } - } catch (InvalidJidException e) { - return; - } - mAccount = new Account(jid.toBareJid(), password); + try { + if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + mAccountJid.requestFocus(); + return; + } + } catch (final InvalidJidException e) { + return; + } + mAccount = new Account(jid.toBareJid(), password); mAccount.setOption(Account.OPTION_USETLS, true); mAccount.setOption(Account.OPTION_USECOMPRESSION, true); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); @@ -127,82 +130,81 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }; - private OnClickListener mCancelButtonClickListener = new OnClickListener() { + private final OnClickListener mCancelButtonClickListener = new OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { finish(); } }; - @Override - public void onAccountUpdate() { - runOnUiThread(new Runnable() { - - @Override - public void run() { - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), + @Override + public void onAccountUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + invalidateOptionsMenu(); + if (mAccount != null + && mAccount.getStatus() != Account.State.ONLINE + && mFetchingAvatar) { + startActivity(new Intent(getApplicationContext(), ManageAccountActivity.class)); - finish(); - } else if (jidToEdit == null && mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { - if (!mFetchingAvatar) { - mFetchingAvatar = true; - xmppConnectionService.checkForAvatar(mAccount, - mAvatarFetchCallback); - } - } else { - updateSaveButton(); - } - if (mAccount != null) { - updateAccountInformation(); + finish(); + } else if (jidToEdit == null && mAccount != null + && mAccount.getStatus() == Account.State.ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + xmppConnectionService.checkForAvatar(mAccount, + mAvatarFetchCallback); } + } else { + updateSaveButton(); } - }); - } - private UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() { + if (mAccount != null) { + updateAccountInformation(); + } + } + }); + } + private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() { @Override - public void userInputRequried(PendingIntent pi, Avatar avatar) { + public void userInputRequried(final PendingIntent pi, final Avatar avatar) { finishInitialSetup(avatar); } @Override - public void success(Avatar avatar) { + public void success(final Avatar avatar) { finishInitialSetup(avatar); } @Override - public void error(int errorCode, Avatar avatar) { + public void error(final int errorCode, final Avatar avatar) { finishInitialSetup(avatar); } }; - private TextWatcher mTextWatcher = new TextWatcher() { + private final TextWatcher mTextWatcher = new TextWatcher() { @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { updateSaveButton(); } @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(final Editable s) { } }; - private OnClickListener mAvatarClickListener = new OnClickListener() { + + private final OnClickListener mAvatarClickListener = new OnClickListener() { @Override - public void onClick(View view) { - if (mAccount!=null) { - Intent intent = new Intent(getApplicationContext(), + public void onClick(final View view) { + if (mAccount != null) { + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); intent.putExtra("account", mAccount.getJid().toBareJid().toString()); startActivity(intent); @@ -215,7 +217,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void run() { - Intent intent; + final Intent intent; if (avatar != null) { intent = new Intent(getApplicationContext(), StartConversationActivity.class); @@ -232,13 +234,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected void updateSaveButton() { - if (mAccount != null - && mAccount.getStatus() == Account.State.CONNECTING) { + if (mAccount != null && mAccount.getStatus() == Account.State.CONNECTING) { this.mSaveButton.setEnabled(false); this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null - && mAccount.getStatus() == Account.State.DISABLED) { + } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setText(R.string.enable); @@ -246,8 +246,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); if (jidToEdit != null) { - if (mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { + if (mAccount != null && mAccount.isOnlineAndConnected()) { this.mSaveButton.setText(R.string.save); if (!accountInfoEdited()) { this.mSaveButton.setEnabled(false); @@ -263,10 +262,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected boolean accountInfoEdited() { - return (!this.mAccount.getJid().toBareJid().equals( - this.mAccountJid.getText().toString())) - || (!this.mAccount.getPassword().equals( - this.mPassword.getText().toString())); + return (!this.mAccount.getJid().toBareJid().toString().equals( + this.mAccountJid.getText().toString())) + || (!this.mAccount.getPassword().equals( + this.mPassword.getText().toString())); } @Override @@ -279,7 +278,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_account); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); @@ -292,7 +291,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); this.mStats = (LinearLayout) findViewById(R.id.stats); this.mSessionEst = (TextView) findViewById(R.id.session_est); + this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons); + this.mServerInfoMam = (TextView) findViewById(R.id.server_info_mam); + this.mServerInfoCSI = (TextView) findViewById(R.id.server_info_csi); + this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); @@ -302,29 +305,41 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); - this.mRegisterNew - .setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - mPasswordConfirm.setVisibility(View.VISIBLE); - } else { - mPasswordConfirm.setVisibility(View.GONE); - } - updateSaveButton(); - } - }); + this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more); + final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton buttonView, + final boolean isChecked) { + if (isChecked) { + mPasswordConfirm.setVisibility(View.VISIBLE); + } else { + mPasswordConfirm.setVisibility(View.GONE); + } + updateSaveButton(); + } + }; + this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.editaccount, menu); - MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code); + final MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code); + final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); + final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); + final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); if (mAccount == null) { showQrCode.setVisible(false); + showBlocklist.setVisible(false); + showMoreInfo.setVisible(false); + changePassword.setVisible(false); + } else if (mAccount.getStatus() != Account.State.ONLINE) { + showBlocklist.setVisible(false); + showMoreInfo.setVisible(false); + changePassword.setVisible(false); + } else if (!mAccount.getXmppConnection().getFeatures().blocking()) { + showBlocklist.setVisible(false); } return true; } @@ -333,32 +348,38 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate protected void onStart() { super.onStart(); if (getIntent() != null) { - try { - this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid")); - } catch (final InvalidJidException | NullPointerException ignored) { - this.jidToEdit = null; - } - if (this.jidToEdit != null) { + try { + this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid")); + } catch (final InvalidJidException | NullPointerException ignored) { + this.jidToEdit = null; + } + if (this.jidToEdit != null) { this.mRegisterNew.setVisibility(View.GONE); - getActionBar().setTitle(getString(R.string.account_details)); + if (getActionBar() != null) { + getActionBar().setTitle(getString(R.string.account_details)); + } } else { this.mAvatar.setVisibility(View.GONE); - getActionBar().setTitle(R.string.action_add_account); + if (getActionBar() != null) { + getActionBar().setTitle(R.string.action_add_account); + } } } } @Override protected void onBackendConnected() { - KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, - xmppConnectionService.getKnownHosts()); + final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, + android.R.layout.simple_list_item_1, + xmppConnectionService.getKnownHosts()); if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); updateAccountInformation(); } else if (this.xmppConnectionService.getAccounts().size() == 0) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setDisplayShowHomeEnabled(false); + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setDisplayShowHomeEnabled(false); + } this.mCancelButton.setEnabled(false); this.mCancelButton.setTextColor(getSecondaryTextColor()); } @@ -366,6 +387,27 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate updateSaveButton(); } + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.action_show_block_list: + final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); + showBlocklistIntent.putExtra("account", mAccount.getJid().toString()); + startActivity(showBlocklistIntent); + break; + case R.id.action_server_info_show_more: + mMoreTable.setVisibility(item.isChecked() ? View.GONE : View.VISIBLE); + item.setChecked(!item.isChecked()); + break; + case R.id.action_change_password_on_server: + final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); + changePasswordIntent.putExtra("account", mAccount.getJid().toString()); + startActivity(changePasswordIntent); + break; + } + return super.onOptionsItemSelected(item); + } + private void updateAccountInformation() { this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); this.mPassword.setText(this.mAccount.getPassword()); @@ -381,18 +423,36 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mRegisterNew.setVisibility(View.GONE); this.mRegisterNew.setChecked(false); } - if (this.mAccount.getStatus() == Account.State.ONLINE - && !this.mFetchingAvatar) { + if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { this.mStats.setVisibility(View.VISIBLE); - this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull( - getApplicationContext(), this.mAccount.getXmppConnection() - .getLastSessionEstablished())); + this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() + .getLastSessionEstablished())); Features features = this.mAccount.getXmppConnection().getFeatures(); + if (features.rosterVersioning()) { + this.mServerInfoRosterVersion.setText(R.string.server_info_available); + } else { + this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable); + } if (features.carbons()) { this.mServerInfoCarbons.setText(R.string.server_info_available); } else { this.mServerInfoCarbons - .setText(R.string.server_info_unavailable); + .setText(R.string.server_info_unavailable); + } + if (features.mam()) { + this.mServerInfoMam.setText(R.string.server_info_available); + } else { + this.mServerInfoMam.setText(R.string.server_info_unavailable); + } + if (features.csi()) { + this.mServerInfoCSI.setText(R.string.server_info_available); + } else { + this.mServerInfoCSI.setText(R.string.server_info_unavailable); + } + if (features.blocking()) { + this.mServerInfoBlocking.setText(R.string.server_info_available); + } else { + this.mServerInfoBlocking.setText(R.string.server_info_unavailable); } if (features.sm()) { this.mServerInfoSm.setText(R.string.server_info_available); @@ -409,21 +469,21 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mOtrFingerprintBox.setVisibility(View.VISIBLE); this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint)); this.mOtrFingerprintToClipboardButton - .setVisibility(View.VISIBLE); + .setVisibility(View.VISIBLE); this.mOtrFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { + .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + @Override + public void onClick(final View v) { - if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_otr_fingerprint, - Toast.LENGTH_SHORT).show(); - } + if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_otr_fingerprint, + Toast.LENGTH_SHORT).show(); } - }); + } + }); } else { this.mOtrFingerprintBox.setVisibility(View.GONE); } diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 906a16cc..b3ab5ee6 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -26,21 +26,25 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda protected Account selectedAccount = null; - protected List<Account> accountList = new ArrayList<Account>(); + protected final List<Account> accountList = new ArrayList<>(); protected ListView accountListView; protected AccountAdapter mAccountAdapter; - @Override - public void onAccountUpdate() { + + @Override + public void onAccountUpdate() { + synchronized (this.accountList) { accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); - runOnUiThread(new Runnable() { - - @Override - public void run() { - mAccountAdapter.notifyDataSetChanged(); - } - }); } + runOnUiThread(new Runnable() { + + @Override + public void run() { + invalidateOptionsMenu(); + mAccountAdapter.notifyDataSetChanged(); + } + }); + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -91,6 +95,14 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.manageaccounts, menu); + MenuItem enableAll = menu.findItem(R.id.action_enable_all); + if (!accountsLeftToEnable()) { + enableAll.setVisible(false); + } + MenuItem disableAll = menu.findItem(R.id.action_disable_all); + if (!accountsLeftToDisable()) { + disableAll.setVisible(false); + } return true; } @@ -120,12 +132,18 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.action_add_account: - startActivity(new Intent(getApplicationContext(), - EditAccountActivity.class)); - break; - default: - break; + case R.id.action_add_account: + startActivity(new Intent(getApplicationContext(), + EditAccountActivity.class)); + break; + case R.id.action_disable_all: + disableAllAccounts(); + break; + case R.id.action_enable_all: + enableAllAccounts(); + break; + default: + break; } return super.onOptionsItemSelected(item); } @@ -158,6 +176,56 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda startActivity(intent); } + private void disableAllAccounts() { + List<Account> list = new ArrayList<>(); + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + list.add(account); + } + } + } + for(Account account : list) { + disableAccount(account); + } + } + + private boolean accountsLeftToDisable() { + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + return true; + } + } + return false; + } + } + + private boolean accountsLeftToEnable() { + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (account.isOptionSet(Account.OPTION_DISABLED)) { + return true; + } + } + return false; + } + } + + private void enableAllAccounts() { + List<Account> list = new ArrayList<>(); + synchronized (this.accountList) { + for (Account account : this.accountList) { + if (account.isOptionSet(Account.OPTION_DISABLED)) { + list.add(account); + } + } + } + for(Account account : list) { + enableAccount(account); + } + } + private void disableAccount(Account account) { account.setOption(Account.OPTION_DISABLED, true); xmppConnectionService.updateAccount(account); diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 609dc280..5e770376 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -1,17 +1,5 @@ package eu.siacs.conversations.ui; -import java.util.ArrayList; -import java.util.List; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.ui.adapter.ConversationAdapter; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; -import eu.siacs.conversations.xmpp.jid.Jid; - import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; @@ -25,10 +13,27 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; +import java.io.UnsupportedEncodingException; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.ui.adapter.ConversationAdapter; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + public class ShareWithActivity extends XmppActivity { private class Share { public Uri uri; + public boolean image; public String account; public String contact; public String text; @@ -38,9 +43,9 @@ public class ShareWithActivity extends XmppActivity { private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; private ListView mListView; - private List<Conversation> mConversations = new ArrayList<Conversation>(); + private List<Conversation> mConversations = new ArrayList<>(); - private UiCallback<Message> attachImageCallback = new UiCallback<Message>() { + private UiCallback<Message> attachFileCallback = new UiCallback<Message>() { @Override public void userInputRequried(PendingIntent pi, Message object) { @@ -78,11 +83,12 @@ public class ShareWithActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } setContentView(R.layout.share_with); setTitle(getString(R.string.title_activity_sharewith)); @@ -114,10 +120,10 @@ public class ShareWithActivity extends XmppActivity { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_add: - Intent intent = new Intent(getApplicationContext(), + final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION); return true; @@ -127,16 +133,20 @@ public class ShareWithActivity extends XmppActivity { @Override public void onStart() { - if (getIntent().getType() != null - && getIntent().getType().startsWith("image/")) { - this.share.uri = (Uri) getIntent().getParcelableExtra( - Intent.EXTRA_STREAM); + final String type = getIntent().getType(); + if (type != null && !type.startsWith("text/")) { + this.share.uri = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM); + try { + this.share.image = type.startsWith("image/") + || URLConnection.guessContentTypeFromName(this.share.uri.toString()).startsWith("image/"); + } catch (final StringIndexOutOfBoundsException ignored) { + this.share.image = false; + } } else { this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT); } if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations( - mConversations, this.share.uri == null); + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uri == null); } super.onStart(); } @@ -177,12 +187,21 @@ public class ShareWithActivity extends XmppActivity { selectPresence(conversation, new OnPresenceSelected() { @Override public void onPresenceSelected() { - Toast.makeText(getApplicationContext(), - getText(R.string.preparing_image), - Toast.LENGTH_LONG).show(); - ShareWithActivity.this.xmppConnectionService + if (share.image) { + Toast.makeText(getApplicationContext(), + getText(R.string.preparing_image), + Toast.LENGTH_LONG).show(); + ShareWithActivity.this.xmppConnectionService .attachImageToConversation(conversation, share.uri, - attachImageCallback); + attachFileCallback); + } else { + Toast.makeText(getApplicationContext(), + getText(R.string.preparing_file), + Toast.LENGTH_LONG).show(); + ShareWithActivity.this.xmppConnectionService + .attachFileToConversation(conversation, share.uri, + attachFileCallback); + } switchToConversation(conversation, null, true); finish(); } @@ -195,4 +214,4 @@ public class ShareWithActivity extends XmppActivity { } -}
\ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index c8b41821..209c0a7b 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -38,6 +38,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; +import android.widget.Checkable; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; @@ -53,19 +54,20 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.adapter.ListItemAdapter; -import eu.siacs.conversations.utils.Validator; +import eu.siacs.conversations.utils.XmppUri; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class StartConversationActivity extends XmppActivity implements OnRosterUpdate { +public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist { public int conference_context_id; public int contact_context_id; @@ -133,7 +135,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { - getActionBar().setSelectedNavigationItem(position); + if (getActionBar() != null) { + getActionBar().setSelectedNavigationItem(position); + } onTabChanged(); } }; @@ -146,16 +150,27 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @Override public void beforeTextChanged(CharSequence s, int start, int count, - int after) { + int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, - int count) { + int count) { } }; private MenuItem mMenuSearchView; private String mInitialJid; + private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() { + @Override + public void onTagClicked(String tag) { + if (mMenuSearchView != null) { + mMenuSearchView.expandActionView(); + mSearchEditText.setText(""); + mSearchEditText.append(tag); + filter(tag); + } + } + }; @Override public void onRosterUpdate() { @@ -179,9 +194,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); mContactsTab = actionBar.newTab().setText(R.string.contacts) - .setTabListener(mTabListener); + .setTabListener(mTabListener); mConferencesTab = actionBar.newTab().setText(R.string.conferences) - .setTabListener(mTabListener); + .setTabListener(mTabListener); actionBar.addTab(mContactsTab); actionBar.addTab(mConferencesTab); @@ -207,35 +222,36 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU mConferenceListFragment.setListAdapter(mConferenceAdapter); mConferenceListFragment.setContextMenu(R.menu.conference_context); mConferenceListFragment - .setOnListItemClickListener(new OnItemClickListener() { + .setOnListItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - openConversationForBookmark(position); - } - }); + @Override + public void onItemClick(AdapterView<?> arg0, View arg1, + int position, long arg3) { + openConversationForBookmark(position); + } + }); mContactsAdapter = new ListItemAdapter(this, contacts); + ((ListItemAdapter) mContactsAdapter).setOnTagClickedListener(this.mOnTagClickedListener); mContactsListFragment.setListAdapter(mContactsAdapter); mContactsListFragment.setContextMenu(R.menu.contact_context); mContactsListFragment - .setOnListItemClickListener(new OnItemClickListener() { + .setOnListItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - openConversationForContact(position); - } - }); + @Override + public void onItemClick(AdapterView<?> arg0, View arg1, + int position, long arg3) { + openConversationForContact(position); + } + }); } protected void openConversationForContact(int position) { Contact contact = (Contact) contacts.get(position); Conversation conversation = xmppConnectionService - .findOrCreateConversation(contact.getAccount(), - contact.getJid(), false); + .findOrCreateConversation(contact.getAccount(), + contact.getJid(), false); switchToConversation(conversation); } @@ -251,8 +267,8 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void openConversationForBookmark(int position) { Bookmark bookmark = (Bookmark) conferences.get(position); Conversation conversation = xmppConnectionService - .findOrCreateConversation(bookmark.getAccount(), - bookmark.getJid(), true); + .findOrCreateConversation(bookmark.getAccount(), + bookmark.getJid(), true); conversation.setBookmark(bookmark); if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); @@ -270,14 +286,19 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU switchToContactDetails(contact); } + protected void toggleContactBlock() { + final int position = contact_context_id; + BlockContactDialog.show(this, xmppConnectionService, (Contact)contacts.get(position)); + } + protected void deleteContact() { - int position = contact_context_id; + final int position = contact_context_id; final Contact contact = (Contact) contacts.get(position); - AlertDialog.Builder builder = new AlertDialog.Builder(this); + final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage(getString(R.string.remove_contact_text, - contact.getJid())); + contact.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -287,7 +308,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } }); builder.create().show(); - } protected void deleteConference() { @@ -298,7 +318,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); builder.setMessage(getString(R.string.remove_bookmark_text, - bookmark.getJid())); + bookmark.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -318,13 +338,10 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.create_contact); - View dialogView = getLayoutInflater().inflate( - R.layout.create_contact_dialog, null); + View dialogView = getLayoutInflater().inflate(R.layout.create_contact_dialog, null); final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); - final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView - .findViewById(R.id.jid); - jid.setAdapter(new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, mKnownHosts)); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + jid.setAdapter(new KnownHostsAdapter(this,android.R.layout.simple_list_item_1, mKnownHosts)); if (prefilledJid != null) { jid.append(prefilledJid); if (fingerprint!=null) { @@ -344,41 +361,37 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { if (!xmppConnectionServiceBound) { return; } - if (Validator.isValidJid(jid.getText().toString())) { - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner - .getSelectedItem()); - } catch (final InvalidJidException e) { - return; - } - final Jid contactJid; - try { - contactJid = Jid.fromString(jid.getText().toString()); - } catch (final InvalidJidException e) { - return; - } - Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); - return; - } - Contact contact = account.getRoster().getContact(contactJid); - if (contact.showInRoster()) { - jid.setError(getString(R.string.contact_already_exists)); - } else { - contact.addOtrFingerprint(fingerprint); - xmppConnectionService.createContact(contact); - dialog.dismiss(); - switchToConversation(contact); - } - } else { + final Jid accountJid; + try { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } catch (final InvalidJidException e) { + return; + } + final Jid contactJid; + try { + contactJid = Jid.fromString(jid.getText().toString()); + } catch (final InvalidJidException e) { jid.setError(getString(R.string.invalid_jid)); + return; + } + final Account account = xmppConnectionService + .findAccountByJid(accountJid); + if (account == null) { + dialog.dismiss(); + return; + } + final Contact contact = account.getRoster().getContact(contactJid); + if (contact.showInRoster()) { + jid.setError(getString(R.string.contact_already_exists)); + } else { + contact.addOtrFingerprint(fingerprint); + xmppConnectionService.createContact(contact); + dialog.dismiss(); + switchToConversation(contact); } } }); @@ -386,22 +399,19 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @SuppressLint("InflateParams") - protected void showJoinConferenceDialog(String prefilledJid) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + protected void showJoinConferenceDialog(final String prefilledJid) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.join_conference); - View dialogView = getLayoutInflater().inflate( - R.layout.join_conference_dialog, null); + final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null); final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); - final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView - .findViewById(R.id.jid); - jid.setAdapter(new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, mKnownConferenceHosts)); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + jid.setAdapter(new KnownHostsAdapter(this,android.R.layout.simple_list_item_1, mKnownConferenceHosts)); if (prefilledJid != null) { jid.append(prefilledJid); } populateAccountSpinner(spinner); - final CheckBox bookmarkCheckBox = (CheckBox) dialogView - .findViewById(R.id.bookmark); + final Checkable bookmarkCheckBox = (CheckBox) dialogView + .findViewById(R.id.bookmark); builder.setView(dialogView); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.join, null); @@ -411,62 +421,59 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { if (!xmppConnectionServiceBound) { return; } - if (Validator.isValidJid(jid.getText().toString())) { - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } catch (final InvalidJidException e) { - return; - } - final Jid conferenceJid; - try { - conferenceJid = Jid.fromString(jid.getText().toString()); - } catch (final InvalidJidException e) { - return; // TODO: Do some error handling... - } - Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); - return; - } - if (bookmarkCheckBox.isChecked()) { - if (account.hasBookmarkFor(conferenceJid)) { - jid.setError(getString(R.string.bookmark_already_exists)); - } else { - Bookmark bookmark = new Bookmark(account, - conferenceJid); - bookmark.setAutojoin(true); - account.getBookmarks().add(bookmark); - xmppConnectionService - .pushBookmarks(account); - Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); - conversation.setBookmark(bookmark); - if (!conversation.getMucOptions().online()) { - xmppConnectionService - .joinMuc(conversation); - } - dialog.dismiss(); - switchToConversation(conversation); - } + final Jid accountJid; + try { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } catch (final InvalidJidException e) { + return; + } + final Jid conferenceJid; + try { + conferenceJid = Jid.fromString(jid.getText().toString()); + } catch (final InvalidJidException e) { + jid.setError(getString(R.string.invalid_jid)); + return; + } + final Account account = xmppConnectionService + .findAccountByJid(accountJid); + if (account == null) { + dialog.dismiss(); + return; + } + if (bookmarkCheckBox.isChecked()) { + if (account.hasBookmarkFor(conferenceJid)) { + jid.setError(getString(R.string.bookmark_already_exists)); } else { - Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + final Bookmark bookmark = new Bookmark(account, + conferenceJid); + bookmark.setAutojoin(true); + account.getBookmarks().add(bookmark); + xmppConnectionService + .pushBookmarks(account); + final Conversation conversation = xmppConnectionService + .findOrCreateConversation(account, + conferenceJid, true); + conversation.setBookmark(bookmark); if (!conversation.getMucOptions().online()) { - xmppConnectionService.joinMuc(conversation); + xmppConnectionService + .joinMuc(conversation); } dialog.dismiss(); switchToConversation(conversation); } } else { - jid.setError(getString(R.string.invalid_jid)); + final Conversation conversation = xmppConnectionService + .findOrCreateConversation(account, + conferenceJid, true); + if (!conversation.getMucOptions().online()) { + xmppConnectionService.joinMuc(conversation); + } + dialog.dismiss(); + switchToConversation(conversation); } } }); @@ -474,8 +481,8 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void switchToConversation(Contact contact) { Conversation conversation = xmppConnectionService - .findOrCreateConversation(contact.getAccount(), - contact.getJid(), false); + .findOrCreateConversation(contact.getAccount(), + contact.getJid(), false); switchToConversation(conversation); } @@ -491,14 +498,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU this.mOptionsMenu = menu; getMenuInflater().inflate(R.menu.start_conversation, menu); MenuItem menuCreateContact = menu - .findItem(R.id.action_create_contact); + .findItem(R.id.action_create_contact); MenuItem menuCreateConference = menu - .findItem(R.id.action_join_conference); + .findItem(R.id.action_join_conference); mMenuSearchView = menu.findItem(R.id.action_search); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener); View mSearchView = mMenuSearchView.getActionView(); mSearchEditText = (EditText) mSearchView - .findViewById(R.id.search_field); + .findViewById(R.id.search_field); mSearchEditText.addTextChangedListener(mSearchTextWatcher); if (getActionBar().getSelectedNavigationIndex() == 0) { menuCreateConference.setVisible(false); @@ -567,7 +574,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } this.mKnownHosts = xmppConnectionService.getKnownHosts(); this.mKnownConferenceHosts = xmppConnectionService - .getKnownConferenceHosts(); + .getKnownConferenceHosts(); if (this.mPendingInvite != null) { mPendingInvite.invite(); this.mPendingInvite = null; @@ -609,7 +616,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU byte[] payload = record.getPayload(); if (payload[0] == 0) { return new Invite(Uri.parse(new String(Arrays.copyOfRange( - payload, 1, payload.length)))).invite(); + payload, 1, payload.length)))).invite(); } } } @@ -690,16 +697,29 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU invalidateOptionsMenu(); } + @Override + public void OnUpdateBlocklist(final Status status) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (mSearchEditText != null) { + filter(mSearchEditText.getText().toString()); + } + } + }); + } + public static class MyListFragment extends ListFragment { private AdapterView.OnItemClickListener mOnItemClickListener; private int mResContextMenu; - public void setContextMenu(int res) { + public void setContextMenu(final int res) { this.mResContextMenu = res; } @Override - public void onListItemClick(ListView l, View v, int position, long id) { + public void onListItemClick(final ListView l, final View v, final int position, final long id) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(l, v, position, id); } @@ -710,28 +730,38 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(final View view, final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); registerForContextMenu(getListView()); getListView().setFastScrollEnabled(true); } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); - StartConversationActivity activity = (StartConversationActivity) getActivity(); + final StartConversationActivity activity = (StartConversationActivity) getActivity(); activity.getMenuInflater().inflate(mResContextMenu, menu); - AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { activity.conference_context_id = acmi.position; } else { activity.contact_context_id = acmi.position; + final Blockable contact = (Contact) activity.contacts.get(acmi.position); + + final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); + if (blockUnblockItem != null) { + if (contact.isBlocked()) { + blockUnblockItem.setTitle(R.string.unblock_contact); + } else { + blockUnblockItem.setTitle(R.string.block_contact); + } + } } } @Override - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(final MenuItem item) { StartConversationActivity activity = (StartConversationActivity) getActivity(); switch (item.getItemId()) { case R.id.context_start_conversation: @@ -740,6 +770,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU case R.id.context_contact_details: activity.openDetailsForContact(); break; + case R.id.context_contact_block_unblock: + activity.toggleContactBlock(); + break; case R.id.context_delete_contact: activity.deleteContact(); break; @@ -755,11 +788,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private class Invite extends XmppUri { - public Invite(Uri uri) { + public Invite(final Uri uri) { super(uri); } - public Invite(String uri) { + public Invite(final String uri) { super(uri); } diff --git a/src/main/java/eu/siacs/conversations/ui/TimePreference.java b/src/main/java/eu/siacs/conversations/ui/TimePreference.java new file mode 100644 index 00000000..e32b068c --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/TimePreference.java @@ -0,0 +1,105 @@ +package eu.siacs.conversations.ui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.DialogPreference; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TimePicker; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; + +public class TimePreference extends DialogPreference implements Preference.OnPreferenceChangeListener { + private TimePicker picker = null; + public final static long DEFAULT_VALUE = 0; + + public TimePreference(final Context context, final AttributeSet attrs) { + super(context, attrs, 0); + this.setOnPreferenceChangeListener(this); + } + + protected void setTime(final long time) { + persistLong(time); + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + + protected void updateSummary(final long time) { + final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getContext()); + final Date date = new Date(time); + setSummary(dateFormat.format(date.getTime())); + } + + @Override + protected View onCreateDialogView() { + picker = new TimePicker(getContext()); + picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(getContext())); + return picker; + } + + protected Calendar getPersistedTime() { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(getPersistedLong(DEFAULT_VALUE)); + + return c; + } + + @SuppressWarnings("NullableProblems") + @Override + protected void onBindDialogView(final View v) { + super.onBindDialogView(v); + final Calendar c = getPersistedTime(); + + picker.setCurrentHour(c.get(Calendar.HOUR_OF_DAY)); + picker.setCurrentMinute(c.get(Calendar.MINUTE)); + } + + @Override + protected void onDialogClosed(final boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + final Calendar c = Calendar.getInstance(); + c.set(Calendar.MINUTE, picker.getCurrentMinute()); + c.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour()); + + + if (!callChangeListener(c.getTimeInMillis())) { + return; + } + + setTime(c.getTimeInMillis()); + } + } + + @Override + protected Object onGetDefaultValue(final TypedArray a, final int index) { + return a.getInteger(index, 0); + } + + @Override + protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) { + long time; + if (defaultValue == null) { + time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; + } else if (defaultValue instanceof Long) { + time = restorePersistedValue ? getPersistedLong((Long) defaultValue) : (Long) defaultValue; + } else if (defaultValue instanceof Calendar) { + time = restorePersistedValue ? getPersistedLong(((Calendar)defaultValue).getTimeInMillis()) : ((Calendar)defaultValue).getTimeInMillis(); + } else { + time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE; + } + + setTime(time); + updateSummary(time); + } + + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + ((TimePreference) preference).updateSummary((Long)newValue); + return true; + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java index e5775ab0..c33decd8 100644 --- a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java @@ -1,15 +1,15 @@ package eu.siacs.conversations.ui; +import android.app.ActionBar; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.RelativeLayout; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -32,57 +32,56 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { public static final String ACTION_VERIFY_CONTACT = "verify_contact"; + public static final int MODE_SCAN_FINGERPRINT = - 0x0502; + public static final int MODE_ASK_QUESTION = 0x0503; + public static final int MODE_ANSWER_QUESTION = 0x0504; + public static final int MODE_MANUAL_VERIFICATION = 0x0505; - private RelativeLayout mVerificationAreaOne; - private RelativeLayout mVerificationAreaTwo; - private TextView mErrorNoSession; - private TextView mRemoteJid; + private LinearLayout mManualVerificationArea; + private LinearLayout mSmpVerificationArea; private TextView mRemoteFingerprint; private TextView mYourFingerprint; - private EditText mSharedSecretHint; - private EditText mSharedSecretSecret; - private Button mButtonScanQrCode; - private Button mButtonShowQrCode; - private Button mButtonSharedSecretPositive; - private Button mButtonSharedSecretNegative; + private TextView mVerificationExplain; private TextView mStatusMessage; + private TextView mSharedSecretHint; + private EditText mSharedSecretHintEditable; + private EditText mSharedSecretSecret; + private Button mLeftButton; + private Button mRightButton; private Account mAccount; private Conversation mConversation; + private int mode = MODE_MANUAL_VERIFICATION; + private XmppUri mPendingUri = null; private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int click) { mConversation.verifyOtrFingerprint(); - updateView(); xmppConnectionService.syncRosterToDisk(mConversation.getAccount()); + Toast.makeText(VerifyOTRActivity.this,R.string.verified,Toast.LENGTH_SHORT).show(); + finish(); } }; - private View.OnClickListener mShowQrCodeListener = new View.OnClickListener() { - @Override - public void onClick(final View view) { - showQrCode(); - } - }; - - private View.OnClickListener mScanQrCodeListener = new View.OnClickListener() { - - @Override - public void onClick(View view) { - new IntentIntegrator(VerifyOTRActivity.this).initiateScan(); - } - - }; - private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() { @Override public void onClick(final View view) { if (isAccountOnline()) { - final String question = mSharedSecretHint.getText().toString(); + final String question = mSharedSecretHintEditable.getText().toString(); final String secret = mSharedSecretSecret.getText().toString(); - initSmp(question, secret); - updateView(); + if (question.trim().isEmpty()) { + mSharedSecretHintEditable.requestFocus(); + mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty)); + } else if (secret.trim().isEmpty()) { + mSharedSecretSecret.requestFocus(); + mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty)); + } else { + mSharedSecretSecret.setError(null); + mSharedSecretHintEditable.setError(null); + initSmp(question, secret); + updateView(); + } } } }; @@ -100,7 +99,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer @Override public void onClick(View view) { if (isAccountOnline()) { - final String question = mSharedSecretHint.getText().toString(); + final String question = mSharedSecretHintEditable.getText().toString(); final String secret = mSharedSecretSecret.getText().toString(); respondSmp(question, secret); updateView(); @@ -124,14 +123,14 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } }; - private XmppUri mPendingUri = null; - protected boolean initSmp(final String question, final String secret) { final Session session = mConversation.getOtrSession(); if (session!=null) { try { session.initSmp(question, secret); mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED; + mConversation.smp().secret = secret; + mConversation.smp().hint = question; return true; } catch (OtrException e) { return false; @@ -172,15 +171,17 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } } - protected void verifyWithUri(XmppUri uri) { + protected boolean verifyWithUri(XmppUri uri) { Contact contact = mConversation.getContact(); if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.getFingerprint() != null) { contact.addOtrFingerprint(uri.getFingerprint()); Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show(); updateView(); xmppConnectionService.syncRosterToDisk(contact.getAccount()); + return true; } else { Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show(); + return false; } } @@ -194,7 +195,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } protected boolean handleIntent(Intent intent) { - if (intent.getAction().equals(ACTION_VERIFY_CONTACT)) { + if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { try { this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account"))); } catch (final InvalidJidException ignored) { @@ -208,6 +209,11 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } catch (final InvalidJidException ignored) { return false; } + this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION); + if (this.mode == MODE_SCAN_FINGERPRINT) { + new IntentIntegrator(this).initiateScan(); + return false; + } return true; } else { return false; @@ -223,9 +229,12 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer XmppUri uri = new XmppUri(data); if (xmppConnectionServiceBound) { verifyWithUri(uri); + finish(); } else { this.mPendingUri = uri; } + } else { + finish(); } } super.onActivityResult(requestCode, requestCode, intent); @@ -234,84 +243,139 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer @Override protected void onBackendConnected() { if (handleIntent(getIntent())) { - if (mPendingUri!=null) { - verifyWithUri(mPendingUri); - mPendingUri = null; - } updateView(); + } else if (mPendingUri!=null) { + verifyWithUri(mPendingUri); + finish(); + mPendingUri = null; } + setIntent(null); } protected void updateView() { if (this.mConversation.hasValidOtrSession()) { + final ActionBar actionBar = getActionBar(); + this.mVerificationExplain.setText(R.string.no_otr_session_found); invalidateOptionsMenu(); - this.mVerificationAreaOne.setVisibility(View.VISIBLE); - this.mVerificationAreaTwo.setVisibility(View.VISIBLE); - this.mErrorNoSession.setVisibility(View.GONE); - this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint())); - this.mRemoteFingerprint.setText(this.mConversation.getOtrFingerprint()); - this.mRemoteJid.setText(this.mConversation.getContact().getJid().toBareJid().toString()); - Conversation.Smp smp = mConversation.smp(); - Session session = mConversation.getOtrSession(); - if (mConversation.isOtrFingerprintVerified()) { - deactivateButton(mButtonScanQrCode, R.string.verified); - } else { - activateButton(mButtonScanQrCode, R.string.scan_qr_code, mScanQrCodeListener); + switch(this.mode) { + case MODE_ASK_QUESTION: + if (actionBar != null ) { + actionBar.setTitle(R.string.ask_question); + } + this.updateViewAskQuestion(); + break; + case MODE_ANSWER_QUESTION: + if (actionBar != null ) { + actionBar.setTitle(R.string.smp_requested); + } + this.updateViewAnswerQuestion(); + break; + case MODE_MANUAL_VERIFICATION: + default: + if (actionBar != null ) { + actionBar.setTitle(R.string.manually_verify); + } + this.updateViewManualVerification(); + break; } - if (smp.status == Conversation.Smp.STATUS_NONE) { - activateButton(mButtonSharedSecretPositive, R.string.create, mCreateSharedSecretListener); - deactivateButton(mButtonSharedSecretNegative, R.string.cancel); - this.mSharedSecretHint.setFocusableInTouchMode(true); - this.mSharedSecretSecret.setFocusableInTouchMode(true); - this.mSharedSecretSecret.setText(""); - this.mSharedSecretHint.setText(""); - this.mSharedSecretHint.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.setVisibility(View.VISIBLE); + } else { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.GONE); + } + } + + protected void updateViewManualVerification() { + this.mVerificationExplain.setText(R.string.manual_verification_explanation); + this.mManualVerificationArea.setVisibility(View.VISIBLE); + this.mSmpVerificationArea.setVisibility(View.GONE); + this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint())); + this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint())); + if (this.mConversation.isOtrFingerprintVerified()) { + deactivateButton(this.mRightButton,R.string.verified); + activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); + } else { + activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); + activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() { + @Override + public void onClick(View view) { + showManuallyVerifyDialog(); + } + }); + } + } + + protected void updateViewAskQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_question); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_WE_REQUESTED: this.mStatusMessage.setVisibility(View.GONE); - } else if (smp.status == Conversation.Smp.STATUS_CONTACT_REQUESTED) { - this.mSharedSecretHint.setFocusable(false); - this.mSharedSecretHint.setText(smp.hint); - this.mSharedSecretSecret.setFocusableInTouchMode(true); - this.mSharedSecretHint.setVisibility(View.VISIBLE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint); + this.mSharedSecretSecret.setText(this.mConversation.smp().secret); + this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener); + this.deactivateButton(this.mRightButton, R.string.in_progress); + break; + case Conversation.Smp.STATUS_FAILED: this.mStatusMessage.setVisibility(View.GONE); - deactivateButton(mButtonSharedSecretNegative, R.string.cancel); - activateButton(mButtonSharedSecretPositive, R.string.respond, mRespondSharedSecretListener); - } else if (smp.status == Conversation.Smp.STATUS_FAILED) { - activateButton(mButtonSharedSecretNegative, R.string.cancel, mFinishListener); - activateButton(mButtonSharedSecretPositive, R.string.try_again, mRetrySharedSecretListener); - this.mSharedSecretHint.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretSecret.setText(""); this.mSharedSecretSecret.setVisibility(View.GONE); this.mStatusMessage.setVisibility(View.VISIBLE); - this.mStatusMessage.setText(R.string.secrets_do_not_match); - this.mStatusMessage.setTextColor(getWarningTextColor()); - } else if (smp.status == Conversation.Smp.STATUS_FINISHED) { - this.mSharedSecretHint.setText(""); + this.deactivateButton(this.mLeftButton, R.string.cancel); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + default: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHintEditable.setVisibility(View.VISIBLE); + this.mSharedSecretSecret.setVisibility(View.VISIBLE); + this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener); + this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener); + break; + } + } + + protected void updateViewAnswerQuestion() { + this.mManualVerificationArea.setVisibility(View.GONE); + this.mSmpVerificationArea.setVisibility(View.VISIBLE); + this.mVerificationExplain.setText(R.string.smp_explain_answer); + this.mSharedSecretHintEditable.setVisibility(View.GONE); + this.mSharedSecretHint.setVisibility(View.VISIBLE); + this.deactivateButton(this.mLeftButton, R.string.cancel); + final int smpStatus = this.mConversation.smp().status; + switch (smpStatus) { + case Conversation.Smp.STATUS_CONTACT_REQUESTED: + this.mStatusMessage.setVisibility(View.GONE); + this.mSharedSecretHint.setText(this.mConversation.smp().hint); + this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener); + break; + case Conversation.Smp.STATUS_VERIFIED: + this.mSharedSecretHintEditable.setText(""); + this.mSharedSecretHintEditable.setVisibility(View.GONE); this.mSharedSecretHint.setVisibility(View.GONE); this.mSharedSecretSecret.setText(""); this.mSharedSecretSecret.setVisibility(View.GONE); this.mStatusMessage.setVisibility(View.VISIBLE); - this.mStatusMessage.setTextColor(getPrimaryColor()); - deactivateButton(mButtonSharedSecretNegative, R.string.cancel); - if (mConversation.isOtrFingerprintVerified()) { - activateButton(mButtonSharedSecretPositive, R.string.finish, mFinishListener); - this.mStatusMessage.setText(R.string.verified); - } else { - activateButton(mButtonSharedSecretPositive,R.string.reset,mRetrySharedSecretListener); - this.mStatusMessage.setText(R.string.secret_accepted); - } - } else if (session != null && session.isSmpInProgress()) { - deactivateButton(mButtonSharedSecretPositive, R.string.in_progress); - activateButton(mButtonSharedSecretNegative, R.string.cancel, mCancelSharedSecretListener); - this.mSharedSecretHint.setVisibility(View.VISIBLE); - this.mSharedSecretSecret.setVisibility(View.VISIBLE); - this.mSharedSecretHint.setFocusable(false); - this.mSharedSecretSecret.setFocusable(false); - } - } else { - this.mVerificationAreaOne.setVisibility(View.GONE); - this.mVerificationAreaTwo.setVisibility(View.GONE); - this.mErrorNoSession.setVisibility(View.VISIBLE); + this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener); + break; + case Conversation.Smp.STATUS_FAILED: + default: + this.mSharedSecretSecret.requestFocus(); + this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match)); + this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener); + break; } } @@ -334,41 +398,25 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer super.onCreate(savedInstanceState); setContentView(R.layout.activity_verify_otr); this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint); - this.mRemoteJid = (TextView) findViewById(R.id.remote_jid); this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint); - this.mButtonSharedSecretNegative = (Button) findViewById(R.id.button_shared_secret_negative); - this.mButtonSharedSecretPositive = (Button) findViewById(R.id.button_shared_secret_positive); - this.mButtonScanQrCode = (Button) findViewById(R.id.button_scan_qr_code); - this.mButtonShowQrCode = (Button) findViewById(R.id.button_show_qr_code); - this.mButtonShowQrCode.setOnClickListener(this.mShowQrCodeListener); + this.mLeftButton = (Button) findViewById(R.id.left_button); + this.mRightButton = (Button) findViewById(R.id.right_button); + this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation); + this.mStatusMessage = (TextView) findViewById(R.id.status_message); this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret); - this.mSharedSecretHint = (EditText) findViewById(R.id.shared_secret_hint); - this.mStatusMessage= (TextView) findViewById(R.id.status_message); - this.mVerificationAreaOne = (RelativeLayout) findViewById(R.id.verification_area_one); - this.mVerificationAreaTwo = (RelativeLayout) findViewById(R.id.verification_area_two); - this.mErrorNoSession = (TextView) findViewById(R.id.error_no_session); + this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable); + this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint); + this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area); + this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area); } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.verify_otr, menu); - if (mConversation != null && mConversation.isOtrFingerprintVerified()) { - MenuItem manuallyVerifyItem = menu.findItem(R.id.manually_verify); - manuallyVerifyItem.setVisible(false); - } return true; } - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (menuItem.getItemId() == R.id.manually_verify) { - showManuallyVerifyDialog(); - return true; - } else { - return super.onOptionsItemSelected(menuItem); - } - } - private void showManuallyVerifyDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.manually_verify); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 5fba1664..69dd47e7 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -71,6 +71,7 @@ import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -94,6 +95,7 @@ public abstract class XmppActivity extends Activity { protected boolean mUseSubject = true; private DisplayMetrics metrics; + protected int mTheme; protected interface OnValueEdited { public void onValueEdited(String value); @@ -198,7 +200,7 @@ public abstract class XmppActivity extends Activity { xmppConnectionServiceBound = false; } stopService(new Intent(XmppActivity.this, - XmppConnectionService.class)); + XmppConnectionService.class)); finish(); } }); @@ -208,13 +210,13 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(DialogInterface dialog, int which) { Uri uri = Uri - .parse("market://details?id=org.sufficientlysecure.keychain"); + .parse("market://details?id=org.sufficientlysecure.keychain"); Intent marketIntent = new Intent(Intent.ACTION_VIEW, uri); PackageManager manager = getApplicationContext() - .getPackageManager(); + .getPackageManager(); List<ResolveInfo> infos = manager - .queryIntentActivities(marketIntent, 0); + .queryIntentActivities(marketIntent, 0); if (infos.size() > 0) { startActivity(marketIntent); } else { @@ -244,6 +246,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnMucRosterUpdate) { this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); + } } protected void unregisterListeners() { @@ -259,9 +264,13 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnMucRosterUpdate) { this.xmppConnectionService.removeOnMucRosterUpdateListener(); } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.removeOnUpdateBlocklistListener(); + } } - public boolean onOptionsItemSelected(MenuItem item) { + @Override + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); @@ -292,15 +301,14 @@ public abstract class XmppActivity extends Activity { mPrimaryColor = getResources().getColor(R.color.primary); mSecondaryBackgroundColor = getResources().getColor( R.color.secondarybackground); - if (getPreferences().getBoolean("use_larger_font", false)) { - setTheme(R.style.ConversationsTheme_LargerText); - } + this.mTheme = findTheme(); + setTheme(this.mTheme); mUseSubject = getPreferences().getBoolean("use_subject", true); } protected SharedPreferences getPreferences() { return PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); + .getDefaultSharedPreferences(getApplicationContext()); } public boolean useSubjectToIdentifyConference() { @@ -312,7 +320,7 @@ public abstract class XmppActivity extends Activity { } public void switchToConversation(Conversation conversation, String text, - boolean newTask) { + boolean newTask) { switchToConversation(conversation,text,null,newTask); } @@ -372,7 +380,7 @@ public abstract class XmppActivity extends Activity { @Override public void userInputRequried(PendingIntent pi, - Account account) { + Account account) { try { startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); @@ -383,15 +391,15 @@ public abstract class XmppActivity extends Activity { @Override public void success(Account account) { xmppConnectionService.databaseBackend - .updateAccount(account); + .updateAccount(account); xmppConnectionService.sendPresencePacket(account, xmppConnectionService.getPresenceGenerator() - .sendPresence(account)); + .sendPresence(account)); if (conversation != null) { conversation - .setNextEncryption(Message.ENCRYPTION_PGP); + .setNextEncryption(Message.ENCRYPTION_PGP); xmppConnectionService.databaseBackend - .updateConversation(conversation); + .updateConversation(conversation); } } @@ -420,7 +428,7 @@ public abstract class XmppActivity extends Activity { } protected void showAddToRosterDialog(final Conversation conversation) { - final Jid jid = conversation.getContactJid(); + final Jid jid = conversation.getJid(); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(jid.toString()); builder.setMessage(getString(R.string.not_in_roster)); @@ -430,7 +438,7 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(DialogInterface dialog, int which) { - final Jid jid = conversation.getContactJid(); + final Jid jid = conversation.getJid(); Account account = conversation.getAccount(); Contact contact = account.getRoster().getContact(jid); xmppConnectionService.createContact(contact); @@ -462,7 +470,7 @@ public abstract class XmppActivity extends Activity { } private void warnMutalPresenceSubscription(final Conversation conversation, - final OnPresenceSelected listener) { + final OnPresenceSelected listener) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(conversation.getContact().getJid().toString()); builder.setMessage(R.string.without_mutual_presence_updates); @@ -485,13 +493,13 @@ public abstract class XmppActivity extends Activity { } protected void quickPasswordEdit(String previousValue, - OnValueEdited callback) { + OnValueEdited callback) { quickEdit(previousValue, callback, true); } @SuppressLint("InflateParams") private void quickEdit(final String previousValue, - final OnValueEdited callback, boolean password) { + final OnValueEdited callback, boolean password) { AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = getLayoutInflater().inflate(R.layout.quickedit, null); final EditText editor = (EditText) view.findViewById(R.id.editor); @@ -521,7 +529,7 @@ public abstract class XmppActivity extends Activity { } public void selectPresence(final Conversation conversation, - final OnPresenceSelected listener) { + final OnPresenceSelected listener) { final Contact contact = conversation.getContact(); if (conversation.hasValidOtrSession()) { SessionID id = conversation.getOtrSession().getSessionID(); @@ -576,7 +584,7 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { presence.delete(0, presence.length()); presence.append(presencesArray[which]); } @@ -600,7 +608,7 @@ public abstract class XmppActivity extends Activity { } protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { @@ -608,19 +616,19 @@ public abstract class XmppActivity extends Activity { Jid jid = Jid.fromString(data.getStringExtra("contact")); String conversationUuid = data.getStringExtra("conversation"); Conversation conversation = xmppConnectionService - .findConversationByUuid(conversationUuid); + .findConversationByUuid(conversationUuid); if (conversation.getMode() == Conversation.MODE_MULTI) { xmppConnectionService.invite(conversation, jid); } else { List<Jid> jids = new ArrayList<Jid>(); - jids.add(conversation.getContactJid().toBareJid()); + jids.add(conversation.getJid().toBareJid()); jids.add(jid); xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback); } } catch (final InvalidJidException ignored) { } - } + } } private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() { @@ -688,18 +696,18 @@ public abstract class XmppActivity extends Activity { } protected void registerNdefPushMessageCallback() { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (nfcAdapter != null && nfcAdapter.isEnabled()) { - nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { - @Override - public NdefMessage createNdefMessage(NfcEvent nfcEvent) { - return new NdefMessage(new NdefRecord[]{ - NdefRecord.createUri(getShareableUri()), - NdefRecord.createApplicationRecord("eu.siacs.conversations") - }); - } - }, this); - } + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (nfcAdapter != null && nfcAdapter.isEnabled()) { + nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { + @Override + public NdefMessage createNdefMessage(NfcEvent nfcEvent) { + return new NdefMessage(new NdefRecord[]{ + NdefRecord.createUri(getShareableUri()), + NdefRecord.createApplicationRecord("eu.siacs.conversations") + }); + } + }, this); + } } protected void unregisterNdefPushMessageCallback() { @@ -721,6 +729,14 @@ public abstract class XmppActivity extends Activity { } } + protected int findTheme() { + if (getPreferences().getBoolean("use_larger_font", false)) { + return R.style.ConversationsTheme_LargerText; + } else { + return R.style.ConversationsTheme; + } + } + @Override public void onPause() { super.onPause(); @@ -823,13 +839,13 @@ public abstract class XmppActivity extends Activity { try { task.execute(message); } catch (final RejectedExecutionException ignored) { - } + } } } } public static boolean cancelPotentialWork(Message message, - ImageView imageView) { + ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { @@ -858,7 +874,7 @@ public abstract class XmppActivity extends Activity { private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, - BitmapWorkerTask bitmapWorkerTask) { + BitmapWorkerTask bitmapWorkerTask) { super(res, bitmap); bitmapWorkerTaskReference = new WeakReference<>( bitmapWorkerTask); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index c5ee9ba6..32f95431 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui.adapter; import java.util.List; -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Downloadable; @@ -35,7 +34,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { public View getView(int position, View view, ViewGroup parent) { if (view == null) { LayoutInflater inflater = (LayoutInflater) activity - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.conversation_list_row, parent, false); } @@ -54,19 +53,19 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { } } TextView convName = (TextView) view - .findViewById(R.id.conversation_name); + .findViewById(R.id.conversation_name); if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { convName.setText(conversation.getName()); } else { - convName.setText(conversation.getContactJid().toBareJid().toString()); + convName.setText(conversation.getJid().toBareJid().toString()); } TextView mLastMessage = (TextView) view - .findViewById(R.id.conversation_lastmsg); + .findViewById(R.id.conversation_lastmsg); TextView mTimestamp = (TextView) view - .findViewById(R.id.conversation_lastupdate); + .findViewById(R.id.conversation_lastupdate); ImageView imagePreview = (ImageView) view - .findViewById(R.id.conversation_lastimage); + .findViewById(R.id.conversation_lastimage); Message message = conversation.getLatestMessage(); @@ -154,12 +153,12 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { imagePreview.setVisibility(View.GONE); } mTimestamp.setText(UIHelper.readableTimeDifference(getContext(), - conversation.getLatestMessage().getTimeSent())); + conversation.getLatestMessage().getTimeSent())); ImageView profilePicture = (ImageView) view - .findViewById(R.id.conversation_image); + .findViewById(R.id.conversation_image); profilePicture.setImageBitmap(activity.avatarService().get( - conversation, activity.getPixel(56))); + conversation, activity.getPixel(56))); return view; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index 0865d1aa..91fb021c 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.ui.adapter; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.ui.XmppActivity; @@ -10,6 +11,7 @@ import eu.siacs.conversations.xmpp.jid.Jid; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +24,17 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { protected XmppActivity activity; protected boolean showDynamicTags = false; + private View.OnClickListener onTagTvClick = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (view instanceof TextView && mOnTagClickedListener != null) { + TextView tv = (TextView) view; + final String tag = tv.getText().toString(); + mOnTagClickedListener.onTagClicked(tag); + } + } + }; + private OnTagClickedListener mOnTagClickedListener = null; public ListItemAdapter(XmppActivity activity, List<ListItem> objects) { super(activity, 0, objects); @@ -53,6 +66,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tagLayout,false); tv.setText(tag.getName()); tv.setBackgroundColor(tag.getColor()); + tv.setOnClickListener(this.onTagTvClick); tagLayout.addView(tv); } } @@ -68,4 +82,12 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { return view; } + public void setOnTagClickedListener(OnTagClickedListener listener) { + this.mOnTagClickedListener = listener; + } + + public interface OnTagClickedListener { + public void onTagClicked(String tag); + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 00000e2b..83b4e41b 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -23,7 +23,6 @@ import android.widget.Toast; import java.util.List; -import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -271,7 +270,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public void onClick(View v) { - startDonwloadable(message); + startDownloadable(message); } }); viewHolder.download_button.setOnLongClickListener(openContextMenu); @@ -287,7 +286,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public void onClick(View v) { - openDonwloadable(file); + openDownloadable(file); } }); viewHolder.download_button.setOnLongClickListener(openContextMenu); @@ -441,6 +440,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { } view.setLayoutParams(view.getLayoutParams()); return view; + } else if (viewHolder.messageBody == null || viewHolder.image == null) { + return view; //avoiding weird platform bugs } else if (type == RECEIVED) { Contact contact = message.getContact(); if (contact != null) { @@ -449,38 +450,36 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(getDisplayedMucCounterpart(message.getCounterpart()), activity.getPixel(48))); } - } else if (type == SENT && viewHolder.contact_picture != null) { + } else if (type == SENT) { viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48))); } - if (viewHolder != null && viewHolder.contact_picture != null) { - viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (MessageAdapter.this.mOnContactPictureClickedListener != null) { - MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); - } + viewHolder.contact_picture + .setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(message); } - }); - viewHolder.contact_picture - .setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { - MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); - return true; - } else { - return false; - } + + } + }); + viewHolder.contact_picture + .setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { + MessageAdapter.this.mOnContactPictureLongClickedListener + .onContactPictureLongClicked(message); + return true; + } else { + return false; } - }); - } + } + }); if (message.getDownloadable() != null && message.getDownloadable().getStatus() != Downloadable.STATUS_UPLOADING) { Downloadable d = message.getDownloadable(); @@ -549,7 +548,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { return view; } - public void startDonwloadable(Message message) { + public void startDownloadable(Message message) { Downloadable downloadable = message.getDownloadable(); if (downloadable != null) { if (!downloadable.start()) { @@ -559,7 +558,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - public void openDonwloadable(DownloadableFile file) { + public void openDownloadable(DownloadableFile file) { if (!file.exists()) { Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show(); return; diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 8c1a8dea..a09b4d0f 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -140,15 +140,18 @@ public class DNSHelper { } ArrayList<Bundle> values = new ArrayList<>(); for (SRV srv : result) { - Bundle namePort = new Bundle(); - namePort.putString("name", srv.getName()); - namePort.putInt("port", srv.getPort()); + boolean added = false; + if (ips6.containsKey(srv.getName())) { + values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6)); + added = true; + } if (ips4.containsKey(srv.getName())) { - ArrayList<String> ip = ips4.get(srv.getName()); - Collections.shuffle(ip, rnd); - namePort.putString("ipv4", ip.get(0)); + values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4)); + added = true; + } + if (!added) { + values.add(createNamePortBundle(srv.getName(),srv.getPort(),null)); } - values.add(namePort); } bundle.putParcelableArrayList("values", values); } catch (SocketTimeoutException e) { @@ -159,6 +162,18 @@ public class DNSHelper { return bundle; } + private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) { + Bundle namePort = new Bundle(); + namePort.putString("name", name); + namePort.putInt("port", port); + if (ips!=null) { + ArrayList<String> ip = ips.get(name); + Collections.shuffle(ip, new Random()); + namePort.putString("ip", ip.get(0)); + } + return namePort; + } + final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String bytesToHex(byte[] bytes) { diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index 87973159..9a5cbaaf 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -16,10 +16,7 @@ import android.provider.ContactsContract.Profile; public class PhoneHelper { - public static void loadPhoneContacts(Context context, - final OnPhoneContactsLoadedListener listener) { - final List<Bundle> phoneContacts = new ArrayList<Bundle>(); - + public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { final String[] PROJECTION = new String[] { ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_URI, diff --git a/src/main/java/eu/siacs/conversations/utils/Validator.java b/src/main/java/eu/siacs/conversations/utils/Validator.java deleted file mode 100644 index 00130fa2..00000000 --- a/src/main/java/eu/siacs/conversations/utils/Validator.java +++ /dev/null @@ -1,14 +0,0 @@ -package eu.siacs.conversations.utils; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Validator { - public static final Pattern VALID_JID = Pattern.compile( - "^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); - - public static boolean isValidJid(String jid) { - Matcher matcher = VALID_JID.matcher(jid); - return matcher.find(); - } -} diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java new file mode 100644 index 00000000..67de7c79 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.utils; + +public final class Xmlns { + public static final String BLOCKING = "urn:xmpp:blocking"; + public static final String ROSTER = "jabber:iq:roster"; + public static final String REGISTER = "jabber:iq:register"; +} diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index a9b8d1c0..aacb6362 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -5,7 +5,6 @@ import android.net.Uri; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -54,7 +53,7 @@ public class XmppUri { final String NEEDLE = "otr-fingerprint="; int index = query.indexOf(NEEDLE); if (index >= 0 && query.length() >= (NEEDLE.length() + index + 40)) { - return CryptoHelper.prettifyFingerprint(query.substring(index + NEEDLE.length(), index + NEEDLE.length() + 40)); + return query.substring(index + NEEDLE.length(), index + NEEDLE.length() + 40); } else { return null; } diff --git a/src/main/java/eu/siacs/conversations/utils/zlib/ZLibInputStream.java b/src/main/java/eu/siacs/conversations/utils/zlib/ZLibInputStream.java deleted file mode 100644 index b777c10c..00000000 --- a/src/main/java/eu/siacs/conversations/utils/zlib/ZLibInputStream.java +++ /dev/null @@ -1,54 +0,0 @@ -package eu.siacs.conversations.utils.zlib; - -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -/** - * ZLibInputStream is a zlib and input stream compatible version of an - * InflaterInputStream. This class solves the incompatibility between - * {@link InputStream#available()} and {@link InflaterInputStream#available()}. - */ -public class ZLibInputStream extends InflaterInputStream { - - /** - * Construct a ZLibInputStream, reading data from the underlying stream. - * - * @param is - * The {@code InputStream} to read data from. - * @throws IOException - * If an {@code IOException} occurs. - */ - public ZLibInputStream(InputStream is) throws IOException { - super(is, new Inflater(), 512); - } - - /** - * Provide a more InputStream compatible version of available. A return - * value of 1 means that it is likly to read one byte without blocking, 0 - * means that the system is known to block for more input. - * - * @return 0 if no data is available, 1 otherwise - * @throws IOException - */ - @Override - public int available() throws IOException { - /* - * This is one of the funny code blocks. InflaterInputStream.available - * violates the contract of InputStream.available, which breaks kXML2. - * - * I'm not sure who's to blame, oracle/sun for a broken api or the - * google guys for mixing a sun bug with a xml reader that can't handle - * it.... - * - * Anyway, this simple if breaks suns distorted reality, but helps to - * use the api as intended. - */ - if (inf.needsInput()) { - return 0; - } - return super.available(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java b/src/main/java/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java deleted file mode 100644 index 8b3f5e68..00000000 --- a/src/main/java/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java +++ /dev/null @@ -1,95 +0,0 @@ -package eu.siacs.conversations.utils.zlib; - -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.NoSuchAlgorithmException; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -/** - * <p> - * Android 2.2 includes Java7 FLUSH_SYNC option, which will be used by this - * Implementation, preferable via reflection. The @hide was remove in API level - * 19. This class might thus go away in the future. - * </p> - * <p> - * Please use {@link ZLibOutputStream#SUPPORTED} to check for flush - * compatibility. - * </p> - */ -public class ZLibOutputStream extends DeflaterOutputStream { - - /** - * The reflection based flush method. - */ - - private final static Method method; - /** - * SUPPORTED is true if a flush compatible method exists. - */ - public final static boolean SUPPORTED; - - /** - * Static block to initialize {@link #SUPPORTED} and {@link #method}. - */ - static { - Method m = null; - try { - m = Deflater.class.getMethod("deflate", byte[].class, int.class, - int.class, int.class); - } catch (SecurityException e) { - } catch (NoSuchMethodException e) { - } - method = m; - SUPPORTED = (method != null); - } - - /** - * Create a new ZLib compatible output stream wrapping the given low level - * stream. ZLib compatiblity means we will send a zlib header. - * - * @param os - * OutputStream The underlying stream. - * @throws IOException - * In case of a lowlevel transfer problem. - * @throws NoSuchAlgorithmException - * In case of a {@link Deflater} error. - */ - public ZLibOutputStream(OutputStream os) throws IOException, - NoSuchAlgorithmException { - super(os, new Deflater(Deflater.BEST_COMPRESSION)); - } - - /** - * Flush the given stream, preferring Java7 FLUSH_SYNC if available. - * - * @throws IOException - * In case of a lowlevel exception. - */ - @Override - public void flush() throws IOException { - if (!SUPPORTED) { - super.flush(); - return; - } - try { - int count = 0; - do { - count = (Integer) method.invoke(def, buf, 0, buf.length, 3); - if (count > 0) { - out.write(buf, 0, count); - } - } while (count > 0); - } catch (IllegalArgumentException e) { - throw new IOException("Can't flush"); - } catch (IllegalAccessException e) { - throw new IOException("Can't flush"); - } catch (InvocationTargetException e) { - throw new IOException("Can't flush"); - } - super.flush(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 02c3e695..51708759 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -1,9 +1,12 @@ package eu.siacs.conversations.xml; +import android.util.Log; + import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -59,16 +62,16 @@ public class Element { if (child.getName().equals(name) && (child.getAttribute("xmlns").equals(xmlns))) { return child; - } + } } return null; } - public boolean hasChild(String name) { + public boolean hasChild(final String name) { return findChild(name) != null; } - public boolean hasChild(String name, String xmlns) { + public boolean hasChild(final String name, final String xmlns) { return findChild(name, xmlns) != null; } @@ -107,14 +110,15 @@ public class Element { public Jid getAttributeAsJid(String name) { final String jid = this.getAttribute(name); - if (jid != null && !jid.isEmpty()) { - try { - return Jid.fromString(jid); - } catch (final InvalidJidException e) { - return null; - } - } - return null; + if (jid != null && !jid.isEmpty()) { + try { + return Jid.fromString(jid); + } catch (final InvalidJidException e) { + Log.e(Config.LOGTAG, "could not parse jid " + jid); + return null; + } + } + return null; } public Hashtable<String, String> getAttributes() { @@ -159,4 +163,9 @@ public class Element { public void setAttribute(String name, int value) { this.setAttribute(name, Integer.toString(value)); } + + public boolean getAttributeAsBoolean(String name) { + String attr = getAttribute(name); + return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java new file mode 100644 index 00000000..e45eba73 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.xmpp; + +import eu.siacs.conversations.entities.Account; + +public interface OnAdvancedStreamFeaturesLoaded { + public void onAdvancedStreamFeaturesAvailable(final Account account); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnContactStatusChanged.java b/src/main/java/eu/siacs/conversations/xmpp/OnContactStatusChanged.java index 849e8e76..20b17f02 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnContactStatusChanged.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnContactStatusChanged.java @@ -3,5 +3,5 @@ package eu.siacs.conversations.xmpp; import eu.siacs.conversations.entities.Contact; public interface OnContactStatusChanged { - public void onContactStatusChanged(Contact contact, boolean online); + public void onContactStatusChanged(final Contact contact, final boolean online); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnUpdateBlocklist.java b/src/main/java/eu/siacs/conversations/xmpp/OnUpdateBlocklist.java new file mode 100644 index 00000000..92e72cfa --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnUpdateBlocklist.java @@ -0,0 +1,13 @@ +package eu.siacs.conversations.xmpp; + +public interface OnUpdateBlocklist { + // Use an enum instead of a boolean to make sure we don't run into the boolean trap + // (`onUpdateBlocklist(true)' doesn't read well, and could be confusing). + public static enum Status { + BLOCKED, + UNBLOCKED + } + + @SuppressWarnings("MethodNameSameAsClassName") + public void OnUpdateBlocklist(final Status status); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index fb151427..f7f0c346 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -30,10 +30,12 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.HostnameVerifier; @@ -48,10 +50,10 @@ import eu.siacs.conversations.crypto.sasl.Plain; import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.crypto.sasl.ScramSha1; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.DNSHelper; -import eu.siacs.conversations.utils.zlib.ZLibInputStream; -import eu.siacs.conversations.utils.zlib.ZLibOutputStream; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Tag; import eu.siacs.conversations.xml.TagWriter; @@ -78,21 +80,20 @@ public class XmppConnection implements Runnable { private static final int PACKET_PRESENCE = 2; private final Context applicationContext; protected Account account; - private WakeLock wakeLock; + private final WakeLock wakeLock; private Socket socket; private XmlReader tagReader; private TagWriter tagWriter; - private Features features = new Features(this); + private final Features features = new Features(this); private boolean shouldBind = true; private boolean shouldAuthenticate = true; private Element streamFeatures; - private HashMap<String, List<String>> disco = new HashMap<>(); + private final HashMap<String, List<String>> disco = new HashMap<>(); private String streamId = null; private int smVersion = 3; - private SparseArray<String> messageReceipts = new SparseArray<>(); + private final SparseArray<String> messageReceipts = new SparseArray<>(); - private boolean enabledCompression = false; private boolean enabledEncryption = false; private boolean enabledCarbons = false; @@ -103,19 +104,20 @@ public class XmppConnection implements Runnable { private long lastConnect = 0; private long lastSessionStarted = 0; private int attempt = 0; - private Hashtable<String, PacketReceived> packetCallbacks = new Hashtable<>(); + private final Map<String, PacketReceived> packetCallbacks = new Hashtable<>(); private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; private OnIqPacketReceived unregisteredIqListener = null; private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; + private final ArrayList<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new ArrayList<>(); private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; private SaslMechanism saslMechanism; - public XmppConnection(Account account, XmppConnectionService service) { + public XmppConnection(final Account account, final XmppConnectionService service) { this.account = account; this.wakeLock = service.getPowerManager().newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); @@ -131,7 +133,7 @@ public class XmppConnection implements Runnable { && (account.getStatus() != Account.State.ONLINE) && (account.getStatus() != Account.State.DISABLED)) { return; - } + } if (nextStatus == Account.State.ONLINE) { this.attempt = 0; } @@ -144,7 +146,6 @@ public class XmppConnection implements Runnable { protected void connect() { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting"); - enabledCompression = false; enabledEncryption = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); @@ -156,15 +157,15 @@ public class XmppConnection implements Runnable { tagWriter = new TagWriter(); packetCallbacks.clear(); this.changeStatus(Account.State.CONNECTING); - Bundle result = DNSHelper.getSRVRecord(account.getServer()); - ArrayList<Parcelable> values = result.getParcelableArrayList("values"); + final Bundle result = DNSHelper.getSRVRecord(account.getServer()); + final ArrayList<Parcelable> values = result.getParcelableArrayList("values"); if ("timeout".equals(result.getString("error"))) { throw new IOException("timeout in dns"); } else if (values != null) { int i = 0; boolean socketError = true; while (socketError && values.size() > i) { - Bundle namePort = (Bundle) values.get(i); + final Bundle namePort = (Bundle) values.get(i); try { String srvRecordServer; try { @@ -173,9 +174,9 @@ public class XmppConnection implements Runnable { // TODO: Handle me?` srvRecordServer = ""; } - int srvRecordPort = namePort.getInt("port"); - String srvIpServer = namePort.getString("ipv4"); - InetSocketAddress addr; + final int srvRecordPort = namePort.getInt("port"); + final String srvIpServer = namePort.getString("ip"); + final InetSocketAddress addr; if (srvIpServer != null) { addr = new InetSocketAddress(srvIpServer, srvRecordPort); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() @@ -190,10 +191,10 @@ public class XmppConnection implements Runnable { socket = new Socket(); socket.connect(addr, 20000); socketError = false; - } catch (UnknownHostException e) { + } catch (final UnknownHostException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); i++; - } catch (IOException e) { + } catch (final IOException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); i++; } @@ -207,9 +208,9 @@ public class XmppConnection implements Runnable { } else { throw new IOException("timeout in dns"); } - OutputStream out = socket.getOutputStream(); + final OutputStream out = socket.getOutputStream(); tagWriter.setOutputStream(out); - InputStream in = socket.getInputStream(); + final InputStream in = socket.getInputStream(); tagReader.setInputStream(in); tagWriter.beginDocument(); sendStartStream(); @@ -225,14 +226,9 @@ public class XmppConnection implements Runnable { if (socket.isConnected()) { socket.close(); } - } catch (UnknownHostException e) { + } catch (final UnknownHostException | ConnectException e) { this.changeStatus(Account.State.SERVER_NOT_FOUND); - } catch (final ConnectException e) { - this.changeStatus(Account.State.SERVER_NOT_FOUND); - } catch (final IOException | XmlPullParserException e) { - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - this.changeStatus(Account.State.OFFLINE); - } catch (NoSuchAlgorithmException e) { + } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.State.OFFLINE); } finally { @@ -261,8 +257,6 @@ public class XmppConnection implements Runnable { processStreamFeatures(nextTag); } else if (nextTag.isStart("proceed")) { switchOverToTls(nextTag); - } else if (nextTag.isStart("compressed")) { - switchOverToZLib(nextTag); } else if (nextTag.isStart("success")) { final String challenge = tagReader.readElement(nextTag).getContent(); try { @@ -273,7 +267,7 @@ public class XmppConnection implements Runnable { } Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); account.setKey(Account.PINNED_MECHANISM_KEY, - String.valueOf(saslMechanism.getPriority())); + String.valueOf(saslMechanism.getPriority())); tagReader.reset(); sendStartStream(); processStream(tagReader.readTag()); @@ -294,7 +288,7 @@ public class XmppConnection implements Runnable { } tagWriter.writeElement(response); } else if (nextTag.isStart("enabled")) { - Element enabled = tagReader.readElement(nextTag); + final Element enabled = tagReader.readElement(nextTag); if ("true".equals(enabled.getAttribute("resume"))) { this.streamId = enabled.getAttribute("id"); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() @@ -306,14 +300,14 @@ public class XmppConnection implements Runnable { } this.lastSessionStarted = SystemClock.elapsedRealtime(); this.stanzasReceived = 0; - RequestPacket r = new RequestPacket(smVersion); + final RequestPacket r = new RequestPacket(smVersion); tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { lastPaketReceived = SystemClock.elapsedRealtime(); - Element resumed = tagReader.readElement(nextTag); - String h = resumed.getAttribute("h"); + final Element resumed = tagReader.readElement(nextTag); + final String h = resumed.getAttribute("h"); try { - int serverCount = Integer.parseInt(h); + final int serverCount = Integer.parseInt(h); if (serverCount != stanzasSent) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed with lost packages"); @@ -332,20 +326,19 @@ public class XmppConnection implements Runnable { } messageReceipts.clear(); } catch (final NumberFormatException ignored) { - } sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryItems(account.getServer()); sendInitialPing(); } else if (nextTag.isStart("r")) { tagReader.readElement(nextTag); - AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); tagWriter.writeStanzaAsync(ack); } else if (nextTag.isStart("a")) { - Element ack = tagReader.readElement(nextTag); + final Element ack = tagReader.readElement(nextTag); lastPaketReceived = SystemClock.elapsedRealtime(); - int serverSequence = Integer.parseInt(ack.getAttribute("h")); - String msgId = this.messageReceipts.get(serverSequence); + final int serverSequence = Integer.parseInt(ack.getAttribute("h")); + final String msgId = this.messageReceipts.get(serverSequence); if (msgId != null) { if (this.acknowledgedListener != null) { this.acknowledgedListener.onMessageAcknowledged( @@ -379,13 +372,12 @@ public class XmppConnection implements Runnable { private void sendInitialPing() { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping"); - IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + public void onIqPacketReceived(final Account account, final IqPacket packet) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": online with resource " + account.getResource()); changeStatus(Account.State.ONLINE); @@ -393,7 +385,7 @@ public class XmppConnection implements Runnable { }); } - private Element processPacket(Tag currentTag, int packetType) + private Element processPacket(final Tag currentTag, final int packetType) throws XmlPullParserException, IOException { Element element; switch (packetType) { @@ -416,8 +408,8 @@ public class XmppConnection implements Runnable { } while (!nextTag.isEnd(element.getName())) { if (!nextTag.isNo()) { - Element child = tagReader.readElement(nextTag); - String type = currentTag.getAttribute("type"); + final Element child = tagReader.readElement(nextTag); + final String type = currentTag.getAttribute("type"); if (packetType == PACKET_IQ && "jingle".equals(child.getName()) && ("set".equalsIgnoreCase(type) || "get" @@ -437,9 +429,9 @@ public class XmppConnection implements Runnable { return element; } - private void processIq(Tag currentTag) throws XmlPullParserException, + private void processIq(final Tag currentTag) throws XmlPullParserException, IOException { - IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); + final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ); if (packet.getId() == null) { return; // an iq packet without id is definitely invalid @@ -466,11 +458,11 @@ public class XmppConnection implements Runnable { } } - private void processMessage(Tag currentTag) throws XmlPullParserException, + private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException { - MessagePacket packet = (MessagePacket) processPacket(currentTag, + final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE); - String id = packet.getAttribute("id"); + final String id = packet.getAttribute("id"); if ((id != null) && (packetCallbacks.containsKey(id))) { if (packetCallbacks.get(id) instanceof OnMessagePacketReceived) { ((OnMessagePacketReceived) packetCallbacks.get(id)) @@ -482,11 +474,11 @@ public class XmppConnection implements Runnable { } } - private void processPresence(Tag currentTag) throws XmlPullParserException, + private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException { PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); - String id = packet.getAttribute("id"); + final String id = packet.getAttribute("id"); if ((id != null) && (packetCallbacks.containsKey(id))) { if (packetCallbacks.get(id) instanceof OnPresencePacketReceived) { ((OnPresencePacketReceived) packetCallbacks.get(id)) @@ -498,30 +490,8 @@ public class XmppConnection implements Runnable { } } - private void sendCompressionZlib() throws IOException { - Element compress = new Element("compress"); - compress.setAttribute("xmlns", "http://jabber.org/protocol/compress"); - compress.addChild("method").setContent("zlib"); - tagWriter.writeElement(compress); - } - - private void switchOverToZLib(final Tag currentTag) - throws XmlPullParserException, IOException, - NoSuchAlgorithmException { - tagReader.readTag(); // read tag close - tagWriter.setOutputStream(new ZLibOutputStream(tagWriter - .getOutputStream())); - tagReader - .setInputStream(new ZLibInputStream(tagReader.getInputStream())); - - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": compression enabled"); - enabledCompression = true; - processStream(tagReader.readTag()); - } - private void sendStartTLS() throws IOException { - Tag startTLS = Tag.empty("starttls"); + final Tag startTLS = Tag.empty("starttls"); startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls"); tagWriter.writeTag(startTLS); } @@ -535,74 +505,61 @@ public class XmppConnection implements Runnable { return getPreferences().getBoolean("enable_legacy_ssl", false); } - private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, - IOException { - tagReader.readTag(); - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, - new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()}, - mXmppConnectionService.getRNG()); - SSLSocketFactory factory = sc.getSocketFactory(); - - if (factory == null) { - throw new IOException("SSLSocketFactory was null"); - } - - final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { + tagReader.readTag(); + try { + final SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null,new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},mXmppConnectionService.getRNG()); + final SSLSocketFactory factory = sc.getSocketFactory(); + final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); + final InetAddress address = socket == null ? null : socket.getInetAddress(); + + if (factory == null || address == null || verifier == null) { + throw new IOException("could not setup ssl"); + } - if (socket == null || socket.isClosed()) { - throw new IOException("socket null or closed"); - } - final InetAddress address = socket.getInetAddress(); - if (address == null) { - throw new IOException("socket address was null"); - } + final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); + if (sslSocket == null) { + throw new IOException("could not initialize ssl socket"); + } - // Support all protocols except legacy SSL. - // The min SDK version prevents us having to worry about SSLv2. In - // future, this may be true of SSLv3 as well. - final String[] supportProtocols; - if (enableLegacySSL()) { - supportProtocols = sslSocket.getSupportedProtocols(); - } else { - final List<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = new String[supportedProtocols.size()]; - supportedProtocols.toArray(supportProtocols); - } - sslSocket.setEnabledProtocols(supportProtocols); - - if (verifier != null - && !verifier.verify(account.getServer().getDomainpart(), - sslSocket.getSession())) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); - disconnect(true); - changeStatus(Account.State.SECURITY_ERROR); - } - tagReader.setInputStream(sslSocket.getInputStream()); - tagWriter.setOutputStream(sslSocket.getOutputStream()); - sendStartStream(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": TLS connection established"); - enabledEncryption = true; - processStream(tagReader.readTag()); - sslSocket.close(); - } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - e1.printStackTrace(); - } + final String[] supportProtocols; + if (enableLegacySSL()) { + supportProtocols = sslSocket.getSupportedProtocols(); + } else { + final Collection<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = new String[supportedProtocols.size()]; + supportedProtocols.toArray(supportProtocols); + } + sslSocket.setEnabledProtocols(supportProtocols); + + if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + disconnect(true); + changeStatus(Account.State.SECURITY_ERROR); + } + tagReader.setInputStream(sslSocket.getInputStream()); + tagWriter.setOutputStream(sslSocket.getOutputStream()); + sendStartStream(); + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); + enabledEncryption = true; + processStream(tagReader.readTag()); + sslSocket.close(); + } catch (final NoSuchAlgorithmException | KeyManagementException e1) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + disconnect(true); + changeStatus(Account.State.SECURITY_ERROR); + } } - private void processStreamFeatures(Tag currentTag) + private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException { this.streamFeatures = tagReader.readElement(currentTag); if (this.streamFeatures.hasChild("starttls") && !enabledEncryption) { sendStartTLS(); - } else if (compressionAvailable()) { - sendCompressionZlib(); } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER) && enabledEncryption) { @@ -619,10 +576,10 @@ public class XmppConnection implements Runnable { auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); if (mechanisms.contains("SCRAM-SHA-1")) { saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); - } else if (mechanisms.contains("DIGEST-MD5")) { - saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); } else if (mechanisms.contains("PLAIN")) { saslMechanism = new Plain(tagWriter, account); + } else if (mechanisms.contains("DIGEST-MD5")) { + saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); } final JSONObject keys = account.getKeys(); try { @@ -634,7 +591,7 @@ public class XmppConnection implements Runnable { "). Possible downgrade attack?"); disconnect(true); changeStatus(Account.State.SECURITY_ERROR); - } + } } catch (final JSONException e) { Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); } @@ -647,7 +604,7 @@ public class XmppConnection implements Runnable { } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) { - ResumePacket resume = new ResumePacket(this.streamId, + final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); this.tagWriter.writeStanzaAsync(resume); } else if (this.streamFeatures.hasChild("bind") && shouldBind) { @@ -658,67 +615,44 @@ public class XmppConnection implements Runnable { } } - private boolean compressionAvailable() { - if (!this.streamFeatures.hasChild("compression", - "http://jabber.org/features/compress")) - return false; - if (!ZLibOutputStream.SUPPORTED) - return false; - if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) - return false; - - Element compression = this.streamFeatures.findChild("compression", - "http://jabber.org/features/compress"); - for (Element child : compression.getChildren()) { - if (!"method".equals(child.getName())) - continue; - - if ("zlib".equalsIgnoreCase(child.getContent())) { - return true; - } - } - return false; - } - - private List<String> extractMechanisms(Element stream) { - ArrayList<String> mechanisms = new ArrayList<>(stream + private List<String> extractMechanisms(final Element stream) { + final ArrayList<String> mechanisms = new ArrayList<>(stream .getChildren().size()); - for (Element child : stream.getChildren()) { + for (final Element child : stream.getChildren()) { mechanisms.add(child.getContent()); } return mechanisms; } private void sendRegistryRequest() { - IqPacket register = new IqPacket(IqPacket.TYPE_GET); + final IqPacket register = new IqPacket(IqPacket.TYPE_GET); register.query("jabber:iq:register"); register.setTo(account.getServer()); sendIqPacket(register, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element instructions = packet.query().findChild("instructions"); + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final Element instructions = packet.query().findChild("instructions"); if (packet.query().hasChild("username") && (packet.query().hasChild("password"))) { - IqPacket register = new IqPacket(IqPacket.TYPE_SET); - Element username = new Element("username") - .setContent(account.getUsername()); - Element password = new Element("password") - .setContent(account.getPassword()); + final IqPacket register = new IqPacket(IqPacket.TYPE_SET); + final Element username = new Element("username") + .setContent(account.getUsername()); + final Element password = new Element("password") + .setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); sendIqPacket(register, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, - IqPacket packet) { + public void onIqPacketReceived(final Account account, final IqPacket packet) { if (packet.getType() == IqPacket.TYPE_RESULT) { account.setOption(Account.OPTION_REGISTER, false); changeStatus(Account.State.REGISTRATION_SUCCESSFUL); } else if (packet.hasChild("error") && (packet.findChild("error") - .hasChild("conflict"))) { + .hasChild("conflict"))) { changeStatus(Account.State.REGISTRATION_CONFLICT); } else { changeStatus(Account.State.REGISTRATION_FAILED); @@ -738,14 +672,14 @@ public class XmppConnection implements Runnable { }); } - private void sendBindRequest() throws IOException { - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + private void sendBindRequest() { + final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") .addChild("resource").setContent(account.getResource()); this.sendUnboundIqPacket(iq, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element bind = packet.findChild("bind"); + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final Element bind = packet.findChild("bind"); if (bind != null) { final Element jid = bind.findChild("jid"); if (jid != null && jid.getContent() != null) { @@ -756,14 +690,14 @@ public class XmppConnection implements Runnable { } if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { smVersion = 3; - EnablePacket enable = new EnablePacket(smVersion); + final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; messageReceipts.clear(); } else if (streamFeatures.hasChild("sm", - "urn:xmpp:sm:2")) { + "urn:xmpp:sm:2")) { smVersion = 2; - EnablePacket enable = new EnablePacket(smVersion); + final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; messageReceipts.clear(); @@ -787,7 +721,7 @@ public class XmppConnection implements Runnable { if (this.streamFeatures.hasChild("session")) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending deprecated session"); - IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); + final IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnboundIqPacket(startSession, null); @@ -806,10 +740,10 @@ public class XmppConnection implements Runnable { this.sendIqPacket(iq, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + public void onIqPacketReceived(final Account account, final IqPacket packet) { final List<Element> elements = packet.query().getChildren(); final List<String> features = new ArrayList<>(); - for (Element element : elements) { + for (final Element element : elements) { if (element.getName().equals("identity")) { if ("irc".equals(element.getAttribute("type"))) { //add fake feature to not confuse irc and real muc @@ -823,6 +757,9 @@ public class XmppConnection implements Runnable { if (account.getServer().equals(server.toDomainJid())) { enableAdvancedStreamFeatures(); + for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); + } } } }); @@ -835,6 +772,10 @@ public class XmppConnection implements Runnable { sendEnableCarbons(); } } + if (getFeatures().blocking()) { + Log.d(Config.LOGTAG, "Requesting block list"); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + } } private void sendServiceDiscoveryItems(final Jid server) { @@ -844,9 +785,9 @@ public class XmppConnection implements Runnable { this.sendIqPacket(iq, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - List<Element> elements = packet.query().getChildren(); - for (Element element : elements) { + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final List<Element> elements = packet.query().getChildren(); + for (final Element element : elements) { if (element.getName().equals("item")) { final Jid jid = element.getAttributeAsJid("jid"); if (jid != null && !jid.equals(account.getServer())) { @@ -859,12 +800,12 @@ public class XmppConnection implements Runnable { } private void sendEnableCarbons() { - IqPacket iq = new IqPacket(IqPacket.TYPE_SET); + final IqPacket iq = new IqPacket(IqPacket.TYPE_SET); iq.addChild("enable", "urn:xmpp:carbons:2"); this.sendIqPacket(iq, new OnIqPacketReceived() { @Override - public void onIqPacketReceived(Account account, IqPacket packet) { + public void onIqPacketReceived(final Account account, final IqPacket packet) { if (!packet.hasChild("error")) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": successfully enabled carbons"); @@ -877,20 +818,20 @@ public class XmppConnection implements Runnable { }); } - private void processStreamError(Tag currentTag) + private void processStreamError(final Tag currentTag) throws XmlPullParserException, IOException { - Element streamError = tagReader.readElement(currentTag); + final Element streamError = tagReader.readElement(currentTag); if (streamError != null && streamError.hasChild("conflict")) { final String resource = account.getResource().split("\\.")[0]; account.setResource(resource + "." + nextRandomId()); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": switching resource due to conflict (" - + account.getResource() + ")"); + + account.getResource() + ")"); } } private void sendStartStream() throws IOException { - Tag stream = Tag.start("stream:stream"); + final Tag stream = Tag.start("stream:stream"); stream.setAttribute("from", account.getJid().toBareJid().toString()); stream.setAttribute("to", account.getServer().toString()); stream.setAttribute("version", "1.0"); @@ -904,33 +845,32 @@ public class XmppConnection implements Runnable { return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } - public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) { + public void sendIqPacket(final IqPacket packet, final PacketReceived callback) { if (packet.getId() == null) { - String id = nextRandomId(); + final String id = nextRandomId(); packet.setAttribute("id", id); } packet.setFrom(account.getJid()); this.sendPacket(packet, callback); } - public void sendUnboundIqPacket(IqPacket packet, OnIqPacketReceived callback) { + public void sendUnboundIqPacket(final IqPacket packet, final PacketReceived callback) { if (packet.getId() == null) { - String id = nextRandomId(); + final String id = nextRandomId(); packet.setAttribute("id", id); } this.sendPacket(packet, callback); } - public void sendMessagePacket(MessagePacket packet) { + public void sendMessagePacket(final MessagePacket packet) { this.sendPacket(packet, null); } - public void sendPresencePacket(PresencePacket packet) { + public void sendPresencePacket(final PresencePacket packet) { this.sendPacket(packet, null); } - private synchronized void sendPacket(final AbstractStanza packet, - PacketReceived callback) { + private synchronized void sendPacket(final AbstractStanza packet, final PacketReceived callback) { if (packet.getName().equals("iq") || packet.getName().equals("message") || packet.getName().equals("presence")) { ++stanzasSent; @@ -955,7 +895,7 @@ public class XmppConnection implements Runnable { if (streamFeatures.hasChild("sm")) { tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); } else { - IqPacket iq = new IqPacket(IqPacket.TYPE_GET); + final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, null); @@ -964,38 +904,44 @@ public class XmppConnection implements Runnable { } public void setOnMessagePacketReceivedListener( - OnMessagePacketReceived listener) { + final OnMessagePacketReceived listener) { this.messageListener = listener; } public void setOnUnregisteredIqPacketReceivedListener( - OnIqPacketReceived listener) { + final OnIqPacketReceived listener) { this.unregisteredIqListener = listener; } public void setOnPresencePacketReceivedListener( - OnPresencePacketReceived listener) { + final OnPresencePacketReceived listener) { this.presenceListener = listener; } public void setOnJinglePacketReceivedListener( - OnJinglePacketReceived listener) { + final OnJinglePacketReceived listener) { this.jingleListener = listener; } - public void setOnStatusChangedListener(OnStatusChanged listener) { + public void setOnStatusChangedListener(final OnStatusChanged listener) { this.statusListener = listener; } - public void setOnBindListener(OnBindListener listener) { + public void setOnBindListener(final OnBindListener listener) { this.bindListener = listener; } - public void setOnMessageAcknowledgeListener(OnMessageAcknowledged listener) { + public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { this.acknowledgedListener = listener; } - public void disconnect(boolean force) { + public void addOnAdvancedStreamFeaturesAvailableListener(final OnAdvancedStreamFeaturesLoaded listener) { + if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) { + this.advancedStreamFeaturesLoadedListeners.add(listener); + } + } + + public void disconnect(final boolean force) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting"); try { if (force) { @@ -1015,23 +961,23 @@ public class XmppConnection implements Runnable { } tagWriter.writeTag(Tag.end("stream:stream")); socket.close(); - } catch (IOException e) { + } catch (final IOException e) { Log.d(Config.LOGTAG, "io exception during disconnect"); - } catch (InterruptedException e) { + } catch (final InterruptedException e) { Log.d(Config.LOGTAG, "interrupted"); } } } }).start(); - } catch (IOException e) { + } catch (final IOException e) { Log.d(Config.LOGTAG, "io exception during disconnect"); } } - public List<String> findDiscoItemsByFeature(String feature) { + public List<String> findDiscoItemsByFeature(final String feature) { final List<String> items = new ArrayList<>(); - for (Entry<String, List<String>> cursor : disco.entrySet()) { + for (final Entry<String, List<String>> cursor : disco.entrySet()) { if (cursor.getValue().contains(feature)) { items.add(cursor.getKey()); } @@ -1039,8 +985,8 @@ public class XmppConnection implements Runnable { return items; } - public String findDiscoItemByFeature(String feature) { - List<String> items = findDiscoItemsByFeature(feature); + public String findDiscoItemByFeature(final String feature) { + final List<String> items = findDiscoItemsByFeature(feature); if (items.size() >= 1) { return items.get(0); } @@ -1052,8 +998,7 @@ public class XmppConnection implements Runnable { } public String getMucServer() { - final List<String> items = new ArrayList<>(); - for (Entry<String, List<String>> cursor : disco.entrySet()) { + for (final Entry<String, List<String>> cursor : disco.entrySet()) { final List<String> value = cursor.getValue(); if (value.contains("http://jabber.org/protocol/muc") && !value.contains("jabber:iq:gateway") && !value.contains("siacs:no:muc")) { return cursor.getKey(); @@ -1063,8 +1008,8 @@ public class XmppConnection implements Runnable { } public int getTimeToNextAttempt() { - int interval = (int) (25 * Math.pow(1.5, attempt)); - int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); + final int interval = (int) (25 * Math.pow(1.5, attempt)); + final int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000); return interval - secondsSinceLast; } @@ -1077,7 +1022,7 @@ public class XmppConnection implements Runnable { } public long getLastSessionEstablished() { - long diff; + final long diff; if (this.lastSessionStarted == 0) { diff = SystemClock.elapsedRealtime() - this.lastConnect; } else { @@ -1109,7 +1054,7 @@ public class XmppConnection implements Runnable { public class Features { XmppConnection connection; - public Features(XmppConnection connection) { + public Features(final XmppConnection connection) { this.connection = connection; } @@ -1122,6 +1067,14 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); } + public boolean blocking() { + return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING); + } + + public boolean register() { + return hasDiscoFeature(account.getServer(), Xmlns.REGISTER); + } + public boolean sm() { return streamId != null; } @@ -1139,6 +1092,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); } + public boolean advancedStreamFeaturesLoaded() { + return disco.containsKey(account.getServer().toString()); + } + public boolean rosterVersioning() { return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); } @@ -1147,9 +1104,9 @@ public class XmppConnection implements Runnable { return connection .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; } + } - public boolean compression() { - return connection.enabledCompression; - } + private IqGenerator getIqGenerator() { + return mXmppConnectionService.getIqGenerator(); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index ff9acb3f..44794c80 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -37,6 +37,7 @@ public class Data extends Element { Field field = getFieldByName(name); if (field == null) { field = new Field(name); + this.addChild(field); } field.setValue(value); } @@ -45,6 +46,7 @@ public class Data extends Element { Field field = getFieldByName(name); if (field == null) { field = new Field(name); + this.addChild(field); } field.setValues(values); } @@ -72,4 +74,12 @@ public class Data extends Element { data.setChildren(element.getChildren()); return data; } + + public void setFormType(String formType) { + this.put("FORM_TYPE",formType); + } + + public String getFormType() { + return this.getAttribute("FORM_TYPE"); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index c40fa0b6..a35ea37c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -32,7 +32,7 @@ public final class Jid { return resourcepart; } - public static Jid fromSessionID(SessionID id) throws InvalidJidException{ + public static Jid fromSessionID(final SessionID id) throws InvalidJidException{ if (id.getUserID().isEmpty()) { return Jid.fromString(id.getAccountID()); } else { @@ -190,4 +190,8 @@ public final class Jid { public boolean isBareJid() { return this.resourcepart.isEmpty(); } + + public boolean isDomainJid() { + return !this.hasLocalpart(); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 3a1ba778..d578ca38 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -191,10 +191,10 @@ public class JingleConnection implements Downloadable { } IqPacket response; if (returnResult) { - response = packet.generateRespone(IqPacket.TYPE_RESULT); + response = packet.generateResponse(IqPacket.TYPE_RESULT); } else { - response = packet.generateRespone(IqPacket.TYPE_ERROR); + response = packet.generateResponse(IqPacket.TYPE_ERROR); } account.getXmppConnection().sendIqPacket(response, null); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 72c960d8..b0a730b1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -46,7 +46,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { return; } } - IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR); + IqPacket response = packet.generateResponse(IqPacket.TYPE_ERROR); Element error = response.addChild("error"); error.setAttribute("type", "cancel"); error.addChild("item-not-found", diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index 04b225d0..e25f7e65 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -208,15 +208,15 @@ public class JingleInbandTransport extends JingleTransport { established = true; connected = true; this.account.getXmppConnection().sendIqPacket( - packet.generateRespone(IqPacket.TYPE_RESULT), null); + packet.generateResponse(IqPacket.TYPE_RESULT), null); } else { this.account.getXmppConnection().sendIqPacket( - packet.generateRespone(IqPacket.TYPE_ERROR), null); + packet.generateResponse(IqPacket.TYPE_ERROR), null); } } else if (connected && payload.getName().equals("data")) { this.receiveNextBlock(payload.getContent()); this.account.getXmppConnection().sendIqPacket( - packet.generateRespone(IqPacket.TYPE_RESULT), null); + packet.generateResponse(IqPacket.TYPE_RESULT), null); } else { // TODO some sort of exception } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java index 9e051472..1a49b45e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.xmpp.stanzas; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; public class AbstractStanza extends Element { @@ -11,24 +10,11 @@ public class AbstractStanza extends Element { } public Jid getTo() { - try { - return Jid.fromString(getAttribute("to")); - } catch (final InvalidJidException e) { - return null; - } + return getAttributeAsJid("to"); } public Jid getFrom() { - String from = getAttribute("from"); - if (from == null) { - return null; - } else { - try { - return Jid.fromString(from); - } catch (final InvalidJidException e) { - return null; - } - } + return getAttributeAsJid("from"); } public String getId() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 9df05e67..2481112b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -9,27 +9,27 @@ public class IqPacket extends AbstractStanza { public static final int TYPE_RESULT = 1; public static final int TYPE_GET = 2; - private IqPacket(String name) { + private IqPacket(final String name) { super(name); } - public IqPacket(int type) { + public IqPacket(final int type) { super("iq"); switch (type) { - case TYPE_SET: - this.setAttribute("type", "set"); - break; - case TYPE_GET: - this.setAttribute("type", "get"); - break; - case TYPE_RESULT: - this.setAttribute("type", "result"); - break; - case TYPE_ERROR: - this.setAttribute("type", "error"); - break; - default: - break; + case TYPE_SET: + this.setAttribute("type", "set"); + break; + case TYPE_GET: + this.setAttribute("type", "get"); + break; + case TYPE_RESULT: + this.setAttribute("type", "result"); + break; + case TYPE_ERROR: + this.setAttribute("type", "error"); + break; + default: + break; } } @@ -45,29 +45,30 @@ public class IqPacket extends AbstractStanza { return query; } - public Element query(String xmlns) { - Element query = query(); + public Element query(final String xmlns) { + final Element query = query(); query.setAttribute("xmlns", xmlns); return query(); } public int getType() { - String type = getAttribute("type"); - if ("error".equals(type)) { - return TYPE_ERROR; - } else if ("result".equals(type)) { - return TYPE_RESULT; - } else if ("set".equals(type)) { - return TYPE_SET; - } else if ("get".equals(type)) { - return TYPE_GET; - } else { - return 1000; + final String type = getAttribute("type"); + switch (type) { + case "error": + return TYPE_ERROR; + case "result": + return TYPE_RESULT; + case "set": + return TYPE_SET; + case "get": + return TYPE_GET; + default: + return 1000; } } - public IqPacket generateRespone(int type) { - IqPacket packet = new IqPacket(type); + public IqPacket generateResponse(final int type) { + final IqPacket packet = new IqPacket(type); packet.setTo(this.getFrom()); packet.setId(this.getId()); return packet; diff --git a/src/main/res/layout/activity_change_password.xml b/src/main/res/layout/activity_change_password.xml new file mode 100644 index 00000000..c33f238f --- /dev/null +++ b/src/main/res/layout/activity_change_password.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primarybackground"> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_above="@+id/button_bar"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/current_password" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/current_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:hint="@string/password" + android:inputType="textPassword" + android:textColor="@color/primarytext" + android:textColorHint="@color/secondarytext" + android:textSize="?attr/TextSizeBody"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/new_password" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/new_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:hint="@string/password" + android:inputType="textPassword" + android:textColor="@color/primarytext" + android:textColorHint="@color/secondarytext" + android:textSize="?attr/TextSizeBody"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_confirm_password" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/new_password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password" + android:inputType="textPassword" + android:textColor="@color/primarytext" + android:textColorHint="@color/secondarytext" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + </ScrollView> + + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true"> + + <Button + android:id="@+id/left_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/cancel"/> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/divider"/> + + <Button + android:id="@+id/right_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/change_password"/> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index ed83cb9a..36987072 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/secondarybackground" > @@ -118,7 +119,8 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content" + tools:ignore="UselessParent"> <TextView android:layout_width="wrap_content" @@ -133,9 +135,18 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> </TableRow> + </TableLayout> + <TableLayout + android:id="@+id/server_info_more" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:stretchColumns="1" + android:visibility="gone" > + <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content" > @@ -153,7 +164,29 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> + + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_blocking" + android:textColor="@color/primarytext" android:textSize="?attr/TextSizeBody" /> + + <TextView + android:id="@+id/server_info_blocking" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> </TableRow> <TableRow @@ -173,7 +206,29 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> + + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_roster_version" + android:textColor="@color/primarytext" android:textSize="?attr/TextSizeBody" /> + + <TextView + android:id="@+id/server_info_roster_version" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> </TableRow> <TableRow @@ -193,11 +248,52 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> + + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_mam" + android:textColor="@color/primarytext" android:textSize="?attr/TextSizeBody" /> + + <TextView + android:id="@+id/server_info_mam" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> </TableRow> - </TableLayout> + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_csi" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" /> + <TextView + android:id="@+id/server_info_csi" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> + </TableLayout> <RelativeLayout android:layout_width="wrap_content" @@ -240,8 +336,6 @@ android:visibility="visible" android:contentDescription="@string/copy_otr_clipboard_description"/> </RelativeLayout> - - </LinearLayout> </LinearLayout> </ScrollView> @@ -251,6 +345,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" > @@ -281,4 +377,4 @@ android:textColor="@color/secondarytext" /> </LinearLayout> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/src/main/res/layout/activity_verify_otr.xml b/src/main/res/layout/activity_verify_otr.xml index 73539e0e..1a4221bc 100644 --- a/src/main/res/layout/activity_verify_otr.xml +++ b/src/main/res/layout/activity_verify_otr.xml @@ -1,189 +1,147 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/secondarybackground"> - - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - <TextView - android:id="@+id/error_no_session" - android:layout_margin="16dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/no_otr_session_found" - android:layout_gravity="center_horizontal" - android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeBody" - /> - <RelativeLayout - android:id="@+id/verification_area_one" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@drawable/infocard_border" - android:layout_margin="8dp"> - <LinearLayout - android:id="@+id/fingerprint_area" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:padding="16dp" - android:orientation="vertical"> - <TextView - android:id="@+id/remote_jid" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeHeadline"/> - <TextView - android:layout_marginTop="16dp" - android:id="@+id/your_fingerprint" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/secondarytext" - android:textSize="?attr/TextSizeInfo" - android:text="@string/your_fingerprint"/> - <TextView - android:layout_marginTop="16dp" - android:id="@+id/remote_fingerprint" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/secondarytext" - android:textSize="?attr/TextSizeInfo" - android:text="@string/remote_fingerprint"/> - </LinearLayout> - <LinearLayout - android:layout_below="@+id/fingerprint_area" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentRight="true" > - - <Button - android:id="@+id/button_show_qr_code" - style="?android:attr/borderlessButtonStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:text="@string/show_qr_code"/> - - <View - android:layout_width="1dp" - android:layout_height="fill_parent" - android:layout_marginBottom="7dp" - android:layout_marginTop="7dp" - android:background="@color/divider" /> - - <Button - android:id="@+id/button_scan_qr_code" - style="?android:attr/borderlessButtonStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:text="@string/scan_qr_code" - android:textColor="@color/primarytext" /> - </LinearLayout> - </RelativeLayout> - <RelativeLayout - android:id="@+id/verification_area_two" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_margin="8dp" - android:background="@drawable/infocard_border"> - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical" - android:id="@+id/shared_secret_box" - android:padding="16dp"> - <TextView - android:text="@string/smp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/primarytext" - android:textSize="?attr/TextSizeHeadline" - android:layout_marginBottom="16dp" - /> - <TextView - android:id="@+id/status_message" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/verified" - android:layout_gravity="center_horizontal" - android:textSize="?attr/TextSizeHeadline" - android:textStyle="bold" - android:visibility="gone"/> - <EditText - android:id="@+id/shared_secret_hint" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textAutoComplete" - android:hint="@string/shared_secret_hint" - android:textColor="@color/primarytext" - android:textColorHint="@color/secondarytext" - android:textSize="?attr/TextSizeBody" - android:layout_marginBottom="8dp"/> - <EditText - android:id="@+id/shared_secret_secret" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/shared_secret_secret" - android:inputType="textPassword" - android:textColor="@color/primarytext" - android:textColorHint="@color/secondarytext" - android:textSize="?attr/TextSizeBody" /> - </LinearLayout> - <LinearLayout - android:layout_below="@+id/shared_secret_box" - android:id="@+id/button_bar" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentRight="true" > - - <Button - android:id="@+id/button_shared_secret_negative" - style="?android:attr/borderlessButtonStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:enabled="false" - android:text="@string/cancel" - android:textColor="@color/secondarytext"/> - - <View - android:layout_width="1dp" - android:layout_height="fill_parent" - android:layout_marginBottom="7dp" - android:layout_marginTop="7dp" - android:background="@color/divider" /> - - <Button - android:id="@+id/button_shared_secret_positive" - style="?android:attr/borderlessButtonStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:text="@string/create" - android:textColor="@color/primarytext" /> - </LinearLayout> - </RelativeLayout> - </LinearLayout> -</ScrollView>
\ No newline at end of file +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primarybackground"> + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_above="@+id/button_bar"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:id="@+id/verification_explanation" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <LinearLayout + android:id="@+id/manual_verification_area" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/your_fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/your_fingerprint" + android:textColor="@color/secondarytext" + android:textSize="?attr/TextSizeInfo"/> + + <TextView + android:id="@+id/remote_fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="20dp" + android:text="@string/remote_fingerprint" + android:textColor="@color/secondarytext" + android:textSize="?attr/TextSizeInfo"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/smp_verification_area" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_marginTop="16dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/status_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:text="@string/verified" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold" + android:visibility="gone"/> + + <TextView + android:id="@+id/shared_secret_hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:textColor="@color/primarytext" + android:textSize="?attr/TextSizeBody" + android:textStyle="bold" + android:visibility="gone"/> + + <EditText + android:id="@+id/shared_secret_hint_editable" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:hint="@string/shared_secret_hint" + android:inputType="textAutoComplete" + android:textColor="@color/primarytext" + android:textColorHint="@color/secondarytext" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/shared_secret_secret" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:hint="@string/shared_secret_secret" + android:inputType="textPassword" + android:textColor="@color/primarytext" + android:textColorHint="@color/secondarytext" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + </LinearLayout> + </ScrollView> + + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true"> + + <Button + android:id="@+id/left_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/divider"/> + + <Button + android:id="@+id/right_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/menu/attachment_choices.xml b/src/main/res/menu/attachment_choices.xml index 12b37c08..5139272c 100644 --- a/src/main/res/menu/attachment_choices.xml +++ b/src/main/res/menu/attachment_choices.xml @@ -7,8 +7,11 @@ <item android:id="@+id/attach_take_picture" android:title="@string/attach_take_picture"/> + <item + android:id="@+id/attach_record_voice" + android:title="@string/attach_record_voice"/> <item - android:id="@+id/attach_record_voice" + android:id="@+id/attach_choose_file" android:title="@string/choose_file"/> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/contact_context.xml b/src/main/res/menu/contact_context.xml index 11ac7d7c..223c7ece 100644 --- a/src/main/res/menu/contact_context.xml +++ b/src/main/res/menu/contact_context.xml @@ -8,6 +8,9 @@ android:id="@+id/context_contact_details" android:title="@string/view_contact_details"/> <item + android:id="@+id/context_contact_block_unblock" + android:title="@string/block_contact"/> + <item android:id="@+id/context_delete_contact" android:title="@string/delete_contact"/> diff --git a/src/main/res/menu/conversations.xml b/src/main/res/menu/conversations.xml index d68c1436..6c22ed18 100644 --- a/src/main/res/menu/conversations.xml +++ b/src/main/res/menu/conversations.xml @@ -57,6 +57,18 @@ android:title="@string/enable_notifications"/> <item + android:id="@+id/action_block" + android:orderInCategory="72" + android:showAsAction="never" + android:title="@string/action_block_contact"/> + + <item + android:id="@+id/action_unblock" + android:orderInCategory="73" + android:showAsAction="never" + android:title="@string/action_unblock_contact"/> + + <item android:id="@+id/action_accounts" android:orderInCategory="90" android:showAsAction="never" diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index 2301caf0..4ce9e1f3 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -5,4 +5,19 @@ android:title="@string/show_qr_code" android:showAsAction="never" /> + <item + android:id="@+id/action_show_block_list" + android:title="@string/show_block_list" + android:showAsAction="never" /> + + <item + android:id="@+id/action_server_info_show_more" + android:title="@string/server_info_show_more" + android:checkable="true" + android:checked="false" + android:showAsAction="never" /> + + <item android:id="@+id/action_change_password_on_server" + android:title="@string/change_password" + android:showAsAction="never" /> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/manageaccounts.xml b/src/main/res/menu/manageaccounts.xml index b5cd9b50..5b04484f 100644 --- a/src/main/res/menu/manageaccounts.xml +++ b/src/main/res/menu/manageaccounts.xml @@ -1,15 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" > +<menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:id="@+id/action_add_account" - android:icon="@drawable/ic_action_add_person" - android:showAsAction="always" - android:title="@string/action_add_account"/> - <item - android:id="@+id/action_settings" - android:orderInCategory="100" - android:showAsAction="never" - android:title="@string/action_settings"/> + <item + android:id="@+id/action_add_account" + android:icon="@drawable/ic_action_add_person" + android:showAsAction="always" + android:title="@string/action_add_account"/> + <item + android:id="@+id/action_enable_all" + android:title="@string/enable_all_accounts"/> + <item + android:id="@+id/action_disable_all" + android:title="@string/disable_all_accounts"/> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_settings"/> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/verification_choices.xml b/src/main/res/menu/verification_choices.xml new file mode 100644 index 00000000..e833ba79 --- /dev/null +++ b/src/main/res/menu/verification_choices.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/scan_fingerprint" + android:title="@string/scan_qr_code"/> + <item + android:id="@+id/ask_question" + android:title="@string/ask_question"/> + + <item + android:id="@+id/manual_verification" + android:title="@string/manually_verify" /> + +</menu>
\ No newline at end of file diff --git a/src/main/res/menu/verify_otr.xml b/src/main/res/menu/verify_otr.xml index 1d4a11b6..42b66ad5 100644 --- a/src/main/res/menu/verify_otr.xml +++ b/src/main/res/menu/verify_otr.xml @@ -1,11 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:id="@+id/manually_verify" - android:orderInCategory="10" - android:showAsAction="never" - android:title="@string/manually_verify" /> + <item + android:id="@+id/action_show_qr_code" + android:title="@string/show_qr_code" + android:showAsAction="never" /> <item android:id="@+id/action_accounts" diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index cfbe428b..ae1da472 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Preferències</string> <string name="action_add">Nova conversa</string> <string name="action_accounts">Gestionar comptes</string> @@ -80,4 +79,4 @@ <string name="pref_never_send_crash_summary">Enviant traces d\'execució ajudes al futur desenvolupament del Conversations.</string> <string name="pref_ui_options">Opcions de UI</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 0078b740..33c7310b 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Nastavení</string> <string name="action_add">Nová konverzace</string> <string name="action_accounts">Nastavení účtů</string> @@ -17,7 +16,7 @@ <string name="title_activity_settings">Nastavení</string> <string name="title_activity_conference_details">Detaily konference</string> <string name="title_activity_contact_details">Detaily kontaktu</string> - <string name="title_activity_conversations">Konverzace</string> + <string name="title_activity_conversations">Conversations</string> <string name="title_activity_sharewith">Sdílet s konverzací</string> <string name="title_activity_start_conversation">Začít konverzaci</string> <string name="title_activity_choose_contact">Vybrat kontakt</string> @@ -41,6 +40,7 @@ <string name="invite_contact">Pozvat kontakt</string> <string name="contacts">Kontakty</string> <string name="cancel">Zrušit</string> + <string name="set">Nastavit</string> <string name="add">Přidat</string> <string name="edit">Upravit</string> <string name="delete">Smazat</string> @@ -58,7 +58,7 @@ <string name="add_contact">Přidat kontakt</string> <string name="send_failed">doručení selhalo</string> <string name="send_rejected">zamítnuto</string> - <string name="receiving_image">Přijímám obrázek. Chvíli strpení…</string> + <string name="receiving_image">Přijímám obrázek (%1$d%%)</string> <string name="preparing_image">Připravuji obrázek na přenos</string> <string name="action_clear_history">Smazat historii</string> <string name="clear_conversation_history">Smaže historii konverzací</string> @@ -258,6 +258,11 @@ <string name="pref_expert_options_summary">S tímto zacházejte velmi opatrně</string> <string name="title_activity_about">O aplikaci Conversations</string> <string name="pref_about_conversations_summary">Informace o sestavení a licenci</string> + <string name="title_pref_quiet_hours">Tichý režim</string> + <string name="title_pref_quiet_hours_start_time">Odkdy</string> + <string name="title_pref_quiet_hours_end_time">Dokdy</string> + <string name="title_pref_enable_quiet_hours">Povolit tichý režim</string> + <string name="pref_quiet_hours_summary">Upozornění budou během tichého režimu ztlumena</string> <string name="pref_use_larger_font">Zvětšit písmo</string> <string name="pref_use_larger_font_summary">Používat v celé aplikaci větší velikost písma</string> <string name="pref_use_send_button_to_indicate_status">Tlačítko pro odeslání zobrazuje stav</string> @@ -336,4 +341,6 @@ <string name="reset">Reset</string> <string name="account_image_description">Avatar účtu</string> <string name="copy_otr_clipboard_description">Zkopírovat otisk OTR do schránky</string> + <string name="fetching_history_from_server">Načíst historii ze serveru</string> + <string name="no_more_history_on_server">Na serveru není žádná další historie</string> </resources> diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index cade782a..9e8e3b3d 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - - <string name="app_name">Conversations</string> <string name="action_settings">Einstellungen</string> <string name="action_add">Neue Unterhaltung</string> <string name="action_accounts">Konten verwalten</string> @@ -13,6 +11,10 @@ <string name="action_edit_contact">Name bearbeiten</string> <string name="action_add_phone_book">Zum Telefonbuch hinzufügen</string> <string name="action_delete_contact">Aus Kontaktliste entfernen</string> + <string name="action_block_contact">Kontakt sperren</string> + <string name="action_unblock_contact">Kontakt entsperren</string> + <string name="action_block_domain">Domain sperren</string> + <string name="action_unblock_domain">Domain entsperren</string> <string name="title_activity_manage_accounts">Konten verwalten</string> <string name="title_activity_settings">Einstellungen</string> <string name="title_activity_conference_details">Konferenzdetails</string> @@ -21,6 +23,7 @@ <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> <string name="title_activity_start_conversation">Beginne Unterhaltung</string> <string name="title_activity_choose_contact">Kontakt auswählen</string> + <string name="title_activity_block_list">Sperrliste</string> <string name="just_now">gerade</string> <string name="minute_ago">vor einer Minute</string> <string name="minutes_ago">vor %d Minuten</string> @@ -34,16 +37,25 @@ <string name="participant">Teilnehmer</string> <string name="visitor">Besucher</string> <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> + <string name="block_contact_text">Möchtest du %s sperren und keine Nachrichten mehr erhalten?</string> + <string name="unblock_contact_text">Möchtest du %s entsperren und wieder Nachrichten empfangen?</string> + <string name="block_domain_text">Sperre alle Kontakte von %s?</string> + <string name="unblock_domain_text">Entsperre alle Kontakte %s?</string> + <string name="contact_blocked">Kontakt gesperrt</string> <string name="remove_bookmark_text">Möchtest du das Lesezeichen %s entfernen? Die Unterhaltung mit diesem Lesezeichen wird dabei nicht entfernt.</string> <string name="register_account">Neues Konto auf dem Server erstellen</string> + <string name="change_password_on_server">Passwort ändern</string> <string name="share_with">Teile mit…</string> <string name="start_conversation">Beginne Unterhaltung</string> <string name="invite_contact">Kontakt einladen</string> <string name="contacts">Kontakte</string> <string name="cancel">Abbrechen</string> + <string name="set">Einstellen</string> <string name="add">Hinzufügen</string> <string name="edit">Bearbeiten</string> <string name="delete">Entfernen</string> + <string name="block">Sperren</string> + <string name="unblock">Entsperren</string> <string name="save">Speichern</string> <string name="ok">OK</string> <string name="crash_report_title">Conversations ist abgestürzt</string> @@ -173,7 +185,12 @@ <string name="contact_status_offline">Offline</string> <string name="muc_details_conference">Konferenz</string> <string name="muc_details_other_members">Andere Mitglieder</string> + <string name="server_info_show_more">Server Info</string> + <string name="server_info_mam">XEP-0313: MAM</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> <string name="server_info_pep">XEP-0163: PEP (Avatare)</string> <string name="server_info_available">verfügbar</string> @@ -201,6 +218,8 @@ <string name="join_conference">Konferenz beitreten</string> <string name="delete_contact">Kontakt löschen</string> <string name="view_contact_details">Kontaktdetails anzeigen</string> + <string name="block_contact">Kontakt sperren</string> + <string name="unblock_contact">Kontakte entsperren</string> <string name="create">Erstellen</string> <string name="contact_already_exists">Der Kontakt existiert bereits</string> <string name="join">Beitreten</string> @@ -258,6 +277,11 @@ <string name="pref_expert_options_summary">Hier bitte vorsichtig sein</string> <string name="title_activity_about">Über Conversations</string> <string name="pref_about_conversations_summary">Versions- und Lizenzinformationen</string> + <string name="title_pref_quiet_hours">Ruhige Stunden</string> + <string name="title_pref_quiet_hours_start_time">Beginn</string> + <string name="title_pref_quiet_hours_end_time">Ende</string> + <string name="title_pref_enable_quiet_hours">Aktiviere ruhige Stunden</string> + <string name="pref_quiet_hours_summary">Benachrichtigungen sind während der ruhigen Stunden stumm.</string> <string name="pref_use_larger_font">Schrift vergrößern</string> <string name="pref_use_larger_font_summary">Größere Schrift verwenden</string> <string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string> @@ -288,7 +312,8 @@ <string name="image_transmission_failed">Bild-Übertragung fehlgeschlagen</string> <string name="scan_qr_code">Scanne QR-Code</string> <string name="show_qr_code">Zeige QR-Code</string> - <string name="account_details">Account Details</string> + <string name="show_block_list">Zeige Sperrliste</string> + <string name="account_details">Konto Details</string> <string name="verify_otr">Prüfe OTR</string> <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> <string name="scan">Scanne</string> @@ -324,7 +349,7 @@ <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> <string name="could_not_verify_fingerprint">Kann Fingerabdruck nicht überprüfen</string> <string name="manually_verify">Manuell überprüfen</string> - <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du die OTR-Fingerabdrücke des Kontakts überprüfen willst?</string> + <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du den OTR-Fingerabdruck des Kontakts überprüfen willst?</string> <string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string> <string name="pref_show_dynamic_tags_summary">Zeige schreibgeschützte Tags unterhalb der Kontakte</string> <string name="enable_notifications">Aktiviere Benachrichtigungen</string> @@ -336,4 +361,23 @@ <string name="reset">Zurücksetzen</string> <string name="account_image_description">Konto-Avatar</string> <string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string> + <string name="fetching_history_from_server">Hole Chatverlauf vom Server</string> + <string name="no_more_history_on_server">Keine weiteren Nachrichten auf dem Server vorhanden.</string> + <string name="updating">Aktualisiere…</string> + <string name="password_changed">Passwort geändert.</string> + <string name="could_not_change_password">Passwort kann nicht geändert werden.</string> + <string name="otr_session_not_started">Sende eine Nachricht, um eine verschlüsselte Unterhaltung zu beginnen</string> + <string name="ask_question">Frage stellen</string> + <string name="smp_explain_question">Falls du mit deinem Kontakt ein gemeinsames Geheimnis hast (z.B. ein Insider-Witz oder was ihr zuletzt gemeinsam zum Mittag gegessen habt), kann dies zur gegenseitigen Überprüfung des Fingerabdrucks genutzt werden.\n\nDu stellst eine Frage oder gibst einen Hinweis und dein Kontakt gibt eine eindeutige Antwort.</string> + <string name="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Schlüssels überprüfen. Dein Kontakt hat dazu folgende Frage gestellt.</string> + <string name="shared_secret_hint_should_not_be_empty">Deine Frage darf nicht leer sein.</string> + <string name="shared_secret_can_not_be_empty">Dein gemeinsamer Schlüssel darf nicht leer sein</string> + <string name="manual_verification_explanation">Vergleiche den angezeigten Fingerabdruck sorgfältig mit dem deines Kontakts.\nDu kannst dazu einen sicheren Kommunikationsweg (z.B. verschlüsselte E-Mail oder Telefonanruf) zum Austausch nutzen.</string> + <string name="change_password">Passwort ändern</string> + <string name="current_password">Aktuelles Passwort</string> + <string name="new_password">Neues Passwort</string> + <string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string> + <string name="enable_all_accounts">Alle Konten anschalten</string> + <string name="disable_all_accounts">Alle Konten abschalten</string> + <string name="perform_action_with">Aktion durchführen mit</string> </resources> diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 792f7671..d47a3adc 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -1,26 +1,30 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Ajustes</string> <string name="action_add">Nueva conversación</string> <string name="action_accounts">Gestionar cuentas</string> <string name="action_end_conversation">Terminar conversación</string> <string name="action_contact_details">Detalles del contacto</string> - <string name="action_muc_details">Detalles de la conferencia</string> + <string name="action_muc_details">Detalles de conversación en grupo</string> <string name="action_secure">Conversación segura</string> <string name="action_add_account">Añadir cuenta</string> <string name="action_edit_contact">Editar contacto</string> <string name="action_delete_contact">Eliminar contacto de la lista</string> <string name="action_add_phone_book">Añadir a contactos del teléfono</string> + <string name="action_block_contact">Bloquear contacto</string> + <string name="action_unblock_contact">Desbloquear contacto</string> + <string name="action_block_domain">Bloquear dominio</string> + <string name="action_unblock_domain">Desbloquear dominio</string> <string name="title_activity_manage_accounts">Gestionar Cuentas</string> <string name="title_activity_settings">Ajustes</string> - <string name="title_activity_conference_details">Detalles de Conferencia</string> + <string name="title_activity_conference_details">Detalles de Conversación en grupo</string> <string name="title_activity_contact_details">Detalles del Contacto</string> <string name="title_activity_conversations">Conversations</string> <string name="title_activity_sharewith">Compartir con Conversación</string> <string name="title_activity_start_conversation">Nueva Conversación</string> <string name="title_activity_choose_contact">Elegir Contacto</string> + <string name="title_activity_block_list">Lista contactos bloqueados</string> <string name="just_now">ahora</string> <string name="minute_ago">hace 1 min</string> <string name="minutes_ago">hace %d min</string> @@ -34,16 +38,25 @@ <string name="participant">Participante</string> <string name="visitor">Visitante</string> <string name="remove_contact_text">¿Quieres eliminar a %s de tu lista? La conversación asociada a esta cuenta no se eliminará.</string> - <string name="remove_bookmark_text">¿Quieres eliminar %s de tus marcadores? La conversación de la conferencia asociada con este marcador no se eliminará.</string> + <string name="block_contact_text">¿Quieres bloquear a %s para que no pueda enviarte mensajes?</string> + <string name="unblock_contact_text">¿Quieres desbloquear a %s y permitirle que te envíe mensajes?</string> + <string name="block_domain_text">¿Bloquear todos los contactos de %s?</string> + <string name="unblock_domain_text">¿Desbloquear todos los contatos de %s?</string> + <string name="contact_blocked">Contacto bloqueado</string> + <string name="remove_bookmark_text">¿Quieres eliminar %s de tus marcadores? La conversación asociada con este marcador no se eliminará.</string> <string name="register_account">Registrar nueva cuenta en servidor</string> + <string name="change_password_on_server">Cambiar contraseña</string> <string name="share_with">Compartir con</string> <string name="start_conversation">Comenzar conversación</string> <string name="invite_contact">Invitar contactos</string> <string name="contacts">Contactos</string> <string name="cancel">Cancelar</string> + <string name="set">Establecer</string> <string name="add">Añadir</string> <string name="edit">Editar</string> <string name="delete">Eliminar</string> + <string name="block">Bloquear</string> + <string name="unblock">Desbloquear</string> <string name="save">Guardar</string> <string name="ok">OK</string> <string name="crash_report_title">Conversations se ha detenido.</string> @@ -64,11 +77,11 @@ <string name="clear_conversation_history">Limpiar historial de conversación</string> <string name="clear_histor_msg">¿Quieres borrar todos los mensajes de esta conversación?\n\n<b>Aviso:</b> Esto no afectará a los mensajes guardados en otros dispositivos o servidores.</string> <string name="delete_messages">Borrar mensajes</string> - <string name="also_end_conversation">Terminar esta conversación más tarde</string> + <string name="also_end_conversation">Además, terminar esta conversación</string> <string name="choose_presence">Selecciona recurso del contacto</string> <string name="send_plain_text_message">Enviar mensaje de texto</string> - <string name="send_otr_message">Enviar mensaje encriptado con OTR</string> - <string name="send_pgp_message">Enviar mensaje encriptado con OpenPGP</string> + <string name="send_otr_message">Enviar mensaje cifrado con OTR</string> + <string name="send_pgp_message">Enviar mensaje cifrado con OpenPGP</string> <string name="your_nick_has_been_changed">Tu apodo se ha modificado</string> <string name="download_image">Descargar imagen</string> <string name="image_offered_for_download"><i>Archivo de imagen ofrecido para descarga</i></string> @@ -84,7 +97,7 @@ <string name="contact_has_no_pgp_key">Conversations no ha podido encriptar tus mensajes porque el contacto no está anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure OpenPGP.</small></string> <string name="no_pgp_keys">Claves OpenPGP no encontradas</string> <string name="contacts_have_no_pgp_keys">Conversations no ha podido encriptar tus mensajes porque tus contactos no están anunciando su clave publica.\n\n<small>Por favor, pide a tus contactos que configuren OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaje encriptado recibido. Pulsa para ver.</i></string> + <string name="encrypted_message_received"><i>Mensaje cifrado recibido. Pulsa para ver.</i></string> <string name="encrypted_image_received"><i>Imagen encriptada recibida. Pulsa para ver.</i></string> <string name="image_file"><i>Imagen recibida. Pulsa para ver</i></string> <string name="pref_general">General</string> @@ -99,12 +112,12 @@ <string name="pref_vibrate_summary">Vibra cuando llega un nuevo mensaje</string> <string name="pref_sound">Sonido</string> <string name="pref_sound_summary">Reproduce tono con la notificación</string> - <string name="pref_conference_notifications">Notificaciones de conferencia</string> - <string name="pref_conference_notifications_summary">Siempre notifica cuando llega un mensaje de conferencia y no solo cuando llega un mensaje destacado</string> + <string name="pref_conference_notifications">Notif. conversación grupo</string> + <string name="pref_conference_notifications_summary">Siempre notifica cuando llega un mensaje a una conversación en grupo y no solo cuando llega un mensaje destacado</string> <string name="pref_notification_grace_period">Notificaciones Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita las notificaciones durante un corto periodo de tiempo después de recibir la copia del mensaje carbon</string> <string name="pref_advanced_options">Opciones avanzadas</string> - <string name="pref_never_send_crash">Nunca enviar informe de fallos</string> + <string name="pref_never_send_crash">Nunca informar de errores</string> <string name="pref_never_send_crash_summary">Si envías registros de error ayudas al desarrollo de Conversations</string> <string name="pref_confirm_messages">Confirmar Mensajes</string> <string name="pref_confirm_messages_summary">Permitir a tus contactos saber cuando recibes y lees un mensaje</string> @@ -171,13 +184,18 @@ <string name="contact_status_extended_away">Ausencia extendida</string> <string name="contact_status_do_not_disturb">No molestar</string> <string name="contact_status_offline">Desconectado</string> - <string name="muc_details_conference">Conferencia</string> + <string name="muc_details_conference">Conversación en grupo</string> <string name="muc_details_other_members">Otros Miembros</string> + <string name="server_info_show_more">Información de servidor</string> + <string name="server_info_mam">XEP-0313: MAM</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> - <string name="server_info_available">Sí</string> - <string name="server_info_unavailable">No</string> + <string name="server_info_available">Disponible</string> + <string name="server_info_unavailable">No disponible</string> <string name="missing_public_keys">Se han perdido las claves de anuncio públicas</string> <string name="last_seen_now">Visto última vez ahora</string> <string name="last_seen_min">Visto última vez hace 1 minuto</string> @@ -187,31 +205,33 @@ <string name="last_seen_day">Visto última vez hace 1 día</string> <string name="last_seen_days">Visto última vez hace %d días</string> <string name="never_seen">Nunca visto</string> - <string name="install_openkeychain">Mensaje encriptado. Por favor instala OpenKeychain para desencriptar.</string> + <string name="install_openkeychain">Mensaje cifrado. Por favor instala OpenKeychain para desencriptar.</string> <string name="unknown_otr_fingerprint">Huella digital OTR desconocida</string> - <string name="openpgp_messages_found">Encontrado mensaje encriptado con OpenPGP</string> + <string name="openpgp_messages_found">Encontrado mensaje cifrado con OpenPGP</string> <string name="reception_failed">Error al recibir</string> <string name="your_fingerprint">Tu huella digital</string> <string name="otr_fingerprint">Huella digital OTR</string> <string name="verify">Verificar</string> <string name="decrypt">Desencriptar</string> - <string name="conferences">Conferencias</string> + <string name="conferences">Conversación Grupo</string> <string name="search">Buscar</string> <string name="create_contact">Crear Contacto</string> - <string name="join_conference">Unirse a Conferencia</string> + <string name="join_conference">Unirse a Conversación en grupo</string> <string name="delete_contact">Eliminar Contacto</string> <string name="view_contact_details">Ver detalles del contacto</string> + <string name="block_contact">Bloquear contacto</string> + <string name="unblock_contact">Desbloquear contacto</string> <string name="create">Crear</string> <string name="contact_already_exists">El contacto ya existe</string> <string name="join">Unirse</string> - <string name="conference_address">Dirección de la Conferencia</string> - <string name="conference_address_example">nombre@conferencia.ejemplo.com</string> + <string name="conference_address">Dirección</string> + <string name="conference_address_example">nombre@salas.ejemplo.com</string> <string name="save_as_bookmark">Guardar en marcadores</string> <string name="delete_bookmark">Eliminar marcador</string> - <string name="bookmark_already_exists">Este marcador ya exsite</string> + <string name="bookmark_already_exists">Este marcador ya existe</string> <string name="you">Tú</string> - <string name="action_edit_subject">Editar asunto de la conferencia</string> - <string name="conference_not_found">Conferencia no encontrada</string> + <string name="action_edit_subject">Editar asunto de la conversación</string> + <string name="conference_not_found">Conversación en grupo no encontrada</string> <string name="leave">Salir</string> <string name="contact_added_you">El contacto te ha añadido a su lista de contactos</string> <string name="add_back">Añadir contacto</string> @@ -238,19 +258,19 @@ <string name="disable_notifications_for_this_conversation">Deshabilitar notificaciones para esta conversación</string> <string name="notifications_disabled">Las notificaciones están deshabilitadas</string> <string name="enable">Habilitar</string> - <string name="conference_requires_password">La conferencia requiere contraseña</string> + <string name="conference_requires_password">La conversación en grupo requiere contraseña</string> <string name="enter_password">Introduce la contraseña</string> <string name="missing_presence_updates">Suscripción de actualizaciones de presencia del contacto perdida</string> <string name="request_presence_updates">Por favor, solicita la suscripción de presencia a tu contacto primero.\n\n<small>Esto será usado para determinar qué cliente(s) está usando tu contacto.</small></string> <string name="request_now">Solicitar ahora</string> <string name="delete_fingerprint">Eliminar huella digital OTR</string> - <string name="sure_delete_fingerprint">¿Estás seguro que quieres eliminar esta huella digital OTR?</string> + <string name="sure_delete_fingerprint">¿Estás seguro de que quieres eliminar esta huella digital OTR?</string> <string name="ignore">Ignorar</string> <string name="without_mutual_presence_updates"><b>Aviso:</b> Enviando esto sin suscripción de presencia por ambas partes podría causar problemas inesperados.\n\n<small>Verficia la suscripción de presencia en detalles del contacto.</small></string> <string name="pref_encryption_settings">Ajustes de encriptación</string> - <string name="pref_force_encryption">Forzar encriptación end-to-end</string> - <string name="pref_force_encryption_summary">Siempre enviar mensajes encriptados (excepto para conferencias)</string> - <string name="pref_dont_save_encrypted">No guardar mensajes encriptados</string> + <string name="pref_force_encryption">Forzar cifrado end-to-end</string> + <string name="pref_force_encryption_summary">Siempre enviar mensajes cifrados (excepto para conversaciones en grupo)</string> + <string name="pref_dont_save_encrypted">No guardar mensajes cifrados</string> <string name="pref_dont_save_encrypted_summary">Aviso: Esto podría llevar a pérdida de mensajes</string> <string name="pref_enable_legacy_ssl">Habilitar SSL heredado</string> <string name="pref_enable_legacy_ssl_summary">Habilita soporte SSLv3 para servidores heredados. Advertencia: SSLv3 se considera no seguro.</string> @@ -258,23 +278,28 @@ <string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string> <string name="title_activity_about">Acerca de Conversations</string> <string name="pref_about_conversations_summary">Información de compilación y licencia</string> - <string name="pref_use_larger_font">Incrementar tamaño de fuente</string> + <string name="title_pref_quiet_hours">Horario de silencio</string> + <string name="title_pref_quiet_hours_start_time">Hora de comienzo</string> + <string name="title_pref_quiet_hours_end_time">Hora de fin</string> + <string name="title_pref_enable_quiet_hours">Habilitar horario de silencio</string> + <string name="pref_quiet_hours_summary">Las notificaciones serán silenciadas durante el horario de silencio</string> + <string name="pref_use_larger_font">Tamaño de fuente grande</string> <string name="pref_use_larger_font_summary">Usar fuentes grandes en toda la aplicación</string> <string name="pref_use_send_button_to_indicate_status">Botón enviar indica estado</string> <string name="pref_use_indicate_received">Solicitar entrega de mensaje</string> <string name="pref_use_indicate_received_summary">Cuando el contacto reciba el mensaje será indicado con una marca verde. Cuidado, esto podría no funcionar en todos los casos.</string> <string name="pref_use_send_button_to_indicate_status_summary">El color del botón enviar indica el estado del contacto</string> <string name="pref_expert_options_other">Otros</string> - <string name="pref_conference_name">Nombre de conferencia</string> - <string name="pref_conference_name_summary">Usar el asunto de la conferencia en lugar del identificador jabber como nombre de conferencia</string> + <string name="pref_conference_name">Nombre conversación grupo</string> + <string name="pref_conference_name_summary">Usar el asunto de la conversación en lugar del identificador jabber como nombre en las conversaciones en grupo</string> <string name="toast_message_otr_fingerprint">¡Huella digital OTR copiada al portapapeles!</string> - <string name="conference_banned">Tu entrada a esta conferencia ha sido prohibida</string> - <string name="conference_members_only">Esta conferencia es solo para miembros</string> - <string name="conference_kicked">Has sido expulsado de esta conferencia</string> + <string name="conference_banned">Tu entrada a esta conversación ha sido prohibida</string> + <string name="conference_members_only">Esta conversación es solo para miembros</string> + <string name="conference_kicked">Has sido expulsado de esta conversación</string> <string name="using_account">Usando cuenta %s</string> <string name="checking_image">Comprobando imagen en servidor HTTP</string> <string name="image_file_deleted">El archivo de imagen ha sido eliminado</string> - <string name="not_connected_try_again">No estás concectado. Inténtalo más tarde</string> + <string name="not_connected_try_again">No estás conectado. Inténtalo más tarde</string> <string name="check_image_filesize">Comprobar el tamaño del archivo de imagen</string> <string name="message_options">Opciones de mensaje</string> <string name="copy_text">Copiar texto</string> @@ -288,6 +313,7 @@ <string name="image_transmission_failed">Falló la transferencia de la imagen</string> <string name="scan_qr_code">Escanear código QR</string> <string name="show_qr_code">Mostrar código QR</string> + <string name="show_block_list">Mostrar contactos bloqueados</string> <string name="account_details">Detalles de la cuenta</string> <string name="verify_otr">Verificar OTR</string> <string name="remote_fingerprint">Huella digital remota</string> @@ -308,13 +334,13 @@ <string name="no_otr_session_found">¡No se ha encontrado una sesión OTR válida!</string> <string name="conversations_foreground_service">Conversations</string> <string name="touch_to_disable">Pulsa para deshabilitar servicio en primer plano</string> - <string name="pref_keep_foreground_service">Mantener servicio en primer plano</string> - <string name="pref_keep_foreground_service_summary">Previene que el sistema cierre la conexión</string> + <string name="pref_keep_foreground_service">Servicio en primer plano</string> + <string name="pref_keep_foreground_service_summary">Mantener el servicio en primer plano previene que el sistema cierre la conexión</string> <string name="choose_file">Seleccionar archivo</string> - <string name="receiving_file">Recibiendo archivo %1$s (%2$d%% completedo)</string> + <string name="receiving_file">Recibiendo archivo %1$s (%2$d%% completado)</string> <string name="download_file">Descargar archivo %s</string> <string name="open_file">Abrir archivo %s</string> - <string name="sending_file">Enviando (%1$d%% completedo)</string> + <string name="sending_file">Enviando (%1$d%% completado)</string> <string name="preparing_file">Preparando transferencia de archivo</string> <string name="file_offered_for_download">Archivo ofrecido para descarga</string> <string name="file">Archivo %s</string> @@ -328,12 +354,17 @@ <string name="pref_show_dynamic_tags">Mostrar etiquetas</string> <string name="pref_show_dynamic_tags_summary">Muestra información en forma de etiquetas debajo de los contactos</string> <string name="enable_notifications">Habilitar notificaciones</string> - <string name="conference_with">Crear conferencia con…</string> - <string name="no_conference_server_found">No se ha encontrado el servidor de conferencias</string> - <string name="conference_creation_failed">¡La creación de la conferencia ha fallado!</string> - <string name="conference_created">¡Conferencia creada!</string> + <string name="conference_with">Crear conversación en grupo</string> + <string name="no_conference_server_found">No se ha encontrado el servidor para crear la conversación en grupo</string> + <string name="conference_creation_failed">¡La creación de la conversación en grupo ha fallado!</string> + <string name="conference_created">¡Conversación en grupo creada!</string> <string name="secret_accepted">¡Secreto aceptado!</string> <string name="reset">Reinicializar</string> <string name="account_image_description">Imagen de perfil</string> <string name="copy_otr_clipboard_description">Copiar huella digital OTR al portapapeles</string> + <string name="fetching_history_from_server">Buscar historial en servidor</string> + <string name="no_more_history_on_server">No más historial del servidor</string> + <string name="updating">Actualizando…</string> + <string name="password_changed">!Contraseña cambiada!</string> + <string name="could_not_change_password">No se puede cambiar la contraseña</string> </resources> diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index 41f3793b..04388f4f 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Ezarpenak</string> <string name="action_add">Elkarrizketa berria</string> <string name="action_accounts">Kontuak kudeatu</string> diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index e1db316d..b3b86f1f 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Paramètres</string> <string name="action_add">Nouvelle conversation</string> <string name="action_accounts">Gérer les comptes</string> diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 58116463..637237b8 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Axustes</string> <string name="action_add">Nova conversa</string> <string name="action_accounts">Xestionar contas</string> @@ -127,4 +126,4 @@ <string name="invalid_jid">O identificador non é un identificador de Jabber válido</string> <string name="pref_ui_options">Opcións de interfaz</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index dab7471d..af4dc3e0 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Impostazioni</string> <string name="action_add">Nuova conversazione</string> <string name="action_accounts">Gestisci utenti</string> @@ -217,7 +216,7 @@ <string name="add_back">Add back</string> <string name="contact_has_read_up_to_this_point">%s ha letto fino a questo punto</string> <string name="publish">Pubblica</string> - <string name="touch_to_choose_picture">Tocca l\'avatar per selezionare l\'immagine dalla gallaria</string> + <string name="touch_to_choose_picture">Tocca l\'avatar per selezionare l\'immagine dalla galleria</string> <string name="publish_avatar_explanation">Nota bene: tutti i contatti sottoscritti agli aggiornamenti della tua presenza avranno il permesso di vedere questa immagine.</string> <string name="publishing">Pubblicazione…</string> <string name="error_publish_avatar_server_reject">Il server ha rifiutato la tua pubblicazione</string> diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index fd8eaa0b..79fe9f3a 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">הגדרות</string> <string name="action_add">דיון חדש</string> <string name="action_accounts">נהל חשבונות</string> @@ -221,4 +220,4 @@ <string name="send_private_message_to">שלח הודעה פרטית אל %s</string> <string name="pref_ui_options">אפשרויות ממשק משתמש</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-nl/arrays.xml b/src/main/res/values-nl/arrays.xml index 9ced79f4..e5ae27e7 100644 --- a/src/main/res/values-nl/arrays.xml +++ b/src/main/res/values-nl/arrays.xml @@ -5,7 +5,7 @@ <item>Mobiel</item> <item>Telefoon</item> <item>Tablet</item> - <item>Conversaties</item> + <item>Conversations</item> <item>Android</item> </string-array> <string-array name="filesizes"> @@ -20,5 +20,20 @@ <item>524288</item> <item>1048576</item> </string-array> + <string-array name="mute_options_descriptions"> + <item>30 minuten</item> + <item>1 uur</item> + <item>2 uur</item> + <item>8 uur</item> + <item>voor onbepaalde duur</item> + </string-array> + + <integer-array name="mute_options_durations"> + <item>1800</item> + <item>3600</item> + <item>7200</item> + <item>28800</item> + <item>-1</item> + </integer-array> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 7b3faca9..cb47ae97 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -1,128 +1,147 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversaties</string> <string name="action_settings">Instellingen</string> - <string name="action_add">Nieuwe conversatie</string> + <string name="action_add">Nieuw gesprek</string> <string name="action_accounts">Beheer account</string> - <string name="action_end_conversation">Beëindig conversatie</string> - <string name="action_contact_details">Contact details</string> - <string name="action_muc_details">Gesprek details</string> - <string name="action_secure">Beveiligde conversatie</string> + <string name="action_end_conversation">Beëindig gesprek</string> + <string name="action_contact_details">Contactgegevens</string> + <string name="action_muc_details">Gespreksgegevens</string> + <string name="action_secure">Beveiligd gesprek</string> <string name="action_add_account">Voeg account toe</string> <string name="action_edit_contact">Verander naam</string> - <string name="action_add_phone_book">Voeg aan telefoonboek toe</string> + <string name="action_add_phone_book">Voeg toe aan telefoonboek</string> <string name="action_delete_contact">Verwijder uit lijst</string> - <string name="title_activity_manage_accounts">Beheer Accounts</string> + <string name="action_block_contact">Blokkeer contact</string> + <string name="action_unblock_contact">Deblokkeer contact</string> + <string name="action_block_domain">Blokkeer domein</string> + <string name="action_unblock_domain">Deblokkeer domein</string> + <string name="title_activity_manage_accounts">Beheer accounts</string> <string name="title_activity_settings">Instellingen</string> - <string name="title_activity_conference_details">Groepsconversatie Details</string> - <string name="title_activity_contact_details">Contact Details</string> - <string name="title_activity_conversations">Conversaties</string> - <string name="title_activity_sharewith">Delen met Conversatie</string> + <string name="title_activity_conference_details">Groepsgespreksgegevens</string> + <string name="title_activity_contact_details">Contactgegevens</string> + <string name="title_activity_conversations">Gesprekken</string> + <string name="title_activity_sharewith">Delen met gesprek</string> + <string name="title_activity_start_conversation">Start gesprek</string> + <string name="title_activity_choose_contact">Kies contact</string> + <string name="title_activity_block_list">Geblokkeerde contacten</string> <string name="just_now">net</string> - <string name="minute_ago">1 min geleden</string> - <string name="minutes_ago">%d min geleden</string> - <string name="unread_conversations">ongelezen Conversaties</string> + <string name="minute_ago">1 min. geleden</string> + <string name="minutes_ago">%d min. geleden</string> + <string name="unread_conversations">ongelezen gesprekken</string> <string name="sending">versturen…</string> - <string name="encrypted_message">Bericht aan het ontsleutelen. Een moment geduld a.u.b.…</string> + <string name="encrypted_message">Bericht aan het ontsleutelen. Even geduld…</string> <string name="nick_in_use">Naam is al in gebruik</string> <string name="admin">Beheerder</string> <string name="owner">Eigenaar</string> <string name="moderator">Moderator</string> <string name="participant">Deelnemer</string> <string name="visitor">Bezoeker</string> - <string name="remove_contact_text">Wilt u %s uit uw lijst verwijderen? De conversatie met deze account zal niet worden verwijderd.</string> + <string name="remove_contact_text">Wil je %s uit je lijst verwijderen? Het gesprek met deze account zal niet worden verwijderd.</string> + <string name="block_contact_text">Wil je alle berichten van %s blokkeren?</string> + <string name="unblock_contact_text">Wil je %s deblokkeren en er weer berichten van kunnen ontvangen?</string> + <string name="block_domain_text">Alle contacten van %s blokkeren?</string> + <string name="unblock_domain_text">Alle contacten van %s deblokkeren?</string> + <string name="contact_blocked">Contact geblokkeerd</string> + <string name="remove_bookmark_text">Wil je %s als bladwijzer verwijderen? Het gesprek met deze account zal niet worden verwijderd.</string> <string name="register_account">Registreer nieuwe account op server</string> + <string name="change_password_on_server">Verander wachtwoord op server</string> <string name="share_with">Deel met</string> - <string name="start_conversation">Start Conversatie</string> + <string name="start_conversation">Start gesprek</string> + <string name="invite_contact">Nodig contact uit</string> <string name="contacts">Contacten</string> <string name="cancel">Annuleer</string> + <string name="set">Stel in</string> <string name="add">Voeg toe</string> <string name="edit">Bewerk</string> <string name="delete">Verwijder</string> + <string name="block">Blokkeer</string> + <string name="unblock">Deblokkeer</string> <string name="save">Sla op</string> <string name="ok">OK</string> - <string name="crash_report_title">Conversaties is gecrashed</string> - <string name="crash_report_message">Door het versturen van crash rapportages helpt u mee met de ontwikkeling van Conversaties.\n<b>Waarschuwing:</b> Deze app zal uw XMPP account gebruiken om de crash rapportages te versturen naar de ontwikkelaars.</string> + <string name="crash_report_title">Conversations is gecrasht</string> + <string name="crash_report_message">Door het versturen van crash rapportages help je de ontwikkeling van Conversations.\n\n<b>Waarschuwing:</b> Deze app zal je XMPP account gebruiken om de crash rapportages te versturen naar de ontwikkelaars.</string> <string name="send_now">Nu versturen</string> <string name="send_never">Niet opnieuw vragen</string> <string name="problem_connecting_to_account">Account verbinden mislukt</string> <string name="problem_connecting_to_accounts">Verbinden met meerdere accounts mislukt</string> <string name="touch_to_fix">Raak hier aan om accounts te beheren</string> <string name="attach_file">Voeg bestand bij</string> - <string name="not_in_roster">Het contact is geen onderdeel van uw lijst. Wilt u het toevoegen?</string> + <string name="not_in_roster">Het contact is geen onderdeel van uw lijst. Wil je het toevoegen?</string> <string name="add_contact">Voeg contact toe</string> <string name="send_failed">afleveren mislukt</string> <string name="send_rejected">geweigerd</string> - <string name="receiving_image">Bezig met ontvangen van afbeelding. Een moment geduld a.u.b.…</string> - <string name="preparing_image">Bezig met voorbereiden van het versturen van afbeelding</string> + <string name="receiving_image">Bezig met ontvangen van afbeelding. Even geduld…</string> + <string name="preparing_image">Bezig met voorbereiden van versturen van afbeelding</string> <string name="action_clear_history">Wis geschiedenis</string> - <string name="clear_conversation_history">Wis conversatie geschiedenis</string> - <string name="clear_histor_msg">Wilt U alle berichten in deze Conversatie verwijderen?\n\n<b>Waarschuwing:</b> Dit zal geen invloed hebben op de berichten opgeslagen op andere apparaten of servers.</string> + <string name="clear_conversation_history">Wis gespreksgeschiedenis</string> + <string name="clear_histor_msg">Wil je alle berichten in dit gesprek verwijderen?\n\n<b>Waarschuwing:</b> Dit zal geen invloed hebben op de berichten opgeslagen op andere apparaten of servers.</string> <string name="delete_messages">Verwijder berichten</string> - <string name="also_end_conversation">Beëindig deze conversatie na afloop</string> + <string name="also_end_conversation">Beëindig dit gesprek na afloop</string> <string name="choose_presence">Kies aanwezigheid om te tonen aan contact</string> <string name="send_plain_text_message">Verstuur eenvoudig tekst bericht</string> <string name="send_otr_message">Verstuur OTR versleuteld bericht</string> <string name="send_pgp_message">Verstuur OpenPGP versleuteld bericht</string> - <string name="your_nick_has_been_changed">Uw naam is veranderd</string> - <string name="download_image">Download Afbeelding</string> + <string name="your_nick_has_been_changed">Je naam is veranderd</string> + <string name="download_image">Download afbeelding</string> <string name="image_offered_for_download"><i>Afbeelding aangeboden voor downloaden</i></string> <string name="send_unencrypted">Verstuur onversleuteld</string> - <string name="decryption_failed">Ontsleutelen mislukt. Misschien hebt U niet de juiste private sleutel.</string> + <string name="decryption_failed">Ontsleutelen mislukt. Misschien heb je niet de juiste private sleutel.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversaties gebruikt een derde partij app genaamd <b>OpenKeychain</b> om berichten te versleutelen en ontsleutelen, en om publieke sleutels te beheren.\n\nOpenKeychain is beschikbaar onder de GPLv3 en beschikbaar op F-Droid en Google Play.\n\n<small>(Herstart Conversaties na installatie.)</small></string> + <string name="openkeychain_required_long">Conversations gebruikt een derde partij app genaamd <b>OpenKeychain</b> om berichten te versleutelen en ontsleutelen, en om publieke sleutels te beheren.\n\nOpenKeychain is beschikbaar onder de GPLv3 en beschikbaar op F-Droid en Google Play.\n\n<small>(Herstart Conversations na installatie.)</small></string> <string name="restart">Herstart</string> <string name="install">Installeer</string> - <string name="offering">offering…</string> - <string name="waiting">wachten…</string> + <string name="offering">offering…</string> + <string name="waiting">wachten…</string> <string name="no_pgp_key">Geen OpenPGP sleutel gevonden</string> - <string name="contact_has_no_pgp_key">Conversaties kan Uw berichten niet versleutelen omdat uw contact geen publieke sleutel heeft ingesteld.\n\n<small>Vraag uw contact om OpenPGP te configureren.</small></string> + <string name="contact_has_no_pgp_key">Conversations kan je berichten niet versleutelen omdat je contact geen publieke sleutel heeft ingesteld.\n\n<small>Vraag je contact om OpenPGP te configureren.</small></string> <string name="no_pgp_keys">Geen OpenPGP sleutels gevonden</string> - <string name="contacts_have_no_pgp_keys">Conversaties kan uw berichten niet versleutelen omdat uw contacten geen publieke sleutel hebben ingesteld.\n\n<small>Vraag uw contacten om OpenPGP te configureren.</small></string> + <string name="contacts_have_no_pgp_keys">Conversations kan je berichten niet versleutelen omdat je contacten geen publieke sleutel hebben ingesteld.\n\n<small>Vraag je contacten om OpenPGP te configureren.</small></string> <string name="encrypted_message_received"><i>Versleuteld bericht ontvangen. Raak aan om te bekijken en te ontsleutelen.</i></string> <string name="encrypted_image_received"><i>Versleutelde afbeelding ontvangen. Raak aan om te bekijken en te ontsleutelen.</i></string> <string name="image_file"><i>Afbeelding ontvangen. Raak aan om te bekijken.</i></string> + <string name="pref_general">Algemeen</string> <string name="pref_xmpp_resource">XMPP resource</string> <string name="pref_xmpp_resource_summary">De naam waarmee deze client zich identificeert</string> <string name="pref_accept_files">Accepteer bestanden</string> - <string name="pref_accept_files_summary">Accepteer automatisch bestanden kleiner dan…</string> - <string name="pref_notification_settings">Notificatie Instellingen</string> - <string name="pref_notifications">Notificaties</string> - <string name="pref_notifications_summary">Notificatie als een nieuw bericht arriveert</string> + <string name="pref_accept_files_summary">Accepteer automatisch bestanden kleiner dan…</string> + <string name="pref_notification_settings">Meldingsinstellingen</string> + <string name="pref_notifications">Meldingen</string> + <string name="pref_notifications_summary">Melding als een nieuw bericht arriveert</string> <string name="pref_vibrate">Trillen</string> <string name="pref_vibrate_summary">Tril ook wanneer een nieuw bericht arriveert</string> <string name="pref_sound">Geluid</string> - <string name="pref_sound_summary">Speel ringtone af bij notificatie</string> - <string name="pref_conference_notifications">Groepsconversatie notificaties</string> - <string name="pref_conference_notifications_summary">Toon altijd notificaties als er nieuwe berichten arriveren in groepsconversaties in plaats van alleen bij highlighting</string> - <string name="pref_notification_grace_period">Notificatie uitstel periode</string> - <string name="pref_notification_grace_period_summary">Zet notificaties voor korte tijd uit als er een carbon copy wordt ontvangen</string> - <string name="pref_advanced_options">Geadvanceerde Opties</string> + <string name="pref_sound_summary">Speel ringtone af bij melding</string> + <string name="pref_conference_notifications">Groepsgespreksmeldingen</string> + <string name="pref_conference_notifications_summary">Toon altijd meldingen als er nieuwe berichten arriveren in groepsgesprekken in plaats van alleen wanneer gemarkeerd</string> + <string name="pref_notification_grace_period">Uitstelperiode voor meldingen</string> + <string name="pref_notification_grace_period_summary">Zet meldingen voor korte tijd uit als er een carbon copy wordt ontvangen</string> + <string name="pref_advanced_options">Geavanceerde instellingen</string> <string name="pref_never_send_crash">Verstuur nooit crash rapportages</string> - <string name="pref_never_send_crash_summary">Door crash rapportages te versturen helpt U mee aan de ontwikkeling van Conversaties</string> - <string name="pref_confirm_messages">Bevestig Berichten</string> - <string name="pref_confirm_messages_summary">Laat uw contacten weten waneer U berichten hebt ontvangen en gelezen</string> + <string name="pref_never_send_crash_summary">Door crash rapportages te versturen help je de ontwikkeling van Conversations</string> + <string name="pref_confirm_messages">Bevestig berichten</string> + <string name="pref_confirm_messages_summary">Laat je contacten weten wanneer je berichten hebt ontvangen en gelezen</string> + <string name="pref_ui_options">UI opties</string> <string name="openpgp_error">OpenKeychain rapporteerde een fout</string> - <string name="error_decrypting_file">I/O Fout tijdens ontsleutelen bestand</string> - <string name="accept">Accepteer</string> + <string name="error_decrypting_file">I/O fout tijdens ontsleutelen bestand</string> + <string name="accept">Aanvaard</string> <string name="error">Er is een fout opgetreden</string> - <string name="pref_grant_presence_updates">Verleen toestemming voor aanwezigheid updates</string> - <string name="pref_grant_presence_updates_summary">Vantevoren toestemming verlenen en vragen aan contacten die U hebt aangemaakt</string> + <string name="pref_grant_presence_updates">Verleen toestemming voor aanwezigheidsupdates</string> + <string name="pref_grant_presence_updates_summary">Op voorhand toestemming verlenen en vragen aan contacten die je hebt aangemaakt</string> <string name="subscriptions">Abonnementen</string> - <string name="your_account">Uw account</string> + <string name="your_account">Je account</string> <string name="keys">Sleutels</string> - <string name="send_presence_updates">Verstuur aanwezigheid updates</string> - <string name="receive_presence_updates">Ontvang aanwezigheid updates</string> - <string name="ask_for_presence_updates">Vraag naar aanwezigheid updates</string> + <string name="send_presence_updates">Verstuur aanwezigheidsupdates</string> + <string name="receive_presence_updates">Ontvang aanwezigheidsupdates</string> + <string name="ask_for_presence_updates">Vraag naar aanwezigheidsupdates</string> <string name="attach_choose_picture">Kies afbeelding</string> <string name="attach_take_picture">Neem foto</string> - <string name="preemptively_grant">Vantevoren toestemming verlenen voor abonneren</string> - <string name="error_not_an_image_file">Het bestand dat U gekozen hebt is geen afbeelding</string> + <string name="preemptively_grant">Op voorhand toestemming verlenen voor abonneren</string> + <string name="error_not_an_image_file">Het bestand dat je gekozen hebt is geen afbeelding</string> <string name="error_compressing_image">Fout tijdens converteren van afbeelding</string> <string name="error_file_not_found">Bestand niet gevonden</string> - <string name="error_io_exception">Generieke I/O fout. Misschien is er geen opslagruimte meer beschikbaar?</string> - <string name="error_security_exception_during_image_copy">De app die U gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere app om een afbeelding te kiezen</small></string> + <string name="error_io_exception">Algemene I/O fout. Misschien is er geen opslagruimte meer beschikbaar?</string> + <string name="error_security_exception_during_image_copy">De app die je gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere app om een afbeelding te kiezen</small></string> <string name="account_status_unknown">Onbekend</string> <string name="account_status_disabled">Tijdelijk uitgezet</string> <string name="account_status_online">Online</string> @@ -135,15 +154,19 @@ <string name="account_status_regis_conflict">Gebruikersnaam bezet</string> <string name="account_status_regis_success">Registratie compleet</string> <string name="account_status_regis_not_sup">Server ondersteunt geen registratie</string> + <string name="account_status_security_error">Fout bij beveiliging</string> + <string name="account_status_incompatible_server">Incompatibele server</string> <string name="encryption_choice_none">Onversleuteld</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Bewerk account</string> <string name="mgmt_account_delete">Verwijder</string> <string name="mgmt_account_disable">Tijdelijk uitzetten</string> + <string name="mgmt_account_publish_avatar">Publish avatar</string> + <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> <string name="mgmt_account_enable">Aanzetten</string> - <string name="mgmt_account_are_you_sure">Weet U het zeker?</string> - <string name="mgmt_account_delete_confirm_text">Als U uw account verwijderd wordt Uw volledige conversatie geschiedenis gewist</string> + <string name="mgmt_account_are_you_sure">Ben je zeker?</string> + <string name="mgmt_account_delete_confirm_text">Als je je account verwijdert wordt je volledige gespreksgeschiedenis gewist</string> <string name="attach_record_voice">Neem stem op</string> <string name="account_settings_jabber_id">Jabber ID:</string> <string name="account_settings_password">Wachtwoord:</string> @@ -154,17 +177,25 @@ <string name="passwords_do_not_match">Wachtwoorden komen niet overeen</string> <string name="invalid_jid">Dit is geen geldig Jabber ID</string> <string name="error_out_of_memory">Geen geheugen beschikbaar. Afbeelding is te groot</string> - <string name="add_phone_book_text">Wilt U %s toevoegen aan de contactenlijst op uw telefoon?</string> + <string name="add_phone_book_text">Wil je %s toevoegen aan de contactenlijst op je telefoon?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">beschikbaar</string> <string name="contact_status_away">weg</string> <string name="contact_status_extended_away">langdurig weg</string> <string name="contact_status_do_not_disturb">niet storen</string> <string name="contact_status_offline">offline</string> - <string name="muc_details_conference">groepsconversatie</string> - <string name="muc_details_other_members">Andere Leden</string> - <string name="server_info_carbon_messages">Carbon Berichten</string> - <string name="server_info_stream_management">Stream Management</string> + <string name="muc_details_conference">Groepsgesprek</string> + <string name="muc_details_other_members">Andere leden</string> + <string name="server_info_show_more">Server info</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_available">beschikbaar</string> + <string name="server_info_unavailable">niet beschikbaar</string> <string name="missing_public_keys">Ontbrekende publieke sleutel aankondigingen</string> <string name="last_seen_now">zonet voor het laatst gezien</string> <string name="last_seen_min">1 minuut geleden voor het laatst gezien</string> @@ -176,58 +207,165 @@ <string name="never_seen">nog nooit gezien</string> <string name="install_openkeychain">Versleuteld bericht. Installeer OpenKeychain om te ontsleutelen.</string> <string name="unknown_otr_fingerprint">Onbekende OTR vingerafdruk</string> - <string name="openpgp_messages_found">OpenPGP encrypted messages found</string> + <string name="openpgp_messages_found">OpenPGP-versleutelde berichten gevonden</string> <string name="reception_failed">Ontvangen mislukt</string> - <string name="join_conference">Aan groepsconversatie deelnemen</string> - <string name="invite_contact">Contact uitnodigen</string> - <string name="your_fingerprint">Uw vingerafdruk</string> - <string name="delete_bookmark">Bladwijzer verwijderen</string> - <string name="join">Deelnemen</string> + <string name="your_fingerprint">Jouw vingerafdruk</string> <string name="otr_fingerprint">OTR vingerafdruk</string> - <string name="you">U</string> - <string name="conference_not_found">Groepsconversatie niet gevonden</string> + <string name="verify">Bevestig</string> + <string name="decrypt">Ontsleutel</string> + <string name="conferences">Groepsgesprekken</string> <string name="search">Zoeken</string> - <string name="contact_already_exists">Het contact bestaat al</string> - <string name="title_activity_start_conversation">Start Groepsconversatie</string> - <string name="title_activity_choose_contact">Kies contact</string> - <string name="contact_added_you">Contact added you to contact list</string> - <string name="view_contact_details">Contactdetails bekijken</string> - <string name="conferences">Groepsconversaties</string> - <string name="verify">Controleren</string> - <string name="create_contact">Contact Aanmaken</string> - <string name="remove_bookmark_text">Wilt u %s als bladwijzer verwijderen? De groepsconversatie die verbonden is met deze bladwijzer zal niet verwijderd worden.</string> - <string name="action_edit_subject">Onderwerp van groepsconversatie veranderen</string> - <string name="delete_contact">Contact Verwijderen</string> + <string name="create_contact">Maak contact aan</string> + <string name="join_conference">Aan groepsgesprek deelnemen</string> + <string name="delete_contact">Verwijder contact</string> + <string name="view_contact_details">Bekijk contactgegevens</string> + <string name="block_contact">Blokkeer contact</string> + <string name="unblock_contact">Deblokkeer contact</string> <string name="create">Aanmaken</string> - <string name="leave">Verlaten</string> - <string name="conference_address">Groepsconversatie adres</string> - <string name="save_as_bookmark">Bladwijzer toevoegen</string> - <string name="conference_address_example">kamer@groepsconversatie.voorbeeld.nl</string> - <string name="add_back">Terug toevoegen</string> + <string name="contact_already_exists">Het contact bestaat al</string> + <string name="join">Deelnemen</string> + <string name="conference_address">Gespreksadres</string> + <string name="conference_address_example">kamer@groepsgesprek.voorbeeld.be</string> + <string name="save_as_bookmark">Opslaan als bladwijzer</string> + <string name="delete_bookmark">Bladwijzer verwijderen</string> <string name="bookmark_already_exists">Deze bladwijzer bestaat al</string> - <string name="decrypt">Ontsleutelen</string> + <string name="you">Jij</string> + <string name="action_edit_subject">Onderwerp groepsgesprek bewerken</string> + <string name="conference_not_found">Groepsgesprek niet gevonden</string> + <string name="leave">Verlaten</string> + <string name="contact_added_you">Contact heeft je toegevoegd aan zijn/haar contacten</string> + <string name="add_back">Contact toevoegen aan eigen contacten</string> <string name="contact_has_read_up_to_this_point">%s heeft tot hier gelezen</string> + <string name="publish">Publiceer</string> + <string name="touch_to_choose_picture">Raak avatar aan om een foto uit de galerij te kiezen</string> + <string name="publish_avatar_explanation"><b>Aandacht:</b> Iedereen die je aanwezigheidsupdates ontvangt zal deze foto kunnen zien.</string> + <string name="publishing">Publiceren…</string> + <string name="error_publish_avatar_server_reject">De server weigerde de publicatie van je afbeelding</string> + <string name="error_publish_avatar_converting">Fout bij converteren van afbeelding</string> + <string name="error_saving_avatar">Fout bij opslaan van avatar</string> + <string name="or_long_press_for_default">(Of hou lang ingedrukt om de oorspronkelijke terug te zetten)</string> + <string name="error_publish_avatar_no_server_support">Je server ondersteunt de publicatie van avatars niet</string> + <string name="private_message">gefluisterd</string> + <string name="private_message_to">naar %s</string> + <string name="send_private_message_to">Stuur privébericht naar %s</string> + <string name="connect">Verbinden</string> + <string name="account_already_exists">Deze account bestaat al</string> <string name="next">Volgende</string> - <string name="publish_avatar_explanation">N.B.: Iedereen die uw aanwezigheid kan zien kan deze afbeelding zien.</string> - <string name="server_info_unavailable">niet beschikbaar</string> - <string name="mgmt_account_publish_pgp">Publiceer publieke OpenPGP sleutel</string> - <string name="additional_information">Extra informatie</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_session_established">Huidige sessie gevestigd</string> + <string name="additional_information">Bijkomstige informatie</string> <string name="skip">Overslaan</string> - <string name="connect">Verbinden</string> - <string name="account_already_exists">Dit account bestaat al</string> - <string name="private_message_to">naar %s</string> - <string name="send_private_message_to">Verstuur privé bericht aan %s</string> - <string name="touch_to_choose_picture">Klik op avatar om een afbeelding te selecteren uit de gallerij</string> - <string name="mgmt_account_publish_avatar">Publiceer avatar</string> - <string name="error_publish_avatar_server_reject">De server weigerde uw publicatie</string> - <string name="error_publish_avatar_converting">Er ging iets mis bij het converteren van uw afbeelding</string> - <string name="error_publish_avatar_no_server_support">Uw server ondersteunt de publicatie van avatars niet</string> - <string name="publishing">Publiceren…</string> - <string name="error_saving_avatar">Kon de avatar niet opslaan</string> - <string name="server_info_session_established">Huidige sessie opgezet</string> - <string name="or_long_press_for_default">(Of houdt lang ingedrukt om de oorspronkelijke terug te zetten)</string> - <string name="server_info_available">beschikbaar</string> - <string name="pref_ui_options">UI Opties</string> + <string name="disable_notifications">Meldingen uitzetten</string> + <string name="disable_notifications_for_this_conversation">Meldingen uitzetten voor dit gesprek</string> + <string name="notifications_disabled">Meldingen zijn uitgezet</string> + <string name="enable">Aanzetten</string> + <string name="conference_requires_password">Wachtwoord nodig voor toegang tot groepsgesprek</string> + <string name="enter_password">Wachtwoord:</string> + <string name="missing_presence_updates">Ontbrekende aanwezigheidsupdates van contact</string> + <string name="request_presence_updates">Vraag eerst aanwezigheidsupdates van je contact aan.\n\n<small>Dit wordt gebruikt om te bepalen welke client(s) je contact gebruikt.</small></string> + <string name="request_now">Nu aanvragen</string> + <string name="delete_fingerprint">Vingerafdruk verwijderen</string> + <string name="sure_delete_fingerprint">Ben je zeker dat je deze vingerafdruk wil verwijderen?</string> + <string name="ignore">Negeren</string> + <string name="without_mutual_presence_updates"><b>Waarschuwing:</b> Dit verzenden zonder wederzijdse aanwezigheidsupdates kan voor onverwachte problemen zorgen.\n\n<small>Ga naar contactgegevens om je aanwezigheidsupdates te bevestigen.</small></string> + <string name="pref_encryption_settings">Versleutelingsinstellingen</string> + <string name="pref_force_encryption">Verplicht end-to-end versleuteling</string> + <string name="pref_force_encryption_summary">Stuur berichten altijd versleuteld (behalve in groepsgesprekken)</string> + <string name="pref_dont_save_encrypted">Sla versleutelde berichten niet op</string> + <string name="pref_dont_save_encrypted_summary"><b>Waarschuwing:</b> Dit kan leiden tot verlies van berichten</string> + <string name="pref_enable_legacy_ssl">Sta legacy SSL toe</string> + <string name="pref_enable_legacy_ssl_summary">Staat ondersteuning voor SSLv3 voor legacy servers toe. <b>Waarschuwing:</b> SSLv3 is niet veilig.</string> + <string name="pref_expert_options">Expert-instellingen</string> + <string name="pref_expert_options_summary">Wees voorzichtig met deze instellingen</string> + <string name="title_activity_about">Over Conversations</string> + <string name="pref_about_conversations_summary">Build en licentie-informatie</string> + <string name="title_pref_quiet_hours">Stille uren</string> + <string name="title_pref_quiet_hours_start_time">Begintijd</string> + <string name="title_pref_quiet_hours_end_time">Eindtijd</string> + <string name="title_pref_enable_quiet_hours">Stille uren aanzetten</string> + <string name="pref_quiet_hours_summary">Tijdens stille uren worden meldingen onderdrukt</string> + <string name="pref_use_larger_font">Vergroot lettergrootte</string> + <string name="pref_use_larger_font_summary">Gebruik grotere lettertypes over de hele app</string> + <string name="pref_use_send_button_to_indicate_status">Verstuur-knop toont status aan</string> + <string name="pref_use_indicate_received">Vraag ontvangstbevestigingen</string> + <string name="pref_use_indicate_received_summary">Indien ondersteund, worden ontvangen berichten met een groen vinkje aangeduid</string> + <string name="pref_use_send_button_to_indicate_status_summary">Kleur verstuur-knop in om status van contact weer te geven</string> + <string name="pref_expert_options_other">Andere</string> + <string name="pref_conference_name">Groepsgespreksnaam</string> + <string name="pref_conference_name_summary">Gebruik onderwerp van kamer ipv JID om groepsgesprekken te identificeren</string> + <string name="toast_message_otr_fingerprint">OTR vingerafdruk naar klembord gekopieerd!</string> + <string name="conference_banned">Je bent verbannen uit dit groepsgesprek</string> + <string name="conference_members_only">Dit groepsgesprek is enkel voor leden</string> + <string name="conference_kicked">Je bent uit dit groepsgesprek geschopt</string> + <string name="using_account">account %s gebruiken</string> + <string name="checking_image">Afbeelding op HTTP host nakijken</string> + <string name="image_file_deleted">De afbeelding is verwijderd</string> + <string name="not_connected_try_again">Je bent niet verbonden. Probeer later opnieuw</string> + <string name="check_image_filesize">Bekijk bestandsgrootte van afbeelding</string> + <string name="message_options">Berichtopties</string> + <string name="copy_text">Kopieer tekst</string> + <string name="share_image">Deel afbeelding</string> + <string name="copy_original_url">Kopieer oorspronkelijke URL</string> + <string name="send_again">Verstuur opnieuw</string> + <string name="image_url">AfbeeldingsURL</string> + <string name="message_text">Berichttekst</string> + <string name="url_copied_to_clipboard">URL gekopieerd naar klembord</string> + <string name="message_copied_to_clipboard">Bericht gekopieerd naar klembord</string> + <string name="image_transmission_failed">Versturen van afbeelding mislukt</string> + <string name="scan_qr_code">Scan QR code</string> + <string name="show_qr_code">Toon QR code</string> + <string name="show_block_list">Toon geblokkeerde contacten</string> + <string name="account_details">Accountgegevens</string> + <string name="verify_otr">Bevestig OTR</string> + <string name="remote_fingerprint">Externe vingerafdruk</string> + <string name="scan">scan</string> + <string name="or_touch_phones">(of raak gsm\'s aan)</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Hint of vraag</string> + <string name="shared_secret_secret">Gedeeld geheim</string> + <string name="confirm">Bevestigen</string> + <string name="in_progress">Bezig</string> + <string name="respond">Antwoorden</string> + <string name="failed">Mislukt</string> + <string name="secrets_do_not_match">Geheimen komen niet overeen</string> + <string name="try_again">Opnieuw proberen</string> + <string name="finish">Afsluiten</string> + <string name="verified">Bevestigd!</string> + <string name="smp_requested">Contact vraagt SMP-bevestiging</string> + <string name="no_otr_session_found">Geen geldige OTR-sessie gevonden!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="touch_to_disable">Raak aan om voorgrond-service uit te zetten</string> + <string name="pref_keep_foreground_service">Hou service in voorgrond</string> + <string name="pref_keep_foreground_service_summary">Belet het besturingssysteem van je verbinding te onderbreken</string> + <string name="choose_file">Kies bestand</string> + <string name="receiving_file">Ontvange van %1$s bestand (%2$d%% voltooid)</string> + <string name="download_file">Download %s bestand</string> + <string name="open_file">Open %s bestand</string> + <string name="sending_file">versturen (%1$d%% voltooid)</string> + <string name="preparing_file">Bestand klaarmaken voor versturen</string> + <string name="file_offered_for_download">Bestand aangeboden om te downloaden</string> + <string name="file">%s bestand</string> + <string name="cancel_transmission">Annuleer bestandsoverdracht</string> + <string name="file_transmission_failed">bestandsoverdracht mislukt</string> + <string name="file_deleted">Het bestand is verwijderd</string> + <string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string> + <string name="could_not_verify_fingerprint">Kon vingerafdruk niet bevestigen</string> + <string name="manually_verify">Handmatig bevestigen</string> + <string name="are_you_sure_verify_fingerprint">Ben je zeker dat je de OTR-vingerafdruk van je contact wil bevestigen?</string> + <string name="pref_show_dynamic_tags">Toon dynamische tags</string> + <string name="pref_show_dynamic_tags_summary">Toon enkel-lezen tags onder contacten</string> + <string name="enable_notifications">Meldingen aanzetten</string> + <string name="conference_with">Groepsgesprek aanmaken met…</string> + <string name="no_conference_server_found">Geen groepsgespreksserver gevonden</string> + <string name="conference_creation_failed">Aanmaken van groepsgesprek mislukt!</string> + <string name="conference_created">Groepsgesprek aangemaakt!</string> + <string name="secret_accepted">Geheim aanvaard!</string> + <string name="reset">Opnieuw instellen</string> + <string name="account_image_description">Account-avatar</string> + <string name="copy_otr_clipboard_description">Kopieer OTR-vingerafdruk naar klembord</string> + <string name="fetching_history_from_server">Geschiedenis van server halen</string> + <string name="no_more_history_on_server">Geen verdere geschiedenis op server</string> + <string name="updating">Bijwerken…</string> + <string name="password_changed">Wachtwoord gewijzigd!</string> + <string name="could_not_change_password">Kon wachtwoord niet wijzigen</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 2aa26b0b..efc5ba6c 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Настройки</string> <string name="action_add">Новая беседа</string> <string name="action_accounts">Управление аккаунтами</string> diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index a3ed9112..2c3cb9c3 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">Inställningar</string> <string name="action_add">Ny konversation</string> <string name="action_accounts">Kontoinställningar</string> @@ -257,4 +256,4 @@ <string name="account_status_disabled">Tillfälligt inaktiverad</string> <string name="mgmt_account_disable">Inaktivera tillfälligt</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index a7898425..b3b9604e 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">设置</string> <string name="action_add">新会话</string> <string name="action_accounts">管理账户</string> @@ -257,4 +256,4 @@ <string name="pref_use_send_button_to_indicate_status">发送按钮显示状态</string> <string name="pref_use_send_button_to_indicate_status_summary">发送按钮采用其他颜色以示发送状态的区别</string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 2c3ea225..24872cf7 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> <string name="action_settings">設定</string> <string name="action_add">新對話</string> <string name="action_accounts">管理帳戶</string> @@ -260,4 +259,4 @@ <string name="pref_conference_name">群組名稱</string> <string name="pref_conference_name_summary">使用群組的名稱而不是 JID 來識別之。 </string> -</resources>
\ No newline at end of file +</resources> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6b639452..45174cd8 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="app_name">Conversations</string> + <string name="app_name" translatable="false">Conversations</string> <string name="action_settings">Settings</string> <string name="action_add">New conversation</string> <string name="action_accounts">Manage accounts</string> @@ -13,6 +13,10 @@ <string name="action_edit_contact">Edit name</string> <string name="action_add_phone_book">Add to phone book</string> <string name="action_delete_contact">Delete from roster</string> + <string name="action_block_contact">Block contact</string> + <string name="action_unblock_contact">Unblock contact</string> + <string name="action_block_domain">Block domain</string> + <string name="action_unblock_domain">Unblock domain</string> <string name="title_activity_manage_accounts">Manage Accounts</string> <string name="title_activity_settings">Settings</string> <string name="title_activity_conference_details">Conference Details</string> @@ -21,6 +25,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_block_list">Block list</string> <string name="just_now">just now</string> <string name="minute_ago">1 min ago</string> <string name="minutes_ago">%d mins ago</string> @@ -34,16 +39,25 @@ <string name="participant">Participant</string> <string name="visitor">Visitor</string> <string name="remove_contact_text">Would you like to remove %s from your roster? The conversation associated with this contact will not be removed.</string> + <string name="block_contact_text">Would you like to block %s from sending you messages?</string> + <string name="unblock_contact_text">Would you like to unblock %s and allow them to send you messages?</string> + <string name="block_domain_text">Block all contacts from %s?</string> + <string name="unblock_domain_text">Unblock all contacts from %s?</string> + <string name="contact_blocked">Contact blocked</string> <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string> <string name="register_account">Register new account on server</string> + <string name="change_password_on_server">Change password on server</string> <string name="share_with">Share with</string> <string name="start_conversation">Start Conversation</string> <string name="invite_contact">Invite Contact</string> <string name="contacts">Contacts</string> <string name="cancel">Cancel</string> + <string name="set">Set</string> <string name="add">Add</string> <string name="edit">Edit</string> <string name="delete">Delete</string> + <string name="block">Block</string> + <string name="unblock">Unblock</string> <string name="save">Save</string> <string name="ok">OK</string> <string name="crash_report_title">Conversations has crashed</string> @@ -173,7 +187,12 @@ <string name="contact_status_offline">offline</string> <string name="muc_details_conference">Conference</string> <string name="muc_details_other_members">Other Members</string> + <string name="server_info_show_more">Server info</string> + <string name="server_info_mam">XEP-0313: MAM</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> <string name="server_info_available">available</string> @@ -201,6 +220,8 @@ <string name="join_conference">Join Conference</string> <string name="delete_contact">Delete Contact</string> <string name="view_contact_details">View contact details</string> + <string name="block_contact">Block contact</string> + <string name="unblock_contact">Unblock contact</string> <string name="create">Create</string> <string name="contact_already_exists">The contact already exists</string> <string name="join">Join</string> @@ -229,7 +250,7 @@ <string name="private_message_to">to %s</string> <string name="send_private_message_to">Send private message to %s</string> <string name="connect">Connect</string> - <string name="account_already_exists">This account does already exist</string> + <string name="account_already_exists">This account already exists</string> <string name="next">Next</string> <string name="server_info_session_established">Current session established</string> <string name="additional_information">Additional Information</string> @@ -258,7 +279,7 @@ <string name="pref_expert_options_summary">Please be careful with these</string> <string name="title_activity_about">About Conversations</string> <string name="pref_about_conversations_summary">Build and licensing information</string> - <string name="pref_about_message" translatable="false"> + <string name="pref_about_message" translatable="false"> Conversations • the very last word in instant messaging. \n\nCopyright © 2014 Daniel Gultsch \n\nThis program is free software: you can redistribute it and/or modify @@ -282,6 +303,11 @@ \n\nhttps://developer.android.com/tools/support-library\n(Apache License, Version 2.0) \n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0) </string> + <string name="title_pref_quiet_hours">Quiet Hours</string> + <string name="title_pref_quiet_hours_start_time">Start time</string> + <string name="title_pref_quiet_hours_end_time">End time</string> + <string name="title_pref_enable_quiet_hours">Enable quiet hours</string> + <string name="pref_quiet_hours_summary">Notifications will be silenced during quiet hours</string> <string name="pref_use_larger_font">Increase font size</string> <string name="pref_use_larger_font_summary">Use larger font sizes across the entire app</string> <string name="pref_use_send_button_to_indicate_status">Send button indicates status</string> @@ -312,6 +338,7 @@ <string name="image_transmission_failed">Image transmission failed</string> <string name="scan_qr_code">Scan QR code</string> <string name="show_qr_code">Show QR code</string> + <string name="show_block_list">Show block list</string> <string name="account_details">Account details</string> <string name="verify_otr">Verify OTR</string> <string name="remote_fingerprint">Remote Fingerprint</string> @@ -360,4 +387,23 @@ <string name="reset">Reset</string> <string name="account_image_description">Account avatar</string> <string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string> + <string name="fetching_history_from_server">Fetching history from server</string> + <string name="no_more_history_on_server">No more history on server</string> + <string name="updating">Updating…</string> + <string name="password_changed">Password changed!</string> + <string name="could_not_change_password">Could not change password</string> + <string name="otr_session_not_started">Send a message to start an encrypted chat</string> + <string name="ask_question">Ask question</string> + <string name="smp_explain_question">If you and your contact have a secret in common that no one else knows (like an inside joke or simply what you had for lunch the last time you met) you can use that secret to verify each other\'s fingerprints.\n\nYou provide a hint or a question for your contact who will respond with a case-sensitive answer.</string> + <string name="smp_explain_answer">Your contact would like to verify your fingerprint by challenging you with a shared secret. Your contact provided the following hint or question for that secret.</string> + <string name="shared_secret_hint_should_not_be_empty">Your hint should not be empty</string> + <string name="shared_secret_can_not_be_empty">Your shared secret can not be empty</string> + <string name="manual_verification_explanation">Carefully compare the fingerprint shown below with the fingerprint of your contact.\nYou can use any trusted form of communication like an encrypted e-mail or a telephone call to exchange those.</string> + <string name="change_password">Change password</string> + <string name="current_password">Current password</string> + <string name="new_password">New password</string> + <string name="password_should_not_be_empty">Password should not be empty</string> + <string name="enable_all_accounts">Enable all accounts</string> + <string name="disable_all_accounts">Disable all accounts</string> + <string name="perform_action_with">Perform action with</string> </resources> diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index f927d915..9974f14d 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -35,7 +35,29 @@ android:key="show_notification" android:summary="@string/pref_notifications_summary" android:title="@string/pref_notifications" /> - <CheckBoxPreference + <PreferenceScreen + android:dependency="show_notification" + android:summary="@string/pref_quiet_hours_summary" + android:title="@string/title_pref_quiet_hours"> + <CheckBoxPreference + android:defaultValue="false" + android:key="enable_quiet_hours" + android:summary="@string/pref_quiet_hours_summary" + android:title="@string/title_pref_enable_quiet_hours" /> + <eu.siacs.conversations.ui.TimePreference + android:dependency="enable_quiet_hours" + android:key="quiet_hours_start" + android:negativeButtonText="@string/cancel" + android:positiveButtonText="@string/set" + android:title="@string/title_pref_quiet_hours_start_time" /> + <eu.siacs.conversations.ui.TimePreference + android:dependency="enable_quiet_hours" + android:key="quiet_hours_end" + android:negativeButtonText="@string/cancel" + android:positiveButtonText="@string/set" + android:title="@string/title_pref_quiet_hours_end_time" /> + </PreferenceScreen> + <CheckBoxPreference android:defaultValue="true" android:dependency="show_notification" android:key="vibrate_on_notification" @@ -123,5 +145,4 @@ <eu.siacs.conversations.ui.AboutPreference android:summary="@string/pref_about_conversations_summary" android:title="@string/title_activity_about" /> - </PreferenceScreen> |