roster versioning. roster updates on air. performance fixes in conversation listview

This commit is contained in:
Daniel Gultsch 2014-02-19 01:35:23 +01:00
parent 0392e6b2dc
commit 746f959155
11 changed files with 490 additions and 288 deletions

View File

@ -3,7 +3,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="8dp"> android:paddingLeft="8dp"
android:paddingBottom="16dp"
android:paddingRight="8dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -34,7 +36,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:textSize="14sp" /> android:textSize="14sp"
android:typeface="monospace"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -49,5 +52,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:textSize="14sp" /> android:textSize="14sp"
android:typeface="monospace"/>
</LinearLayout> </LinearLayout>

View File

@ -80,7 +80,8 @@
android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8" android:text="2674D6A0 0B1421B1 BFC42AEC C56F3719 672437D8"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:textSize="14sp"/> android:textSize="14sp"
android:typeface="monospace"/>
</LinearLayout> </LinearLayout>

View File

@ -201,4 +201,16 @@ public class Account extends AbstractEntity{
} }
return this.otrFingerprint; return this.otrFingerprint;
} }
public String getRosterVersion() {
if (this.rosterVersion==null) {
return "";
} else {
return this.rosterVersion;
}
}
public void setRosterVersion(String version) {
this.rosterVersion = version;
}
} }

View File

@ -32,7 +32,7 @@ public class Contact extends AbstractEntity implements Serializable {
protected String subscription; protected String subscription;
protected String systemAccount; protected String systemAccount;
protected String photoUri; protected String photoUri;
protected JSONObject keys; protected JSONObject keys = new JSONObject();
protected Presences presences = new Presences(); protected Presences presences = new Presences();
protected Account account; protected Account account;
@ -47,6 +47,7 @@ public class Contact extends AbstractEntity implements Serializable {
this.displayName = displayName; this.displayName = displayName;
this.jid = jid; this.jid = jid;
this.photoUri = photoUri; this.photoUri = photoUri;
this.uuid = java.util.UUID.randomUUID().toString();
} }
public Contact(String uuid, String account, String displayName, String jid, public Contact(String uuid, String account, String displayName, String jid,

View File

@ -245,6 +245,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
return list; return list;
} }
public List<Contact> getContats(String where) {
List<Contact> list = new ArrayList<Contact>();
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) { public Contact findContact(Account account, String jid) {
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
@ -263,4 +273,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = { message.getUuid() }; String[] args = { message.getUuid() };
db.delete(Message.TABLENAME, Message.UUID + "=?", args); 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);
}
} }

View File

@ -0,0 +1,5 @@
package de.gultsch.chat.persistance;
public interface OnPhoneContactsMerged {
public void phoneContactsMerged();
}

View File

