From 746f9591553a5373e8ce1211ed066cbbf6a31cb5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 19 Feb 2014 01:35:23 +0100 Subject: [PATCH] roster versioning. roster updates on air. performance fixes in conversation listview --- res/layout/dialog_verify_otr.xml | 10 +- res/layout/fragment_conversation.xml | 3 +- src/de/gultsch/chat/entities/Account.java | 12 + src/de/gultsch/chat/entities/Contact.java | 3 +- .../chat/persistance/DatabaseBackend.java | 18 + .../persistance/OnPhoneContactsMerged.java | 5 + .../chat/services/XmppConnectionService.java | 390 ++++++++---------- .../gultsch/chat/ui/ConversationFragment.java | 203 ++++++--- src/de/gultsch/chat/utils/MessageParser.java | 119 ++++++ src/de/gultsch/chat/utils/PhoneHelper.java | 11 +- src/de/gultsch/chat/xml/XmlReader.java | 4 + 11 files changed, 490 insertions(+), 288 deletions(-) create mode 100644 src/de/gultsch/chat/persistance/OnPhoneContactsMerged.java create mode 100644 src/de/gultsch/chat/utils/MessageParser.java diff --git a/res/layout/dialog_verify_otr.xml b/res/layout/dialog_verify_otr.xml index 9f389c73a..41ce3b07d 100644 --- a/res/layout/dialog_verify_otr.xml +++ b/res/layout/dialog_verify_otr.xml @@ -3,7 +3,9 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingLeft="8dp"> + android:paddingLeft="8dp" + android:paddingBottom="16dp" + android:paddingRight="8dp"> + android:textSize="14sp" + android:typeface="monospace"/> + android:textSize="14sp" + android:typeface="monospace"/> diff --git a/res/layout/fragment_conversation.xml b/res/layout/fragment_conversation.xml index 5db687481..ad5f34b80 100644 --- a/res/layout/fragment_conversation.xml +++ b/res/layout/fragment_conversation.xml @@ -80,7 +80,8 @@ android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:paddingLeft="8dp" android:paddingBottom="8dp" - android:textSize="14sp"/> + android:textSize="14sp" + android:typeface="monospace"/> diff --git a/src/de/gultsch/chat/entities/Account.java b/src/de/gultsch/chat/entities/Account.java index 1bced45fc..dce5677e1 100644 --- a/src/de/gultsch/chat/entities/Account.java +++ b/src/de/gultsch/chat/entities/Account.java @@ -201,4 +201,16 @@ public class Account extends AbstractEntity{ } return this.otrFingerprint; } + + public String getRosterVersion() { + if (this.rosterVersion==null) { + return ""; + } else { + return this.rosterVersion; + } + } + + public void setRosterVersion(String version) { + this.rosterVersion = version; + } } diff --git a/src/de/gultsch/chat/entities/Contact.java b/src/de/gultsch/chat/entities/Contact.java index 5d5710a5c..c899603fc 100644 --- a/src/de/gultsch/chat/entities/Contact.java +++ b/src/de/gultsch/chat/entities/Contact.java @@ -32,7 +32,7 @@ public class Contact extends AbstractEntity implements Serializable { protected String subscription; protected String systemAccount; protected String photoUri; - protected JSONObject keys; + protected JSONObject keys = new JSONObject(); protected Presences presences = new Presences(); protected Account account; @@ -47,6 +47,7 @@ public class Contact extends AbstractEntity implements Serializable { this.displayName = displayName; this.jid = jid; this.photoUri = photoUri; + this.uuid = java.util.UUID.randomUUID().toString(); } public Contact(String uuid, String account, String displayName, String jid, diff --git a/src/de/gultsch/chat/persistance/DatabaseBackend.java b/src/de/gultsch/chat/persistance/DatabaseBackend.java index 4607883c3..caee4e8f6 100644 --- a/src/de/gultsch/chat/persistance/DatabaseBackend.java +++ b/src/de/gultsch/chat/persistance/DatabaseBackend.java @@ -245,6 +245,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { } return list; } + + public List getContats(String where) { + List list = new ArrayList(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query(Contact.TABLENAME, null, where, null, null, null, null); + while (cursor.moveToNext()) { + list.add(Contact.fromCursor(cursor)); + } + return list; + } public Contact findContact(Account account, String jid) { SQLiteDatabase db = this.getReadableDatabase(); @@ -263,4 +273,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { message.getUuid() }; db.delete(Message.TABLENAME, Message.UUID + "=?", args); } + + public void deleteContact(Contact contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = { contact.getUuid() }; + db.delete(Contact.TABLENAME, Contact.UUID + "=?", args); + } + + } diff --git a/src/de/gultsch/chat/persistance/OnPhoneContactsMerged.java b/src/de/gultsch/chat/persistance/OnPhoneContactsMerged.java new file mode 100644 index 000000000..a7918efb9 --- /dev/null +++ b/src/de/gultsch/chat/persistance/OnPhoneContactsMerged.java @@ -0,0 +1,5 @@ +package de.gultsch.chat.persistance; + +public interface OnPhoneContactsMerged { + public void phoneContactsMerged(); +} diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java index 63b894abc..7111d94a2 100644 --- a/src/de/gultsch/chat/services/XmppConnectionService.java +++ b/src/de/gultsch/chat/services/XmppConnectionService.java @@ -19,9 +19,11 @@ import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Presences; import de.gultsch.chat.persistance.DatabaseBackend; +import de.gultsch.chat.persistance.OnPhoneContactsMerged; import de.gultsch.chat.ui.OnAccountListChangedListener; import de.gultsch.chat.ui.OnConversationListChangedListener; import de.gultsch.chat.ui.OnRosterFetchedListener; +import de.gultsch.chat.utils.MessageParser; import de.gultsch.chat.utils.OnPhoneContactsLoadedListener; import de.gultsch.chat.utils.PhoneHelper; import de.gultsch.chat.utils.UIHelper; @@ -39,6 +41,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; +import android.database.DatabaseUtils; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -49,14 +52,14 @@ import android.util.Log; public class XmppConnectionService extends Service { protected static final String LOGTAG = "xmppService"; - protected DatabaseBackend databaseBackend; + public DatabaseBackend databaseBackend; public long startDate; private List accounts; private List conversations = null; - private OnConversationListChangedListener convChangedListener = null; + public OnConversationListChangedListener convChangedListener = null; private OnAccountListChangedListener accountChangedListener = null; private ContentObserver contactObserver = new ContentObserver(null) { @@ -64,148 +67,74 @@ public class XmppConnectionService extends Service { public void onChange(boolean selfChange) { super.onChange(selfChange); Log.d(LOGTAG, "contact list has changed"); - mergePhoneContactsWithRoster(); + mergePhoneContactsWithRoster(null); } }; + private XmppConnectionService service = this; + private final IBinder mBinder = new XmppConnectionBinder(); private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() { @Override public void onMessagePacketReceived(Account account, MessagePacket packet) { - if ((packet.getType() == MessagePacket.TYPE_CHAT) - || (packet.getType() == MessagePacket.TYPE_GROUPCHAT)) { - boolean notify = true; - boolean runOtrCheck = false; - int status = Message.STATUS_RECIEVED; - int encryption = Message.ENCRYPTION_NONE; - String body; - String fullJid; - if (!packet.hasChild("body")) { - Element forwarded; - if (packet.hasChild("received")) { - forwarded = packet.findChild("received").findChild( - "forwarded"); - } else if (packet.hasChild("sent")) { - forwarded = packet.findChild("sent").findChild( - "forwarded"); - status = Message.STATUS_SEND; - notify = false; - } else { - return; // massage has no body and is not carbon. just - // skip - } - if (forwarded != null) { - Element message = forwarded.findChild("message"); - if ((message == null) || (!message.hasChild("body"))) - return; // either malformed or boring - if (status == Message.STATUS_RECIEVED) { - fullJid = message.getAttribute("from"); - } else { - fullJid = message.getAttribute("to"); - } - body = message.findChild("body").getContent(); - } else { - return; // packet malformed. has no forwarded element - } - } else { - fullJid = packet.getFrom(); - body = packet.getBody(); - runOtrCheck = true; + Message message = null; + boolean notify = false; + if ((packet.getType() == MessagePacket.TYPE_CHAT)) { + if (packet.hasChild("body") + && (packet.getBody().startsWith("?OTR"))) { + message = MessageParser.parseOtrChat(packet, account, + service); + notify = true; + } else if (packet.hasChild("body")) { + message = MessageParser.parsePlainTextChat(packet, account, + service); + notify = true; + } else if (packet.hasChild("received") + || (packet.hasChild("sent"))) { + message = MessageParser.parseCarbonMessage(packet, account, + service); } - Conversation conversation = null; - String[] fromParts = fullJid.split("/"); - String jid = fromParts[0]; - boolean muc = (packet.getType() == MessagePacket.TYPE_GROUPCHAT); - String counterPart = null; - conversation = findOrCreateConversation(account, jid, muc); - if (muc) { - if ((fromParts.length == 1) || (packet.hasChild("subject"))) { - return; - } - counterPart = fromParts[1]; - if (counterPart.equals(account.getUsername())) { - status = Message.STATUS_SEND; - notify = false; - } - } else { - counterPart = fullJid; - if ((runOtrCheck) && body.startsWith("?OTR")) { - if (!conversation.hasValidOtrSession()) { - conversation.startOtrSession( - getApplicationContext(), fromParts[1]); - } - try { - Session otrSession = conversation.getOtrSession(); - SessionStatus before = otrSession - .getSessionStatus(); - body = otrSession.transformReceiving(body); - SessionStatus after = otrSession.getSessionStatus(); - if ((before != after) - && (after == SessionStatus.ENCRYPTED)) { - Log.d(LOGTAG, "otr session etablished"); - List messages = conversation - .getMessages(); - for (int i = 0; i < messages.size(); ++i) { - Message msg = messages.get(i); - if ((msg.getStatus() == Message.STATUS_UNSEND) - && (msg.getEncryption() == Message.ENCRYPTION_OTR)) { - MessagePacket outPacket = prepareMessagePacket( - account, msg, otrSession); - msg.setStatus(Message.STATUS_SEND); - databaseBackend.updateMessage(msg); - account.getXmppConnection() - .sendMessagePacket(outPacket); - } - } - if (convChangedListener!=null) { - convChangedListener.onConversationListChanged(); - } - } else if ((before != after) && (after == SessionStatus.FINISHED)) { - conversation.resetOtrSession(); - Log.d(LOGTAG,"otr session stoped"); - } - } catch (Exception e) { - Log.d(LOGTAG, "error receiving otr. resetting"); - conversation.resetOtrSession(); - return; - } - if (body == null) { - return; - } - encryption = Message.ENCRYPTION_OTR; - } + + } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) { + message = MessageParser + .parseGroupchat(packet, account, service); + if (message != null) { + notify = (message.getStatus() == Message.STATUS_RECIEVED); } - Message message = new Message(conversation, counterPart, body, - encryption, status); - if (packet.hasChild("delay")) { - try { - String stamp = packet.findChild("delay").getAttribute( - "stamp"); - stamp = stamp.replace("Z", "+0000"); - Date date = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ssZ").parse(stamp); - message.setTime(date.getTime()); - } catch (ParseException e) { - Log.d(LOGTAG, - "error trying to parse date" + e.getMessage()); - } + } else { + Log.d(LOGTAG, "unparsed message " + packet.toString()); + } + if (message == null) { + return; + } + if (packet.hasChild("delay")) { + try { + String stamp = packet.findChild("delay").getAttribute( + "stamp"); + stamp = stamp.replace("Z", "+0000"); + Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .parse(stamp); + message.setTime(date.getTime()); + } catch (ParseException e) { + Log.d(LOGTAG, "error trying to parse date" + e.getMessage()); } + } + if (notify) { + message.markUnread(); + } + Conversation conversation = message.getConversation(); + conversation.getMessages().add(message); + databaseBackend.createMessage(message); + if (convChangedListener != null) { + convChangedListener.onConversationListChanged(); + } else { if (notify) { - message.markUnread(); - } - conversation.getMessages().add(message); - databaseBackend.createMessage(message); - if (convChangedListener != null) { - convChangedListener.onConversationListChanged(); - } else { - if (notify) { - NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - mNotificationManager.notify(2342, UIHelper - .getUnreadMessageNotification( - getApplicationContext(), conversation)); - } + NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationManager.notify(2342, UIHelper + .getUnreadMessageNotification( + getApplicationContext(), conversation)); } } } @@ -271,11 +200,54 @@ public class XmppConnectionService extends Service { replaceContactInConversation(contact); } }; - + + private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.hasChild("query")) { + Element query = packet.findChild("query"); + String xmlns = query.getAttribute("xmlns"); + if ((xmlns != null) && (xmlns.equals("jabber:iq:roster"))) { + processRosterItems(account, query); + mergePhoneContactsWithRoster(null); + } + } + } + }; + + private void processRosterItems(Account account, Element elements) { + for (Element item : elements.getChildren()) { + if (item.getName().equals("item")) { + String jid = item.getAttribute("jid"); + String subscription = item.getAttribute("subscription"); + Contact contact = databaseBackend.findContact(account, jid); + if (contact == null) { + String name = item.getAttribute("name"); + if (name == null) { + name = jid.split("@")[0]; + } + contact = new Contact(account, name, jid, null); + contact.setSubscription(subscription); + databaseBackend.createContact(contact); + } else { + if (subscription.equals("remove")) { + databaseBackend.deleteContact(contact); + } else { + contact.setSubscription(subscription); + databaseBackend.updateContact(contact); + replaceContactInConversation(contact); + } + } + } + } + } + private void replaceContactInConversation(Contact contact) { List conversations = getConversations(); - for(int i = 0; i < conversations.size(); ++i) { - if (conversations.get(i).getContact().equals(contact)) { + for (int i = 0; i < conversations.size(); ++i) { + if ((conversations.get(i).getContact() != null) + && (conversations.get(i).getContact().equals(contact))) { conversations.get(i).setContact(contact); break; } @@ -325,11 +297,13 @@ public class XmppConnectionService extends Service { connection.setOnMessagePacketReceivedListener(this.messageListener); connection.setOnStatusChangedListener(this.statusListener); connection.setOnPresencePacketReceivedListener(this.presenceListener); + connection + .setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener); Thread thread = new Thread(connection); thread.start(); return connection; } - + public void sendMessage(Account account, Message message, String presence) { Conversation conv = message.getConversation(); boolean saveInDb = false; @@ -338,10 +312,11 @@ public class XmppConnectionService extends Service { MessagePacket packet; if (message.getEncryption() == Message.ENCRYPTION_OTR) { if (!conv.hasValidOtrSession()) { - //starting otr session. messages will be send later + // starting otr session. messages will be send later conv.startOtrSession(getApplicationContext(), presence); - } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED){ - //otr session aleary exists, creating message packet accordingly + } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) { + // otr session aleary exists, creating message packet + // accordingly packet = prepareMessagePacket(account, message, conv.getOtrSession()); account.getXmppConnection().sendMessagePacket(packet); @@ -356,7 +331,7 @@ public class XmppConnectionService extends Service { saveInDb = true; addToConversation = true; } - + packet = prepareMessagePacket(account, message, null); account.getXmppConnection().sendMessagePacket(packet); } @@ -398,8 +373,8 @@ public class XmppConnectionService extends Service { } } - private MessagePacket prepareMessagePacket(Account account, - Message message, Session otrSession) { + public MessagePacket prepareMessagePacket(Account account, Message message, + Session otrSession) { MessagePacket packet = new MessagePacket(); if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { packet.setType(MessagePacket.TYPE_CHAT); @@ -416,7 +391,8 @@ public class XmppConnectionService extends Service { Element privateMarker = new Element("private"); privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2"); packet.addChild(privateMarker); - packet.setTo(otrSession.getSessionID().getAccountID()+"/"+otrSession.getSessionID().getUserID()); + packet.setTo(otrSession.getSessionID().getAccountID() + "/" + + otrSession.getSessionID().getUserID()); packet.setFrom(account.getFullJid()); } else { packet.setBody(message.getBody()); @@ -445,81 +421,65 @@ public class XmppConnectionService extends Service { public void updateRoster(final Account account, final OnRosterFetchedListener listener) { - - PhoneHelper.loadPhoneContacts(this, - new OnPhoneContactsLoadedListener() { + IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); + Element query = new Element("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + query.setAttribute("ver", account.getRosterVersion()); + iqPacket.addChild(query); + account.getXmppConnection().sendIqPacket(iqPacket, + new OnIqPacketReceived() { @Override - public void onPhoneContactsLoaded( - final Hashtable phoneContacts) { - IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); - Element query = new Element("query"); - query.setAttribute("xmlns", "jabber:iq:roster"); - query.setAttribute("ver", ""); - iqPacket.addChild(query); - account.getXmppConnection().sendIqPacket(iqPacket, - new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived( - Account account, IqPacket packet) { - List contacts = new ArrayList(); - Element roster = packet - .findChild("query"); - if (roster != null) { - for (Element item : roster - .getChildren()) { - Contact contact; - String name = item - .getAttribute("name"); - String jid = item - .getAttribute("jid"); - if (phoneContacts - .containsKey(jid)) { - Bundle phoneContact = phoneContacts - .get(jid); - String systemAccount = phoneContact - .getInt("phoneid") - + "#" - + phoneContact - .getString("lookup"); - contact = new Contact( - account, - phoneContact - .getString("displayname"), - jid, - phoneContact - .getString("photouri")); - contact.setSystemAccount(systemAccount); - } else { - if (name == null) { - name = jid.split("@")[0]; - } - contact = new Contact( - account, name, jid, - null); - - } - contact.setAccount(account); - contact.setSubscription(item - .getAttribute("subscription")); - contacts.add(contact); - } - databaseBackend - .mergeContacts(contacts); - if (listener != null) { - listener.onRosterFetched(contacts); - } - } + public void onIqPacketReceived(final Account account, + IqPacket packet) { + Element roster = packet.findChild("query"); + if (roster != null) { + String version = roster.getAttribute("ver"); + processRosterItems(account, roster); + if (version!=null) { + account.setRosterVersion(version); + databaseBackend.updateAccount(account); + } else { + StringBuilder mWhere = new StringBuilder(); + mWhere.append("jid NOT IN("); + List items = roster.getChildren(); + for(int i = 0; i < items.size(); ++i) { + mWhere.append("\""); + mWhere.append(DatabaseUtils.sqlEscapeString(items.get(i).getAttribute("jid"))); + if (i != items.size() - 1) { + mWhere.append("\","); + } else { + mWhere.append("\""); } - }); - + } + mWhere.append(") and accountUuid = \""); + mWhere.append(account.getUuid()); + mWhere.append("\""); + List contactsToDelete = databaseBackend.getContats(mWhere.toString()); + for(Contact contact : contactsToDelete) { + databaseBackend.deleteContact(contact); + } + } + mergePhoneContactsWithRoster(new OnPhoneContactsMerged() { + + @Override + public void phoneContactsMerged() { + if (listener != null) { + getRoster(account, listener); + } + } + }); + } else { + if (listener != null) { + getRoster(account, listener); + } + } } }); } - public void mergePhoneContactsWithRoster() { - PhoneHelper.loadPhoneContacts(this, + public void mergePhoneContactsWithRoster(final OnPhoneContactsMerged listener) { + PhoneHelper.loadPhoneContacts(getApplicationContext(), new OnPhoneContactsLoadedListener() { @Override public void onPhoneContactsLoaded( @@ -550,6 +510,9 @@ public class XmppConnectionService extends Service { } } } + if (listener!=null) { + listener.phoneContactsMerged(); + } } }); } @@ -606,7 +569,8 @@ public class XmppConnectionService extends Service { conversation.setMode(Conversation.MODE_SINGLE); } this.databaseBackend.updateConversation(conversation); - conversation.setContact(findContact(account, conversation.getContactJid())); + conversation.setContact(findContact(account, + conversation.getContactJid())); } else { String conversationName; Contact contact = findContact(account, jid); @@ -728,8 +692,8 @@ public class XmppConnectionService extends Service { if (conversation.getMessages().size() != 0) { Element history = new Element("history"); long lastMsgTime = conversation.getLatestMessage().getTimeSent(); - long diff = (System.currentTimeMillis() - lastMsgTime) / 1000; - history.setAttribute("seconds",diff+""); + long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1; + history.setAttribute("seconds", diff + ""); x.addChild(history); } packet.addChild(x); diff --git a/src/de/gultsch/chat/ui/ConversationFragment.java b/src/de/gultsch/chat/ui/ConversationFragment.java index 9620c16a7..8be44f492 100644 --- a/src/de/gultsch/chat/ui/ConversationFragment.java +++ b/src/de/gultsch/chat/ui/ConversationFragment.java @@ -1,6 +1,9 @@ package de.gultsch.chat.ui; +import java.io.FileNotFoundException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; @@ -19,13 +22,17 @@ import android.app.AlertDialog; import android.app.Fragment; import android.content.DialogInterface; import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; @@ -43,6 +50,7 @@ public class ConversationFragment extends Fragment { protected List messageList = new ArrayList(); protected ArrayAdapter messageListAdapter; protected Contact contact; + protected BitmapCache mBitmapCache = new BitmapCache(); private EditText chatMsg; @@ -104,13 +112,24 @@ public class ConversationFragment extends Fragment { boolean showPhoneSelfContactPicture = sharedPref.getBoolean( "show_phone_selfcontact_picture", true); - final Uri selfiUri; + Bitmap self; + if (showPhoneSelfContactPicture) { - selfiUri = PhoneHelper.getSefliUri(getActivity()); + Uri selfiUri = PhoneHelper.getSefliUri(getActivity()); + try { + self = BitmapFactory.decodeStream(getActivity() + .getContentResolver().openInputStream(selfiUri)); + } catch (FileNotFoundException e) { + self = UIHelper.getUnknownContactPicture(conversation + .getAccount().getJid(), 200); + } } else { - selfiUri = null; + self = UIHelper.getUnknownContactPicture(conversation.getAccount() + .getJid(), 200); } + final Bitmap selfBitmap = self; + messageListAdapter = new ArrayAdapter(this.getActivity() .getApplicationContext(), R.layout.message_sent, this.messageList) { @@ -136,68 +155,73 @@ public class ConversationFragment extends Fragment { public View getView(int position, View view, ViewGroup parent) { Message item = getItem(position); int type = getItemViewType(position); + ViewHolder viewHolder; if (view == null) { switch (type) { case SENT: + viewHolder = new ViewHolder(); view = (View) inflater.inflate(R.layout.message_sent, null); + viewHolder.imageView = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.messageBody = (TextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + view.setTag(viewHolder); break; case RECIEVED: + viewHolder = new ViewHolder(); view = (View) inflater.inflate( R.layout.message_recieved, null); + viewHolder.imageView = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.messageBody = (TextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + view.setTag(viewHolder); + break; + default: + viewHolder = null; break; } + } else { + viewHolder = (ViewHolder) view.getTag(); } - ImageView imageView = (ImageView) view - .findViewById(R.id.message_photo); if (type == RECIEVED) { if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { Uri uri = item.getConversation().getProfilePhotoUri(); if (uri != null) { - imageView.setImageURI(uri); + viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(), uri)); } else { - imageView.setImageBitmap(UIHelper - .getUnknownContactPicture(item - .getConversation().getName(), 200)); + viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null)); } } else if (item.getConversation().getMode() == Conversation.MODE_MULTI) { if (item.getCounterpart() != null) { - imageView.setImageBitmap(UIHelper - .getUnknownContactPicture( - item.getCounterpart(), 200)); + viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getCounterpart(),null)); } else { - imageView.setImageBitmap(UIHelper - .getUnknownContactPicture(item - .getConversation().getName(), 200)); + viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null)); } } } else { - if (selfiUri != null) { - imageView.setImageURI(selfiUri); - } else { - imageView.setImageBitmap(UIHelper - .getUnknownContactPicture(conversation - .getAccount().getJid(), 200)); - } + viewHolder.imageView.setImageBitmap(selfBitmap); } - TextView messageBody = (TextView) view - .findViewById(R.id.message_body); String body = item.getBody(); if (body != null) { - messageBody.setText(body.trim()); + viewHolder.messageBody.setText(body.trim()); } - TextView time = (TextView) view.findViewById(R.id.message_time); if (item.getStatus() == Message.STATUS_UNSEND) { - time.setTypeface(null, Typeface.ITALIC); - time.setText("sending\u2026"); + viewHolder.time.setTypeface(null, Typeface.ITALIC); + viewHolder.time.setText("sending\u2026"); } else { - time.setTypeface(null, Typeface.NORMAL); + viewHolder.time.setTypeface(null, Typeface.NORMAL); if ((item.getConversation().getMode() == Conversation.MODE_SINGLE) || (type != RECIEVED)) { - time.setText(UIHelper.readableTimeDifference(item - .getTimeSent())); + viewHolder.time.setText(UIHelper + .readableTimeDifference(item.getTimeSent())); } else { - time.setText(item.getCounterpart() + viewHolder.time.setText(item.getCounterpart() + " \u00B7 " + UIHelper.readableTimeDifference(item .getTimeSent())); @@ -275,24 +299,31 @@ public class ConversationFragment extends Fragment { protected void makeFingerprintWarning(int latestEncryption) { final LinearLayout fingerprintWarning = (LinearLayout) getView() .findViewById(R.id.new_fingerprint); - Set knownFingerprints = conversation.getContact() - .getOtrFingerprints(); - if ((latestEncryption == Message.ENCRYPTION_OTR) - && (conversation.hasValidOtrSession() - && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints - .contains(conversation.getOtrFingerprint())))) { - fingerprintWarning.setVisibility(View.VISIBLE); - TextView fingerprint = (TextView) getView().findViewById( - R.id.otr_fingerprint); - fingerprint.setText(conversation.getOtrFingerprint()); - fingerprintWarning.setOnClickListener(new OnClickListener() { + if (conversation.getContact() != null) { + Set knownFingerprints = conversation.getContact() + .getOtrFingerprints(); + if ((latestEncryption == Message.ENCRYPTION_OTR) + && (conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints + .contains(conversation.getOtrFingerprint())))) { + fingerprintWarning.setVisibility(View.VISIBLE); + TextView fingerprint = (TextView) getView().findViewById( + R.id.otr_fingerprint); + fingerprint.setText(conversation.getOtrFingerprint()); + fingerprintWarning.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - AlertDialog dialog = UIHelper.getVerifyFingerprintDialog((ConversationActivity) getActivity(),conversation,fingerprintWarning); - dialog.show(); - } - }); + @Override + public void onClick(View v) { + AlertDialog dialog = UIHelper + .getVerifyFingerprintDialog( + (ConversationActivity) getActivity(), + conversation, fingerprintWarning); + dialog.show(); + } + }); + } else { + fingerprintWarning.setVisibility(View.GONE); + } } else { fingerprintWarning.setVisibility(View.GONE); } @@ -300,11 +331,11 @@ public class ConversationFragment extends Fragment { protected void sendPlainTextMessage(Message message) { ConversationActivity activity = (ConversationActivity) getActivity(); - activity.xmppConnectionService.sendMessage(conversation.getAccount(), message, - null); + activity.xmppConnectionService.sendMessage(conversation.getAccount(), + message, null); chatMsg.setText(""); } - + protected void sendOtrMessage(final Message message) { ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; @@ -313,9 +344,13 @@ public class ConversationFragment extends Fragment { conversation.getAccount(), message, null); chatMsg.setText(""); } else { - Hashtable presences = conversation - .getContact().getPresences(); - if (presences.size() == 0) { + Hashtable presences; + if (conversation.getContact() != null) { + presences = conversation.getContact().getPresences(); + } else { + presences = null; + } + if ((presences != null) && (presences.size() == 0)) { AlertDialog.Builder builder = new AlertDialog.Builder( getActivity()); builder.setTitle("Contact is offline"); @@ -330,16 +365,15 @@ public class ConversationFragment extends Fragment { conversation.nextMessageEncryption = Message.ENCRYPTION_NONE; message.setEncryption(Message.ENCRYPTION_NONE); xmppService.sendMessage( - conversation.getAccount(), - message, null); + conversation.getAccount(), message, + null); chatMsg.setText(""); } }); builder.setNegativeButton("Cancel", null); builder.create().show(); } else if (presences.size() == 1) { - xmppService.sendMessage(conversation.getAccount(), - message, + xmppService.sendMessage(conversation.getAccount(), message, (String) presences.keySet().toArray()[0]); chatMsg.setText(""); } else { @@ -348,16 +382,51 @@ public class ConversationFragment extends Fragment { builder.setTitle("Choose Presence"); final String[] presencesArray = new String[presences.size()]; presences.keySet().toArray(presencesArray); - builder.setItems(presencesArray, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - xmppService.sendMessage(conversation.getAccount(), message, presencesArray[which]); - chatMsg.setText(""); - } - }); + builder.setItems(presencesArray, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + xmppService.sendMessage( + conversation.getAccount(), message, + presencesArray[which]); + chatMsg.setText(""); + } + }); builder.create().show(); } } } + + private static class ViewHolder { + + protected TextView time; + protected TextView messageBody; + protected ImageView imageView; + + } + + private class BitmapCache { + private HashMap bitmaps = new HashMap(); + public Bitmap get(String name, Uri uri) { + if (bitmaps.containsKey(name)) { + return bitmaps.get(name); + } else { + Bitmap bm; + if (uri!=null) { + try { + bm = BitmapFactory.decodeStream(getActivity() + .getContentResolver().openInputStream(uri)); + } catch (FileNotFoundException e) { + bm = UIHelper.getUnknownContactPicture(name, 200); + } + } else { + bm = UIHelper.getUnknownContactPicture(name, 200); + } + bitmaps.put(name, bm); + return bm; + } + } + } } diff --git a/src/de/gultsch/chat/utils/MessageParser.java b/src/de/gultsch/chat/utils/MessageParser.java new file mode 100644 index 000000000..aec492c84 --- /dev/null +++ b/src/de/gultsch/chat/utils/MessageParser.java @@ -0,0 +1,119 @@ +package de.gultsch.chat.utils; + +import java.util.List; + +import net.java.otr4j.session.Session; +import net.java.otr4j.session.SessionStatus; +import android.util.Log; +import de.gultsch.chat.entities.Account; +import de.gultsch.chat.entities.Conversation; +import de.gultsch.chat.entities.Message; +import de.gultsch.chat.services.XmppConnectionService; +import de.gultsch.chat.xml.Element; +import de.gultsch.chat.xmpp.MessagePacket; + +public class MessageParser { + + protected static final String LOGTAG = "xmppService"; + + public static Message parsePlainTextChat(MessagePacket packet, Account account, XmppConnectionService service) { + String[] fromParts = packet.getFrom().split("/"); + Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); + String body = packet.getBody(); + return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED); + } + + public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) { + String[] fromParts = packet.getFrom().split("/"); + Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); + String body = packet.getBody(); + if (!conversation.hasValidOtrSession()) { + conversation.startOtrSession(service.getApplicationContext(), fromParts[1]); + } + try { + Session otrSession = conversation.getOtrSession(); + SessionStatus before = otrSession + .getSessionStatus(); + body = otrSession.transformReceiving(body); + SessionStatus after = otrSession.getSessionStatus(); + if ((before != after) + && (after == SessionStatus.ENCRYPTED)) { + Log.d(LOGTAG, "otr session etablished"); + List messages = conversation + .getMessages(); + for (int i = 0; i < messages.size(); ++i) { + Message msg = messages.get(i); + if ((msg.getStatus() == Message.STATUS_UNSEND) + && (msg.getEncryption() == Message.ENCRYPTION_OTR)) { + MessagePacket outPacket = service.prepareMessagePacket( + account, msg, otrSession); + msg.setStatus(Message.STATUS_SEND); + service.databaseBackend.updateMessage(msg); + account.getXmppConnection() + .sendMessagePacket(outPacket); + } + } + if (service.convChangedListener!=null) { + service.convChangedListener.onConversationListChanged(); + } + } else if ((before != after) && (after == SessionStatus.FINISHED)) { + conversation.resetOtrSession(); + Log.d(LOGTAG,"otr session stoped"); + } + } catch (Exception e) { + Log.d(LOGTAG, "error receiving otr. resetting"); + conversation.resetOtrSession(); + return null; + } + if (body == null) { + return null; + } + return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_OTR,Message.STATUS_RECIEVED); + } + + public static Message parseGroupchat(MessagePacket packet, Account account, XmppConnectionService service) { + int status; + String[] fromParts = packet.getFrom().split("/"); + Conversation conversation = service.findOrCreateConversation(account, fromParts[0],true); + if ((fromParts.length == 1) || (packet.hasChild("subject"))) { + return null; + } + String counterPart = fromParts[1]; + if (counterPart.equals(account.getUsername())) { + status = Message.STATUS_SEND; + } else { + status = Message.STATUS_RECIEVED; + } + return new Message(conversation, counterPart, packet.getBody(), Message.ENCRYPTION_NONE, status); + } + + public static Message parseCarbonMessage(MessagePacket packet, + Account account, XmppConnectionService service) { + // TODO Auto-generated method stub + int status; + String fullJid; + Element forwarded; + if (packet.hasChild("received")) { + forwarded = packet.findChild("received").findChild( + "forwarded"); + status = Message.STATUS_RECIEVED; + } else if (packet.hasChild("sent")) { + forwarded = packet.findChild("sent").findChild( + "forwarded"); + status = Message.STATUS_SEND; + } else { + return null; + } + Element message = forwarded.findChild("message"); + if ((message == null) || (!message.hasChild("body"))) + return null; // either malformed or boring + if (status == Message.STATUS_RECIEVED) { + fullJid = message.getAttribute("from"); + } else { + fullJid = message.getAttribute("to"); + } + String[] parts = fullJid.split("/"); + Conversation conversation = service.findOrCreateConversation(account, parts[0],false); + return new Message(conversation,fullJid, message.findChild("body").getContent(), Message.ENCRYPTION_NONE,status); + } +} diff --git a/src/de/gultsch/chat/utils/PhoneHelper.java b/src/de/gultsch/chat/utils/PhoneHelper.java index 3a53c08f3..14773caa4 100644 --- a/src/de/gultsch/chat/utils/PhoneHelper.java +++ b/src/de/gultsch/chat/utils/PhoneHelper.java @@ -1,6 +1,5 @@ package de.gultsch.chat.utils; -import java.util.ArrayList; import java.util.Hashtable; import android.app.Activity; @@ -11,14 +10,19 @@ import android.content.Loader.OnLoadCompleteListener; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.Looper; import android.provider.ContactsContract; import android.provider.ContactsContract.Profile; public class PhoneHelper { public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { + if (Looper.myLooper()==null) { + Looper.prepare(); + } + final Looper mLooper = Looper.myLooper(); final Hashtable phoneContacts = new Hashtable(); - + final String[] PROJECTION = new String[] { ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, @@ -31,7 +35,7 @@ public class PhoneHelper { + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + "\")"; - + CursorLoader mCursorLoader = new CursorLoader(context, ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null); @@ -61,6 +65,7 @@ public class PhoneHelper { if (listener!=null) { listener.onPhoneContactsLoaded(phoneContacts); } + mLooper.quit(); } }); mCursorLoader.startLoading(); diff --git a/src/de/gultsch/chat/xml/XmlReader.java b/src/de/gultsch/chat/xml/XmlReader.java index e94296659..0ff2e7858 100644 --- a/src/de/gultsch/chat/xml/XmlReader.java +++ b/src/de/gultsch/chat/xml/XmlReader.java @@ -59,6 +59,10 @@ public class XmlReader { for(int i = 0; i < parser.getAttributeCount(); ++i) { tag.setAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); } + String xmlns = parser.getNamespace(); + if (xmlns!=null) { + tag.setAttribute("xmlns",xmlns); + } return tag; } else if (parser.getEventType() == XmlPullParser.END_TAG) { Tag tag = Tag.end(parser.getName());