Merge pull request #796 from SamWhited/xep0191

XEP-0191 Support
This commit is contained in:
Daniel Gultsch 2014-12-22 15:00:51 +01:00
commit a94663aaa4
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>