Add support for XEP-0191 (Blocking command)

Fixes #791

Squash of commits:
534f25d7dae3ce6852243e28fdd0a69ac01e9463
808fdf5147f27a912a60bee39aa4bf1ddd4f43b4
1eaf8a8330710ad35ba7c368e04f909af623ae4c
31585242c2359efdcd0eeddb9745077f54dbc9eb
2e69bd0bd0286ed1e98a42f4c3421ba4d8cf524b
e904fb5015bf3a1904ab941a1957edf3b1e7abd2
eebbadf3b3816bbf8fcccb763e419fed252d266f
7c5b87724ce494e5a6e8026557ed50a8fd9f23e8
b0eaaf446937794fe19cbdb4f8309c3ff83d4e42
8c652f9e8bb3512958d9ad8c6f1326505f2d98c8
ad0ea1ad948ff6f8fde7b0b10f5163dc8852032f
f5d49897e0dba691ef53a0eddb9ed34d129ad442
a08fa64c505bd895b7c626cfad182380373be20b
de67079113e08394a276048c31f6b21baa300829
9069f342173ba30c2b20c67529c7ff497a6a257d
0169fa79d161ee898c4b6762e207087682a952d8
8585a5bd75a5d56927fed8317729bd15fffe4dcc
0053528a078369e0b65dcf71bda251072a1299c7
e901a9c3554bd7cca193e92919b463991eadfea7
c5c78257434813c69ab9b7558bcc8f7cbe858433
e905af348d46d77bc46b5f7211527684acc02fab
13a0f9a10c7892b0f90f5fabd2f2615701b0fd66
2cfba1e24b0139839e4453b92be7e20634d150cf
58e074fb5bb44b05a8104250fccd7c024c808c1a
0d6cf98fc8eab212d798ac79b336f9b70a14f06d
e23620f56b85bcab9f3b5d9ce1c01524cd9674dc
d72cd2fcc8d54176c3ff53411a69b9bb4642eff3
195143dff8836623a37094a6b8fa6aa01ef31580
5f5f3caf3a1e480a99d27ee5c34ba516419c52e4
1dee3d5861c9f9c710da4cbda3688d94c622ca93
23949b8aa32c78b27bab49bb3c4f3ff588925ce1
9bf97f8ae522796e0dacb7f6fe7a7f90f86a93a1
This commit is contained in:
Sam Whited 2014-12-21 15:43:58 -05:00
parent 9656970051
commit af7a64491f
44 changed files with 1485 additions and 904 deletions

View File

@ -50,9 +50,11 @@ run your own XMPP server for you and your friends. These XEP's are:
* 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 let the server know whether or not
* 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

View File

@ -16,3 +16,4 @@
* XEP-0313: Message Archive Management
* XEP-0333: Chat Markers
* XEP-0352: Client State Indication
* XEP-0191: Blocking command

View File

@ -76,6 +76,9 @@
<activity
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.ManageAccountActivity"
android:configChanges="orientation|screenSize"

View File

@ -11,8 +11,10 @@ import org.json.JSONException;
import org.json.JSONObject;
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 +58,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;
@ -120,6 +122,7 @@ public class Account extends AbstractEntity {
private String otrFingerprint;
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";
@ -279,7 +282,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);
}
@ -291,7 +294,7 @@ public class Account extends AbstractEntity {
return this.xmppConnection;
}
public void setXmppConnection(XmppConnection connection) {
public void setXmppConnection(final XmppConnection connection) {
this.xmppConnection = connection;
}
@ -323,7 +326,7 @@ public class Account extends AbstractEntity {
}
}
public void setRosterVersion(String version) {
public void setRosterVersion(final String version) {
this.rosterVersion = version;
}
@ -351,7 +354,7 @@ public class Account extends AbstractEntity {
return this.bookmarks;
}
public void setBookmarks(List<Bookmark> bookmarks) {
public void setBookmarks(final List<Bookmark> bookmarks) {
this.bookmarks = bookmarks;
}
@ -399,4 +402,21 @@ public class Account extends AbstractEntity {
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();
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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";
@ -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);
}
@ -457,22 +459,40 @@ public class Contact implements ListItem {
}
}
@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;

