diff options
Diffstat (limited to 'src/eu/siacs')
33 files changed, 1746 insertions, 1282 deletions
diff --git a/src/eu/siacs/conversations/entities/AbstractEntity.java b/src/eu/siacs/conversations/entities/AbstractEntity.java index 0297fa66..4891723e 100644 --- a/src/eu/siacs/conversations/entities/AbstractEntity.java +++ b/src/eu/siacs/conversations/entities/AbstractEntity.java @@ -1,12 +1,9 @@ package eu.siacs.conversations.entities; -import java.io.Serializable; - import android.content.ContentValues; -public abstract class AbstractEntity implements Serializable { +public abstract class AbstractEntity { - private static final long serialVersionUID = -1895605706690653719L; public static final String UUID = "uuid"; diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index ac62cf7b..b19889bf 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -4,6 +4,7 @@ import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; @@ -18,8 +19,6 @@ import android.content.Context; import android.database.Cursor; public class Account extends AbstractEntity{ - - private static final long serialVersionUID = 6174825093869578035L; public static final String TABLENAME = "accounts"; @@ -41,7 +40,6 @@ public class Account extends AbstractEntity{ public static final int STATUS_ONLINE = 1; public static final int STATUS_NO_INTERNET = 2; public static final int STATUS_UNAUTHORIZED = 3; - public static final int STATUS_TLS_ERROR = 4; public static final int STATUS_SERVER_NOT_FOUND = 5; public static final int STATUS_SERVER_REQUIRES_TLS = 6; @@ -72,6 +70,9 @@ public class Account extends AbstractEntity{ private List<Bookmark> bookmarks = new ArrayList<Bookmark>(); + public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>(); + public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>(); + public Account() { this.uuid = "0"; } diff --git a/src/eu/siacs/conversations/entities/Bookmark.java b/src/eu/siacs/conversations/entities/Bookmark.java index c4e151cb..38c03410 100644 --- a/src/eu/siacs/conversations/entities/Bookmark.java +++ b/src/eu/siacs/conversations/entities/Bookmark.java @@ -122,4 +122,10 @@ public class Bookmark implements ListItem { } return element; } + + public void unregisterConversation() { + if (this.mJoinedConversation != null) { + this.mJoinedConversation.deregisterWithBookmark(); + } + } } diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index 70752adc..76fe84cf 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -16,9 +16,6 @@ import android.database.Cursor; import android.net.Uri; public class Conversation extends AbstractEntity { - - private static final long serialVersionUID = -6727528868973996739L; - public static final String TABLENAME = "conversations"; public static final int STATUS_AVAILABLE = 0; @@ -117,7 +114,7 @@ public class Conversation extends AbstractEntity { this.messages.get(i).markRead(); } } - + public String popLatestMarkableMessageId() { String id = this.latestMarkableMessageId; this.latestMarkableMessageId = null; @@ -144,7 +141,8 @@ public class Conversation extends AbstractEntity { if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null) && useSubject) { return getMucOptions().getSubject(); - } else if (getMode() == MODE_MULTI && bookmark!=null && bookmark.getName() != null) { + } else if (getMode() == MODE_MULTI && bookmark != null + && bookmark.getName() != null) { return bookmark.getName(); } else { return this.getContact().getDisplayName(); @@ -241,7 +239,7 @@ public class Conversation extends AbstractEntity { this.otrSessionNeedsStarting = false; return this.otrSession; } else { - this.otrSessionNeedsStarting = true; + this.otrSessionNeedsStarting = true; } return this.otrSession; } catch (OtrException e) { @@ -270,7 +268,7 @@ public class Conversation extends AbstractEntity { } } } - + public void endOtrIfNeeded() { if (this.otrSession != null) { if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { @@ -293,6 +291,9 @@ public class Conversation extends AbstractEntity { public String getOtrFingerprint() { if (this.otrFingerprint == null) { try { + if (getOtrSession()== null) { + return ""; + } DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession() .getRemotePublicKey(); StringBuilder builder = new StringBuilder( @@ -375,7 +376,7 @@ public class Conversation extends AbstractEntity { public void setSymmetricKey(byte[] key) { this.symmetricKey = key; } - + public byte[] getSymmetricKey() { return this.symmetricKey; } @@ -384,7 +385,7 @@ public class Conversation extends AbstractEntity { this.bookmark = bookmark; this.bookmark.setConversation(this); } - + public void deregisterWithBookmark() { if (this.bookmark != null) { this.bookmark.setConversation(null); diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java index 8e669c65..49c5ce58 100644 --- a/src/eu/siacs/conversations/entities/Message.java +++ b/src/eu/siacs/conversations/entities/Message.java @@ -7,8 +7,6 @@ import android.content.Context; import android.database.Cursor; public class Message extends AbstractEntity { - - private static final long serialVersionUID = 7222081895167103025L; public static final String TABLENAME = "messages"; diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java index 0f8e3565..0bb9b295 100644 --- a/src/eu/siacs/conversations/entities/MucOptions.java +++ b/src/eu/siacs/conversations/entities/MucOptions.java @@ -10,7 +10,9 @@ import android.annotation.SuppressLint; @SuppressLint("DefaultLocale") public class MucOptions { + public static final int ERROR_NO_ERROR = 0; public static final int ERROR_NICK_IN_USE = 1; + public static final int ERROR_ROOM_NOT_FOUND = 2; public interface OnRenameListener { public void onRename(boolean success); @@ -82,11 +84,12 @@ public class MucOptions { private ArrayList<User> users = new ArrayList<User>(); private Conversation conversation; private boolean isOnline = false; - private int error = 0; + private int error = ERROR_ROOM_NOT_FOUND; private OnRenameListener renameListener = null; private boolean aboutToRename = false; private User self = new User(); private String subject = null; + private String joinnick; public MucOptions(Account account) { this.account = account; @@ -123,10 +126,16 @@ public class MucOptions { user.setAffiliation(item.getAttribute("affiliation")); user.setRole(item.getAttribute("role")); user.setName(name); - if (name.equals(getNick())) { + if (name.equals(this.joinnick)) { this.isOnline = true; - this.error = 0; + this.error = ERROR_NO_ERROR; self = user; + if (aboutToRename) { + if (renameListener!=null) { + renameListener.onRename(true); + } + aboutToRename = false; + } } else { addUser(user); } @@ -145,17 +154,6 @@ public class MucOptions { } } } else if (type.equals("unavailable")) { - if (name.equals(getNick())) { - Element item = packet.findChild("x","http://jabber.org/protocol/muc#user").findChild("item"); - String nick = item.getAttribute("nick"); - if (nick!=null) { - aboutToRename = false; - if (renameListener!=null) { - renameListener.onRename(true); - } - this.setNick(nick); - } - } deleteUser(packet.getAttribute("from").split("/")[1]); } else if (type.equals("error")) { Element error = packet.findChild("error"); @@ -165,6 +163,7 @@ public class MucOptions { renameListener.onRename(false); } aboutToRename = false; + this.setJoinNick(getActualNick()); } else { this.error = ERROR_NICK_IN_USE; } @@ -177,22 +176,29 @@ public class MucOptions { return this.users; } - public String getNick() { - String[] split = conversation.getContactJid().split("/"); - if (split.length == 2) { - return split[1]; + public String getProposedNick() { + String[] mucParts = conversation.getContactJid().split("/"); + if (conversation.getBookmark() != null && conversation.getBookmark().getNick() != null) { + return conversation.getBookmark().getNick(); } else { - if (conversation.getAccount()!=null) { - return conversation.getAccount().getUsername(); + if (mucParts.length == 2) { + return mucParts[1]; } else { - return null; + return account.getUsername(); } } } - public void setNick(String nick) { - String jid = conversation.getContactJid().split("/")[0]+"/"+nick; - conversation.setContactJid(jid); + public String getActualNick() { + if (this.self.getName()!=null) { + return this.self.getName(); + } else { + return this.getProposedNick(); + } + } + + public void setJoinNick(String nick) { + this.joinnick = nick; } public void setConversation(Conversation conversation) { @@ -268,4 +274,8 @@ public class MucOptions { } return true; } + + public String getJoinJid() { + return this.conversation.getContactJid().split("/")[0]+"/"+this.joinnick; + } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/generator/AbstractGenerator.java b/src/eu/siacs/conversations/generator/AbstractGenerator.java new file mode 100644 index 00000000..49b5d614 --- /dev/null +++ b/src/eu/siacs/conversations/generator/AbstractGenerator.java @@ -0,0 +1,49 @@ +package eu.siacs.conversations.generator; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import android.util.Base64; + +public abstract class AbstractGenerator { + public final String[] FEATURES = { "urn:xmpp:jingle:1", + "urn:xmpp:jingle:apps:file-transfer:3", + "urn:xmpp:jingle:transports:s5b:1", + "urn:xmpp:jingle:transports:ibb:1", + "urn:xmpp:receipts", + "urn:xmpp:chat-markers:0", + "http://jabber.org/protocol/muc", + "jabber:x:conference", + "http://jabber.org/protocol/caps", + "http://jabber.org/protocol/disco#info"}; + //public final String[] FEATURES = { "http://jabber.org/protocol/muc","http://jabber.org/protocol/disco#info", "http://jabber.org/protocol/disco#items", "http://jabber.org/protocol/caps" }; + + //public final String IDENTITY_NAME = "Exodus 0.9.1"; + //public final String IDENTITY_TYPE = "pc"; + + + public final String IDENTITY_NAME = "Conversations 0.5"; + public final String IDENTITY_TYPE = "phone"; + + public String getCapHash() { + StringBuilder s = new StringBuilder(); + s.append("client/"+IDENTITY_TYPE+"//"+IDENTITY_NAME+"<"); + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-1"); + } + catch(NoSuchAlgorithmException e) { + return null; + } + List<String> features = Arrays.asList(FEATURES); + Collections.sort(features); + for(String feature : features) { + s.append(feature+"<"); + } + byte[] sha1 = md.digest(s.toString().getBytes()); + return new String(Base64.encode(sha1, Base64.DEFAULT)); + } +} diff --git a/src/eu/siacs/conversations/generator/IqGenerator.java b/src/eu/siacs/conversations/generator/IqGenerator.java new file mode 100644 index 00000000..7b3350d4 --- /dev/null +++ b/src/eu/siacs/conversations/generator/IqGenerator.java @@ -0,0 +1,31 @@ +package eu.siacs.conversations.generator; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class IqGenerator extends AbstractGenerator { + + + + public IqPacket discoResponse(IqPacket request) { + IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT); + packet.setId(request.getId()); + packet.setTo(request.getFrom()); + Element query = packet.addChild("query","http://jabber.org/protocol/disco#info"); + query.setAttribute("node", request.query().getAttribute("node")); + 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); + Collections.sort(features); + for(String feature : features) { + query.addChild("feature").setAttribute("var",feature); + } + return packet; + } +} diff --git a/src/eu/siacs/conversations/generator/MessageGenerator.java b/src/eu/siacs/conversations/generator/MessageGenerator.java index 5a216a7e..4449a7ec 100644 --- a/src/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/eu/siacs/conversations/generator/MessageGenerator.java @@ -128,4 +128,37 @@ public class MessageGenerator { packet.setFrom(conversation.getAccount().getJid()); return packet; } + + public MessagePacket directInvite(Conversation conversation, String contact) { + MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_NORMAL); + packet.setTo(contact); + packet.setFrom(conversation.getAccount().getFullJid()); + Element x = packet.addChild("x", "jabber:x:conference"); + x.setAttribute("jid", conversation.getContactJid().split("/")[0]); + return packet; + } + + public MessagePacket invite(Conversation conversation, String contact) { + MessagePacket packet = new MessagePacket(); + packet.setTo(conversation.getContactJid().split("/")[0]); + packet.setFrom(conversation.getAccount().getFullJid()); + Element x = new Element("x"); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); + Element invite = new Element("invite"); + invite.setAttribute("to", contact); + x.addChild(invite); + packet.addChild(x); + return packet; + } + + public MessagePacket received(Account account, MessagePacket originalMessage, String namespace) { + MessagePacket receivedPacket = new MessagePacket(); + receivedPacket.setType(MessagePacket.TYPE_NORMAL); + receivedPacket.setTo(originalMessage.getFrom()); + receivedPacket.setFrom(account.getFullJid()); + Element received = receivedPacket.addChild("received",namespace); + received.setAttribute("id", originalMessage.getId()); + return receivedPacket; + } } diff --git a/src/eu/siacs/conversations/generator/PresenceGenerator.java b/src/eu/siacs/conversations/generator/PresenceGenerator.java index a301392e..b3431568 100644 --- a/src/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/eu/siacs/conversations/generator/PresenceGenerator.java @@ -2,9 +2,10 @@ package eu.siacs.conversations.generator; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; -public class PresenceGenerator { +public class PresenceGenerator extends AbstractGenerator { private PresencePacket subscription(String type, Contact contact) { PresencePacket packet = new PresencePacket(); @@ -38,6 +39,13 @@ public class PresenceGenerator { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } + String capHash = getCapHash(); + if (capHash != null) { + Element cap = packet.addChild("c","http://jabber.org/protocol/caps"); + cap.setAttribute("hash", "sha-1"); + cap.setAttribute("node","http://conversions.siacs.eu"); + cap.setAttribute("ver", capHash); + } return packet; } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/parser/AbstractParser.java b/src/eu/siacs/conversations/parser/AbstractParser.java index f9a7b1c0..c4c6720a 100644 --- a/src/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/eu/siacs/conversations/parser/AbstractParser.java @@ -2,6 +2,8 @@ package eu.siacs.conversations.parser; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Locale; @@ -20,11 +22,16 @@ public abstract class AbstractParser { protected long getTimestamp(Element packet) { long now = System.currentTimeMillis(); - if (packet.hasChild("delay")) { + ArrayList<String> stamps = new ArrayList<String>(); + for(Element child : packet.getChildren()) { + if (child.getName().equals("delay")) { + stamps.add(child.getAttribute("stamp").replace("Z", "+0000")); + } + } + Collections.sort(stamps); + if (stamps.size() >= 1) { try { - String stamp = packet.findChild("delay").getAttribute( - "stamp"); - stamp = stamp.replace("Z", "+0000"); + String stamp = stamps.get(stamps.size() - 1); if (stamp.contains(".")) { Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.US) .parse(stamp); diff --git a/src/eu/siacs/conversations/parser/IqParser.java b/src/eu/siacs/conversations/parser/IqParser.java index acbeee4d..023fb4df 100644 --- a/src/eu/siacs/conversations/parser/IqParser.java +++ b/src/eu/siacs/conversations/parser/IqParser.java @@ -38,6 +38,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } } + mXmppConnectionService.updateRosterUi(); } @Override @@ -55,23 +56,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.getJingleConnectionManager().deliverIbbPacket(account, packet); } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - IqPacket iqResponse = packet - .generateRespone(IqPacket.TYPE_RESULT); - Element query = iqResponse.addChild("query", - "http://jabber.org/protocol/disco#info"); - query.addChild("feature").setAttribute("var", - "urn:xmpp:jingle:1"); - query.addChild("feature").setAttribute("var", - "urn:xmpp:jingle:apps:file-transfer:3"); - query.addChild("feature").setAttribute("var", - "urn:xmpp:jingle:transports:s5b:1"); - query.addChild("feature").setAttribute("var", - "urn:xmpp:jingle:transports:ibb:1"); - if (mXmppConnectionService.confirmMessages()) { - query.addChild("feature").setAttribute("var", - "urn:xmpp:receipts"); - } - account.getXmppConnection().sendIqPacket(iqResponse, null); + IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet); + account.getXmppConnection().sendIqPacket(response, null); } else { if ((packet.getType() == IqPacket.TYPE_GET) || (packet.getType() == IqPacket.TYPE_SET)) { diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index 1673fbf0..a4fcc810 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -80,6 +80,7 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.onOtrSessionEstablished(conversation); } else if ((before != after) && (after == SessionStatus.FINISHED)) { conversation.resetOtrSession(); + mXmppConnectionService.updateConversationUi(); } if ((body == null) || (body.isEmpty())) { return null; @@ -109,6 +110,9 @@ public class MessageParser extends AbstractParser implements private Message parseGroupchat(MessagePacket packet, Account account) { int status; String[] fromParts = packet.getFrom().split("/"); + if (mXmppConnectionService.find(account.pendingConferenceLeaves,account,fromParts[0]) != null) { + return null; + } Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, fromParts[0], true); if (packet.hasChild("subject")) { @@ -121,7 +125,7 @@ public class MessageParser extends AbstractParser implements return null; } String counterPart = fromParts[1]; - if (counterPart.equals(conversation.getMucOptions().getNick())) { + if (counterPart.equals(conversation.getMucOptions().getActualNick())) { if (mXmppConnectionService.markMessage(conversation, packet.getId(), Message.STATUS_SEND)) { return null; @@ -174,6 +178,9 @@ public class MessageParser extends AbstractParser implements } else { fullJid = message.getAttribute("to"); } + if (fullJid==null) { + return null; + } String[] parts = fullJid.split("/"); Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, parts[0], false); @@ -214,15 +221,29 @@ public class MessageParser extends AbstractParser implements updateLastseen(packet, account, false); mXmppConnectionService.markMessage(account, fromParts[0], id, Message.STATUS_SEND_RECEIVED); - } else if (packet.hasChild("x")) { - Element x = packet.findChild("x"); + } else if (packet.hasChild("x","http://jabber.org/protocol/muc#user")) { + Element x = packet.findChild("x","http://jabber.org/protocol/muc#user"); if (x.hasChild("invite")) { - mXmppConnectionService + Conversation conversation = mXmppConnectionService .findOrCreateConversation(account, packet.getAttribute("from"), true); - mXmppConnectionService.updateConversationUi(); + if (!conversation.getMucOptions().online()) { + mXmppConnectionService.joinMuc(conversation); + mXmppConnectionService.updateConversationUi(); + } } + } else if (packet.hasChild("x", "jabber:x:conference")) { + Element x = packet.findChild("x", "jabber:x:conference"); + String jid = x.getAttribute("jid"); + if (jid!=null) { + Conversation conversation = mXmppConnectionService + .findOrCreateConversation(account,jid, true); + if (!conversation.getMucOptions().online()) { + mXmppConnectionService.joinMuc(conversation); + mXmppConnectionService.updateConversationUi(); + } + } } } @@ -274,6 +295,8 @@ public class MessageParser extends AbstractParser implements message.markUnread(); } } + } else { + parseNormal(packet, account); } } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) { @@ -283,6 +306,8 @@ public class MessageParser extends AbstractParser implements message.markUnread(); } else { message.getConversation().markRead(); + lastCarbonMessageReceived = SystemClock + .elapsedRealtime(); notify = false; } } @@ -291,26 +316,20 @@ public class MessageParser extends AbstractParser implements return; } else if (packet.getType() == MessagePacket.TYPE_NORMAL) { this.parseNormal(packet, account); + return; } if ((message == null) || (message.getBody() == null)) { return; } if ((mXmppConnectionService.confirmMessages()) && ((packet.getId() != null))) { - MessagePacket receivedPacket = new MessagePacket(); - receivedPacket.setType(MessagePacket.TYPE_NORMAL); - receivedPacket.setTo(message.getCounterpart()); - receivedPacket.setFrom(account.getFullJid()); if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - Element received = receivedPacket.addChild("received", - "urn:xmpp:chat-markers:0"); - received.setAttribute("id", packet.getId()); - account.getXmppConnection().sendMessagePacket(receivedPacket); - } else if (packet.hasChild("request", "urn:xmpp:receipts")) { - Element received = receivedPacket.addChild("received", - "urn:xmpp:receipts"); - received.setAttribute("id", packet.getId()); - account.getXmppConnection().sendMessagePacket(receivedPacket); + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0"); + mXmppConnectionService.sendMessagePacket(account, receipt); + } + if (packet.hasChild("request", "urn:xmpp:receipts")) { + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:receipts"); + mXmppConnectionService.sendMessagePacket(account, receipt); } } Conversation conversation = message.getConversation(); diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java index bd2aa636..33f4185f 100644 --- a/src/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/eu/siacs/conversations/parser/PresenceParser.java @@ -21,22 +21,19 @@ public class PresenceParser extends AbstractParser implements public void parseConferencePresence(PresencePacket packet, Account account) { PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) { - Conversation muc = mXmppConnectionService.findMuc(packet - .getAttribute("from").split("/")[0], account); + Conversation muc = mXmppConnectionService.find(account,packet + .getAttribute("from").split("/")[0]); if (muc != null) { muc.getMucOptions().processPacket(packet, mPgpEngine); } } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { - Conversation muc = mXmppConnectionService.findMuc(packet - .getAttribute("from").split("/")[0], account); + Conversation muc = mXmppConnectionService.find(account,packet + .getAttribute("from").split("/")[0]); if (muc != null) { - int error = muc.getMucOptions().getError(); muc.getMucOptions().processPacket(packet, mPgpEngine); - if (muc.getMucOptions().getError() != error) { - mXmppConnectionService.updateConversationUi(); - } } } + mXmppConnectionService.updateConversationUi(); } public void parseContactPresence(PresencePacket packet, Account account) { @@ -99,6 +96,7 @@ public class PresenceParser extends AbstractParser implements } } } + mXmppConnectionService.updateRosterUi(); } @Override diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index c43a34b7..de0658d4 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -15,6 +15,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; +import de.duenndns.ssl.MemorizingTrustManager; + import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; @@ -27,6 +29,7 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; import eu.siacs.conversations.parser.IqParser; @@ -46,7 +49,6 @@ import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnStatusChanged; -import eu.siacs.conversations.xmpp.OnTLSExceptionReceived; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; @@ -90,6 +92,8 @@ public class XmppConnectionService extends Service { public static final long CARBON_GRACE_PERIOD = 60000L; private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + + private MemorizingTrustManager mMemorizingTrustManager; private MessageParser mMessageParser = new MessageParser(this); private PresenceParser mPresenceParser = new PresenceParser(this); @@ -105,12 +109,12 @@ public class XmppConnectionService extends Service { private OnConversationUpdate mOnConversationUpdate = null; private int convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; - private OnTLSExceptionReceived tlsException = null; + private OnRosterUpdate mOnRosterUpdate = null; public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override public void onContactStatusChanged(Contact contact, boolean online) { - Conversation conversation = findActiveConversation(contact); + Conversation conversation = find(getConversations(),contact); if (conversation != null) { conversation.endOtrIfNeeded(); if (online && (contact.getPresences().size() == 1)) { @@ -120,11 +124,6 @@ public class XmppConnectionService extends Service { } }; - public void setOnTLSExceptionReceivedListener( - OnTLSExceptionReceived listener) { - tlsException = listener; - } - private SecureRandom mRandom; private ContentObserver contactObserver = new ContentObserver(null) { @@ -147,6 +146,12 @@ public class XmppConnectionService extends Service { mOnAccountUpdate.onAccountUpdate();; } if (account.getStatus() == Account.STATUS_ONLINE) { + for(Conversation conversation : account.pendingConferenceLeaves) { + leaveMuc(conversation); + } + for(Conversation conversation : account.pendingConferenceJoins) { + joinMuc(conversation); + } mJingleConnectionManager.cancelInTransmission(); List<Conversation> conversations = getConversations(); for (int i = 0; i < conversations.size(); ++i) { @@ -195,6 +200,21 @@ public class XmppConnectionService extends Service { private PendingIntent pendingPingIntent = null; private WakeLock wakeLock; private PowerManager pm; + private OnBindListener mOnBindListener = new OnBindListener() { + + @Override + public void onBind(final Account account) { + account.getRoster().clearPresences(); + account.clearPresences(); // self presences + account.pendingConferenceJoins.clear(); + account.pendingConferenceLeaves.clear(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); + connectMultiModeConversations(account); + updateConversationUi(); + } + }; public PgpEngine getPgpEngine() { if (pgpServiceConnection.isBound()) { @@ -246,18 +266,12 @@ public class XmppConnectionService extends Service { return message; } - public Conversation findMuc(Bookmark bookmark) { - return findMuc(bookmark.getJid(), bookmark.getAccount()); + public Conversation find(Bookmark bookmark) { + return find(bookmark.getAccount(),bookmark.getJid()); } - public Conversation findMuc(String jid, Account account) { - for (Conversation conversation : this.conversations) { - if (conversation.getContactJid().split("/")[0].equals(jid) - && (conversation.getAccount() == account)) { - return conversation; - } - } - return null; + public Conversation find(Account account, String jid) { + return find(getConversations(),account,jid); } public class XmppConnectionBinder extends Binder { @@ -352,6 +366,7 @@ public class XmppConnectionService extends Service { ExceptionHelper.init(getApplicationContext()); PRNGFixes.apply(); this.mRandom = new SecureRandom(); + this.mMemorizingTrustManager = new MemorizingTrustManager(getApplicationContext()); this.databaseBackend = DatabaseBackend .getInstance(getApplicationContext()); this.fileBackend = new FileBackend(getApplicationContext()); @@ -451,32 +466,7 @@ public class XmppConnectionService extends Service { connection .setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener(this.jingleListener); - connection - .setOnTLSExceptionReceivedListener(new OnTLSExceptionReceived() { - - @Override - public void onTLSExceptionReceived(String fingerprint, - Account account) { - Log.d(LOGTAG, "tls exception arrived in service"); - if (tlsException != null) { - tlsException.onTLSExceptionReceived(fingerprint, - account); - } - } - }); - connection.setOnBindListener(new OnBindListener() { - - @Override - public void onBind(final Account account) { - account.getRoster().clearPresences(); - account.clearPresences(); // self presences - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); - connectMultiModeConversations(account); - updateConversationUi(); - } - }); + connection.setOnBindListener(this.mOnBindListener); return connection; } @@ -528,11 +518,13 @@ public class XmppConnectionService extends Service { } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { message.getConversation().endOtrIfNeeded(); + failWaitingOtrMessages(message.getConversation()); packet = mMessageGenerator.generatePgpChat(message); message.setStatus(Message.STATUS_SEND); send = true; } else { message.getConversation().endOtrIfNeeded(); + failWaitingOtrMessages(message.getConversation()); if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { message.setStatus(Message.STATUS_SEND); } @@ -681,16 +673,16 @@ public class XmppConnectionService extends Service { if (storage!=null) { for(Element item : storage.getChildren()) { if (item.getName().equals("conference")) { - Log.d(LOGTAG,item.toString()); Bookmark bookmark = Bookmark.parse(item,account); bookmarks.add(bookmark); - Conversation conversation = findMuc(bookmark); + Conversation conversation = find(bookmark); if (conversation!=null) { conversation.setBookmark(bookmark); } else { if (bookmark.autojoin()) { conversation = findOrCreateConversation(account, bookmark.getJid(), true); conversation.setBookmark(bookmark); + joinMuc(conversation); } } } @@ -792,8 +784,8 @@ public class XmppConnectionService extends Service { return this.accounts; } - public Conversation findActiveConversation(Contact contact) { - for (Conversation conversation : this.getConversations()) { + public Conversation find(List<Conversation> haystack, Contact contact) { + for (Conversation conversation : haystack) { if (conversation.getContact() == contact) { return conversation; } @@ -801,16 +793,24 @@ public class XmppConnectionService extends Service { return null; } + public Conversation find(List<Conversation> haystack, Account account, String jid) { + for (Conversation conversation : haystack) { + if ((conversation.getAccount().equals(account)) + && (conversation.getContactJid().split("/")[0].equals(jid))) { + return conversation; + } + } + return null; + } + + public Conversation findOrCreateConversation(Account account, String jid, boolean muc) { - for (Conversation conv : this.getConversations()) { - if ((conv.getAccount().equals(account)) - && (conv.getContactJid().split("/")[0].equals(jid))) { - return conv; - } + Conversation conversation = find(account, jid); + if (conversation != null) { + return conversation; } - Conversation conversation = databaseBackend.findConversation(account, - jid); + conversation = databaseBackend.findConversation(account,jid); if (conversation != null) { conversation.setStatus(Conversation.STATUS_AVAILABLE); conversation.setAccount(account); @@ -840,10 +840,6 @@ public class XmppConnectionService extends Service { this.databaseBackend.createConversation(conversation); } this.conversations.add(conversation); - if ((account.getStatus() == Account.STATUS_ONLINE) - && (conversation.getMode() == Conversation.MODE_MULTI)) { - joinMuc(conversation); - } updateConversationUi(); return conversation; } @@ -891,6 +887,16 @@ public class XmppConnectionService extends Service { } public void deleteAccount(Account account) { + for(Conversation conversation : conversations) { + if (conversation.getAccount() == account) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + leaveMuc(conversation); + } else if (conversation.getMode() == Conversation.MODE_SINGLE) { + conversation.endOtrIfNeeded(); + } + conversations.remove(conversation); + } + } if (account.getXmppConnection() != null) { this.disconnect(account, true); } @@ -920,6 +926,14 @@ public class XmppConnectionService extends Service { public void removeOnAccountListChangedListener() { this.mOnAccountUpdate = null; } + + public void setOnRosterUpdateListener(OnRosterUpdate listener) { + this.mOnRosterUpdate = listener; + } + + public void removeOnRosterUpdateListener() { + this.mOnRosterUpdate = null; + } public void connectMultiModeConversations(Account account) { List<Conversation> conversations = getConversations(); @@ -934,39 +948,44 @@ public class XmppConnectionService extends Service { public void joinMuc(Conversation conversation) { Account account = conversation.getAccount(); - String[] mucParts = conversation.getContactJid().split("/"); - String muc; - String nick; - if (mucParts.length == 2) { - muc = mucParts[0]; - nick = mucParts[1]; + account.pendingConferenceJoins.remove(conversation); + account.pendingConferenceLeaves.remove(conversation); + if (account.getStatus() == Account.STATUS_ONLINE) { + Log.d(LOGTAG,"joining conversation "+conversation.getContactJid()); + String nick = conversation.getMucOptions().getProposedNick(); + conversation.getMucOptions().setJoinNick(nick); + PresencePacket packet = new PresencePacket(); + String joinJid = conversation.getMucOptions().getJoinJid(); + packet.setAttribute("to",conversation.getMucOptions().getJoinJid()); + Element x = new Element("x"); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + String sig = account.getPgpSignature(); + if (sig != null) { + packet.addChild("status").setContent("online"); + packet.addChild("x", "jabber:x:signed").setContent(sig); + } + if (conversation.getMessages().size() != 0) { + final SimpleDateFormat mDateFormat = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = new Date( + conversation.getLatestMessage().getTimeSent() + 1000); + x.addChild("history").setAttribute("since", + mDateFormat.format(date)); + } + packet.addChild(x); + sendPresencePacket(account, packet); + if (!joinJid.equals(conversation.getContactJid())) { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + } } else { - muc = mucParts[0]; - nick = account.getUsername(); - } - PresencePacket packet = new PresencePacket(); - packet.setAttribute("to", muc + "/" + nick); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); - String sig = account.getPgpSignature(); - if (sig != null) { - packet.addChild("status").setContent("online"); - packet.addChild("x", "jabber:x:signed").setContent(sig); + account.pendingConferenceJoins.add(conversation); } - if (conversation.getMessages().size() != 0) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = new Date( - conversation.getLatestMessage().getTimeSent() + 1000); - x.addChild("history").setAttribute("since", - mDateFormat.format(date)); - } - packet.addChild(x); - sendPresencePacket(account, packet); } private OnRenameListener renameListener = null; + private IqGenerator mIqGenerator = new IqGenerator(); public void setOnRenameListener(OnRenameListener listener) { this.renameListener = listener; @@ -974,6 +993,7 @@ public class XmppConnectionService extends Service { public void renameInMuc(final Conversation conversation, final String nick) { final MucOptions options = conversation.getMucOptions(); + options.setJoinNick(nick); if (options.online()) { Account account = conversation.getAccount(); options.setOnRenameListener(new OnRenameListener() { @@ -984,17 +1004,19 @@ public class XmppConnectionService extends Service { renameListener.onRename(success); } if (success) { - String jid = conversation.getContactJid().split("/")[0] - + "/" + nick; - conversation.setContactJid(jid); + conversation.setContactJid(conversation.getMucOptions().getJoinJid()); databaseBackend.updateConversation(conversation); + Bookmark bookmark = conversation.getBookmark(); + if (bookmark!=null) { + bookmark.setNick(nick); + pushBookmarks(bookmark.getAccount()); + } } } }); options.flagAboutToRename(); PresencePacket packet = new PresencePacket(); - packet.setAttribute("to", - conversation.getContactJid().split("/")[0] + "/" + nick); + packet.setAttribute("to",options.getJoinJid()); packet.setAttribute("from", conversation.getAccount().getFullJid()); String sig = account.getPgpSignature(); @@ -1004,26 +1026,35 @@ public class XmppConnectionService extends Service { } sendPresencePacket(account,packet); } else { - String jid = conversation.getContactJid().split("/")[0] + "/" - + nick; - conversation.setContactJid(jid); + conversation.setContactJid(options.getJoinJid()); databaseBackend.updateConversation(conversation); if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) { + Bookmark bookmark = conversation.getBookmark(); + if (bookmark!=null) { + bookmark.setNick(nick); + pushBookmarks(bookmark.getAccount()); + } joinMuc(conversation); } } } public void leaveMuc(Conversation conversation) { - PresencePacket packet = new PresencePacket(); - packet.setAttribute("to", conversation.getContactJid().split("/")[0] - + "/" + conversation.getMucOptions().getNick()); - packet.setAttribute("from", conversation.getAccount().getFullJid()); - packet.setAttribute("type", "unavailable"); - sendPresencePacket(conversation.getAccount(),packet); - conversation.getMucOptions().setOffline(); - conversation.deregisterWithBookmark(); - Log.d(LOGTAG,conversation.getAccount().getJid()+" leaving muc "+conversation.getContactJid()); + Account account = conversation.getAccount(); + account.pendingConferenceJoins.remove(conversation); + account.pendingConferenceLeaves.remove(conversation); + if (account.getStatus() == Account.STATUS_ONLINE) { + PresencePacket packet = new PresencePacket(); + packet.setAttribute("to", conversation.getMucOptions().getJoinJid()); + packet.setAttribute("from", conversation.getAccount().getFullJid()); + packet.setAttribute("type", "unavailable"); + sendPresencePacket(conversation.getAccount(),packet); + conversation.getMucOptions().setOffline(); + conversation.deregisterWithBookmark(); + Log.d(LOGTAG,conversation.getAccount().getJid()+" leaving muc "+conversation.getContactJid()); + } else { + account.pendingConferenceLeaves.add(conversation); + } } public void disconnect(Account account, boolean force) { @@ -1170,10 +1201,6 @@ public class XmppConnectionService extends Service { this.databaseBackend.updateConversation(conversation); } - public void removeOnTLSExceptionReceivedListener() { - this.tlsException = null; - } - public void reconnectAccount(final Account account, final boolean force) { new Thread(new Runnable() { @@ -1194,22 +1221,9 @@ public class XmppConnectionService extends Service { }).start(); } - public void inviteToConference(Conversation conversation, - List<Contact> contacts) { - for (Contact contact : contacts) { - MessagePacket packet = new MessagePacket(); - packet.setTo(conversation.getContactJid().split("/")[0]); - packet.setFrom(conversation.getAccount().getFullJid()); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user"); - Element invite = new Element("invite"); - invite.setAttribute("to", contact.getJid()); - x.addChild(invite); - packet.addChild(x); - Log.d(LOGTAG, packet.toString()); - sendMessagePacket(conversation.getAccount(),packet); - } - + public void invite(Conversation conversation, String contact) { + MessagePacket packet = mMessageGenerator.invite(conversation, contact); + sendMessagePacket(conversation.getAccount(),packet); } public boolean markMessage(Account account, String recipient, String uuid, @@ -1269,6 +1283,12 @@ public class XmppConnectionService extends Service { mOnAccountUpdate.onAccountUpdate(); } } + + public void updateRosterUi() { + if (mOnRosterUpdate != null) { + mOnRosterUpdate.onRosterUpdate(); + } + } public Account findAccountByJid(String accountJid) { for (Account account : this.accounts) { @@ -1278,6 +1298,15 @@ public class XmppConnectionService extends Service { } return null; } + + public Conversation findConversationByUuid(String uuid) { + for (Conversation conversation : getConversations()) { + if (conversation.getUuid().equals(uuid)) { + return conversation; + } + } + return null; + } public void markRead(Conversation conversation) { conversation.markRead(); @@ -1288,10 +1317,23 @@ public class XmppConnectionService extends Service { this.sendMessagePacket(conversation.getAccount(), mMessageGenerator.confirm(account, to, id)); } } + + public void failWaitingOtrMessages(Conversation conversation) { + for (Message message : conversation.getMessages()) { + if (message.getEncryption() == Message.ENCRYPTION_OTR + && message.getStatus() == Message.STATUS_WAITING) { + markMessage(message, Message.STATUS_SEND_FAILED); + } + } + } public SecureRandom getRNG() { return this.mRandom; } + + public MemorizingTrustManager getMemorizingTrustManager() { + return this.mMemorizingTrustManager; + } public PowerManager getPowerManager() { return this.pm; @@ -1367,6 +1409,10 @@ public class XmppConnectionService extends Service { return this.mPresenceGenerator; } + public IqGenerator getIqGenerator() { + return this.mIqGenerator ; + } + public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } diff --git a/src/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/eu/siacs/conversations/ui/ChooseContactActivity.java new file mode 100644 index 00000000..4236ea70 --- /dev/null +++ b/src/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -0,0 +1,140 @@ +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 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<ListItem>(); + private ArrayAdapter<ListItem> mContactsAdapter; + + private EditText mSearchEditText; + + private TextWatcher mSearchTextWatcher = new TextWatcher() { + + @Override + public void afterTextChanged(Editable editable) { + filterContacts(editable.toString()); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + } + }; + + private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { + + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + mSearchEditText.post(new Runnable() { + + @Override + public void run() { + mSearchEditText.requestFocus(); + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mSearchEditText, + InputMethodManager.SHOW_IMPLICIT); + } + }); + + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + mSearchEditText.setText(""); + filterContacts(null); + return true; + } + }; + + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_choose_contact); + mListView = (ListView) findViewById(R.id.choose_contact_list); + mContactsAdapter = new ListItemAdapter(getApplicationContext(), contacts); + mListView.setAdapter(mContactsAdapter); + mListView.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(), + InputMethodManager.HIDE_IMPLICIT_ONLY); + Intent request = getIntent(); + Intent data = new Intent(); + data.putExtra("contact",contacts.get(position).getJid()); + data.putExtra("account",request.getStringExtra("account")); + data.putExtra("conversation",request.getStringExtra("conversation")); + setResult(RESULT_OK, data); + finish(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.choose_contact, menu); + MenuItem menuSearchView = (MenuItem) 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()) { + if (account.getStatus() != Account.STATUS_DISABLED) { + for (Contact contact : account.getRoster().getContacts()) { + if (contact.showInRoster() && contact.match(needle)) { + this.contacts.add(contact); + } + } + } + } + Collections.sort(this.contacts); + mContactsAdapter.notifyDataSetChanged(); + } + +} diff --git a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java new file mode 100644 index 00000000..56903da8 --- /dev/null +++ b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -0,0 +1,268 @@ +package eu.siacs.conversations.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.openintents.openpgp.util.OpenPgpUtils; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.MucOptions; +import eu.siacs.conversations.entities.MucOptions.OnRenameListener; +import eu.siacs.conversations.entities.MucOptions.User; +import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.stanzas.MessagePacket; +import android.app.PendingIntent; +import android.content.Context; +import android.content.IntentSender.SendIntentException; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +public class ConferenceDetailsActivity extends XmppActivity { + public static final String ACTION_VIEW_MUC = "view_muc"; + private Conversation conversation; + private TextView mYourNick; + private ImageView mYourPhoto; + private ImageButton mEditNickButton; + private TextView mRoleAffiliaton; + private TextView mFullJid; + private LinearLayout membersView; + private LinearLayout mMoreDetails; + private Button mInviteButton; + private String uuid = null; + + private OnClickListener inviteListener = new OnClickListener() { + + @Override + public void onClick(View v) { + inviteToConversation(conversation); + } + }; + + private List<User> users = new ArrayList<MucOptions.User>(); + private OnConversationUpdate onConvChanged = new OnConversationUpdate() { + + @Override + public void onConversationUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + populateView(); + } + }); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_muc_details); + mYourNick = (TextView) findViewById(R.id.muc_your_nick); + mYourPhoto = (ImageView) findViewById(R.id.your_photo); + mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button); + mFullJid = (TextView) findViewById(R.id.muc_jabberid); + membersView = (LinearLayout) findViewById(R.id.muc_members); + mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); + mMoreDetails.setVisibility(View.GONE); + mInviteButton = (Button) findViewById(R.id.invite); + mInviteButton.setOnClickListener(inviteListener); + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + mEditNickButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + quickEdit(conversation.getMucOptions().getActualNick(), + new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + xmppConnectionService.renameInMuc(conversation, + value); + } + }); + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.action_edit_subject: + if (conversation != null) { + quickEdit(conversation.getName(true), new OnValueEdited() { + + @Override + public void onValueEdited(String value) { + MessagePacket packet = xmppConnectionService + .getMessageGenerator().conferenceSubject( + conversation, value); + xmppConnectionService.sendMessagePacket( + conversation.getAccount(), packet); + } + }); + } + break; + } + return super.onOptionsItemSelected(menuItem); + } + + public String getReadableRole(int role) { + switch (role) { + case User.ROLE_MODERATOR: + return getString(R.string.moderator); + case User.ROLE_PARTICIPANT: + return getString(R.string.participant); + case User.ROLE_VISITOR: + return getString(R.string.visitor); + default: + return ""; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.muc_details, menu); + return true; + } + + @Override + void onBackendConnected() { + registerListener(); + if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { + this.uuid = getIntent().getExtras().getString("uuid"); + } + if (uuid != null) { + this.conversation = xmppConnectionService.findConversationByUuid(uuid); + if (this.conversation != null) { + populateView(); + } + } + } + + @Override + protected void onStop() { + if (xmppConnectionServiceBound) { + xmppConnectionService.removeOnConversationListChangedListener(); + } + super.onStop(); + } + + protected void registerListener() { + if (xmppConnectionServiceBound) { + xmppConnectionService + .setOnConversationListChangedListener(this.onConvChanged); + xmppConnectionService.setOnRenameListener(new OnRenameListener() { + + @Override + public void onRename(final boolean success) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + populateView(); + if (success) { + Toast.makeText(ConferenceDetailsActivity.this, + getString(R.string.your_nick_has_been_changed), + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ConferenceDetailsActivity.this, + getString(R.string.nick_in_use), + Toast.LENGTH_SHORT).show(); + } + } + }); + } + }); + } + } + + private void populateView() { + mYourPhoto.setImageBitmap(UIHelper.getContactPicture(conversation + .getMucOptions().getActualNick(), 48, this, false)); + setTitle(conversation.getName(true)); + mFullJid.setText(conversation.getContactJid().split("/")[0]); + mYourNick.setText(conversation.getMucOptions().getActualNick()); + mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); + if (conversation.getMucOptions().online()) { + mMoreDetails.setVisibility(View.VISIBLE); + User self = conversation.getMucOptions().getSelf(); + switch (self.getAffiliation()) { + case User.AFFILIATION_ADMIN: + mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " (" + + getString(R.string.admin) + ")"); + break; + case User.AFFILIATION_OWNER: + mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " (" + + getString(R.string.owner) + ")"); + break; + default: + mRoleAffiliaton.setText(getReadableRole(self.getRole())); + break; + } + } + this.users.clear(); + this.users.addAll(conversation.getMucOptions().getUsers()); + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + membersView.removeAllViews(); + for (final User contact : conversation.getMucOptions().getUsers()) { + View view = (View) inflater.inflate(R.layout.contact, null); + TextView displayName = (TextView) view + .findViewById(R.id.contact_display_name); + TextView key = (TextView) view.findViewById(R.id.key); + displayName.setText(contact.getName()); + TextView role = (TextView) view.findViewById(R.id.contact_jid); + role.setText(getReadableRole(contact.getRole())); + if (contact.getPgpKeyId() != 0) { + key.setVisibility(View.VISIBLE); + key.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + viewPgpKey(contact); + } + }); + key.setText(OpenPgpUtils.convertKeyIdToHex(contact + .getPgpKeyId())); + } + Bitmap bm = UIHelper.getContactPicture(contact.getName(), 48, this, + false); + ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); + iv.setImageBitmap(bm); + membersView.addView(view); + } + } + + private void viewPgpKey(User user) { + PgpEngine pgp = xmppConnectionService.getPgpEngine(); + if (pgp != null) { + PendingIntent intent = pgp.getIntentForKey( + conversation.getAccount(), user.getPgpKeyId()); + if (intent != null) { + try { + startIntentSenderForResult(intent.getIntentSender(), 0, + null, 0, 0, 0); + } catch (SendIntentException e) { + + } + } + } + } +} diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java index 9321f229..7c13c518 100644 --- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -21,19 +21,19 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.CheckBox; -import android.widget.EditText; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.QuickContactBadge; import android.widget.TextView; -import android.widget.Toast; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Presences; -import eu.siacs.conversations.services.XmppConnectionService; +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.stanzas.PresencePacket; public class ContactDetailsActivity extends XmppActivity { public static final String ACTION_VIEW_CONTACT = "view_contact"; @@ -41,11 +41,10 @@ public class ContactDetailsActivity extends XmppActivity { protected ContactDetailsActivity activity = this; private Contact contact; - + private String accountJid; private String contactJid; - - private EditText name; + private TextView contactJidTv; private TextView accountJidTv; private TextView status; @@ -63,16 +62,6 @@ public class ContactDetailsActivity extends XmppActivity { } }; - private DialogInterface.OnClickListener editContactNameListener = new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - contact.setServerName(name.getText().toString()); - activity.xmppConnectionService.pushContactToServer(contact); - populateView(); - } - }; - private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { @Override @@ -92,7 +81,8 @@ public class ContactDetailsActivity extends XmppActivity { public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(getString(R.string.action_add_phone_book)); - builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid())); + builder.setMessage(getString(R.string.add_phone_book_text, + contact.getJid())); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.add), addToPhonebook); builder.create().show(); @@ -101,6 +91,74 @@ public class ContactDetailsActivity extends XmppActivity { private LinearLayout keys; + private OnRosterUpdate rosterUpdate = new OnRosterUpdate() { + + @Override + public void onRosterUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + populateView(); + } + }); + } + }; + + private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + xmppConnectionService.sendPresencePacket(contact + .getAccount(), + xmppConnectionService.getPresenceGenerator() + .sendPresenceUpdatesTo(contact)); + } else { + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + } + } else { + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .stopPresenceUpdatesTo(contact)); + } + } + }; + + private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); + } else { + xmppConnectionService.sendPresencePacket(contact.getAccount(), + xmppConnectionService.getPresenceGenerator() + .stopPresenceUpdatesFrom(contact)); + } + } + }; + + private OnAccountUpdate accountUpdate = new OnAccountUpdate() { + + @Override + public void onAccountUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + populateView(); + } + }); + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -136,20 +194,21 @@ public class ContactDetailsActivity extends XmppActivity { .setMessage( getString(R.string.remove_contact_text, contact.getJid())) - .setPositiveButton(getString(R.string.delete), removeFromRoster).create() - .show(); + .setPositiveButton(getString(R.string.delete), + removeFromRoster).create().show(); break; case R.id.action_edit_contact: if (contact.getSystemAccount() == null) { + quickEdit(contact.getDisplayName(), new OnValueEdited() { - View view = (View) getLayoutInflater().inflate( - R.layout.edit_contact_name, null); - name = (EditText) view.findViewById(R.id.editText1); - name.setText(contact.getDisplayName()); - builder.setView(view).setTitle(contact.getJid()) - .setPositiveButton(getString(R.string.edit), editContactNameListener) - .create().show(); - + @Override + public void onValueEdited(String value) { + contact.setServerName(value); + activity.xmppConnectionService + .pushContactToServer(contact); + populateView(); + } + }); } else { Intent intent = new Intent(Intent.ACTION_EDIT); String[] systemAccount = contact.getSystemAccount().split("#"); @@ -171,21 +230,26 @@ public class ContactDetailsActivity extends XmppActivity { } private void populateView() { + send.setOnCheckedChangeListener(null); + receive.setOnCheckedChangeListener(null); setTitle(contact.getDisplayName()); if (contact.getOption(Contact.Options.FROM)) { + send.setText(R.string.send_presence_updates); send.setChecked(true); - } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)){ + } else if (contact + .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { send.setChecked(false); + send.setText(R.string.send_presence_updates); } else { send.setText(R.string.preemptively_grant); - if (contact - .getOption(Contact.Options.PREEMPTIVE_GRANT)) { + if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { send.setChecked(true); } else { send.setChecked(false); } } if (contact.getOption(Contact.Options.TO)) { + receive.setText(R.string.receive_presence_updates); receive.setChecked(true); } else { receive.setText(R.string.ask_for_presence_updates); @@ -195,8 +259,19 @@ public class ContactDetailsActivity extends XmppActivity { receive.setChecked(false); } } + if (contact.getAccount().getStatus() == Account.STATUS_ONLINE) { + receive.setEnabled(true); + send.setEnabled(true); + } else { + receive.setEnabled(false); + send.setEnabled(false); + } + + send.setOnCheckedChangeListener(this.mOnSendCheckedChange); + receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); - lastseen.setText(UIHelper.lastseen(getApplicationContext(),contact.lastseen.time)); + lastseen.setText(UIHelper.lastseen(getApplicationContext(), + contact.lastseen.time)); switch (contact.getMostAvailableStatus()) { case Presences.CHAT: @@ -229,13 +304,15 @@ public class ContactDetailsActivity extends XmppActivity { break; } if (contact.getPresences().size() > 1) { - contactJidTv.setText(contact.getJid()+" ("+contact.getPresences().size()+")"); + contactJidTv.setText(contact.getJid() + " (" + + contact.getPresences().size() + ")"); } else { contactJidTv.setText(contact.getJid()); } accountJidTv.setText(contact.getAccount().getJid()); - UIHelper.prepareContactBadge(this, badge, contact, getApplicationContext()); + UIHelper.prepareContactBadge(this, badge, contact, + getApplicationContext()); if (contact.getSystemAccount() == null) { badge.setOnClickListener(onBadgeClick); @@ -260,17 +337,20 @@ public class ContactDetailsActivity extends XmppActivity { keyType.setText("PGP Key ID"); key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId())); view.setOnClickListener(new OnClickListener() { - + @Override public void onClick(View v) { - PgpEngine pgp = activity.xmppConnectionService.getPgpEngine(); - if (pgp!=null) { + PgpEngine pgp = activity.xmppConnectionService + .getPgpEngine(); + if (pgp != null) { PendingIntent intent = pgp.getIntentForKey(contact); - if (intent!=null) { + if (intent != null) { try { - startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0); + startIntentSenderForResult( + intent.getIntentSender(), 0, null, 0, + 0, 0); } catch (SendIntentException e) { - + } } } @@ -282,9 +362,12 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onBackendConnected() { - if ((accountJid != null)&&(contactJid != null)) { - Account account = xmppConnectionService.findAccountByJid(accountJid); - if (account==null) { + xmppConnectionService.setOnRosterUpdateListener(this.rosterUpdate); + xmppConnectionService.setOnAccountListChangedListener(this.accountUpdate ); + if ((accountJid != null) && (contactJid != null)) { + Account account = xmppConnectionService + .findAccountByJid(accountJid); + if (account == null) { return; } this.contact = account.getRoster().getContact(contactJid); @@ -295,79 +378,8 @@ public class ContactDetailsActivity extends XmppActivity { @Override protected void onStop() { super.onStop(); - XmppConnectionService xcs = activity.xmppConnectionService; - PresencePacket packet = null; - boolean updated = false; - if (contact!=null) { - boolean online = contact.getAccount().getStatus() == Account.STATUS_ONLINE; - if (contact.getOption(Contact.Options.FROM)) { - if (!send.isChecked()) { - if (online) { - contact.resetOption(Contact.Options.FROM); - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - packet = xcs.getPresenceGenerator().stopPresenceUpdatesTo(contact); - } - updated = true; - } - } else { - if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - if (!send.isChecked()) { - if (online) { - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - } - updated = true; - } - } else { - if (send.isChecked()) { - if (online) { - if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - packet = xcs.getPresenceGenerator().sendPresenceUpdatesTo(contact); - } else { - contact.setOption(Contact.Options.PREEMPTIVE_GRANT); - } - } - updated = true; - } - } - } - if (contact.getOption(Contact.Options.TO)) { - if (!receive.isChecked()) { - if (online) { - contact.resetOption(Contact.Options.TO); - packet = xcs.getPresenceGenerator().stopPresenceUpdatesFrom(contact); - } - updated = true; - } - } else { - if (contact.getOption(Contact.Options.ASKING)) { - if (!receive.isChecked()) { - if (online) { - contact.resetOption(Contact.Options.ASKING); - packet = xcs.getPresenceGenerator().stopPresenceUpdatesFrom(contact); - } - updated = true; - } - } else { - if (receive.isChecked()) { - if (online) { - contact.setOption(Contact.Options.ASKING); - packet = xcs.getPresenceGenerator().requestPresenceUpdatesFrom(contact); - } - updated = true; - } - } - } - if (updated) { - if (online) { - if (packet!=null) { - xcs.sendPresencePacket(contact.getAccount(), packet); - } - Toast.makeText(getApplicationContext(), getString(R.string.subscription_updated), Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(getApplicationContext(), getString(R.string.subscription_not_updated_offline), Toast.LENGTH_SHORT).show(); - } - } - } + xmppConnectionService.removeOnRosterUpdateListener(); + xmppConnectionService.removeOnAccountListChangedListener(); } } diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index 66db353b..aa4fda4e 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -98,7 +98,7 @@ public class ConversationActivity extends XmppActivity { swapConversationFragment(); } else { startActivity(new Intent(getApplicationContext(), - StartConversation.class)); + StartConversationActivity.class)); finish(); } } @@ -306,23 +306,22 @@ public class ConversationActivity extends XmppActivity { .findItem(R.id.action_muc_details); MenuItem menuContactDetails = (MenuItem) menu .findItem(R.id.action_contact_details); - MenuItem menuInviteContacts = (MenuItem) menu - .findItem(R.id.action_invite); MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file); MenuItem menuClearHistory = (MenuItem) menu .findItem(R.id.action_clear_history); + MenuItem menuAdd = (MenuItem) menu.findItem(R.id.action_add); + MenuItem menuInviteContact = (MenuItem) menu.findItem(R.id.action_invite); if ((spl.isOpen() && (spl.isSlideable()))) { menuArchive.setVisible(false); menuMucDetails.setVisible(false); menuContactDetails.setVisible(false); menuSecure.setVisible(false); - menuInviteContacts.setVisible(false); + menuInviteContact.setVisible(false); menuAttach.setVisible(false); menuClearHistory.setVisible(false); } else { - ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl - .isSlideable()); + menuAdd.setVisible(!spl.isSlideable()); if (this.getSelectedConversation() != null) { if (this.getSelectedConversation().getLatestMessage() .getEncryption() != Message.ENCRYPTION_NONE) { @@ -333,7 +332,7 @@ public class ConversationActivity extends XmppActivity { menuAttach.setVisible(false); } else { menuMucDetails.setVisible(false); - menuInviteContacts.setVisible(false); + menuInviteContact.setVisible(false); } } } @@ -458,7 +457,7 @@ public class ConversationActivity extends XmppActivity { attachFilePopup.show(); break; case R.id.action_add: - startActivity(new Intent(this, StartConversation.class)); + startActivity(new Intent(this, StartConversationActivity.class)); break; case R.id.action_archive: this.endConversation(getSelectedConversation()); @@ -472,17 +471,13 @@ public class ConversationActivity extends XmppActivity { } break; case R.id.action_muc_details: - Intent intent = new Intent(this, MucDetailsActivity.class); - intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC); + Intent intent = new Intent(this, ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); intent.putExtra("uuid", getSelectedConversation().getUuid()); startActivity(intent); break; case R.id.action_invite: - /*Intent inviteIntent = new Intent(getApplicationContext(), - ContactsActivity.class); - inviteIntent.setAction("invite"); - inviteIntent.putExtra("uuid", getSelectedConversation().getUuid()); - startActivity(inviteIntent);*/ + inviteToConversation(getSelectedConversation()); break; case R.id.action_security: final Conversation conversation = getSelectedConversation(); @@ -569,7 +564,7 @@ public class ConversationActivity extends XmppActivity { return super.onOptionsItemSelected(item); } - private void endConversation(Conversation conversation) { + public void endConversation(Conversation conversation) { conversation.setStatus(Conversation.STATUS_ARCHIVED); paneShouldBeOpen = true; spl.openPane(); @@ -699,7 +694,7 @@ public class ConversationActivity extends XmppActivity { finish(); } else if (conversationList.size() <= 0) { // add no history - startActivity(new Intent(this, StartConversation.class)); + startActivity(new Intent(this, StartConversationActivity.class)); finish(); } else { spl.openPane(); diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index 01bab773..1df59843 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -1,53 +1,45 @@ package eu.siacs.conversations.ui; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Set; import net.java.otr4j.session.SessionStatus; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; -import eu.siacs.conversations.entities.MucOptions.OnRenameListener; -import eu.siacs.conversations.services.ImageProvider; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected; +import eu.siacs.conversations.ui.adapter.MessageAdapter; +import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.jingle.JingleConnection; import android.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.content.IntentSender.SendIntentException; -import android.graphics.Bitmap; -import android.graphics.Typeface; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Editable; import android.text.Selection; -import android.util.DisplayMetrics; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.AbsListView.OnScrollListener; import android.widget.AbsListView; -import android.widget.ArrayAdapter; -import android.widget.Button; + import android.widget.EditText; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ImageButton; -import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -58,12 +50,8 @@ public class ConversationFragment extends Fragment { protected ListView messagesView; protected LayoutInflater inflater; protected List<Message> messageList = new ArrayList<Message>(); - protected ArrayAdapter<Message> messageListAdapter; + protected MessageAdapter messageListAdapter; protected Contact contact; - protected BitmapCache mBitmapCache = new BitmapCache(); - - protected int mPrimaryTextColor; - protected int mSecondaryTextColor; protected String queuedPqpMessage = null; @@ -73,8 +61,6 @@ public class ConversationFragment extends Fragment { private TextView snackbarMessage; private TextView snackbarAction; - protected Bitmap selfBitmap; - private boolean useSubject = true; private boolean messagesLoaded = false; @@ -113,18 +99,27 @@ public class ConversationFragment extends Fragment { } } }; - + private OnClickListener clickToMuc = new OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(getActivity(), MucDetailsActivity.class); - intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC); + Intent intent = new Intent(getActivity(), + ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); intent.putExtra("uuid", conversation.getUuid()); startActivity(intent); } }; + private OnClickListener leaveMuc = new OnClickListener() { + + @Override + public void onClick(View v) { + activity.endConversation(conversation); + } + }; + private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override @@ -172,17 +167,18 @@ public class ConversationFragment extends Fragment { @Override public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - - this.inflater = inflater; - - mPrimaryTextColor = getResources().getColor(R.color.primarytext); - mSecondaryTextColor = getResources().getColor(R.color.secondarytext); - final View view = inflater.inflate(R.layout.fragment_conversation, container, false); chatMsg = (EditText) view.findViewById(R.id.textinput); + chatMsg.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (activity.getSlidingPaneLayout().isSlideable()) { + activity.getSlidingPaneLayout().closePane(); + } + } + }); ImageButton sendButton = (ImageButton) view .findViewById(R.id.textSendButton); @@ -195,376 +191,16 @@ public class ConversationFragment extends Fragment { messagesView = (ListView) view.findViewById(R.id.messages_view); messagesView.setOnScrollListener(mOnScrollListener); messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); - - messageListAdapter = new ArrayAdapter<Message>(this.getActivity() - .getApplicationContext(), R.layout.message_sent, - this.messageList) { - - private static final int SENT = 0; - private static final int RECIEVED = 1; - private static final int STATUS = 2; - - @Override - public int getViewTypeCount() { - return 3; - } - - @Override - public int getItemViewType(int position) { - if (getItem(position).getType() == Message.TYPE_STATUS) { - return STATUS; - } else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) { - return RECIEVED; - } else { - return SENT; - } - } - - private void displayStatus(ViewHolder viewHolder, Message message) { - String filesize = null; - String info = null; - boolean error = false; - boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI - && message.getStatus() <= Message.STATUS_RECIEVED; - if (message.getType() == Message.TYPE_IMAGE) { - String[] fileParams = message.getBody().split(","); - try { - long size = Long.parseLong(fileParams[0]); - filesize = size / 1024 + " KB"; - } catch (NumberFormatException e) { - filesize = "0 KB"; - } - } - switch (message.getStatus()) { - case Message.STATUS_WAITING: - info = getString(R.string.waiting); - break; - case Message.STATUS_UNSEND: - info = getString(R.string.sending); - break; - case Message.STATUS_OFFERED: - info = getString(R.string.offering); - break; - case Message.STATUS_SEND_FAILED: - info = getString(R.string.send_failed); - error = true; - break; - case Message.STATUS_SEND_REJECTED: - info = getString(R.string.send_rejected); - error = true; - break; - case Message.STATUS_RECEPTION_FAILED: - info = getString(R.string.reception_failed); - error = true; - default: - if (multiReceived) { - info = message.getCounterpart(); - } - break; - } - if (error) { - viewHolder.time.setTextColor(0xFFe92727); - } else { - viewHolder.time.setTextColor(mSecondaryTextColor); - } - if (message.getEncryption() == Message.ENCRYPTION_NONE) { - viewHolder.indicator.setVisibility(View.GONE); - } else { - viewHolder.indicator.setVisibility(View.VISIBLE); - } - - String formatedTime = UIHelper.readableTimeDifference( - getContext(), message.getTimeSent()); - if (message.getStatus() <= Message.STATUS_RECIEVED) { - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info); - } else if ((filesize == null) && (info != null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " - + info); - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(formatedTime + " \u00B7 " - + filesize); - } else { - viewHolder.time.setText(formatedTime); - } - } else { - if ((filesize != null) && (info != null)) { - viewHolder.time.setText(filesize + " \u00B7 " + info); - } else if ((filesize == null) && (info != null)) { - if (error) { - viewHolder.time.setText(info + " \u00B7 " - + formatedTime); - } else { - viewHolder.time.setText(info); - } - } else if ((filesize != null) && (info == null)) { - viewHolder.time.setText(filesize + " \u00B7 " - + formatedTime); - } else { - viewHolder.time.setText(formatedTime); - } - } - } - - private void displayInfoMessage(ViewHolder viewHolder, int r) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setText(getString(r)); - viewHolder.messageBody.setTextColor(0xff33B5E5); - viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); - viewHolder.messageBody.setTextIsSelectable(false); - } - - private void displayDecryptionFailed(ViewHolder viewHolder) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody - .setText(getString(R.string.decryption_failed)); - viewHolder.messageBody.setTextColor(0xFFe92727); - viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setTextIsSelectable(false); - } - - private void displayTextMessage(ViewHolder viewHolder, String text) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - if (text != null) { - viewHolder.messageBody.setText(text.trim()); - } else { - viewHolder.messageBody.setText(""); - } - viewHolder.messageBody.setTextColor(mPrimaryTextColor); - viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setTextIsSelectable(true); - } - - private void displayImageMessage(ViewHolder viewHolder, - final Message message) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.image.setVisibility(View.VISIBLE); - String[] fileParams = message.getBody().split(","); - if (fileParams.length == 3) { - double target = metrics.density * 288; - int w = Integer.parseInt(fileParams[1]); - int h = Integer.parseInt(fileParams[2]); - int scalledW; - int scalledH; - if (w <= h) { - scalledW = (int) (w / ((double) h / target)); - scalledH = (int) target; - } else { - scalledW = (int) target; - scalledH = (int) (h / ((double) w / target)); - } - viewHolder.image - .setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); - } - activity.loadBitmap(message, viewHolder.image); - viewHolder.image.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType( - ImageProvider.getContentUri(message), "image/*"); - startActivity(intent); - } - }); - viewHolder.image - .setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, - ImageProvider.getContentUri(message)); - shareIntent.setType("image/webp"); - startActivity(Intent.createChooser(shareIntent, - getText(R.string.share_with))); - return true; - } - }); - } - + messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); + messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { + @Override - public View getView(int position, View view, ViewGroup parent) { - final Message item = getItem(position); - int type = getItemViewType(position); - ViewHolder viewHolder; - if (view == null) { - viewHolder = new ViewHolder(); - switch (type) { - case SENT: - view = (View) inflater.inflate(R.layout.message_sent, - null); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - viewHolder.contact_picture.setImageBitmap(selfBitmap); - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - view.setTag(viewHolder); - break; - case RECIEVED: - view = (View) inflater.inflate( - R.layout.message_recieved, null); - viewHolder.message_box = (LinearLayout) view - .findViewById(R.id.message_box); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - - viewHolder.download_button = (Button) view - .findViewById(R.id.download_button); - - if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { - - viewHolder.contact_picture - .setImageBitmap(mBitmapCache.get( - item.getConversation().getName( - useSubject), item - .getConversation() - .getContact(), - getActivity() - .getApplicationContext())); - - } - viewHolder.indicator = (ImageView) view - .findViewById(R.id.security_indicator); - viewHolder.image = (ImageView) view - .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view - .findViewById(R.id.message_body); - viewHolder.time = (TextView) view - .findViewById(R.id.message_time); - view.setTag(viewHolder); - break; - case STATUS: - view = (View) inflater.inflate(R.layout.message_status, - null); - viewHolder.contact_picture = (ImageView) view - .findViewById(R.id.message_photo); - if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { - - viewHolder.contact_picture - .setImageBitmap(mBitmapCache.get( - item.getConversation().getName( - useSubject), item - .getConversation() - .getContact(), - getActivity() - .getApplicationContext())); - viewHolder.contact_picture.setAlpha(128); - - } - break; - default: - viewHolder = null; - break; - } - } else { - viewHolder = (ViewHolder) view.getTag(); - } - - if (type == STATUS) { - return view; - } - - if (type == RECIEVED) { - if (item.getConversation().getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(mBitmapCache - .get(item.getCounterpart(), null, getActivity() - .getApplicationContext())); - viewHolder.contact_picture - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - highlightInConference(item - .getCounterpart()); - } - }); - } + public void onContactPictureClicked(Message message) { + if (message.getConversation().getMode() == Conversation.MODE_MULTI) { + highlightInConference(message.getCounterpart()); } - - if (item.getType() == Message.TYPE_IMAGE) { - if (item.getStatus() == Message.STATUS_RECIEVING) { - displayInfoMessage(viewHolder, R.string.receiving_image); - } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) { - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - JingleConnection connection = item - .getJingleConnection(); - if (connection != null) { - connection.accept(); - } - } - }); - } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED) - || (item.getEncryption() == Message.ENCRYPTION_NONE) - || (item.getEncryption() == Message.ENCRYPTION_OTR)) { - displayImageMessage(viewHolder, item); - } else if (item.getEncryption() == Message.ENCRYPTION_PGP) { - displayInfoMessage(viewHolder, - R.string.encrypted_message); - } else { - displayDecryptionFailed(viewHolder); - } - } else { - if (item.getEncryption() == Message.ENCRYPTION_PGP) { - if (activity.hasPgp()) { - displayInfoMessage(viewHolder, - R.string.encrypted_message); - } else { - displayInfoMessage(viewHolder, - R.string.install_openkeychain); - viewHolder.message_box - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - activity.showInstallPgpDialog(); - } - }); - } - } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder); - } else { - displayTextMessage(viewHolder, item.getBody()); - } - } - - displayStatus(viewHolder, item); - - return view; } - }; + }); messagesView.setAdapter(messageListAdapter); return view; @@ -582,17 +218,6 @@ public class ConversationFragment extends Fragment { Selection.setSelection(etext, position); } - protected Bitmap findSelfPicture() { - SharedPreferences sharedPref = PreferenceManager - .getDefaultSharedPreferences(getActivity() - .getApplicationContext()); - boolean showPhoneSelfContactPicture = sharedPref.getBoolean( - "show_phone_selfcontact_picture", true); - - return UIHelper.getSelfContactPicture(conversation.getAccount(), 48, - showPhoneSelfContactPicture, getActivity()); - } - @Override public void onStart() { super.onStart(); @@ -633,7 +258,6 @@ public class ConversationFragment extends Fragment { int position = chatMsg.length(); Editable etext = chatMsg.getText(); Selection.setSelection(etext, position); - this.selfBitmap = findSelfPicture(); updateMessages(); if (activity.getSlidingPaneLayout().isSlideable()) { if (!activity.shouldPaneBeOpen()) { @@ -645,34 +269,6 @@ public class ConversationFragment extends Fragment { activity.invalidateOptionsMenu(); } } - if (conversation.getMode() == Conversation.MODE_MULTI) { - activity.xmppConnectionService - .setOnRenameListener(new OnRenameListener() { - - @Override - public void onRename(final boolean success) { - activity.xmppConnectionService - .updateConversation(conversation); - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - if (success) { - Toast.makeText( - getActivity(), - getString(R.string.your_nick_has_been_changed), - Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText( - getActivity(), - getString(R.string.nick_in_use), - Toast.LENGTH_SHORT).show(); - } - } - }); - } - }); - } } private void decryptMessage(Message message) { @@ -683,7 +279,8 @@ public class ConversationFragment extends Fragment { @Override public void userInputRequried(PendingIntent pi, Message message) { askForPassphraseIntent = pi.getIntentSender(); - showSnackbar(R.string.openpgp_messages_found,R.string.decrypt,clickToDecryptListener); + showSnackbar(R.string.openpgp_messages_found, + R.string.decrypt, clickToDecryptListener); } @Override @@ -706,8 +303,20 @@ public class ConversationFragment extends Fragment { if (getView() == null) { return; } - ConversationActivity activity = (ConversationActivity) getActivity(); + hideSnackbar(); + final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { + final Contact contact = this.conversation.getContact(); + if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + showSnackbar(R.string.contact_added_you, R.string.add_back, new OnClickListener() { + + @Override + public void onClick(View v) { + activity.xmppConnectionService.createContact(contact); + activity.switchToContactDetails(contact); + } + }); + } for (Message message : this.conversation.getMessages()) { if ((message.getEncryption() == Message.ENCRYPTION_PGP) && ((message.getStatus() == Message.STATUS_RECIEVED) || (message @@ -734,10 +343,14 @@ public class ConversationFragment extends Fragment { makeFingerprintWarning(conversation.getLatestEncryption()); } } else { - if (conversation.getMucOptions().getError() != 0) { - showSnackbar(R.string.nick_in_use, R.string.edit,clickToMuc); + if (!conversation.getMucOptions().online() + && conversation.getAccount().getStatus() == Account.STATUS_ONLINE) { if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) { - showSnackbar(R.string.nick_in_use, R.string.edit,clickToMuc); + showSnackbar(R.string.nick_in_use, R.string.edit, + clickToMuc); + } else if (conversation.getMucOptions().getError() == MucOptions.ERROR_ROOM_NOT_FOUND) { + showSnackbar(R.string.conference_not_found, + R.string.leave, leaveMuc); } } } @@ -792,28 +405,31 @@ public class ConversationFragment extends Fragment { && (conversation.hasValidOtrSession() && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints .contains(conversation.getOtrFingerprint())))) { - showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, new OnClickListener() { + showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, + new OnClickListener() { - @Override - public void onClick(View v) { - if (conversation.getOtrFingerprint() != null) { - AlertDialog dialog = UIHelper.getVerifyFingerprintDialog( - (ConversationActivity) getActivity(), conversation, - snackbar); - dialog.show(); - } - } - }); + @Override + public void onClick(View v) { + if (conversation.getOtrFingerprint() != null) { + AlertDialog dialog = UIHelper + .getVerifyFingerprintDialog( + (ConversationActivity) getActivity(), + conversation, snackbar); + dialog.show(); + } + } + }); } } - - protected void showSnackbar(int message, int action, OnClickListener clickListener) { + + protected void showSnackbar(int message, int action, + OnClickListener clickListener) { snackbar.setVisibility(View.VISIBLE); snackbarMessage.setText(message); snackbarAction.setText(action); snackbarAction.setOnClickListener(clickListener); } - + protected void hideSnackbar() { snackbar.setVisibility(View.GONE); } @@ -939,38 +555,6 @@ public class ConversationFragment extends Fragment { } } - private static class ViewHolder { - - protected LinearLayout message_box; - protected Button download_button; - protected ImageView image; - protected ImageView indicator; - protected TextView time; - protected TextView messageBody; - protected ImageView contact_picture; - - } - - private class BitmapCache { - private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>(); - - public Bitmap get(String name, Contact contact, Context context) { - if (bitmaps.containsKey(name)) { - return bitmaps.get(name); - } else { - Bitmap bm; - if (contact != null) { - bm = UIHelper - .getContactPicture(contact, 48, context, false); - } else { - bm = UIHelper.getContactPicture(name, 48, context, false); - } - bitmaps.put(name, bm); - return bm; - } - } - } - public void setText(String text) { this.pastedText = text; } diff --git a/src/eu/siacs/conversations/ui/EditAccount.java b/src/eu/siacs/conversations/ui/EditAccountDialog.java index 9a0b8d84..7c135fc1 100644 --- a/src/eu/siacs/conversations/ui/EditAccount.java +++ b/src/eu/siacs/conversations/ui/EditAccountDialog.java @@ -4,7 +4,7 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.utils.KnownHostsAdapter; +import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.utils.Validator; import android.app.AlertDialog; import android.app.Dialog; @@ -21,7 +21,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.TextView; -public class EditAccount extends DialogFragment { +public class EditAccountDialog extends DialogFragment { protected Account account; diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java index c3f1e105..e56e2db9 100644 --- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -6,8 +6,7 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; -import eu.siacs.conversations.ui.EditAccount.EditAccountListener; -import eu.siacs.conversations.xmpp.OnTLSExceptionReceived; +import eu.siacs.conversations.ui.EditAccountDialog.EditAccountListener; import eu.siacs.conversations.xmpp.XmppConnection; import android.app.Activity; import android.app.AlertDialog; @@ -59,57 +58,6 @@ public class ManageAccountActivity extends XmppActivity { } }; - protected OnTLSExceptionReceived tlsExceptionReceived = new OnTLSExceptionReceived() { - - @Override - public void onTLSExceptionReceived(final String fingerprint, - final Account account) { - activity.runOnUiThread(new Runnable() { - - @Override - public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder( - activity); - builder.setTitle(getString(R.string.account_status_error)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - View view = (View) getLayoutInflater().inflate( - R.layout.cert_warning, null); - TextView sha = (TextView) view.findViewById(R.id.sha); - TextView hint = (TextView) view.findViewById(R.id.hint); - StringBuilder humanReadableSha = new StringBuilder(); - humanReadableSha.append(fingerprint); - for (int i = 2; i < 59; i += 3) { - if ((i == 14) || (i == 29) || (i == 44)) { - humanReadableSha.insert(i, "\n"); - } else { - humanReadableSha.insert(i, ":"); - } - - } - hint.setText(getString(R.string.untrusted_cert_hint, - account.getServer())); - sha.setText(humanReadableSha.toString()); - builder.setView(view); - builder.setNegativeButton( - getString(R.string.certif_no_trust), null); - builder.setPositiveButton(getString(R.string.certif_trust), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - account.setSSLCertFingerprint(fingerprint); - activity.xmppConnectionService - .updateAccount(account); - } - }); - builder.create().show(); - } - }); - - } - }; - @Override protected void onCreate(Bundle savedInstanceState) { @@ -172,11 +120,6 @@ public class ManageAccountActivity extends XmppActivity { .setText(getString(R.string.account_status_requires_tls)); statusView.setTextColor(0xFFe92727); break; - case Account.STATUS_TLS_ERROR: - statusView - .setText(getString(R.string.account_status_error)); - statusView.setTextColor(0xFFe92727); - break; case Account.STATUS_REGISTRATION_FAILED: statusView .setText(getString(R.string.account_status_regis_fail)); @@ -214,14 +157,13 @@ public class ManageAccountActivity extends XmppActivity { int position, long arg3) { if (!isActionMode) { Account account = accountList.get(position); - if ((account.getStatus() == Account.STATUS_OFFLINE) - || (account.getStatus() == Account.STATUS_TLS_ERROR)) { + if (account.getStatus() == Account.STATUS_OFFLINE) { activity.xmppConnectionService.reconnectAccount( accountList.get(position), true); } else if (account.getStatus() == Account.STATUS_ONLINE) { activity.startActivity(new Intent(activity .getApplicationContext(), - StartConversation.class)); + StartConversationActivity.class)); } else if (account.getStatus() != Account.STATUS_DISABLED) { editAccount(account); } @@ -471,7 +413,6 @@ public class ManageAccountActivity extends XmppActivity { protected void onStop() { if (xmppConnectionServiceBound) { xmppConnectionService.removeOnAccountListChangedListener(); - xmppConnectionService.removeOnTLSExceptionReceivedListener(); } super.onStop(); } @@ -479,8 +420,6 @@ public class ManageAccountActivity extends XmppActivity { @Override void onBackendConnected() { xmppConnectionService.setOnAccountListChangedListener(accountChanged); - xmppConnectionService - .setOnTLSExceptionReceivedListener(tlsExceptionReceived); this.accountList.clear(); this.accountList.addAll(xmppConnectionService.getAccounts()); accountListViewAdapter.notifyDataSetChanged(); @@ -513,7 +452,7 @@ public class ManageAccountActivity extends XmppActivity { @Override public boolean onNavigateUp() { if (xmppConnectionService.getConversations().size() == 0) { - Intent contactsIntent = new Intent(this, StartConversation.class); + Intent contactsIntent = new Intent(this, StartConversationActivity.class); contactsIntent.setFlags( // if activity exists in stack, pop the stack and go back to it Intent.FLAG_ACTIVITY_CLEAR_TOP | @@ -531,7 +470,7 @@ public class ManageAccountActivity extends XmppActivity { } private void editAccount(Account account) { - EditAccount dialog = new EditAccount(); + EditAccountDialog dialog = new EditAccountDialog(); dialog.setAccount(account); dialog.setEditAccountListener(new EditAccountListener() { @@ -550,7 +489,7 @@ public class ManageAccountActivity extends XmppActivity { protected void addAccount() { final Activity activity = this; - EditAccount dialog = new EditAccount(); + EditAccountDialog dialog = new EditAccountDialog(); dialog.setEditAccountListener(new EditAccountListener() { @Override diff --git a/src/eu/siacs/conversations/ui/MucDetailsActivity.java b/src/eu/siacs/conversations/ui/MucDetailsActivity.java deleted file mode 100644 index 8226e381..00000000 --- a/src/eu/siacs/conversations/ui/MucDetailsActivity.java +++ /dev/null @@ -1,214 +0,0 @@ -package eu.siacs.conversations.ui; - -import java.util.ArrayList; -import java.util.List; - -import org.openintents.openpgp.util.OpenPgpUtils; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.MucOptions; -import eu.siacs.conversations.entities.MucOptions.User; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import android.app.PendingIntent; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.IntentSender.SendIntentException; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class MucDetailsActivity extends XmppActivity { - public static final String ACTION_VIEW_MUC = "view_muc"; - private Conversation conversation; - private EditText mYourNick; - private EditText mSubject; - private TextView mRoleAffiliaton; - private TextView mFullJid; - private LinearLayout membersView; - private LinearLayout mMoreDetails; - private Button mInviteButton; - private String uuid = null; - private OnClickListener changeNickListener = new OnClickListener() { - - @Override - public void onClick(View arg0) { - MucOptions options = conversation.getMucOptions(); - String nick = mYourNick.getText().toString(); - if (!options.getNick().equals(nick)) { - xmppConnectionService.renameInMuc(conversation, nick); - finish(); - } - } - }; - - private OnClickListener changeSubjectListener = new OnClickListener() { - - @Override - public void onClick(View arg0) { - String subject = mSubject.getText().toString(); - MucOptions options = conversation.getMucOptions(); - if (!subject.equals(options.getSubject())) { - MessagePacket packet = xmppConnectionService.getMessageGenerator().conferenceSubject(conversation, subject); - xmppConnectionService.sendMessagePacket(conversation.getAccount(), packet); - finish(); - } - } - }; - - private OnClickListener inviteListener = new OnClickListener() { - - @Override - public void onClick(View v) { - /*Intent intent = new Intent(getApplicationContext(), - ContactsActivity.class); - intent.setAction("invite"); - intent.putExtra("uuid",conversation.getUuid()); - startActivity(intent);*/ - } - }; - - private List<User> users = new ArrayList<MucOptions.User>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_muc_details); - mYourNick = (EditText) findViewById(R.id.muc_your_nick); - mFullJid = (TextView) findViewById(R.id.muc_jabberid); - ImageButton editNickButton = (ImageButton) findViewById(R.id.muc_edit_nick); - editNickButton.setOnClickListener(this.changeNickListener); - ImageButton editSubjectButton = (ImageButton) findViewById(R.id.muc_edit_subject); - editSubjectButton.setOnClickListener(this.changeSubjectListener); - membersView = (LinearLayout) findViewById(R.id.muc_members); - mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); - mMoreDetails.setVisibility(View.GONE); - mSubject = (EditText) findViewById(R.id.muc_subject); - mInviteButton = (Button) findViewById(R.id.invite); - mInviteButton.setOnClickListener(inviteListener); - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); - - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch (menuItem.getItemId()) { - case android.R.id.home: - finish(); - } - return super.onOptionsItemSelected(menuItem); - } - - public String getReadableRole(int role) { - switch (role) { - case User.ROLE_MODERATOR: - return getString(R.string.moderator); - case User.ROLE_PARTICIPANT: - return getString(R.string.participant); - case User.ROLE_VISITOR: - return getString(R.string.visitor); - default: - return ""; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.muc_details, menu); - return true; - } - - @Override - void onBackendConnected() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean useSubject = preferences.getBoolean("use_subject_in_muc", true); - if (getIntent().getAction().equals(ACTION_VIEW_MUC)) { - this.uuid = getIntent().getExtras().getString("uuid"); - } - if (uuid != null) { - for (Conversation mConv : xmppConnectionService.getConversations()) { - if (mConv.getUuid().equals(uuid)) { - this.conversation = mConv; - } - } - if (this.conversation != null) { - mSubject.setText(conversation.getMucOptions().getSubject()); - setTitle(conversation.getName(useSubject)); - mFullJid.setText(conversation.getContactJid().split("/")[0]); - mYourNick.setText(conversation.getMucOptions().getNick()); - mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); - if (conversation.getMucOptions().online()) { - mMoreDetails.setVisibility(View.VISIBLE); - User self = conversation.getMucOptions().getSelf(); - switch (self.getAffiliation()) { - case User.AFFILIATION_ADMIN: - mRoleAffiliaton.setText(getReadableRole(self.getRole()) - + " (" + getString(R.string.admin) + ")"); - break; - case User.AFFILIATION_OWNER: - mRoleAffiliaton.setText(getReadableRole(self.getRole()) - + " (" + getString(R.string.owner) + ")"); - break; - default: - mRoleAffiliaton - .setText(getReadableRole(self.getRole())); - break; - } - } - this.users.clear(); - this.users.addAll(conversation.getMucOptions().getUsers()); - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - membersView.removeAllViews(); - for(final User contact : conversation.getMucOptions().getUsers()) { - View view = (View) inflater.inflate(R.layout.contact, null); - TextView displayName = (TextView) view.findViewById(R.id.contact_display_name); - TextView key = (TextView) view.findViewById(R.id.key); - displayName.setText(contact.getName()); - TextView role = (TextView) view.findViewById(R.id.contact_jid); - role.setText(getReadableRole(contact.getRole())); - if (contact.getPgpKeyId()!=0) { - key.setVisibility(View.VISIBLE); - key.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - PgpEngine pgp = xmppConnectionService.getPgpEngine(); - if (pgp!=null) { - PendingIntent intent = pgp.getIntentForKey(conversation.getAccount(), contact.getPgpKeyId()); - if (intent!=null) { - try { - startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0); - } catch (SendIntentException e) { - - } - } - } - } - }); - key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId())); - } - ImageView imageView = (ImageView) view - .findViewById(R.id.contact_photo); - imageView.setImageBitmap(UIHelper.getContactPicture(contact.getName(), 48,this.getApplicationContext(), false)); - membersView.addView(view); - } - } - } else { - Log.d("xmppService","uuid in muc details was null"); - } - } -} diff --git a/src/eu/siacs/conversations/ui/OnPresenceSelected.java b/src/eu/siacs/conversations/ui/OnPresenceSelected.java deleted file mode 100644 index 1c967224..00000000 --- a/src/eu/siacs/conversations/ui/OnPresenceSelected.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.siacs.conversations.ui; - -public interface OnPresenceSelected { - public void onPresenceSelected(); -} diff --git a/src/eu/siacs/conversations/ui/StartConversation.java b/src/eu/siacs/conversations/ui/StartConversationActivity.java index c9166c39..d12d2878 100644 --- a/src/eu/siacs/conversations/ui/StartConversation.java +++ b/src/eu/siacs/conversations/ui/StartConversationActivity.java @@ -12,6 +12,8 @@ import android.app.Fragment; import android.app.FragmentTransaction; import android.app.ListFragment; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.os.Bundle; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; @@ -19,11 +21,9 @@ import android.text.Editable; import android.text.TextWatcher; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; @@ -32,21 +32,20 @@ import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.ImageView; import android.widget.ListView; import android.widget.Spinner; -import android.widget.TextView; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; 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.KnownHostsAdapter; -import eu.siacs.conversations.utils.UIHelper; +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; -public class StartConversation extends XmppActivity { +public class StartConversationActivity extends XmppActivity { private Tab mContactsTab; private Tab mConferencesTab; @@ -65,7 +64,7 @@ public class StartConversation extends XmppActivity { private List<String> mKnownConferenceHosts; private EditText mSearchEditText; - + public int conference_context_id; public int contact_context_id; @@ -141,6 +140,19 @@ public class StartConversation extends XmppActivity { int count) { } }; + private OnRosterUpdate onRosterUpdate = new OnRosterUpdate() { + + @Override + public void onRosterUpdate() { + runOnUiThread(new Runnable() { + + @Override + public void run() { + filter(mSearchEditText.getText().toString()); + } + }); + } + }; @Override public void onCreate(Bundle savedInstanceState) { @@ -175,19 +187,20 @@ public class StartConversation extends XmppActivity { } }); - mConferenceAdapter = new ListItemAdapter(conferences); + mConferenceAdapter = new ListItemAdapter(getApplicationContext(),conferences); mConferenceListFragment.setListAdapter(mConferenceAdapter); mConferenceListFragment.setContextMenu(R.menu.conference_context); - mConferenceListFragment.setOnListItemClickListener(new OnItemClickListener() { + mConferenceListFragment + .setOnListItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - openConversationForBookmark(position); - } - }); + @Override + public void onItemClick(AdapterView<?> arg0, View arg1, + int position, long arg3) { + openConversationForBookmark(position); + } + }); - mContactsAdapter = new ListItemAdapter(contacts); + mContactsAdapter = new ListItemAdapter(getApplicationContext(),contacts); mContactsListFragment.setListAdapter(mContactsAdapter); mContactsListFragment.setContextMenu(R.menu.contact_context); mContactsListFragment @@ -201,6 +214,12 @@ public class StartConversation extends XmppActivity { }); } + + @Override + public void onStop() { + super.onStop(); + xmppConnectionService.removeOnRosterUpdateListener(); + } protected void openConversationForContact(int position) { Contact contact = (Contact) contacts.get(position); @@ -209,20 +228,25 @@ public class StartConversation extends XmppActivity { contact.getJid(), false); switchToConversation(conversation); } - + protected void openConversationForContact() { int position = contact_context_id; openConversationForContact(position); } - + protected void openConversationForBookmark() { openConversationForBookmark(conference_context_id); } - + protected void openConversationForBookmark(int position) { Bookmark bookmark = (Bookmark) conferences.get(position); - Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), bookmark.getJid(), true); + Conversation conversation = xmppConnectionService + .findOrCreateConversation(bookmark.getAccount(), + bookmark.getJid(), true); conversation.setBookmark(bookmark); + if (!conversation.getMucOptions().online()) { + xmppConnectionService.joinMuc(conversation); + } if (!bookmark.autojoin()) { bookmark.setAutojoin(true); xmppConnectionService.pushBookmarks(bookmark.getAccount()); @@ -238,18 +262,48 @@ public class StartConversation extends XmppActivity { protected void deleteContact() { int position = contact_context_id; - Contact contact = (Contact) contacts.get(position); - xmppConnectionService.deleteContactOnServer(contact); - filter(mSearchEditText.getText().toString()); + final Contact contact = (Contact) contacts.get(position); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setNegativeButton(R.string.cancel, null); + builder.setTitle(R.string.action_delete_contact); + builder.setMessage( + getString(R.string.remove_contact_text, + contact.getJid())); + builder.setPositiveButton(R.string.delete,new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + xmppConnectionService.deleteContactOnServer(contact); + filter(mSearchEditText.getText().toString()); + } + }); + builder.create().show(); + } - + protected void deleteConference() { - int position = contact_context_id; - Bookmark bookmark = (Bookmark) conferences.get(position); - Account account = bookmark.getAccount(); - account.getBookmarks().remove(bookmark); - xmppConnectionService.pushBookmarks(account); - filter(mSearchEditText.getText().toString()); + int position = conference_context_id; + final Bookmark bookmark = (Bookmark) conferences.get(position); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setNegativeButton(R.string.cancel, null); + builder.setTitle(R.string.delete_bookmark); + builder.setMessage( + getString(R.string.remove_bookmark_text, + bookmark.getJid())); + builder.setPositiveButton(R.string.delete,new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + bookmark.unregisterConversation(); + Account account = bookmark.getAccount(); + account.getBookmarks().remove(bookmark); + xmppConnectionService.pushBookmarks(account); + filter(mSearchEditText.getText().toString()); + } + }); + builder.create().show(); + } protected void showCreateContactDialog() { @@ -307,7 +361,8 @@ public class StartConversation extends XmppActivity { jid.setAdapter(new KnownHostsAdapter(this, android.R.layout.simple_list_item_1, mKnownConferenceHosts)); populateAccountSpinner(spinner); - final CheckBox bookmarkCheckBox = (CheckBox) dialogView.findViewById(R.id.bookmark); + final CheckBox bookmarkCheckBox = (CheckBox) dialogView + .findViewById(R.id.bookmark); builder.setView(dialogView); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.join, null); @@ -328,20 +383,28 @@ public class StartConversation extends XmppActivity { if (account.hasBookmarkFor(conferenceJid)) { jid.setError(getString(R.string.bookmark_already_exists)); } else { - Bookmark bookmark = new Bookmark(account, conferenceJid); + Bookmark bookmark = new Bookmark(account, + conferenceJid); bookmark.setAutojoin(true); account.getBookmarks().add(bookmark); - xmppConnectionService.pushBookmarks(account); + xmppConnectionService + .pushBookmarks(account); Conversation conversation = xmppConnectionService .findOrCreateConversation(account, conferenceJid, true); conversation.setBookmark(bookmark); - switchToConversation(conversation); + if (!conversation.getMucOptions().online()) { + xmppConnectionService.joinMuc(conversation); + } + switchToConversation(conversation); } } else { Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + .findOrCreateConversation(account, + conferenceJid, true); + if (!conversation.getMucOptions().online()) { + xmppConnectionService.joinMuc(conversation); + } switchToConversation(conversation); } } else { @@ -401,6 +464,7 @@ public class StartConversation extends XmppActivity { @Override void onBackendConnected() { + xmppConnectionService.setOnRosterUpdateListener(this.onRosterUpdate ); if (mSearchEditText != null) { filter(mSearchEditText.getText().toString()); } else { @@ -416,7 +480,7 @@ public class StartConversation extends XmppActivity { this.mKnownConferenceHosts = xmppConnectionService .getKnownConferenceHosts(); } - + protected void filter(String needle) { this.filterContacts(needle); this.filterConferences(needle); @@ -436,12 +500,12 @@ public class StartConversation extends XmppActivity { Collections.sort(this.contacts); mContactsAdapter.notifyDataSetChanged(); } - + protected void filterConferences(String needle) { this.conferences.clear(); for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.STATUS_DISABLED) { - for(Bookmark bookmark : account.getBookmarks()) { + for (Bookmark bookmark : account.getBookmarks()) { if (bookmark.match(needle)) { this.conferences.add(bookmark); } @@ -456,37 +520,10 @@ public class StartConversation extends XmppActivity { invalidateOptionsMenu(); } - private class ListItemAdapter extends ArrayAdapter<ListItem> { - - public ListItemAdapter(List<ListItem> objects) { - super(getApplicationContext(), 0, objects); - } - - @Override - public View getView(int position, View view, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - ListItem item = getItem(position); - if (view == null) { - view = (View) inflater.inflate(R.layout.contact, null); - } - TextView name = (TextView) view - .findViewById(R.id.contact_display_name); - TextView jid = (TextView) view.findViewById(R.id.contact_jid); - ImageView picture = (ImageView) view - .findViewById(R.id.contact_photo); - - jid.setText(item.getJid()); - name.setText(item.getDisplayName()); - picture.setImageBitmap(item.getImage(48, getApplicationContext())); - return view; - } - - } - public static class MyListFragment extends ListFragment { private AdapterView.OnItemClickListener mOnItemClickListener; private int mResContextMenu; - + public void setContextMenu(int res) { this.mResContextMenu = res; } @@ -512,9 +549,8 @@ public class StartConversation extends XmppActivity { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); - StartConversation activity = (StartConversation) getActivity(); - activity.getMenuInflater().inflate(mResContextMenu, - menu); + StartConversationActivity activity = (StartConversationActivity) getActivity(); + activity.getMenuInflater().inflate(mResContextMenu, menu); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { activity.conference_context_id = acmi.position; @@ -525,7 +561,7 @@ public class StartConversation extends XmppActivity { @Override public boolean onContextItemSelected(MenuItem item) { - StartConversation activity = (StartConversation) getActivity(); + StartConversationActivity activity = (StartConversationActivity) getActivity(); switch (item.getItemId()) { case R.id.context_start_conversation: activity.openConversationForContact(); diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java index 217bae55..fad4d026 100644 --- a/src/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -27,16 +27,29 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; public abstract class XmppActivity extends Activity { public static final int REQUEST_ANNOUNCE_PGP = 0x73731; + protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x341830; protected final static String LOGTAG = "xmppService"; public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; protected boolean handledViewIntent = false; + + protected int mPrimaryTextColor; + protected int mSecondaryTextColor; + + protected interface OnValueEdited { + public void onValueEdited(String value); + } + + public interface OnPresenceSelected { + public void onPresenceSelected(); + } protected ServiceConnection mConnection = new ServiceConnection() { @@ -147,6 +160,8 @@ public abstract class XmppActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ExceptionHelper.init(getApplicationContext()); + mPrimaryTextColor = getResources().getColor(R.color.primarytext); + mSecondaryTextColor = getResources().getColor(R.color.secondarytext); } public void switchToConversation(Conversation conversation) { @@ -183,6 +198,12 @@ public abstract class XmppActivity extends Activity { startActivity(intent); } + protected void inviteToConversation(Conversation conversation) { + Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class); + intent.putExtra("conversation",conversation.getUuid()); + startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION); + } + protected void announcePgp(Account account, final Conversation conversation) { xmppConnectionService.getPgpEngine().generateSignature(account, "online", new UiCallback<Account>() { @@ -193,10 +214,7 @@ public abstract class XmppActivity extends Activity { try { startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); - } catch (SendIntentException e) { - Log.d("xmppService", - "coulnd start intent for pgp anncouncment"); - } + } catch (SendIntentException e) {} } @Override @@ -251,11 +269,32 @@ public abstract class XmppActivity extends Activity { Account account = conversation.getAccount(); Contact contact = account.getRoster().getContact(jid); xmppConnectionService.createContact(contact); + switchToContactDetails(contact); } }); builder.create().show(); } + protected void quickEdit(final String previousValue, final OnValueEdited callback) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = (View) getLayoutInflater().inflate(R.layout.quickedit, null); + final EditText editor = (EditText) view.findViewById(R.id.editor); + editor.setText(previousValue); + builder.setView(view); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.edit, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + String value = editor.getText().toString(); + if (!previousValue.equals(value) && value.trim().length() > 0) { + callback.onValueEdited(value); + } + } + }); + builder.create().show(); + } + public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) { Contact contact = conversation.getContact(); @@ -307,4 +346,26 @@ public abstract class XmppActivity extends Activity { } } } + + protected void onActivityResult(int requestCode, int resultCode, + final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { + String contactJid = data.getStringExtra("contact"); + String conversationUuid = data.getStringExtra("conversation"); + Conversation conversation = xmppConnectionService.findConversationByUuid(conversationUuid); + if (conversation.getMode() == Conversation.MODE_MULTI) { + xmppConnectionService.invite(conversation, contactJid); + } + Log.d("xmppService","inviting "+contactJid+" to "+conversation.getName(true)); + } + } + + public int getSecondaryTextColor() { + return this.mSecondaryTextColor; + } + + public int getPrimaryTextColor() { + return this.mPrimaryTextColor; + } } diff --git a/src/eu/siacs/conversations/utils/KnownHostsAdapter.java b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index a0a223dd..040e6266 100644 --- a/src/eu/siacs/conversations/utils/KnownHostsAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -1,4 +1,4 @@ -package eu.siacs.conversations.utils; +package eu.siacs.conversations.ui.adapter; import java.util.ArrayList; import java.util.List; @@ -42,7 +42,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { @Override protected void publishResults(CharSequence constraint, FilterResults results) { - ArrayList<String> filteredList = ((ArrayList<String>) results.values); + ArrayList<String> filteredList = (ArrayList<String>) results.values; if (results != null && results.count > 0) { clear(); for (String c : filteredList) { diff --git a/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java new file mode 100644 index 00000000..9ef427fc --- /dev/null +++ b/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -0,0 +1,39 @@ +package eu.siacs.conversations.ui.adapter; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.ListItem; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class ListItemAdapter extends ArrayAdapter<ListItem> { + + public ListItemAdapter(Context context, List<ListItem> objects) { + super(context, 0, objects); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + ListItem item = getItem(position); + if (view == null) { + view = (View) inflater.inflate(R.layout.contact, null); + } + TextView name = (TextView) view.findViewById(R.id.contact_display_name); + TextView jid = (TextView) view.findViewById(R.id.contact_jid); + ImageView picture = (ImageView) view.findViewById(R.id.contact_photo); + + jid.setText(item.getJid()); + name.setText(item.getDisplayName()); + picture.setImageBitmap(item.getImage(48, getContext())); + return view; + } + +}
\ No newline at end of file diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java new file mode 100644 index 00000000..0a2857d2 --- /dev/null +++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -0,0 +1,478 @@ +package eu.siacs.conversations.ui.adapter; + +import java.util.HashMap; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.ImageProvider; +import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.jingle.JingleConnection; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Typeface; +import android.preference.PreferenceManager; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +public class MessageAdapter extends ArrayAdapter<Message> { + + private static final int SENT = 0; + private static final int RECIEVED = 1; + private static final int STATUS = 2; + + private ConversationActivity activity; + + private Bitmap selfBitmap2; + + private BitmapCache mBitmapCache = new BitmapCache(); + private DisplayMetrics metrics; + + private boolean useSubject = true; + + private OnContactPictureClicked mOnContactPictureClickedListener; + + public MessageAdapter(ConversationActivity activity, List<Message> messages) { + super(activity, 0, messages); + this.activity = activity; + metrics = getContext().getResources().getDisplayMetrics(); + SharedPreferences preferences = PreferenceManager + .getDefaultSharedPreferences(getContext()); + useSubject = preferences.getBoolean("use_subject_in_muc", true); + } + + private Bitmap getSelfBitmap() { + if (this.selfBitmap2 == null) { + + if (getCount() > 0) { + SharedPreferences preferences = PreferenceManager + .getDefaultSharedPreferences(getContext()); + boolean showPhoneSelfContactPicture = preferences.getBoolean( + "show_phone_selfcontact_picture", true); + + this.selfBitmap2 = UIHelper.getSelfContactPicture(getItem(0) + .getConversation().getAccount(), 48, + showPhoneSelfContactPicture, getContext()); + } + } + return this.selfBitmap2; + } + + public void setOnContactPictureClicked(OnContactPictureClicked listener) { + this.mOnContactPictureClickedListener = listener; + } + + @Override + public int getViewTypeCount() { + return 3; + } + + @Override + public int getItemViewType(int position) { + if (getItem(position).getType() == Message.TYPE_STATUS) { + return STATUS; + } else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) { + return RECIEVED; + } else { + return SENT; + } + } + + private void displayStatus(ViewHolder viewHolder, Message message) { + String filesize = null; + String info = null; + boolean error = false; + boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI + && message.getStatus() <= Message.STATUS_RECIEVED; + if (message.getType() == Message.TYPE_IMAGE) { + String[] fileParams = message.getBody().split(","); + try { + long size = Long.parseLong(fileParams[0]); + filesize = size / 1024 + " KB"; + } catch (NumberFormatException e) { + filesize = "0 KB"; + } + } + switch (message.getStatus()) { + case Message.STATUS_WAITING: + info = getContext().getString(R.string.waiting); + break; + case Message.STATUS_UNSEND: + info = getContext().getString(R.string.sending); + break; + case Message.STATUS_OFFERED: + info = getContext().getString(R.string.offering); + break; + case Message.STATUS_SEND_FAILED: + info = getContext().getString(R.string.send_failed); + error = true; + break; + case Message.STATUS_SEND_REJECTED: + info = getContext().getString(R.string.send_rejected); + error = true; + break; + case Message.STATUS_RECEPTION_FAILED: + info = getContext().getString(R.string.reception_failed); + error = true; + default: + if (multiReceived) { + info = message.getCounterpart(); + } + break; + } + if (error) { + viewHolder.time.setTextColor(0xFFe92727); + } else { + viewHolder.time.setTextColor(activity.getSecondaryTextColor()); + } + if (message.getEncryption() == Message.ENCRYPTION_NONE) { + viewHolder.indicator.setVisibility(View.GONE); + } else { + viewHolder.indicator.setVisibility(View.VISIBLE); + } + + String formatedTime = UIHelper.readableTimeDifference(getContext(), + message.getTimeSent()); + if (message.getStatus() <= Message.STATUS_RECIEVED) { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(filesize + " \u00B7 " + info); + } else if ((filesize == null) && (info != null)) { + viewHolder.time.setText(formatedTime + " \u00B7 " + info); + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(formatedTime + " \u00B7 " + filesize); + } else { + viewHolder.time.setText(formatedTime); + } + } else { + if ((filesize != null) && (info != null)) { + viewHolder.time.setText(filesize + " \u00B7 " + info); + } else if ((filesize == null) && (info != null)) { + if (error) { + viewHolder.time.setText(info + " \u00B7 " + formatedTime); + } else { + viewHolder.time.setText(info); + } + } else if ((filesize != null) && (info == null)) { + viewHolder.time.setText(filesize + " \u00B7 " + formatedTime); + } else { + viewHolder.time.setText(formatedTime); + } + } + } + + private void displayInfoMessage(ViewHolder viewHolder, int r) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setText(getContext().getString(r)); + viewHolder.messageBody.setTextColor(0xff33B5E5); + viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); + viewHolder.messageBody.setTextIsSelectable(false); + } + + private void displayDecryptionFailed(ViewHolder viewHolder) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + viewHolder.messageBody.setText(getContext().getString( + R.string.decryption_failed)); + viewHolder.messageBody.setTextColor(0xFFe92727); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + viewHolder.messageBody.setTextIsSelectable(false); + } + + private void displayTextMessage(ViewHolder viewHolder, String text) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.VISIBLE); + if (text != null) { + viewHolder.messageBody.setText(text.trim()); + } else { + viewHolder.messageBody.setText(""); + } + viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor()); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + viewHolder.messageBody.setTextIsSelectable(true); + } + + private void displayImageMessage(ViewHolder viewHolder, + final Message message) { + if (viewHolder.download_button != null) { + viewHolder.download_button.setVisibility(View.GONE); + } + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.VISIBLE); + String[] fileParams = message.getBody().split(","); + if (fileParams.length == 3) { + double target = metrics.density * 288; + int w = Integer.parseInt(fileParams[1]); + int h = Integer.parseInt(fileParams[2]); + int scalledW; + int scalledH; + if (w <= h) { + scalledW = (int) (w / ((double) h / target)); + scalledH = (int) target; + } else { + scalledW = (int) target; + scalledH = (int) (h / ((double) w / target)); + } + viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( + scalledW, scalledH)); + } + activity.loadBitmap(message, viewHolder.image); + viewHolder.image.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(ImageProvider.getContentUri(message), + "image/*"); + getContext().startActivity(intent); + } + }); + viewHolder.image.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, + ImageProvider.getContentUri(message)); + shareIntent.setType("image/webp"); + getContext().startActivity( + Intent.createChooser(shareIntent, + getContext().getText(R.string.share_with))); + return true; + } + }); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + final Message item = getItem(position); + int type = getItemViewType(position); + ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + switch (type) { + case SENT: + view = (View) activity.getLayoutInflater().inflate( + R.layout.message_sent, null); + viewHolder.message_box = (LinearLayout) view + .findViewById(R.id.message_box); + viewHolder.contact_picture = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.contact_picture.setImageBitmap(getSelfBitmap()); + viewHolder.indicator = (ImageView) view + .findViewById(R.id.security_indicator); + viewHolder.image = (ImageView) view + .findViewById(R.id.message_image); + viewHolder.messageBody = (TextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + view.setTag(viewHolder); + break; + case RECIEVED: + view = (View) activity.getLayoutInflater().inflate( + R.layout.message_recieved, null); + viewHolder.message_box = (LinearLayout) view + .findViewById(R.id.message_box); + viewHolder.contact_picture = (ImageView) view + .findViewById(R.id.message_photo); + + viewHolder.download_button = (Button) view + .findViewById(R.id.download_button); + + if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { + + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( + item.getConversation().getName(useSubject), item + .getConversation().getContact(), + getContext())); + + } + viewHolder.indicator = (ImageView) view + .findViewById(R.id.security_indicator); + viewHolder.image = (ImageView) view + .findViewById(R.id.message_image); + viewHolder.messageBody = (TextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + view.setTag(viewHolder); + break; + case STATUS: + view = (View) activity.getLayoutInflater().inflate( + R.layout.message_status, null); + viewHolder.contact_picture = (ImageView) view + .findViewById(R.id.message_photo); + if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { + + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( + item.getConversation().getName(useSubject), item + .getConversation().getContact(), + getContext())); + viewHolder.contact_picture.setAlpha(128); + viewHolder.contact_picture + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + String name = item.getConversation() + .getName(true); + String read = getContext() + .getString( + R.string.contact_has_read_up_to_this_point, + name); + Toast.makeText(getContext(), read, + Toast.LENGTH_SHORT).show(); + } + }); + + } + break; + default: + viewHolder = null; + break; + } + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + if (type == STATUS) { + return view; + } + + if (type == RECIEVED) { + if (item.getConversation().getMode() == Conversation.MODE_MULTI) { + viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( + item.getCounterpart(), null, getContext())); + viewHolder.contact_picture + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (MessageAdapter.this.mOnContactPictureClickedListener != null) { + MessageAdapter.this.mOnContactPictureClickedListener + .onContactPictureClicked(item); + ; + } + + } + }); + } + } + + if (item.getType() == Message.TYPE_IMAGE) { + if (item.getStatus() == Message.STATUS_RECIEVING) { + displayInfoMessage(viewHolder, R.string.receiving_image); + } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) { + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + JingleConnection connection = item + .getJingleConnection(); + if (connection != null) { + connection.accept(); + } + } + }); + } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED) + || (item.getEncryption() == Message.ENCRYPTION_NONE) + || (item.getEncryption() == Message.ENCRYPTION_OTR)) { + displayImageMessage(viewHolder, item); + } else if (item.getEncryption() == Message.ENCRYPTION_PGP) { + displayInfoMessage(viewHolder, R.string.encrypted_message); + } else { + displayDecryptionFailed(viewHolder); + } + } else { + if (item.getEncryption() == Message.ENCRYPTION_PGP) { + if (activity.hasPgp()) { + displayInfoMessage(viewHolder, R.string.encrypted_message); + } else { + displayInfoMessage(viewHolder, + R.string.install_openkeychain); + viewHolder.message_box + .setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + activity.showInstallPgpDialog(); + } + }); + } + } else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + displayDecryptionFailed(viewHolder); + } else { + displayTextMessage(viewHolder, item.getBody()); + } + } + + displayStatus(viewHolder, item); + + return view; + } + + private static class ViewHolder { + + protected LinearLayout message_box; + protected Button download_button; + protected ImageView image; + protected ImageView indicator; + protected TextView time; + protected TextView messageBody; + protected ImageView contact_picture; + + } + + private class BitmapCache { + private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>(); + + public Bitmap get(String name, Contact contact, Context context) { + if (bitmaps.containsKey(name)) { + return bitmaps.get(name); + } else { + Bitmap bm; + if (contact != null) { + bm = UIHelper + .getContactPicture(contact, 48, context, false); + } else { + bm = UIHelper.getContactPicture(name, 48, context, false); + } + bitmaps.put(name, bm); + return bm; + } + } + } + + public interface OnContactPictureClicked { + public void onContactPictureClicked(Message message); + } +} diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index ede43830..1cd3403c 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -13,7 +13,6 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.ui.ConversationActivity; @@ -216,7 +215,7 @@ public class UIHelper { bgColor, fgColor); } String[] names = new String[members.size() + 1]; - names[0] = conversation.getMucOptions().getNick(); + names[0] = conversation.getMucOptions().getActualNick(); for (int i = 0; i < members.size(); ++i) { names[i + 1] = members.get(i).getName(); } @@ -343,7 +342,7 @@ public class UIHelper { if ((currentCon != null) && (currentCon.getMode() == Conversation.MODE_MULTI) && (!alwaysNotify)) { - String nick = currentCon.getMucOptions().getNick(); + String nick = currentCon.getMucOptions().getActualNick(); Pattern highlight = generateNickHighlightPattern(nick); Matcher m = highlight.matcher(currentCon.getLatestMessage() .getBody()); @@ -463,7 +462,7 @@ public class UIHelper { private static boolean wasHighlighted(Conversation conversation) { List<Message> messages = conversation.getMessages(); - String nick = conversation.getMucOptions().getNick(); + String nick = conversation.getMucOptions().getActualNick(); Pattern highlight = generateNickHighlightPattern(nick); for (int i = messages.size() - 1; i >= 0; --i) { if (messages.get(i).isRead()) { diff --git a/src/eu/siacs/conversations/utils/Validator.java b/src/eu/siacs/conversations/utils/Validator.java index 51d8b25c..9ca1e34f 100644 --- a/src/eu/siacs/conversations/utils/Validator.java +++ b/src/eu/siacs/conversations/utils/Validator.java @@ -5,7 +5,7 @@ import java.util.regex.Pattern; public class Validator { public static final Pattern VALID_JID = - Pattern.compile("\\b^[A-Z0-9._%+-]+@([A-Z0-9.-]+\\.)?\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}\\b$|^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); + Pattern.compile("\\b^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE); public static boolean isValidJid(String jid) { Matcher matcher = VALID_JID.matcher(jid); diff --git a/src/eu/siacs/conversations/xmpp/OnTLSExceptionReceived.java b/src/eu/siacs/conversations/xmpp/OnTLSExceptionReceived.java deleted file mode 100644 index 0e232ee4..00000000 --- a/src/eu/siacs/conversations/xmpp/OnTLSExceptionReceived.java +++ /dev/null @@ -1,7 +0,0 @@ -package eu.siacs.conversations.xmpp; - -import eu.siacs.conversations.entities.Account; - -public interface OnTLSExceptionReceived { - public void onTLSExceptionReceived(String fingerprint, Account account); -} diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index 72018394..45bac2f6 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -21,6 +21,7 @@ import java.util.Hashtable; import java.util.List; import java.util.Map.Entry; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -28,8 +29,12 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import org.bouncycastle.pqc.math.linearalgebra.GoppaCode.MaMaPe; import org.xmlpull.v1.XmlPullParserException; +import de.duenndns.ssl.MemorizingTrustManager; + +import android.content.Context; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -97,11 +102,12 @@ public class XmppConnection implements Runnable { private OnIqPacketReceived unregisteredIqListener = null; private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; - private OnTLSExceptionReceived tlsListener = null; private OnBindListener bindListener = null; + private MemorizingTrustManager mMemorizingTrustManager; public XmppConnection(Account account, XmppConnectionService service) { this.mRandom = service.getRNG(); + this.mMemorizingTrustManager = service.getMemorizingTrustManager(); this.account = account; this.wakeLock = service.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, account.getJid()); @@ -186,9 +192,7 @@ public class XmppConnection implements Runnable { } return; } catch (IOException e) { - if (account.getStatus() != Account.STATUS_TLS_ERROR) { - this.changeStatus(Account.STATUS_OFFLINE); - } + this.changeStatus(Account.STATUS_OFFLINE); if (wakeLock.isHeld()) { try { wakeLock.release();} catch (RuntimeException re) {} } @@ -440,67 +444,19 @@ public class XmppConnection implements Runnable { tagReader.readTag(); try { SSLContext sc = SSLContext.getInstance("TLS"); - TrustManagerFactory tmf = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - try { - tmf.init((KeyStore) null); - } catch (KeyStoreException e1) { - e1.printStackTrace(); - } - - TrustManager[] trustManagers = tmf.getTrustManagers(); - final X509TrustManager origTrustmanager = (X509TrustManager) trustManagers[0]; - - TrustManager[] wrappedTrustManagers = new TrustManager[] { new X509TrustManager() { - - @Override - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - origTrustmanager.checkClientTrusted(chain, authType); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - try { - origTrustmanager.checkServerTrusted(chain, authType); - } catch (CertificateException e) { - if (e.getCause() instanceof CertPathValidatorException) { - String sha; - try { - MessageDigest sha1 = MessageDigest - .getInstance("SHA1"); - sha1.update(chain[0].getEncoded()); - sha = CryptoHelper.bytesToHex(sha1.digest()); - if (!sha.equals(account.getSSLFingerprint())) { - changeStatus(Account.STATUS_TLS_ERROR); - if (tlsListener != null) { - tlsListener.onTLSExceptionReceived(sha, - account); - } - throw new CertificateException(); - } - } catch (NoSuchAlgorithmException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - } else { - throw new CertificateException(); - } - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return origTrustmanager.getAcceptedIssuers(); - } - - } }; - sc.init(null, wrappedTrustManagers, null); + sc.init(null, new X509TrustManager[] { this.mMemorizingTrustManager }, mRandom); SSLSocketFactory factory = sc.getSocketFactory(); + + HostnameVerifier verifier = this.mMemorizingTrustManager.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); + + if (verifier != null && !verifier.verify(account.getServer(), sslSocket.getSession())) { + Log.d(LOGTAG, account.getJid() + ": host mismatch in TLS connection"); + sslSocket.close(); + throw new IOException(); + } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); @@ -508,10 +464,8 @@ public class XmppConnection implements Runnable { processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { - // TODO Auto-generated catch block e1.printStackTrace(); } catch (KeyManagementException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } @@ -844,11 +798,6 @@ public class XmppConnection implements Runnable { this.statusListener = listener; } - public void setOnTLSExceptionReceivedListener( - OnTLSExceptionReceived listener) { - this.tlsListener = listener; - } - public void setOnBindListener(OnBindListener listener) { this.bindListener = listener; } |