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

View file

@ -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"/>
</LinearLayout>

View file

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

View file

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

View file

@ -245,6 +245,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
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) {
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);
}
}

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.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<Account> accounts;
private List<Conversation> 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<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;
}
} 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<Conversation> 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<String, Bundle> 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<Contact> contacts = new ArrayList<Contact>();
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<Element> 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<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() {
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);

View file

@ -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<Message> messageList = new ArrayList<Message>();
protected ArrayAdapter<Message> 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<Message>(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<String> 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<String> 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<String, Integer> presences = conversation
.getContact().getPresences();
if (presences.size() == 0) {
Hashtable<String, Integer> 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<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;
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<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
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();

View file

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