View File

@ -3,7 +3,6 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.SystemClock;
import android.util.Log;
import net.java.otr4j.OtrException;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
@ -25,7 +24,7 @@ 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;
@ -174,13 +173,29 @@ public class Conversation extends AbstractEntity {
return null;
}
public void populateWithMessages(List<Message> messages) {
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);
}
@ -267,7 +282,7 @@ public class Conversation extends AbstractEntity {
if (generatedName != null) {
return generatedName;
} else {
return getContactJid().getLocalpart();
return getJid().getLocalpart();
}
}
} else {
@ -287,11 +302,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;
}
@ -352,7 +368,7 @@ public class Conversation extends AbstractEntity {
if (this.otrSession != null) {
return this.otrSession;
} else {
final SessionID sessionId = new SessionID(this.getContactJid().toBareJid().toString(),
final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(),
presence,
"xmpp");
this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine());

View File

@ -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);
}

View File

@ -77,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(),

View File

@ -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;
@ -211,7 +211,7 @@ public class MucOptions {
}
} 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();
}
@ -389,7 +389,7 @@ public class MucOptions {
public Jid createJoinJid(String nick) {
try {
return Jid.fromString(this.conversation.getContactJid().toBareJid().toString() + "/"+nick);
return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/"+nick);
} catch (final InvalidJidException e) {
return null;
}

View File

@ -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);

View File

@ -1,14 +1,12 @@
package eu.siacs.conversations.generator;
import android.util.Log;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import eu.siacs.conversations.Config;
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;
@ -17,44 +15,44 @@ 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",
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);
@ -63,19 +61,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
final Element metadata = item
.addChild("metadata", "urn:xmpp:avatar:metadata");
Element info = metadata.addChild("info");
final Element info = metadata.addChild("info");
info.setAttribute("bytes", avatar.size);
info.setAttribute("id", avatar.sha1sum);
info.setAttribute("height", avatar.height);
@ -84,10 +82,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;
}
@ -100,11 +98,11 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket queryMessageArchiveManagement(MessageArchiveService.Query mam) {
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
final IqPacket packet = new IqPacket(IqPacket.TYPE_SET);
Element query = packet.query("urn:xmpp:mam:0");
final Element query = packet.query("urn:xmpp:mam:0");
query.setAttribute("queryid",mam.getQueryId());
Data data = new Data();
final Data data = new Data();
data.setFormType("urn:xmpp:mam:0");
if (mam.getWith()!=null) {
data.put("with", mam.getWith().toString());
@ -119,4 +117,25 @@ public class IqGenerator extends AbstractGenerator {
}
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;
}
}

View File

@ -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");

View File