@ -19,9 +19,11 @@ import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Message;
import de.gultsch.chat.entities.Presences; import de.gultsch.chat.entities.Presences;
import de.gultsch.chat.persistance.DatabaseBackend; import de.gultsch.chat.persistance.DatabaseBackend;
import de.gultsch.chat.persistance.OnPhoneContactsMerged;
import de.gultsch.chat.ui.OnAccountListChangedListener; import de.gultsch.chat.ui.OnAccountListChangedListener;
import de.gultsch.chat.ui.OnConversationListChangedListener; import de.gultsch.chat.ui.OnConversationListChangedListener;
import de.gultsch.chat.ui.OnRosterFetchedListener; import de.gultsch.chat.ui.OnRosterFetchedListener;
import de.gultsch.chat.utils.MessageParser;
import de.gultsch.chat.utils.OnPhoneContactsLoadedListener; import de.gultsch.chat.utils.OnPhoneContactsLoadedListener;
import de.gultsch.chat.utils.PhoneHelper; import de.gultsch.chat.utils.PhoneHelper;
import de.gultsch.chat.utils.UIHelper; import de.gultsch.chat.utils.UIHelper;
@ -39,6 +41,7 @@ import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.database.DatabaseUtils;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@ -49,14 +52,14 @@ import android.util.Log;
public class XmppConnectionService extends Service { public class XmppConnectionService extends Service {
protected static final String LOGTAG = "xmppService"; protected static final String LOGTAG = "xmppService";
protected DatabaseBackend databaseBackend; public DatabaseBackend databaseBackend;
public long startDate; public long startDate;
private List<Account> accounts; private List<Account> accounts;
private List<Conversation> conversations = null; private List<Conversation> conversations = null;
private OnConversationListChangedListener convChangedListener = null; public OnConversationListChangedListener convChangedListener = null;
private OnAccountListChangedListener accountChangedListener = null; private OnAccountListChangedListener accountChangedListener = null;
private ContentObserver contactObserver = new ContentObserver(null) { private ContentObserver contactObserver = new ContentObserver(null) {
@ -64,148 +67,74 @@ public class XmppConnectionService extends Service {
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
super.onChange(selfChange); super.onChange(selfChange);
Log.d(LOGTAG, "contact list has changed"); Log.d(LOGTAG, "contact list has changed");
mergePhoneContactsWithRoster(); mergePhoneContactsWithRoster(null);
} }
}; };
private XmppConnectionService service = this;
private final IBinder mBinder = new XmppConnectionBinder(); private final IBinder mBinder = new XmppConnectionBinder();
private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() { private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
@Override @Override
public void onMessagePacketReceived(Account account, public void onMessagePacketReceived(Account account,
MessagePacket packet) { MessagePacket packet) {
if ((packet.getType() == MessagePacket.TYPE_CHAT) Message message = null;
|| (packet.getType() == MessagePacket.TYPE_GROUPCHAT)) { boolean notify = false;
boolean notify = true; if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
boolean runOtrCheck = false; if (packet.hasChild("body")
int status = Message.STATUS_RECIEVED; && (packet.getBody().startsWith("?OTR"))) {
int encryption = Message.ENCRYPTION_NONE; message = MessageParser.parseOtrChat(packet, account,
String body; service);
String fullJid; notify = true;
if (!packet.hasChild("body")) { } else if (packet.hasChild("body")) {
Element forwarded; message = MessageParser.parsePlainTextChat(packet, account,
if (packet.hasChild("received")) { service);
forwarded = packet.findChild("received").findChild( notify = true;
"forwarded"); } else if (packet.hasChild("received")
} else if (packet.hasChild("sent")) { || (packet.hasChild("sent"))) {
forwarded = packet.findChild("sent").findChild( message = MessageParser.parseCarbonMessage(packet, account,
"forwarded"); service);
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;
} }
Conversation conversation = null;
String[] fromParts = fullJid.split("/"); } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
String jid = fromParts[0]; message = MessageParser
boolean muc = (packet.getType() == MessagePacket.TYPE_GROUPCHAT); .parseGroupchat(packet, account, service);
String counterPart = null; if (message != null) {
conversation = findOrCreateConversation(account, jid, muc); notify = (message.getStatus() == Message.STATUS_RECIEVED);
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<Message> 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;
}
} }
Message message = new Message(conversation, counterPart, body, } else {
encryption, status); Log.d(LOGTAG, "unparsed message " + packet.toString());
if (packet.hasChild("delay")) { }
try { if (message == null) {
String stamp = packet.findChild("delay").getAttribute( return;
"stamp"); }
stamp = stamp.replace("Z", "+0000"); if (packet.hasChild("delay")) {
Date date = new SimpleDateFormat( try {
"yyyy-MM-dd'T'HH:mm:ssZ").parse(stamp); String stamp = packet.findChild("delay").getAttribute(
message.setTime(date.getTime()); "stamp");
} catch (ParseException e) { stamp = stamp.replace("Z", "+0000");
Log.d(LOGTAG, Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
"error trying to parse date" + e.getMessage()); .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) { if (notify) {
message.markUnread(); NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
} mNotificationManager.notify(2342, UIHelper
conversation.getMessages().add(message); .getUnreadMessageNotification(
databaseBackend.createMessage(message); getApplicationContext(), conversation));
if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
} else {
if (notify) {
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); 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) { private void replaceContactInConversation(Contact contact) {
List<Conversation> conversations = getConversations(); List<Conversation> conversations = getConversations();
for(int i = 0; i < conversations.size(); ++i) { for (int i = 0; i < conversations.size(); ++i) {
if (conversations.get(i).getContact().equals(contact)) { if ((conversations.get(i).getContact() != null)
&& (conversations.get(i).getContact().equals(contact))) {
conversations.get(i).setContact(contact); conversations.get(i).setContact(contact);
break; break;
} }
@ -325,11 +297,13 @@ public class XmppConnectionService extends Service {
connection.setOnMessagePacketReceivedListener(this.messageListener); connection.setOnMessagePacketReceivedListener(this.messageListener);
connection.setOnStatusChangedListener(this.statusListener); connection.setOnStatusChangedListener(this.statusListener);
connection.setOnPresencePacketReceivedListener(this.presenceListener); connection.setOnPresencePacketReceivedListener(this.presenceListener);
connection
.setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
Thread thread = new Thread(connection); Thread thread = new Thread(connection);
thread.start(); thread.start();
return connection; return connection;
} }
public void sendMessage(Account account, Message message, String presence) { public void sendMessage(Account account, Message message, String presence) {
Conversation conv = message.getConversation(); Conversation conv = message.getConversation();
boolean saveInDb = false; boolean saveInDb = false;
@ -338,10 +312,11 @@ public class XmppConnectionService extends Service {
MessagePacket packet; MessagePacket packet;
if (message.getEncryption() == Message.ENCRYPTION_OTR) { if (message.getEncryption() == Message.ENCRYPTION_OTR) {
if (!conv.hasValidOtrSession()) { if (!conv.hasValidOtrSession()) {
//starting otr session. messages will be send later // starting otr session. messages will be send later
conv.startOtrSession(getApplicationContext(), presence); conv.startOtrSession(getApplicationContext(), presence);
} else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED){ } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
//otr session aleary exists, creating message packet accordingly // otr session aleary exists, creating message packet
// accordingly
packet = prepareMessagePacket(account, message, packet = prepareMessagePacket(account, message,
conv.getOtrSession()); conv.getOtrSession());
account.getXmppConnection().sendMessagePacket(packet); account.getXmppConnection().sendMessagePacket(packet);
@ -356,7 +331,7 @@ public class XmppConnectionService extends Service {
saveInDb = true; saveInDb = true;
addToConversation = true; addToConversation = true;
} }
packet = prepareMessagePacket(account, message, null); packet = prepareMessagePacket(account, message, null);
account.getXmppConnection().sendMessagePacket(packet); account.getXmppConnection().sendMessagePacket(packet);
} }
@ -398,8 +373,8 @@ public class XmppConnectionService extends Service {
} }
} }
private MessagePacket prepareMessagePacket(Account account, public MessagePacket prepareMessagePacket(Account account, Message message,
Message message, Session otrSession) { Session otrSession) {
MessagePacket packet = new MessagePacket(); MessagePacket packet = new MessagePacket();
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(MessagePacket.TYPE_CHAT);
@ -416,7 +391,8 @@ public class XmppConnectionService extends Service {
Element privateMarker = new Element("private"); Element privateMarker = new Element("private");
privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2"); privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
packet.addChild(privateMarker); packet.addChild(privateMarker);
packet.setTo(otrSession.getSessionID().getAccountID()+"/"+otrSession.getSessionID().getUserID()); packet.setTo(otrSession.getSessionID().getAccountID() + "/"
+ otrSession.getSessionID().getUserID());
packet.setFrom(account.getFullJid()); packet.setFrom(account.getFullJid());
} else { } else {
packet.setBody(message.getBody()); packet.setBody(message.getBody());
@ -445,81 +421,65 @@ public class XmppConnectionService extends Service {
public void updateRoster(final Account account, public void updateRoster(final Account account,
final OnRosterFetchedListener listener) { final OnRosterFetchedListener listener) {
IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
PhoneHelper.loadPhoneContacts(this, Element query = new Element("query");
new OnPhoneContactsLoadedListener() { query.setAttribute("xmlns", "jabber:iq:roster");
query.setAttribute("ver", account.getRosterVersion());
iqPacket.addChild(query);
account.getXmppConnection().sendIqPacket(iqPacket,
new OnIqPacketReceived() {
@Override @Override
public void onPhoneContactsLoaded( public void onIqPacketReceived(final Account account,
final Hashtable<String, Bundle> phoneContacts) { IqPacket packet) {
IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET); Element roster = packet.findChild("query");
Element query = new Element("query"); if (roster != null) {
query.setAttribute("xmlns", "jabber:iq:roster"); String version = roster.getAttribute("ver");
query.setAttribute("ver", ""); processRosterItems(account, roster);
iqPacket.addChild(query); if (version!=null) {
account.getXmppConnection().sendIqPacket(iqPacket, account.setRosterVersion(version);
new OnIqPacketReceived() { databaseBackend.updateAccount(account);
} else {
@Override StringBuilder mWhere = new StringBuilder();
public void onIqPacketReceived( mWhere.append("jid NOT IN(");
Account account, IqPacket packet) { List<Element> items = roster.getChildren();
List<Contact> contacts = new ArrayList<Contact>(); for(int i = 0; i < items.size(); ++i) {
Element roster = packet mWhere.append("\"");
.findChild("query"); mWhere.append(DatabaseUtils.sqlEscapeString(items.get(i).getAttribute("jid")));
if (roster != null) { if (i != items.size() - 1) {
for (Element item : roster mWhere.append("\",");
.getChildren()) { } else {
Contact contact; mWhere.append("\"");
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);
}
}
} }
}); }
mWhere.append(") and accountUuid = \"");
mWhere.append(account.getUuid());
mWhere.append("\"");
List<Contact> 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() { public void mergePhoneContactsWithRoster(final OnPhoneContactsMerged listener) {
PhoneHelper.loadPhoneContacts(this, PhoneHelper.loadPhoneContacts(getApplicationContext(),
new OnPhoneContactsLoadedListener() { new OnPhoneContactsLoadedListener() {
@Override @Override
public void onPhoneContactsLoaded( 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); conversation.setMode(Conversation.MODE_SINGLE);
} }
this.databaseBackend.updateConversation(conversation); this.databaseBackend.updateConversation(conversation);
conversation.setContact(findContact(account, conversation.getContactJid())); conversation.setContact(findContact(account,
conversation.getContactJid()));
} else { } else {
String conversationName; String conversationName;
Contact contact = findContact(account, jid); Contact contact = findContact(account, jid);
@ -728,8 +692,8 @@ public class XmppConnectionService extends Service {
if (conversation.getMessages().size() != 0) { if (conversation.getMessages().size() != 0) {
Element history = new Element("history"); Element history = new Element("history");
long lastMsgTime = conversation.getLatestMessage().getTimeSent(); long lastMsgTime = conversation.getLatestMessage().getTimeSent();
long diff = (System.currentTimeMillis() - lastMsgTime) / 1000; long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
history.setAttribute("seconds",diff+""); history.setAttribute("seconds", diff + "");
x.addChild(history); x.addChild(history);
} }
packet.addChild(x); packet.addChild(x);

View File

@ -1,6 +1,9 @@
package de.gultsch.chat.ui; package de.gultsch.chat.ui;
import java.io.FileNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -19,13 +22,17 @@ import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
@ -43,6 +50,7 @@ public class ConversationFragment extends Fragment {
protected List<Message> messageList = new ArrayList<Message>(); protected List<Message> messageList = new ArrayList<Message>();
protected ArrayAdapter<Message> messageListAdapter; protected ArrayAdapter<Message> messageListAdapter;
protected Contact contact; protected Contact contact;
protected BitmapCache mBitmapCache = new BitmapCache();
private EditText chatMsg; private EditText chatMsg;
@ -104,13 +112,24 @@ public class ConversationFragment extends Fragment {
boolean showPhoneSelfContactPicture = sharedPref.getBoolean( boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
"show_phone_selfcontact_picture", true); "show_phone_selfcontact_picture", true);
final Uri selfiUri; Bitmap self;
if (showPhoneSelfContactPicture) { 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 { } else {
selfiUri = null; self = UIHelper.getUnknownContactPicture(conversation.getAccount()
.getJid(), 200);
} }
final Bitmap selfBitmap = self;
messageListAdapter = new ArrayAdapter<Message>(this.getActivity() messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
.getApplicationContext(), R.layout.message_sent, .getApplicationContext(), R.layout.message_sent,
this.messageList) { this.messageList) {
@ -136,68 +155,73 @@ public class ConversationFragment extends Fragment {
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
Message item = getItem(position); Message item = getItem(position);
int type = getItemViewType(position); int type = getItemViewType(position);
ViewHolder viewHolder;
if (view == null) { if (view == null) {
switch (type) { switch (type) {
case SENT: case SENT:
viewHolder = new ViewHolder();
view = (View) inflater.inflate(R.layout.message_sent, view = (View) inflater.inflate(R.layout.message_sent,
null); 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; break;
case RECIEVED: case RECIEVED:
viewHolder = new ViewHolder();
view = (View) inflater.inflate( view = (View) inflater.inflate(
R.layout.message_recieved, null); 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; break;
} }
} else {
viewHolder = (ViewHolder) view.getTag();
} }
ImageView imageView = (ImageView) view
.findViewById(R.id.message_photo);
if (type == RECIEVED) { if (type == RECIEVED) {
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
Uri uri = item.getConversation().getProfilePhotoUri(); Uri uri = item.getConversation().getProfilePhotoUri();
if (uri != null) { if (uri != null) {
imageView.setImageURI(uri); viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(), uri));
} else { } else {
imageView.setImageBitmap(UIHelper viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null));
.getUnknownContactPicture(item
.getConversation().getName(), 200));
} }
} else if (item.getConversation().getMode() == Conversation.MODE_MULTI) { } else if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
if (item.getCounterpart() != null) { if (item.getCounterpart() != null) {
imageView.setImageBitmap(UIHelper viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getCounterpart(),null));
.getUnknownContactPicture(
item.getCounterpart(), 200));
} else { } else {
imageView.setImageBitmap(UIHelper viewHolder.imageView.setImageBitmap(mBitmapCache.get(item.getConversation().getName(),null));
.getUnknownContactPicture(item
.getConversation().getName(), 200));
} }
} }
} else { } else {
if (selfiUri != null) { viewHolder.imageView.setImageBitmap(selfBitmap);
imageView.setImageURI(selfiUri);
} else {
imageView.setImageBitmap(UIHelper
.getUnknownContactPicture(conversation
.getAccount().getJid(), 200));
}
} }
TextView messageBody = (TextView) view
.findViewById(R.id.message_body);
String body = item.getBody(); String body = item.getBody();
if (body != null) { 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) { if (item.getStatus() == Message.STATUS_UNSEND) {
time.setTypeface(null, Typeface.ITALIC); viewHolder.time.setTypeface(null, Typeface.ITALIC);
time.setText("sending\u2026"); viewHolder.time.setText("sending\u2026");
} else { } else {
time.setTypeface(null, Typeface.NORMAL); viewHolder.time.setTypeface(null, Typeface.NORMAL);
if ((item.getConversation().getMode() == Conversation.MODE_SINGLE) if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
|| (type != RECIEVED)) { || (type != RECIEVED)) {
time.setText(UIHelper.readableTimeDifference(item viewHolder.time.setText(UIHelper
.getTimeSent())); .readableTimeDifference(item.getTimeSent()));
} else { } else {
time.setText(item.getCounterpart() viewHolder.time.setText(item.getCounterpart()
+ " \u00B7 " + " \u00B7 "
+ UIHelper.readableTimeDifference(item + UIHelper.readableTimeDifference(item
.getTimeSent())); .getTimeSent()));
@ -275,24 +299,31 @@ public class ConversationFragment extends Fragment {
protected void makeFingerprintWarning(int latestEncryption) { protected void makeFingerprintWarning(int latestEncryption) {
final LinearLayout fingerprintWarning = (LinearLayout) getView() final LinearLayout fingerprintWarning = (LinearLayout) getView()
.findViewById(R.id.new_fingerprint); .findViewById(R.id.new_fingerprint);
Set<String> knownFingerprints = conversation.getContact() if (conversation.getContact() != null) {
.getOtrFingerprints(); Set<String> knownFingerprints = conversation.getContact()
if ((latestEncryption == Message.ENCRYPTION_OTR) .getOtrFingerprints();
&& (conversation.hasValidOtrSession() if ((latestEncryption == Message.ENCRYPTION_OTR)
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints && (conversation.hasValidOtrSession()
.contains(conversation.getOtrFingerprint())))) { && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
fingerprintWarning.setVisibility(View.VISIBLE); .contains(conversation.getOtrFingerprint())))) {
TextView fingerprint = (TextView) getView().findViewById( fingerprintWarning.setVisibility(View.VISIBLE);
R.id.otr_fingerprint); TextView fingerprint = (TextView) getView().findViewById(
fingerprint.setText(conversation.getOtrFingerprint()); R.id.otr_fingerprint);
fingerprintWarning.setOnClickListener(new OnClickListener() { fingerprint.setText(conversation.getOtrFingerprint());
fingerprintWarning.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
AlertDialog dialog = UIHelper.getVerifyFingerprintDialog((ConversationActivity) getActivity(),conversation,fingerprintWarning); AlertDialog dialog = UIHelper
dialog.show(); .getVerifyFingerprintDialog(
} (ConversationActivity) getActivity(),
}); conversation, fingerprintWarning);
dialog.show();
}
});
} else {
fingerprintWarning.setVisibility(View.GONE);
}
} else { } else {
fingerprintWarning.setVisibility(View.GONE); fingerprintWarning.setVisibility(View.GONE);
} }
@ -300,11 +331,11 @@ public class ConversationFragment extends Fragment {
protected void sendPlainTextMessage(Message message) { protected void sendPlainTextMessage(Message message) {
ConversationActivity activity = (ConversationActivity) getActivity(); ConversationActivity activity = (ConversationActivity) getActivity();
activity.xmppConnectionService.sendMessage(conversation.getAccount(), message, activity.xmppConnectionService.sendMessage(conversation.getAccount(),
null); message, null);
chatMsg.setText(""); chatMsg.setText("");
} }
protected void sendOtrMessage(final Message message) { protected void sendOtrMessage(final Message message) {
ConversationActivity activity = (ConversationActivity) getActivity(); ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService; final XmppConnectionService xmppService = activity.xmppConnectionService;
@ -313,9 +344,13 @@ public class ConversationFragment extends Fragment {
conversation.getAccount(), message, null); conversation.getAccount(), message, null);
chatMsg.setText(""); chatMsg.setText("");
} else { } else {
Hashtable<String, Integer> presences = conversation Hashtable<String, Integer> presences;
.getContact().getPresences(); if (conversation.getContact() != null) {
if (presences.size() == 0) { presences = conversation.getContact().getPresences();
} else {
presences = null;
}
if ((presences != null) && (presences.size() == 0)) {
AlertDialog.Builder builder = new AlertDialog.Builder( AlertDialog.Builder builder = new AlertDialog.Builder(
getActivity()); getActivity());
builder.setTitle("Contact is offline"); builder.setTitle("Contact is offline");
@ -330,16 +365,15 @@ public class ConversationFragment extends Fragment {
conversation.nextMessageEncryption = Message.ENCRYPTION_NONE; conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage( xmppService.sendMessage(
conversation.getAccount(), conversation.getAccount(), message,
message, null); null);
chatMsg.setText(""); chatMsg.setText("");
} }
}); });
builder.setNegativeButton("Cancel", null); builder.setNegativeButton("Cancel", null);
builder.create().show(); builder.create().show();
} else if (presences.size() == 1) { } else if (presences.size() == 1) {
xmppService.sendMessage(conversation.getAccount(), xmppService.sendMessage(conversation.getAccount(), message,
message,
(String) presences.keySet().toArray()[0]); (String) presences.keySet().toArray()[0]);
chatMsg.setText(""); chatMsg.setText("");
} else { } else {
@ -348,16 +382,51 @@ public class ConversationFragment extends Fragment {
builder.setTitle("Choose Presence"); builder.setTitle("Choose Presence");
final String[] presencesArray = new String[presences.size()]; final String[] presencesArray = new String[presences.size()];
presences.keySet().toArray(presencesArray); presences.keySet().toArray(presencesArray);
builder.setItems(presencesArray, new DialogInterface.OnClickListener() { builder.setItems(presencesArray,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { @Override
xmppService.sendMessage(conversation.getAccount(), message, presencesArray[which]); public void onClick(DialogInterface dialog,
chatMsg.setText(""); int which) {
} xmppService.sendMessage(
}); conversation.getAccount(), message,
presencesArray[which]);
chatMsg.setText("");
}
});
builder.create().show(); builder.create().show();
} }
} }
} }
private static class ViewHolder {
protected TextView time;
protected TextView messageBody;
protected ImageView imageView;
}
private class BitmapCache {
private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
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;
}
}
}
} }

View File

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

View File

@ -1,6 +1,5 @@
package de.gultsch.chat.utils; package de.gultsch.chat.utils;
import java.util.ArrayList;
import java.util.Hashtable; import java.util.Hashtable;
import android.app.Activity; import android.app.Activity;
@ -11,14 +10,19 @@ import android.content.Loader.OnLoadCompleteListener;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.Profile;
public class PhoneHelper { public class PhoneHelper {
public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) { public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) {
if (Looper.myLooper()==null) {
Looper.prepare();
}
final Looper mLooper = Looper.myLooper();
final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>(); final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
final String[] PROJECTION = new String[] { final String[] PROJECTION = new String[] {
ContactsContract.Data._ID, ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.DISPLAY_NAME,
@ -31,7 +35,7 @@ public class PhoneHelper {
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
+ "\")"; + "\")";
CursorLoader mCursorLoader = new CursorLoader(context, CursorLoader mCursorLoader = new CursorLoader(context,
ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
null); null);
@ -61,6 +65,7 @@ public class PhoneHelper {
if (listener!=null) { if (listener!=null) {
listener.onPhoneContactsLoaded(phoneContacts); listener.onPhoneContactsLoaded(phoneContacts);
} }
mLooper.quit();
} }
}); });
mCursorLoader.startLoading(); mCursorLoader.startLoading();

View File

@ -59,6 +59,10 @@ public class XmlReader {
for(int i = 0; i < parser.getAttributeCount(); ++i) { for(int i = 0; i < parser.getAttributeCount(); ++i) {
tag.setAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); tag.setAttribute(parser.getAttributeName(i), parser.getAttributeValue(i));
} }
String xmlns = parser.getNamespace();
if (xmlns!=null) {
tag.setAttribute("xmlns",xmlns);
}
return tag; return tag;
} else if (parser.getEventType() == XmlPullParser.END_TAG) { } else if (parser.getEventType() == XmlPullParser.END_TAG) {
Tag tag = Tag.end(parser.getName()); Tag tag = Tag.end(parser.getName());