@ -53,10 +53,10 @@ public abstract class AbstractParser {
protected void updateLastseen(final Element packet, final Account account,
final boolean presenceOverwrite) {
Jid from = packet.getAttributeAsJid("from");
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) {

View File

@ -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");
public 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) {
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,13 +58,13 @@ 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;
}
@ -68,13 +72,76 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
public void onIqPacketReceived(final Account account, final 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");
final Element query = packet.findChild("query");
this.rosterItems(account, query);
}
} else if (packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) {
// Only accept block list changes from the server.
// The server should probably prevent other people from faking a blocklist push,
// but just in case let's prevent it client side as well.
final Jid from = packet.getFrom();
if (from == null || from.equals(account.getServer()) || from.equals(account.getJid().toBareJid())) {
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 {
Log.d(Config.LOGTAG, "Received blocklist update from invalid jid: " + from.toString());
}
} else if (packet.hasChild("unblock", Xmlns.BLOCKING)) {
final Jid from = packet.getFrom();
if ((from == null || from.equals(account.getServer()) || from.equals(account.getJid().toBareJid())) &&
packet.getType() == IqPacket.TYPE_SET) {
Log.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.getChildren().get(0).getChildren();
if (items.size() == 0) {
// No children to unblock == unblock all
account.getBlocklist().clear();
} else {
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.getType() == IqPacket.TYPE_SET) {
Log.d(Config.LOGTAG, "Received unblock update from invalid jid " + from.toString());
} else {
Log.d(Config.LOGTAG, "Received unblock update with invalid type " + packet.getType());
}
}
} else {
if (packet.getFrom() == null) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": received iq with invalid from "+packet.toString());
@ -84,17 +151,17 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
IqPacket response = mXmppConnectionService.getIqGenerator()
final 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);
final IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else {
if ((packet.getType() == IqPacket.TYPE_GET)
|| (packet.getType() == IqPacket.TYPE_SET)) {
IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR);
Element error = response.addChild("error");
final IqPacket response = packet.generateRespone(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");

View File

@ -228,9 +228,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);
}

View File

@ -236,7 +236,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
public Query(Conversation conversation, long start, long end) {
this(conversation.getAccount(), start, end);
this.conversation = conversation;
this.with = conversation.getContactJid().toBareJid();
this.with = conversation.getJid().toBareJid();
}
public Query(Conversation conversation, long start, long end, PagingOrder order) {

View File

@ -35,11 +35,13 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import de.duenndns.ssl.MemorizingTrustManager;
@ -47,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;
@ -77,7 +79,11 @@ 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;
@ -93,9 +99,10 @@ import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class XmppConnectionService extends Service implements OnPhoneContactsLoadedListener {
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";
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";
private ContentObserver contactObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@ -129,13 +136,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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(
@ -208,6 +215,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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;
@ -354,13 +363,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@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);
}
}
@ -573,11 +582,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
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);
@ -589,10 +598,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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;
@ -694,7 +703,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateConversationUi();
}
private void sendUnsentMessages(Conversation conversation) {
private void sendUnsentMessages(final Conversation conversation) {
conversation.findWaitingMessages(new Conversation.OnMessageFound() {
@Override
@ -704,7 +713,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
});
}
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) {
@ -731,7 +740,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
mJingleConnectionManager.createNewConnection(message);
}
} catch (InvalidJidException e) {
} catch (final InvalidJidException ignored) {
}
}
@ -774,8 +783,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
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());
@ -789,8 +798,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onIqPacketReceived(final Account account,
IqPacket packet) {
Element query = packet.findChild("query");
final IqPacket packet) {
final Element query = packet.findChild("query");
if (query != null) {
account.getRoster().markAllAsNotInRoster();
mIqParser.rosterItems(account, query);
@ -799,22 +808,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
});
}
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) {
@ -832,7 +841,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
};
sendIqPacket(account, iqPacket, callback);
}
public void pushBookmarks(Account account) {
@ -885,7 +893,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
}
@ -992,8 +1000,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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;
}
@ -1001,15 +1009,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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;
}
}
@ -1177,7 +1183,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
public void setOnRosterUpdateListener(OnRosterUpdate listener) {
public void setOnRosterUpdateListener(final OnRosterUpdate listener) {
synchronized (this) {
if (checkListeners()) {
switchToForeground();
@ -1202,6 +1208,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
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()) {
@ -1293,7 +1324,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
packet.addChild("x", "jabber:x:signed").setContent(sig);
}
sendPresencePacket(account, packet);
if (!joinJid.equals(conversation.getContactJid())) {
if (!joinJid.equals(conversation.getJid())) {
conversation.setContactJid(joinJid);
databaseBackend.updateConversation(conversation);
}
@ -1369,14 +1400,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
}
@ -1401,7 +1432,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return null;
}
public void createAdhocConference(final Account account, final List<Jid> jids, final UiCallback<Conversation> callback) {
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 {
@ -1454,7 +1485,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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
@ -1468,7 +1499,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
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
@ -1506,7 +1537,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.endOtrIfNeeded()) {
Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": ended otr session with "
+ conversation.getContactJid());
+ conversation.getJid());
}
}
}
@ -1552,7 +1583,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final Session otrSession = conversation.getOtrSession();
Log.d(Config.LOGTAG,
account.getJid().toBareJid() + " otr session established with "
+ conversation.getContactJid() + "/"
+ conversation.getJid() + "/"
+ otrSession.getSessionID().getUserID());
conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
@ -1848,7 +1879,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
}
@ -1922,6 +1953,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
public void updateBlocklistUi(final OnUpdateBlocklist.Status status) {
if (mOnUpdateBlocklist != null) {
mOnUpdateBlocklist.OnUpdateBlocklist(status);
}
}
public void updateMucRosterUi() {
if (mOnMucRosterUpdate != null) {
mOnMucRosterUpdate.onMucRosterUpdate();
@ -2034,9 +2071,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
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);
}
@ -2054,6 +2090,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mIqGenerator;
}
public IqParser getIqParser() { return this.mIqParser; }
public JingleConnectionManager getJingleConnectionManager() {
return this.mJingleConnectionManager;
}
@ -2083,8 +2121,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
@ -2094,7 +2132,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
break;
}
}
for (Message msg : messages) {
for (final Message msg : messages) {
markMessage(msg, Message.STATUS_WAITING);
this.resendMessage(msg);
}
@ -2136,4 +2174,35 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
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);
}
}
});
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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() {
public class ChooseContactActivity extends AbstractSearchableListItemActivity {
@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;
}
};
@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();
}
}

View File

@ -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);
@ -301,7 +301,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
mYourPhoto.setImageBitmap(avatarService().get(
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()) {

View File

@ -38,10 +38,11 @@ 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.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;
@ -166,7 +167,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 +189,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);
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()) {
@ -363,8 +366,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);
@ -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();
}
});
}
}

View File

@ -15,7 +15,6 @@ 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;
@ -32,6 +31,7 @@ import java.util.ArrayList;
import java.util.List;
import eu.siacs.conversations.R;
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,9 +40,10 @@ 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";
@ -244,7 +245,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);
@ -269,17 +270,18 @@ public class ConversationActivity extends XmppActivity implements
@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,6 +294,8 @@ 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) {
@ -302,9 +306,20 @@ public class ConversationActivity extends XmppActivity implements
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);
}
if (!this.getSelectedConversation().getAccount().getXmppConnection().getFeatures().blocking()) {
menuBlock.setVisible(false);
menuUnblock.setVisible(false);
}
}
if (this.getSelectedConversation().isMuted()) {
menuMute.setVisible(false);
@ -410,7 +425,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 +470,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;
}
@ -619,8 +640,8 @@ 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 {
@ -983,4 +1004,23 @@ public class ConversationActivity extends XmppActivity implements
}
});
}
@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);
}
}

View File

@ -9,7 +9,6 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
@ -39,7 +38,6 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
@ -583,12 +581,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);
}
});
@ -787,12 +803,13 @@ public class ConversationFragment extends Fragment {
}
}
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);
}

View File

@ -319,12 +319,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
@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);
if (mAccount == null) {
showQrCode.setVisible(false);
showBlocklist.setVisible(false);
} else if (!mAccount.getXmppConnection().getFeatures().blocking()) {
showBlocklist.setVisible(false);
}
return true;
}
@ -340,25 +344,31 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
if (this.jidToEdit != null) {
this.mRegisterNew.setVisibility(View.GONE);
if (getActionBar() != null) {
getActionBar().setTitle(getString(R.string.account_details));
}
} else {
this.mAvatar.setVisibility(View.GONE);
if (getActionBar() != null) {
getActionBar().setTitle(R.string.action_add_account);
}
}
}
}
@Override
protected void onBackendConnected() {
KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
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) {
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(false);
getActionBar().setDisplayShowHomeEnabled(false);
}
this.mCancelButton.setEnabled(false);
this.mCancelButton.setTextColor(getSecondaryTextColor());
}
@ -366,6 +376,18 @@ 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 intent = new Intent(this, BlocklistActivity.class);
intent.putExtra("account", mAccount.getJid().toString());
startActivity(intent);
break;
}
return super.onOptionsItemSelected(item);
}
private void updateAccountInformation() {
this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString());
this.mPassword.setText(this.mAccount.getPassword());
@ -387,7 +409,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(
getApplicationContext(), this.mAccount.getXmppConnection()
.getLastSessionEstablished()));
Features features = this.mAccount.getXmppConnection().getFeatures();
final Features features = this.mAccount.getXmppConnection().getFeatures();
if (features.carbons()) {
this.mServerInfoCarbons.setText(R.string.server_info_available);
} else {

View File

@ -117,10 +117,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;

View File

@ -53,19 +53,21 @@ 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) {
if (getActionBar() != null) {
getActionBar().setSelectedNavigationItem(position);
}
onTabChanged();
}
};
@ -270,10 +274,15 @@ 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,
@ -287,7 +296,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
});
builder.create().show();
}
protected void deleteConference() {
@ -685,16 +693,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);
}
@ -705,28 +726,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:
@ -735,6 +766,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;
@ -750,11 +784,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);
}

View File

@ -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;
@ -245,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() {
@ -260,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));
@ -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);
@ -613,7 +621,7 @@ public abstract class XmppActivity extends Activity {
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);
}

View File

@ -58,7 +58,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|| 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);

View File

@ -0,0 +1,5 @@
package eu.siacs.conversations.utils;
public final class Xmlns {
public static final String BLOCKING = "urn:xmpp:blocking";
}

View File

@ -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;

View File

@ -67,11 +67,11 @@ public class Element {
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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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,8 +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.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Tag;
import eu.siacs.conversations.xml.TagWriter;
@ -76,19 +80,19 @@ 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 enabledEncryption = false;
private boolean enabledCarbons = false;
@ -100,20 +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 ArrayList<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new ArrayList<>();
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());
@ -153,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 {
@ -170,9 +174,9 @@ public class XmppConnection implements Runnable {
// TODO: Handle me?`
srvRecordServer = "";
}
int srvRecordPort = namePort.getInt("port");
String srvIpServer = namePort.getString("ip");
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()
@ -187,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++;
}
@ -204,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();
@ -222,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 {
@ -289,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()
@ -301,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");
@ -327,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(
@ -374,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);
@ -388,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) {
@ -411,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"
@ -432,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
@ -461,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))
@ -477,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))
@ -494,7 +491,7 @@ public class XmppConnection implements Runnable {
}
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);
}
@ -512,11 +509,11 @@ public class XmppConnection implements Runnable {
IOException {
tagReader.readTag();
try {
SSLContext sc = SSLContext.getInstance("TLS");
final SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null,
new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},
mXmppConnectionService.getRNG());
SSLSocketFactory factory = sc.getSocketFactory();
final SSLSocketFactory factory = sc.getSocketFactory();
if (factory == null) {
throw new IOException("SSLSocketFactory was null");
@ -541,7 +538,7 @@ public class XmppConnection implements Runnable {
if (enableLegacySSL()) {
supportProtocols = sslSocket.getSupportedProtocols();
} else {
final List<String> supportedProtocols = new LinkedList<>(
final Collection<String> supportedProtocols = new LinkedList<>(
Arrays.asList(sslSocket.getSupportedProtocols()));
supportedProtocols.remove("SSLv3");
supportProtocols = new String[supportedProtocols.size()];
@ -569,7 +566,7 @@ public class XmppConnection implements Runnable {
}
}
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) {
@ -618,7 +615,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) {
@ -629,38 +626,37 @@ public class XmppConnection implements Runnable {
}
}
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")
final IqPacket register = new IqPacket(IqPacket.TYPE_SET);
final Element username = new Element("username")
.setContent(account.getUsername());
Element password = new Element("password")
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);
@ -687,14 +683,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) {
@ -705,14 +701,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")) {
smVersion = 2;
EnablePacket enable = new EnablePacket(smVersion);
final EnablePacket enable = new EnablePacket(smVersion);
tagWriter.writeStanzaAsync(enable);
stanzasSent = 0;
messageReceipts.clear();
@ -736,7 +732,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);
@ -755,10 +751,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
@ -772,7 +768,7 @@ public class XmppConnection implements Runnable {
if (account.getServer().equals(server.toDomainJid())) {
enableAdvancedStreamFeatures();
for(OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
listener.onAdvancedStreamFeaturesAvailable(account);
}
}
@ -787,6 +783,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) {
@ -796,9 +796,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())) {
@ -811,12 +811,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");
@ -829,9 +829,9 @@ 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());
@ -842,7 +842,7 @@ public class XmppConnection implements Runnable {
}
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");
@ -856,33 +856,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;
@ -907,7 +906,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);
@ -916,44 +915,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 addOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesLoaded listener) {
public void addOnAdvancedStreamFeaturesAvailableListener(final OnAdvancedStreamFeaturesLoaded listener) {
if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) {
this.advancedStreamFeaturesLoadedListeners.add(listener);
}
}
public void disconnect(boolean force) {
public void disconnect(final boolean force) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting");
try {
if (force) {
@ -973,23 +972,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());
}
@ -997,8 +996,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);
}
@ -1010,8 +1009,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();
@ -1021,8 +1019,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;
}
@ -1035,7 +1033,7 @@ public class XmppConnection implements Runnable {
}
public long getLastSessionEstablished() {
long diff;
final long diff;
if (this.lastSessionStarted == 0) {
diff = SystemClock.elapsedRealtime() - this.lastConnect;
} else {
@ -1067,7 +1065,7 @@ public class XmppConnection implements Runnable {
public class Features {
XmppConnection connection;
public Features(XmppConnection connection) {
public Features(final XmppConnection connection) {
this.connection = connection;
}
@ -1080,6 +1078,10 @@ public class XmppConnection implements Runnable {
return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2");
}
public boolean blocking() {
return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING);
}
public boolean sm() {
return streamId != null;
}
@ -1110,4 +1112,8 @@ public class XmppConnection implements Runnable {
.findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null;
}
}
private IqGenerator getIqGenerator() {
return mXmppConnectionService.getIqGenerator();
}
}

View File

@ -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();
}
}

View File

@ -9,11 +9,11 @@ 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:
@ -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)) {
final String type = getAttribute("type");
switch (type) {
case "error":
return TYPE_ERROR;
} else if ("result".equals(type)) {
case "result":
return TYPE_RESULT;
} else if ("set".equals(type)) {
case "set":
return TYPE_SET;
} else if ("get".equals(type)) {
case "get":
return TYPE_GET;
} else {
default:
return 1000;
}
}
public IqPacket generateRespone(int type) {
IqPacket packet = new IqPacket(type);
public IqPacket generateRespone(final int type) {
final IqPacket packet = new IqPacket(type);
packet.setTo(this.getFrom());
packet.setId(this.getId());
return packet;

View File

@ -7,6 +7,9 @@
<item
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"/>

View File

@ -56,6 +56,18 @@
android:showAsAction="never"
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"

View File

@ -4,5 +4,9 @@
android:id="@+id/action_show_qr_code"
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" />
</menu>

View File

@ -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,6 +39,11 @@
<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="share_with">Share with</string>
@ -45,6 +55,8 @@
<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>
@ -202,6 +214,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>
@ -318,6 +332,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>