aboutsummaryrefslogtreecommitdiffstats
path: root/src/eu/siacs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eu/siacs')
-rw-r--r--src/eu/siacs/conversations/entities/Contact.java6
-rw-r--r--src/eu/siacs/conversations/entities/Conversation.java10
-rw-r--r--src/eu/siacs/conversations/entities/Message.java33
-rw-r--r--src/eu/siacs/conversations/entities/Presences.java8
-rw-r--r--src/eu/siacs/conversations/persistance/DatabaseBackend.java8
-rw-r--r--src/eu/siacs/conversations/persistance/FileBackend.java113
-rw-r--r--src/eu/siacs/conversations/services/XmppConnectionService.java217
-rw-r--r--src/eu/siacs/conversations/ui/ContactDetailsActivity.java8
-rw-r--r--src/eu/siacs/conversations/ui/ContactsActivity.java3
-rw-r--r--src/eu/siacs/conversations/ui/ConversationActivity.java325
-rw-r--r--src/eu/siacs/conversations/ui/ConversationFragment.java157
-rw-r--r--src/eu/siacs/conversations/ui/MucDetailsActivity.java2
-rw-r--r--src/eu/siacs/conversations/ui/OnPresenceSelected.java5
-rw-r--r--src/eu/siacs/conversations/ui/ShareWithActivity.java8
-rw-r--r--src/eu/siacs/conversations/utils/ExceptionHelper.java27
-rw-r--r--src/eu/siacs/conversations/utils/MessageParser.java16
-rw-r--r--src/eu/siacs/conversations/utils/PhoneHelper.java27
-rw-r--r--src/eu/siacs/conversations/utils/UIHelper.java272
-rw-r--r--src/eu/siacs/conversations/xml/Element.java4
-rw-r--r--src/eu/siacs/conversations/xmpp/PacketReceived.java2
-rw-r--r--src/eu/siacs/conversations/xmpp/XmppConnection.java110
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java138
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java494
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java130
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleFile.java35
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/OnFileTransmitted.java5
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java (renamed from src/eu/siacs/conversations/xmpp/OnJinglePacketReceived.java)5
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java5
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java6
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java207
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java124
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java (renamed from src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java)53
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java (renamed from src/eu/siacs/conversations/xmpp/stanzas/jingle/Reason.java)2
-rw-r--r--src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java8
-rw-r--r--src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java13
35 files changed, 2202 insertions, 384 deletions
diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java
index 4b97cc41..132b2717 100644
--- a/src/eu/siacs/conversations/entities/Contact.java
+++ b/src/eu/siacs/conversations/entities/Contact.java
@@ -13,6 +13,7 @@ import eu.siacs.conversations.xml.Element;
import android.content.ContentValues;
import android.database.Cursor;
+import android.util.Log;
public class Contact extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4570817093119419962L;
@@ -163,11 +164,16 @@ public class Contact extends AbstractEntity implements Serializable {
public void updatePresence(String resource, int status) {
this.presences.updatePresence(resource, status);
+ Log.d("xmppService","updatingPresence for contact="+this.jid+" resource="+resource+" num="+presences.size());
}
public void removePresence(String resource) {
this.presences.removePresence(resource);
}
+
+ public void clearPresences() {
+ this.presences.clearPresences();
+ }
public int getMostAvailableStatus() {
return this.presences.getMostAvailableStatus();
diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java
index f8c6a2a6..6eb688ae 100644
--- a/src/eu/siacs/conversations/entities/Conversation.java
+++ b/src/eu/siacs/conversations/entities/Conversation.java
@@ -45,6 +45,8 @@ public class Conversation extends AbstractEntity {
private int status;
private long created;
private int mode;
+
+ private String nextPresence;
private transient List<Message> messages = null;
private transient Account account = null;
@@ -308,4 +310,12 @@ public class Conversation extends AbstractEntity {
public void setContactJid(String jid) {
this.contactJid = jid;
}
+
+ public void setNextPresence(String presence) {
+ this.nextPresence = presence;
+ }
+
+ public String getNextPresence() {
+ return this.nextPresence;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java
index b91d822d..e2b03967 100644
--- a/src/eu/siacs/conversations/entities/Message.java
+++ b/src/eu/siacs/conversations/entities/Message.java
@@ -9,15 +9,21 @@ public class Message extends AbstractEntity {
public static final String TABLENAME = "messages";
+ public static final int STATUS_RECIEVING = -1;
public static final int STATUS_RECIEVED = 0;
public static final int STATUS_UNSEND = 1;
public static final int STATUS_SEND = 2;
- public static final int STATUS_ERROR = 3;
+ public static final int STATUS_SEND_FAILED = 3;
+ public static final int STATUS_SEND_REJECTED = 4;
+ public static final int STATUS_PREPARING = 5;
public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1;
public static final int ENCRYPTION_OTR = 2;
public static final int ENCRYPTION_DECRYPTED = 3;
+
+ public static final int TYPE_TEXT = 0;
+ public static final int TYPE_IMAGE = 1;
public static String CONVERSATION = "conversationUuid";
public static String COUNTERPART = "counterpart";
@@ -25,6 +31,7 @@ public class Message extends AbstractEntity {
public static String TIME_SENT = "timeSent";
public static String ENCRYPTION = "encryption";
public static String STATUS = "status";
+ public static String TYPE = "type";
protected String conversationUuid;
protected String counterpart;
@@ -33,6 +40,7 @@ public class Message extends AbstractEntity {
protected long timeSent;
protected int encryption;
protected int status;
+ protected int type;
protected boolean read = true;
protected transient Conversation conversation = null;
@@ -40,17 +48,17 @@ public class Message extends AbstractEntity {
public Message(Conversation conversation, String body, int encryption) {
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
conversation.getContactJid(), body, System.currentTimeMillis(), encryption,
- Message.STATUS_UNSEND);
+ Message.STATUS_UNSEND,TYPE_TEXT);
this.conversation = conversation;
}
public Message(Conversation conversation, String counterpart, String body, int encryption, int status) {
- this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),counterpart, body, System.currentTimeMillis(), encryption,status);
+ this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),counterpart, body, System.currentTimeMillis(), encryption,status,TYPE_TEXT);
this.conversation = conversation;
}
public Message(String uuid, String conversationUUid, String counterpart,
- String body, long timeSent, int encryption, int status) {
+ String body, long timeSent, int encryption, int status, int type) {
this.uuid = uuid;
this.conversationUuid = conversationUUid;
this.counterpart = counterpart;
@@ -58,6 +66,7 @@ public class Message extends AbstractEntity {
this.timeSent = timeSent;
this.encryption = encryption;
this.status = status;
+ this.type = type;
}
@Override
@@ -70,6 +79,7 @@ public class Message extends AbstractEntity {
values.put(TIME_SENT, timeSent);
values.put(ENCRYPTION, encryption);
values.put(STATUS, status);
+ values.put(TYPE, type);
return values;
}
@@ -108,7 +118,8 @@ public class Message extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(BODY)),
cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
- cursor.getInt(cursor.getColumnIndex(STATUS)));
+ cursor.getInt(cursor.getColumnIndex(STATUS)),
+ cursor.getInt(cursor.getColumnIndex(TYPE)));
}
public void setConversation(Conversation conv) {
@@ -150,4 +161,16 @@ public class Message extends AbstractEntity {
public void setEncryptedBody(String body) {
this.encryptedBody = body;
}
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public int getType() {
+ return this.type;
+ }
+
+ public void setPresence(String presence) {
+ this.counterpart = this.counterpart.split("/")[0] + "/" + presence;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Presences.java b/src/eu/siacs/conversations/entities/Presences.java
index 9a22e559..acbaafca 100644
--- a/src/eu/siacs/conversations/entities/Presences.java
+++ b/src/eu/siacs/conversations/entities/Presences.java
@@ -33,6 +33,10 @@ public class Presences {
this.presences.remove(resource);
}
+ public void clearPresences() {
+ this.presences.clear();
+ }
+
public int getMostAvailableStatus() {
int status = OFFLINE;
Iterator<Entry<String, Integer>> it = presences.entrySet().iterator();
@@ -54,7 +58,7 @@ public class Presences {
jObj.put("resource", entry.getKey());
jObj.put("status", entry.getValue());
} catch (JSONException e) {
-
+
}
json.put(jObj);
}
@@ -71,7 +75,7 @@ public class Presences {
jObj.getInt("status"));
}
} catch (JSONException e1) {
-
+
}
return presences;
}
diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
index 4c0d6216..852a9315 100644
--- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -23,7 +23,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 2;
+ private static final int DATABASE_VERSION = 3;
public DatabaseBackend(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -50,7 +50,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
+ " TEXT, " + Message.BODY + " TEXT, " + Message.ENCRYPTION
- + " NUMBER, " + Message.STATUS + " NUMBER," + "FOREIGN KEY("
+ + " NUMBER, " + Message.STATUS + " NUMBER," +Message.TYPE +" NUMBER, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
+ ") ON DELETE CASCADE);");
@@ -72,6 +72,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("update " + Account.TABLENAME
+ " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
}
+ if (oldVersion < 3 && newVersion >= 3) {
+ //add field type to message
+ db.execSQL("ALTER TABLE "+Message.TABLENAME+" ADD COLUMN "+Message.TYPE+" NUMBER");;
+ }
}
public static synchronized DatabaseBackend getInstance(Context context) {
diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java
new file mode 100644
index 00000000..f7f986f2
--- /dev/null
+++ b/src/eu/siacs/conversations/persistance/FileBackend.java
@@ -0,0 +1,113 @@
+package eu.siacs.conversations.persistance;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.LruCache;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.xmpp.jingle.JingleFile;
+
+public class FileBackend {
+
+ private static int IMAGE_SIZE = 1920;
+
+ private Context context;
+ private LruCache<String, Bitmap> thumbnailCache;
+
+ public FileBackend(Context context) {
+ this.context = context;
+
+ int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ int cacheSize = maxMemory / 8;
+ thumbnailCache = new LruCache<String, Bitmap>(cacheSize) {
+ @Override
+ protected int sizeOf(String key, Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
+ }
+ };
+
+ }
+
+ public JingleFile getJingleFile(Message message) {
+ Conversation conversation = message.getConversation();
+ String prefix = context.getFilesDir().getAbsolutePath();
+ String path = prefix + "/" + conversation.getAccount().getJid() + "/"
+ + conversation.getContactJid();
+ String filename = message.getUuid() + ".webp";
+ return new JingleFile(path + "/" + filename);
+ }
+
+ private Bitmap resize(Bitmap originalBitmap, int size) {
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (Math.max(w, h) > size) {
+ int scalledW;
+ int scalledH;
+ if (w <= h) {
+ scalledW = (int) (w / ((double) h / size));
+ scalledH = size;
+ } else {
+ scalledW = size;
+ scalledH = (int) (h / ((double) w / size));
+ }
+ Bitmap scalledBitmap = Bitmap.createScaledBitmap(
+ originalBitmap, scalledW, scalledH, true);
+ return scalledBitmap;
+ } else {
+ return originalBitmap;
+ }
+ }
+
+ public JingleFile copyImageToPrivateStorage(Message message, Uri image) {
+ try {
+ InputStream is = context.getContentResolver()
+ .openInputStream(image);
+ JingleFile file = getJingleFile(message);
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ OutputStream os = new FileOutputStream(file);
+ Bitmap originalBitmap = BitmapFactory.decodeStream(is);
+ is.close();
+ Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
+ boolean success = scalledBitmap.compress(Bitmap.CompressFormat.WEBP,75,os);
+ if (!success) {
+ //Log.d("xmppService", "couldnt compress");
+ }
+ os.close();
+ return file;
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public Bitmap getImageFromMessage(Message message) {
+ return BitmapFactory
+ .decodeFile(getJingleFile(message).getAbsolutePath());
+ }
+
+ public Bitmap getThumbnailFromMessage(Message message, int size) {
+ Bitmap thumbnail = thumbnailCache.get(message.getUuid());
+ if (thumbnail==null) {
+ Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
+ .getAbsolutePath());
+ thumbnail = resize(fullsize, size);
+ this.thumbnailCache.put(message.getUuid(), thumbnail);
+ }
+ return thumbnail;
+ }
+}
diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java
index 9dbce675..0dea3731 100644
--- a/src/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/eu/siacs/conversations/services/XmppConnectionService.java
@@ -27,6 +27,7 @@ import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.persistance.OnPhoneContactsMerged;
import eu.siacs.conversations.ui.OnAccountListChangedListener;
import eu.siacs.conversations.ui.OnConversationListChangedListener;
@@ -39,16 +40,17 @@ import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.OnJinglePacketReceived;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
+import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
-import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -59,6 +61,7 @@ import android.database.ContentObserver;
import android.database.DatabaseUtils;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -73,6 +76,7 @@ public class XmppConnectionService extends Service {
protected static final String LOGTAG = "xmppService";
public DatabaseBackend databaseBackend;
+ private FileBackend fileBackend;
public long startDate;
@@ -80,10 +84,12 @@ public class XmppConnectionService extends Service {
private static final int PING_MIN_INTERVAL = 10;
private static final int PING_TIMEOUT = 5;
private static final int CONNECT_TIMEOUT = 60;
+ private static final long CARBON_GRACE_PERIOD = 60000L;
private List<Account> accounts;
private List<Conversation> conversations = null;
-
+ private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this);
+
public OnConversationListChangedListener convChangedListener = null;
private int convChangedListenerCount = 0;
private OnAccountListChangedListener accountChangedListener = null;
@@ -96,6 +102,8 @@ public class XmppConnectionService extends Service {
private Random mRandom = new Random(System.currentTimeMillis());
+ private long lastCarbonMessageReceived = -CARBON_GRACE_PERIOD;
+
private ContentObserver contactObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
@@ -115,14 +123,17 @@ public class XmppConnectionService extends Service {
MessagePacket packet) {
Message message = null;
boolean notify = true;
+ if(getPreferences().getBoolean("notification_grace_period_after_carbon_received", true)){
+ notify=(SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD;
+ }
+
if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
String pgpBody = MessageParser.getPgpBody(packet);
if (pgpBody != null) {
message = MessageParser.parsePgpChat(pgpBody, packet,
account, service);
message.markUnread();
- } else if (packet.hasChild("body")
- && (packet.getBody().startsWith("?OTR"))) {
+ } else if ((packet.getBody()!=null) && (packet.getBody().startsWith("?OTR"))) {
message = MessageParser.parseOtrChat(packet, account,
service);
if (message != null) {
@@ -138,6 +149,7 @@ public class XmppConnectionService extends Service {
service);
if (message != null) {
if (message.getStatus() == Message.STATUS_SEND) {
+ lastCarbonMessageReceived = SystemClock.elapsedRealtime();
notify = false;
message.getConversation().markRead();
} else {
@@ -158,7 +170,8 @@ public class XmppConnectionService extends Service {
}
}
} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
- message = MessageParser.parseError(packet, account, service);
+ MessageParser.parseError(packet, account, service);
+ return;
} else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
if (packet.hasChild("x")) {
Element x = packet.findChild("x");
@@ -274,7 +287,11 @@ public class XmppConnectionService extends Service {
} else {
Contact contact = findContact(account, fromParts[0]);
if (contact == null) {
- // most likely roster not synced
+ if ("subscribe".equals(type)) {
+ account.getXmppConnection().addPendingSubscription(fromParts[0]);
+ } else {
+ Log.d(LOGTAG,packet.getFrom()+ " could not be found");
+ }
return;
}
if (type == null) {
@@ -299,20 +316,24 @@ public class XmppConnectionService extends Service {
}
}
}
+ replaceContactInConversation(contact.getJid(), contact);
databaseBackend.updateContact(contact);
} else {
- // Log.d(LOGTAG,"presence without resource "+packet.toString());
+ //Log.d(LOGTAG,"presence without resource "+packet.toString());
}
} else if (type.equals("unavailable")) {
if (fromParts.length != 2) {
- // Log.d(LOGTAG,"received presence with no resource "+packet.toString());
+ contact.clearPresences();
} else {
contact.removePresence(fromParts[1]);
- databaseBackend.updateContact(contact);
}
+ replaceContactInConversation(contact.getJid(), contact);
+ databaseBackend.updateContact(contact);
} else if (type.equals("subscribe")) {
+ Log.d(LOGTAG,"received subscribe packet from "+packet.getFrom());
if (contact
.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
+ Log.d(LOGTAG,"preemptive grant; granting");
sendPresenceUpdatesTo(contact);
contact.setSubscriptionOption(Contact.Subscription.FROM);
contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
@@ -326,12 +347,11 @@ public class XmppConnectionService extends Service {
requestPresenceUpdatesFrom(contact);
}
} else {
- // TODO: ask user to handle it maybe
+ account.getXmppConnection().addPendingSubscription(fromParts[0]);
}
} else {
//Log.d(LOGTAG, packet.toString());
}
- replaceContactInConversation(contact.getJid(), contact);
}
}
}
@@ -348,6 +368,8 @@ public class XmppConnectionService extends Service {
processRosterItems(account, query);
mergePhoneContactsWithRoster(null);
}
+ } else {
+ Log.d(LOGTAG,"iq packet arrived "+packet.toString());
}
}
};
@@ -356,7 +378,7 @@ public class XmppConnectionService extends Service {
@Override
public void onJinglePacketReceived(Account account, JinglePacket packet) {
- Log.d(LOGTAG,account.getJid()+": jingle packet received"+packet.toString());
+ mJingleConnectionManager.deliverPacket(account, packet);
}
};
@@ -381,6 +403,35 @@ public class XmppConnectionService extends Service {
}
+ public FileBackend getFileBackend() {
+ return this.fileBackend;
+ }
+
+ public void attachImageToConversation(final Conversation conversation, final String presence, final Uri uri) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ Message message = new Message(conversation, "", Message.ENCRYPTION_NONE);
+ message.setPresence(presence);
+ message.setType(Message.TYPE_IMAGE);
+ message.setStatus(Message.STATUS_PREPARING);
+ conversation.getMessages().add(message);
+ if (convChangedListener!=null) {
+ convChangedListener.onConversationListChanged();
+ }
+ getFileBackend().copyImageToPrivateStorage(message, uri);
+ message.setStatus(Message.STATUS_UNSEND);
+ databaseBackend.createMessage(message);
+ if (convChangedListener!=null) {
+ convChangedListener.onConversationListChanged();
+ }
+ sendMessage(message, null);
+ }
+ }).start();
+ }
+
+
protected Conversation findMuc(String name, Account account) {
for (Conversation conversation : this.conversations) {
if (conversation.getContactJid().split("/")[0].equals(name)
@@ -522,7 +573,8 @@ public class XmppConnectionService extends Service {
@Override
public void onCreate() {
ExceptionHelper.init(getApplicationContext());
- databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
+ this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
+ this.fileBackend = new FileBackend(getApplicationContext());
this.accounts = databaseBackend.getAccounts();
this.getConversations();
@@ -589,8 +641,7 @@ public class XmppConnectionService extends Service {
}
public XmppConnection createConnection(Account account) {
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences sharedPref = getPreferences();
account.setResource(sharedPref.getString("resource", "mobile").toLowerCase(Locale.getDefault()));
XmppConnection connection = new XmppConnection(account, this.pm);
connection.setOnMessagePacketReceivedListener(this.messageListener);
@@ -633,51 +684,55 @@ public class XmppConnectionService extends Service {
synchronized public void sendMessage(Message message, String presence) {
Account account = message.getConversation().getAccount();
Conversation conv = message.getConversation();
+ MessagePacket packet = null;
boolean saveInDb = false;
boolean addToConversation = false;
+ boolean send = false;
if (account.getStatus() == Account.STATUS_ONLINE) {
- MessagePacket packet;
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession()) {
- // starting otr session. messages will be send later
- conv.startOtrSession(getApplicationContext(), presence,true);
- } 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);
- message.setStatus(Message.STATUS_SEND);
- }
- saveInDb = true;
- addToConversation = true;
- } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- message.getConversation().endOtrIfNeeded();
- long keyId = message.getConversation().getContact()
- .getPgpKeyId();
- packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT);
- packet.setFrom(message.getConversation().getAccount()
- .getFullJid());
- packet.setTo(message.getCounterpart());
- packet.setBody("This is an XEP-0027 encryted message");
- packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
- account.getXmppConnection().sendMessagePacket(packet);
- message.setStatus(Message.STATUS_SEND);
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- saveInDb = true;
- addToConversation = true;
+ if (message.getType() == Message.TYPE_IMAGE) {
+ mJingleConnectionManager.createNewConnection(message);
} else {
- message.getConversation().endOtrIfNeeded();
- // don't encrypt
- if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ if (!conv.hasValidOtrSession()) {
+ // starting otr session. messages will be send later
+ conv.startOtrSession(getApplicationContext(), presence,true);
+ } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
+ // otr session aleary exists, creating message packet
+ // accordingly
+ packet = prepareMessagePacket(account, message,
+ conv.getOtrSession());
+ send = true;
+ message.setStatus(Message.STATUS_SEND);
+ }
+ saveInDb = true;
+ addToConversation = true;
+ } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ message.getConversation().endOtrIfNeeded();
+ long keyId = message.getConversation().getContact()
+ .getPgpKeyId();
+ packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setFrom(message.getConversation().getAccount()
+ .getFullJid());
+ packet.setTo(message.getCounterpart());
+ packet.setBody("This is an XEP-0027 encryted message");
+ packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody());
message.setStatus(Message.STATUS_SEND);
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
saveInDb = true;
addToConversation = true;
+ send = true;
+ } else {
+ message.getConversation().endOtrIfNeeded();
+ // don't encrypt
+ if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+ message.setStatus(Message.STATUS_SEND);
+ saveInDb = true;
+ addToConversation = true;
+ }
+ packet = prepareMessagePacket(account, message, null);
+ send = true;
}
-
- packet = prepareMessagePacket(account, message, null);
- account.getXmppConnection().sendMessagePacket(packet);
}
} else {
// account is offline
@@ -694,6 +749,9 @@ public class XmppConnectionService extends Service {
convChangedListener.onConversationListChanged();
}
}
+ if ((send)&&(packet!=null)) {
+ account.getXmppConnection().sendMessagePacket(packet);
+ }
}
@@ -748,6 +806,7 @@ public class XmppConnectionService extends Service {
packet.setTo(message.getCounterpart().split("/")[0]);
packet.setFrom(account.getJid());
}
+ packet.setId(message.getUuid());
return packet;
}
@@ -974,6 +1033,10 @@ public class XmppConnectionService extends Service {
this.convChangedListener.onConversationListChanged();
}
}
+
+ public void clearConversationHistory(Conversation conversation) {
+
+ }
public int getConversationCount() {
return this.databaseBackend.getConversationCount();
@@ -1018,12 +1081,10 @@ public class XmppConnectionService extends Service {
OnConversationListChangedListener listener) {
this.convChangedListener = listener;
this.convChangedListenerCount++;
- Log.d(LOGTAG,"registered on conv changed in backend ("+convChangedListenerCount+")");
}
public void removeOnConversationListChangedListener() {
this.convChangedListenerCount--;
- Log.d(LOGTAG,"someone on conv changed listener removed listener ("+convChangedListenerCount+")");
if (this.convChangedListenerCount==0) {
this.convChangedListener = null;
}
@@ -1160,8 +1221,7 @@ public class XmppConnectionService extends Service {
}
public void createContact(Contact contact) {
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences sharedPref = getPreferences();
boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
if (autoGrant) {
contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
@@ -1180,6 +1240,10 @@ public class XmppConnectionService extends Service {
account.getXmppConnection().sendIqPacket(iq, null);
if (autoGrant) {
requestPresenceUpdatesFrom(contact);
+ if (account.getXmppConnection().hasPendingSubscription(contact.getJid())) {
+ Log.d("xmppService","contact had pending subscription");
+ sendPresenceUpdatesTo(contact);
+ }
}
replaceContactInConversation(contact.getJid(), contact);
}
@@ -1253,9 +1317,11 @@ public class XmppConnectionService extends Service {
public Contact findContact(String uuid) {
Contact contact = this.databaseBackend.getContact(uuid);
- for (Account account : getAccounts()) {
- if (contact.getAccountUuid().equals(account.getUuid())) {
- contact.setAccount(account);
+ if (contact!=null) {
+ for (Account account : getAccounts()) {
+ if (contact.getAccountUuid().equals(account.getUuid())) {
+ contact.setAccount(account);
+ }
}
}
return contact;
@@ -1325,4 +1391,33 @@ public class XmppConnectionService extends Service {
}
}
-} \ No newline at end of file
+
+ public boolean markMessage(Account account, String recipient, String uuid, int status) {
+ boolean marked = false;
+ for(Conversation conversation : getConversations()) {
+ if (conversation.getContactJid().equals(recipient)&&conversation.getAccount().equals(account)) {
+ for(Message message : conversation.getMessages()) {
+ if (message.getUuid().equals(uuid)) {
+ markMessage(message, status);
+ marked = true;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return marked;
+ }
+
+ public void markMessage(Message message, int status) {
+ message.setStatus(status);
+ databaseBackend.updateMessage(message);
+ if (convChangedListener!=null) {
+ convChangedListener.onConversationListChanged();
+ }
+ }
+
+ public SharedPreferences getPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ }
+}
diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 5dc6eb3b..1ed3fa13 100644
--- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -227,10 +227,14 @@ public class ContactDetailsActivity extends XmppActivity {
status.setTextColor(0xFFe92727);
break;
}
- contactJid.setText(contact.getJid());
+ if (contact.getPresences().size() > 1) {
+ contactJid.setText(contact.getJid()+" ("+contact.getPresences().size()+")");
+ } else {
+ contactJid.setText(contact.getJid());
+ }
accountJid.setText(contact.getAccount().getJid());
- UIHelper.prepareContactBadge(this, badge, contact);
+ UIHelper.prepareContactBadge(this, badge, contact, getApplicationContext());
if (contact.getSystemAccount() == null) {
badge.setOnClickListener(onBadgeClick);
diff --git a/src/eu/siacs/conversations/ui/ContactsActivity.java b/src/eu/siacs/conversations/ui/ContactsActivity.java
index 2e9e55f5..e403450a 100644
--- a/src/eu/siacs/conversations/ui/ContactsActivity.java
+++ b/src/eu/siacs/conversations/ui/ContactsActivity.java
@@ -381,8 +381,7 @@ public class ContactsActivity extends XmppActivity {
contactJid.setText(contact.getJid());
ImageView imageView = (ImageView) view
.findViewById(R.id.contact_photo);
- imageView.setImageBitmap(UIHelper.getContactPicture(contact,
- null, 90, this.getContext()));
+ imageView.setImageBitmap(UIHelper.getContactPicture(contact, 48, this.getContext(), false));
return view;
}
};
diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java
index f47e8a8c..6e56d2b3 100644
--- a/src/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/eu/siacs/conversations/ui/ConversationActivity.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.ui;
import java.util.ArrayList;
+import java.util.Hashtable;
import java.util.List;
import eu.siacs.conversations.R;
@@ -13,16 +14,17 @@ import eu.siacs.conversations.utils.UIHelper;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -43,39 +45,43 @@ public class ConversationActivity extends XmppActivity {
public static final String VIEW_CONVERSATION = "viewConversation";
public static final String CONVERSATION = "conversationUuid";
public static final String TEXT = "text";
-
+ public static final String PRESENCE = "eu.siacs.conversations.presence";
+
public static final int REQUEST_SEND_MESSAGE = 0x75441;
public static final int REQUEST_DECRYPT_PGP = 0x76783;
+ private static final int ATTACH_FILE = 0x48502;
protected SlidingPaneLayout spl;
private List<Conversation> conversationList = new ArrayList<Conversation>();
private Conversation selectedConversation = null;
private ListView listView;
-
+
private boolean paneShouldBeOpen = true;
private boolean useSubject = true;
private ArrayAdapter<Conversation> listAdapter;
-
+
private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
-
+
@Override
public void onConversationListChanged() {
runOnUiThread(new Runnable() {
-
+
@Override
- public void run() {
+ public void run() {
updateConversationList();
- if(paneShouldBeOpen) {
+ if (paneShouldBeOpen) {
if (conversationList.size() >= 1) {
swapConversationFragment();
} else {
- startActivity(new Intent(getApplicationContext(), ContactsActivity.class));
+ startActivity(new Intent(getApplicationContext(),
+ ContactsActivity.class));
finish();
}
}
- ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
- if (selectedFragment!=null) {
+ ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (selectedFragment != null) {
selectedFragment.updateMessages();
}
}
@@ -83,19 +89,8 @@ public class ConversationActivity extends XmppActivity {
}
};
- private DialogInterface.OnClickListener addToRoster = new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String jid = getSelectedConversation().getContactJid();
- Account account = getSelectedConversation().getAccount();
- String name = jid.split("@")[0];
- Contact contact = new Contact(account, name, jid, null);
- xmppConnectionService.createContact(contact);
- }
- };
protected ConversationActivity activity = this;
-
+
public List<Conversation> getConversationList() {
return this.conversationList;
}
@@ -103,19 +98,19 @@ public class ConversationActivity extends XmppActivity {
public Conversation getSelectedConversation() {
return this.selectedConversation;
}
-
+
public ListView getConversationListView() {
return this.listView;
}
-
+
public SlidingPaneLayout getSlidingPaneLayout() {
return this.spl;
}
-
+
public boolean shouldPaneBeOpen() {
return paneShouldBeOpen;
}
-
+
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -141,7 +136,7 @@ public class ConversationActivity extends XmppActivity {
return view;
}
if (!spl.isSlideable()) {
- if (conv==getSelectedConversation()) {
+ if (conv == getSelectedConversation()) {
view.setBackgroundColor(0xffdddddd);
} else {
view.setBackgroundColor(Color.TRANSPARENT);
@@ -149,29 +144,34 @@ public class ConversationActivity extends XmppActivity {
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
- TextView convName = (TextView) view.findViewById(R.id.conversation_name);
+ TextView convName = (TextView) view
+ .findViewById(R.id.conversation_name);
convName.setText(conv.getName(useSubject));
- TextView convLastMsg = (TextView) view.findViewById(R.id.conversation_lastmsg);
+ TextView convLastMsg = (TextView) view
+ .findViewById(R.id.conversation_lastmsg);
convLastMsg.setText(conv.getLatestMessage().getBody());
-
- if(!conv.isRead()) {
- convName.setTypeface(null,Typeface.BOLD);
- convLastMsg.setTypeface(null,Typeface.BOLD);
+
+ if (!conv.isRead()) {
+ convName.setTypeface(null, Typeface.BOLD);
+ convLastMsg.setTypeface(null, Typeface.BOLD);
} else {
- convName.setTypeface(null,Typeface.NORMAL);
- convLastMsg.setTypeface(null,Typeface.NORMAL);
+ convName.setTypeface(null, Typeface.NORMAL);
+ convLastMsg.setTypeface(null, Typeface.NORMAL);
}
-
+
((TextView) view.findViewById(R.id.conversation_lastupdate))
- .setText(UIHelper.readableTimeDifference(conv.getLatestMessage().getTimeSent()));
-
- ImageView imageView = (ImageView) view.findViewById(R.id.conversation_image);
- imageView.setImageBitmap(UIHelper.getContactPicture(conv.getContact(), conv.getName(useSubject),200, activity.getApplicationContext()));
+ .setText(UIHelper.readableTimeDifference(conv
+ .getLatestMessage().getTimeSent()));
+
+ ImageView imageView = (ImageView) view
+ .findViewById(R.id.conversation_image);
+ imageView.setImageBitmap(UIHelper.getContactPicture(
+ conv, 56, activity.getApplicationContext(), false));
return view;
}
};
-
+
listView.setAdapter(this.listAdapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@@ -182,7 +182,7 @@ public class ConversationActivity extends XmppActivity {
paneShouldBeOpen = false;
if (selectedConversation != conversationList.get(position)) {
selectedConversation = conversationList.get(position);
- swapConversationFragment(); //.onBackendConnected(conversationList.get(position));
+ swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
} else {
spl.closePane();
}
@@ -206,13 +206,16 @@ public class ConversationActivity extends XmppActivity {
@Override
public void onPanelClosed(View arg0) {
paneShouldBeOpen = false;
- if ((conversationList.size() > 0)&&(getSelectedConversation()!=null)) {
+ if ((conversationList.size() > 0)
+ && (getSelectedConversation() != null)) {
getActionBar().setDisplayHomeAsUpEnabled(true);
- getActionBar().setTitle(getSelectedConversation().getName(useSubject));
+ getActionBar().setTitle(
+ getSelectedConversation().getName(useSubject));
invalidateOptionsMenu();
if (!getSelectedConversation().isRead()) {
getSelectedConversation().markRead();
- UIHelper.updateNotification(getApplicationContext(), getConversationList(), null, false);
+ UIHelper.updateNotification(getApplicationContext(),
+ getConversationList(), null, false);
listView.invalidateViews();
}
}
@@ -231,29 +234,36 @@ public class ConversationActivity extends XmppActivity {
getMenuInflater().inflate(R.menu.conversations, menu);
MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
- MenuItem menuMucDetails = (MenuItem) menu.findItem(R.id.action_muc_details);
- MenuItem menuContactDetails = (MenuItem) menu.findItem(R.id.action_contact_details);
- MenuItem menuInviteContacts = (MenuItem) menu.findItem(R.id.action_invite);
-
- if ((spl.isOpen()&&(spl.isSlideable()))) {
+ MenuItem menuMucDetails = (MenuItem) menu
+ .findItem(R.id.action_muc_details);
+ MenuItem menuContactDetails = (MenuItem) menu
+ .findItem(R.id.action_contact_details);
+ MenuItem menuInviteContacts = (MenuItem) menu
+ .findItem(R.id.action_invite);
+ MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
+ MenuItem menuClearHistory = (MenuItem) menu.findItem(R.id.action_clear_history);
+
+ if ((spl.isOpen() && (spl.isSlideable()))) {
menuArchive.setVisible(false);
menuMucDetails.setVisible(false);
menuContactDetails.setVisible(false);
menuSecure.setVisible(false);
menuInviteContacts.setVisible(false);
+ menuAttach.setVisible(false);
+ menuClearHistory.setVisible(false);
} else {
- ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl.isSlideable());
- if (this.getSelectedConversation()!=null) {
+ ((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
+ .isSlideable());
+ if (this.getSelectedConversation() != null) {
if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
- menuMucDetails.setVisible(true);
menuContactDetails.setVisible(false);
menuSecure.setVisible(false);
- menuInviteContacts.setVisible(true);
+ menuAttach.setVisible(false);
} else {
- menuContactDetails.setVisible(true);
menuMucDetails.setVisible(false);
menuInviteContacts.setVisible(false);
- if (this.getSelectedConversation().getLatestMessage().getEncryption() != Message.ENCRYPTION_NONE) {
+ if (this.getSelectedConversation().getLatestMessage()
+ .getEncryption() != Message.ENCRYPTION_NONE) {
menuSecure.setIcon(R.drawable.ic_action_secure);
}
}
@@ -268,6 +278,21 @@ public class ConversationActivity extends XmppActivity {
case android.R.id.home:
spl.openPane();
break;
+ case R.id.action_attach_file:
+ selectPresence(getSelectedConversation(), new OnPresenceSelected() {
+
+ @Override
+ public void onPresenceSelected(boolean success, String presence) {
+ if (success) {
+ Intent attachFileIntent = new Intent();
+ attachFileIntent.setType("image/*");
+ attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
+ Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
+ startActivityForResult(chooser, ATTACH_FILE);
+ }
+ }
+ });
+ break;
case R.id.action_add:
startActivity(new Intent(this, ContactsActivity.class));
break;
@@ -286,22 +311,16 @@ public class ConversationActivity extends XmppActivity {
case R.id.action_contact_details:
Contact contact = this.getSelectedConversation().getContact();
if (contact != null) {
- Intent intent = new Intent(this,ContactDetailsActivity.class);
+ Intent intent = new Intent(this, ContactDetailsActivity.class);
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
intent.putExtra("uuid", contact.getUuid());
startActivity(intent);
} else {
- String jid = getSelectedConversation().getContactJid();
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(jid);
- builder.setMessage("The contact is not in your roster. Would you like to add it.");
- builder.setNegativeButton("Cancel", null);
- builder.setPositiveButton("Add",addToRoster);
- builder.create().show();
+ showAddToRosterDialog(getSelectedConversation());
}
break;
case R.id.action_muc_details:
- Intent intent = new Intent(this,MucDetailsActivity.class);
+ Intent intent = new Intent(this, MucDetailsActivity.class);
intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
intent.putExtra("uuid", getSelectedConversation().getUuid());
startActivity(intent);
@@ -310,17 +329,18 @@ public class ConversationActivity extends XmppActivity {
Intent inviteIntent = new Intent(getApplicationContext(),
ContactsActivity.class);
inviteIntent.setAction("invite");
- inviteIntent.putExtra("uuid",selectedConversation.getUuid());
+ inviteIntent.putExtra("uuid", selectedConversation.getUuid());
startActivity(inviteIntent);
break;
case R.id.action_security:
final Conversation selConv = getSelectedConversation();
View menuItemView = findViewById(R.id.action_security);
PopupMenu popup = new PopupMenu(this, menuItemView);
- final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
- if (fragment!=null) {
+ final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (fragment != null) {
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-
+
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
@@ -344,40 +364,66 @@ public class ConversationActivity extends XmppActivity {
return true;
}
});
- popup.inflate(R.menu.encryption_choices);
- switch (selConv.nextMessageEncryption) {
+ popup.inflate(R.menu.encryption_choices);
+ switch (selConv.nextMessageEncryption) {
case Message.ENCRYPTION_NONE:
- popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
+ popup.getMenu().findItem(R.id.encryption_choice_none)
+ .setChecked(true);
break;
case Message.ENCRYPTION_OTR:
- popup.getMenu().findItem(R.id.encryption_choice_otr).setChecked(true);
+ popup.getMenu().findItem(R.id.encryption_choice_otr)
+ .setChecked(true);
break;
case Message.ENCRYPTION_PGP:
- popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
+ popup.getMenu().findItem(R.id.encryption_choice_pgp)
+ .setChecked(true);
break;
case Message.ENCRYPTION_DECRYPTED:
- popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
+ popup.getMenu().findItem(R.id.encryption_choice_pgp)
+ .setChecked(true);
break;
default:
- popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
+ popup.getMenu().findItem(R.id.encryption_choice_none)
+ .setChecked(true);
break;
}
- popup.show();
- }
+ popup.show();
+ }
break;
+ case R.id.action_clear_history:
+ clearHistoryDialog(getSelectedConversation());
+ break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
+
+ protected void clearHistoryDialog(Conversation conversation) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.clear_conversation_history));
+ View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
+ builder.setView(dialogView);
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.delete_messages), new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+ builder.create().show();
+ }
protected ConversationFragment swapConversationFragment() {
ConversationFragment selectedFragment = new ConversationFragment();
-
+
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
- transaction.replace(R.id.selected_conversation, selectedFragment,"conversation");
+ transaction.replace(R.id.selected_conversation, selectedFragment,
+ "conversation");
transaction.commit();
return selectedFragment;
}
@@ -392,24 +438,25 @@ public class ConversationActivity extends XmppActivity {
}
return super.onKeyDown(keyCode, event);
}
-
+
@Override
public void onStart() {
super.onStart();
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(this);
this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
if (this.xmppConnectionServiceBound) {
this.onBackendConnected();
}
- if (conversationList.size()>=1) {
+ if (conversationList.size() >= 1) {
onConvChanged.onConversationListChanged();
}
}
-
+
@Override
protected void onStop() {
if (xmppConnectionServiceBound) {
- xmppConnectionService.removeOnConversationListChangedListener();
+ xmppConnectionService.removeOnConversationListChangedListener();
}
super.onStop();
}
@@ -417,18 +464,20 @@ public class ConversationActivity extends XmppActivity {
@Override
void onBackendConnected() {
this.registerListener();
- if (conversationList.size()==0) {
+ if (conversationList.size() == 0) {
updateConversationList();
}
- if ((getIntent().getAction()!=null)&&(getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
+ if ((getIntent().getAction() != null)
+ && (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
if (getIntent().getType().equals(
ConversationActivity.VIEW_CONVERSATION)) {
handledViewIntent = true;
- String convToView = (String) getIntent().getExtras().get(CONVERSATION);
+ String convToView = (String) getIntent().getExtras().get(
+ CONVERSATION);
- for(int i = 0; i < conversationList.size(); ++i) {
+ for (int i = 0; i < conversationList.size(); ++i) {
if (conversationList.get(i).getUuid().equals(convToView)) {
selectedConversation = conversationList.get(i);
}
@@ -442,46 +491,108 @@ public class ConversationActivity extends XmppActivity {
startActivity(new Intent(this, ManageAccountActivity.class));
finish();
} else if (conversationList.size() <= 0) {
- //add no history
+ // add no history
startActivity(new Intent(this, ContactsActivity.class));
finish();
} else {
spl.openPane();
- //find currently loaded fragment
- ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
- if (selectedFragment!=null) {
+ // find currently loaded fragment
+ ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (selectedFragment != null) {
selectedFragment.onBackendConnected();
} else {
selectedConversation = conversationList.get(0);
swapConversationFragment();
}
- ExceptionHelper.checkForCrash(this,this.xmppConnectionService);
+ ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
}
}
}
+
public void registerListener() {
- if (xmppConnectionServiceBound) {
- xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged);
- }
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService
+ .setOnConversationListChangedListener(this.onConvChanged);
+ }
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == RESULT_OK) {
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_DECRYPT_PGP) {
- ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
- if (selectedFragment!=null) {
+ ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (selectedFragment != null) {
selectedFragment.hidePgpPassphraseBox();
}
+ } else if (requestCode == ATTACH_FILE) {
+ Conversation conversation = getSelectedConversation();
+ String presence = conversation.getNextPresence();
+ xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
+
}
- }
- }
+ }
+ }
public void updateConversationList() {
conversationList.clear();
- conversationList.addAll(xmppConnectionService
- .getConversations());
+ conversationList.addAll(xmppConnectionService.getConversations());
listView.invalidateViews();
}
+
+ public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) {
+ Contact contact = conversation.getContact();
+ if (contact==null) {
+ showAddToRosterDialog(conversation);
+ listener.onPresenceSelected(false,null);
+ } else {
+ Hashtable<String, Integer> presences = contact.getPresences();
+ if (presences.size() == 0) {
+ listener.onPresenceSelected(false, null);
+ } else if (presences.size() == 1) {
+ String presence = (String) presences.keySet().toArray()[0];
+ conversation.setNextPresence(presence);
+ listener.onPresenceSelected(true, presence);
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.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) {
+ String presence = presencesArray[which];
+ conversation.setNextPresence(presence);
+ listener.onPresenceSelected(true,presence);
+ }
+ });
+ builder.create().show();
+ }
+ }
+ }
+
+ private void showAddToRosterDialog(final Conversation conversation) {
+ String jid = conversation.getContactJid();
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(jid);
+ builder.setMessage(getString(R.string.not_in_roster));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String jid = conversation.getContactJid();
+ Account account = getSelectedConversation().getAccount();
+ String name = jid.split("@")[0];
+ Contact contact = new Contact(account, name, jid, null);
+ xmppConnectionService.createContact(contact);
+ }
+ });
+ builder.create().show();
+ }
}
diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java
index 51caafbd..f43f37a3 100644
--- a/src/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/eu/siacs/conversations/ui/ConversationFragment.java
@@ -33,6 +33,7 @@ import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -89,7 +90,6 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- Log.d("gultsch", "clicked to decrypt");
if (askForPassphraseIntent != null) {
try {
getActivity().startIntentSenderForResult(
@@ -97,7 +97,7 @@ public class ConversationFragment extends Fragment {
ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
0, 0);
} catch (SendIntentException e) {
- Log.d("gultsch", "couldnt fire intent");
+ Log.d("xmppService", "couldnt fire intent");
}
}
}
@@ -124,20 +124,20 @@ public class ConversationFragment extends Fragment {
public void updateChatMsgHint() {
if (conversation.getMode() == Conversation.MODE_MULTI) {
- chatMsg.setHint("Send message to conference");
+ chatMsg.setHint(getString(R.string.send_message_to_conference));
} else {
switch (conversation.nextMessageEncryption) {
case Message.ENCRYPTION_NONE:
- chatMsg.setHint("Send plain text message");
+ chatMsg.setHint(getString(R.string.send_plain_text_message));
break;
case Message.ENCRYPTION_OTR:
- chatMsg.setHint("Send OTR encrypted message");
+ chatMsg.setHint(getString(R.string.send_otr_message));
break;
case Message.ENCRYPTION_PGP:
- chatMsg.setHint("Send openPGP encryted messeage");
+ chatMsg.setHint(getString(R.string.send_pgp_message));
break;
case Message.ENCRYPTION_DECRYPTED:
- chatMsg.setHint("Send openPGP encryted messeage");
+ chatMsg.setHint(getString(R.string.send_pgp_message));
break;
default:
break;
@@ -149,6 +149,8 @@ public class ConversationFragment extends Fragment {
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
+ final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
this.inflater = inflater;
final View view = inflater.inflate(R.layout.fragment_conversation,
@@ -177,19 +179,16 @@ public class ConversationFragment extends Fragment {
private static final int SENT = 0;
private static final int RECIEVED = 1;
- private static final int ERROR = 2;
@Override
public int getViewTypeCount() {
- return 3;
+ return 2;
}
@Override
public int getItemViewType(int position) {
- if (getItem(position).getStatus() == Message.STATUS_RECIEVED) {
+ if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
return RECIEVED;
- } else if (getItem(position).getStatus() == Message.STATUS_ERROR) {
- return ERROR;
} else {
return SENT;
}
@@ -206,20 +205,19 @@ public class ConversationFragment extends Fragment {
case SENT:
view = (View) inflater.inflate(R.layout.message_sent,
null);
- viewHolder.imageView = (ImageView) view
+ viewHolder.contact_picture = (ImageView) view
.findViewById(R.id.message_photo);
- viewHolder.imageView.setImageBitmap(selfBitmap);
- viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
+ viewHolder.contact_picture.setImageBitmap(selfBitmap);
break;
case RECIEVED:
view = (View) inflater.inflate(
R.layout.message_recieved, null);
- viewHolder.imageView = (ImageView) view
+ viewHolder.contact_picture = (ImageView) view
.findViewById(R.id.message_photo);
- viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
+
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
- viewHolder.imageView.setImageBitmap(mBitmapCache
+ viewHolder.contact_picture.setImageBitmap(mBitmapCache
.get(item.getConversation().getName(useSubject), item
.getConversation().getContact(),
getActivity()
@@ -227,18 +225,12 @@ public class ConversationFragment extends Fragment {
}
break;
- case ERROR:
- view = (View) inflater.inflate(R.layout.message_error,
- null);
- viewHolder.imageView = (ImageView) view
- .findViewById(R.id.message_photo);
- viewHolder.imageView.setImageBitmap(mBitmapCache
- .getError());
- break;
default:
viewHolder = null;
break;
}
+ viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
+ viewHolder.image = (ImageView) view.findViewById(R.id.message_image);
viewHolder.messageBody = (TextView) view
.findViewById(R.id.message_body);
viewHolder.time = (TextView) view
@@ -250,52 +242,90 @@ public class ConversationFragment extends Fragment {
if (type == RECIEVED) {
if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
if (item.getCounterpart() != null) {
- viewHolder.imageView.setImageBitmap(mBitmapCache
+ viewHolder.contact_picture.setImageBitmap(mBitmapCache
.get(item.getCounterpart(), null,
getActivity()
.getApplicationContext()));
} else {
- viewHolder.imageView.setImageBitmap(mBitmapCache
+ viewHolder.contact_picture.setImageBitmap(mBitmapCache
.get(item.getConversation().getName(useSubject),
null, getActivity()
.getApplicationContext()));
}
}
}
- String body = item.getBody();
- if (body != null) {
- if (item.getEncryption() == Message.ENCRYPTION_PGP) {
- viewHolder.messageBody
- .setText(getString(R.string.encrypted_message));
+
+ if (item.getEncryption() == Message.ENCRYPTION_NONE) {
+ viewHolder.indicator.setVisibility(View.GONE);
+ } else {
+ viewHolder.indicator.setVisibility(View.VISIBLE);
+ }
+
+
+ if (item.getType() == Message.TYPE_IMAGE) {
+ if (item.getStatus() == Message.STATUS_PREPARING) {
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setText(getString(R.string.preparing_image));
+ viewHolder.messageBody.setTextColor(0xff33B5E5);
+ viewHolder.messageBody.setTypeface(null,Typeface.ITALIC);
+ } else if (item.getStatus() == Message.STATUS_RECIEVING) {
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setText(getString(R.string.receiving_image));
viewHolder.messageBody.setTextColor(0xff33B5E5);
- viewHolder.messageBody.setTypeface(null,
- Typeface.ITALIC);
- viewHolder.indicator.setVisibility(View.VISIBLE);
- } else if ((item.getEncryption() == Message.ENCRYPTION_OTR)||(item.getEncryption() == Message.ENCRYPTION_DECRYPTED)) {
- viewHolder.messageBody.setText(body.trim());
- viewHolder.messageBody.setTextColor(0xff000000);
- viewHolder.messageBody.setTypeface(null,
- Typeface.NORMAL);
- viewHolder.indicator.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setTypeface(null,Typeface.ITALIC);
} else {
- viewHolder.messageBody.setText(body.trim());
- viewHolder.messageBody.setTextColor(0xff000000);
- viewHolder.messageBody.setTypeface(null,
- Typeface.NORMAL);
- if (item.getStatus() != Message.STATUS_ERROR) {
- viewHolder.indicator.setVisibility(View.GONE);
- }
+ viewHolder.image.setImageBitmap(activity.xmppConnectionService.getFileBackend().getThumbnailFromMessage(item,(int) (metrics.density * 288)));
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.VISIBLE);
}
} else {
- viewHolder.indicator.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ String body = item.getBody();
+ if (body != null) {
+ if (item.getEncryption() == Message.ENCRYPTION_PGP) {
+ viewHolder.messageBody
+ .setText(getString(R.string.encrypted_message));
+ viewHolder.messageBody.setTextColor(0xff33B5E5);
+ viewHolder.messageBody.setTypeface(null,
+ Typeface.ITALIC);
+ } else if ((item.getEncryption() == Message.ENCRYPTION_OTR)||(item.getEncryption() == Message.ENCRYPTION_DECRYPTED)) {
+ viewHolder.messageBody.setText(body.trim());
+ viewHolder.messageBody.setTextColor(0xff333333);
+ viewHolder.messageBody.setTypeface(null,
+ Typeface.NORMAL);
+ } else {
+ viewHolder.messageBody.setText(body.trim());
+ viewHolder.messageBody.setTextColor(0xff333333);
+ viewHolder.messageBody.setTypeface(null,
+ Typeface.NORMAL);
+ }
+ }
}
- if (item.getStatus() == Message.STATUS_UNSEND) {
+ switch (item.getStatus()) {
+ case Message.STATUS_UNSEND:
viewHolder.time.setTypeface(null, Typeface.ITALIC);
+ viewHolder.time.setTextColor(0xFF8e8e8e);
viewHolder.time.setText("sending\u2026");
- } else {
+ break;
+ case Message.STATUS_SEND_FAILED:
+ viewHolder.time.setText(getString(R.string.send_failed) + " \u00B7 " + UIHelper.readableTimeDifference(item
+ .getTimeSent()));
+ viewHolder.time.setTextColor(0xFFe92727);
+ viewHolder.time.setTypeface(null,Typeface.NORMAL);
+ break;
+ case Message.STATUS_SEND_REJECTED:
+ viewHolder.time.setText(getString(R.string.send_rejected));
+ viewHolder.time.setTextColor(0xFFe92727);
+ viewHolder.time.setTypeface(null,Typeface.NORMAL);
+ break;
+ default:
viewHolder.time.setTypeface(null, Typeface.NORMAL);
- if ((item.getConversation().getMode() == Conversation.MODE_SINGLE)
- || (type != RECIEVED)) {
+ viewHolder.time.setTextColor(0xFF8e8e8e);
+ if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
viewHolder.time.setText(UIHelper
.readableTimeDifference(item.getTimeSent()));
} else {
@@ -304,6 +334,7 @@ public class ConversationFragment extends Fragment {
+ UIHelper.readableTimeDifference(item
.getTimeSent()));
}
+ break;
}
return view;
}
@@ -320,7 +351,7 @@ public class ConversationFragment extends Fragment {
boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
"show_phone_selfcontact_picture", true);
- return UIHelper.getSelfContactPicture(conversation.getAccount(), 200,
+ return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
showPhoneSelfContactPicture, getActivity());
}
@@ -372,11 +403,11 @@ public class ConversationFragment extends Fragment {
if (success) {
Toast.makeText(
getActivity(),
- "Your nickname has been changed",
+ getString(R.string.your_nick_has_been_changed),
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(),
- "Nichname is already in use",
+ getString(R.string.nick_in_use),
Toast.LENGTH_SHORT).show();
}
}
@@ -505,7 +536,7 @@ public class ConversationFragment extends Fragment {
getActivity());
builder.setTitle("No openPGP key found");
builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage("There is no openPGP key assoziated with this contact");
+ builder.setMessage("There is no openPGP key associated with this contact");
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Send plain text",
new DialogInterface.OnClickListener() {
@@ -585,10 +616,11 @@ public class ConversationFragment extends Fragment {
private static class ViewHolder {
+ protected ImageView image;
protected ImageView indicator;
protected TextView time;
protected TextView messageBody;
- protected ImageView imageView;
+ protected ImageView contact_picture;
}
@@ -600,7 +632,12 @@ public class ConversationFragment extends Fragment {
if (bitmaps.containsKey(name)) {
return bitmaps.get(name);
} else {
- Bitmap bm = UIHelper.getContactPicture(contact, name, 200, context);
+ Bitmap bm;
+ if (contact != null){
+ bm = UIHelper.getContactPicture(contact, 48, context, false);
+ } else {
+ bm = UIHelper.getContactPicture(name, 48, context, false);
+ }
bitmaps.put(name, bm);
return bm;
}
diff --git a/src/eu/siacs/conversations/ui/MucDetailsActivity.java b/src/eu/siacs/conversations/ui/MucDetailsActivity.java
index bf36f156..c6807c61 100644
--- a/src/eu/siacs/conversations/ui/MucDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/MucDetailsActivity.java
@@ -179,7 +179,7 @@ public class MucDetailsActivity extends XmppActivity {
role.setText(getReadableRole(contact.getRole()));
ImageView imageView = (ImageView) view
.findViewById(R.id.contact_photo);
- imageView.setImageBitmap(UIHelper.getContactPicture(null,contact.getName(), 90,this.getApplicationContext()));
+ imageView.setImageBitmap(UIHelper.getContactPicture(contact.getName(), 48,this.getApplicationContext(), false));
membersView.addView(view);
}
}
diff --git a/src/eu/siacs/conversations/ui/OnPresenceSelected.java b/src/eu/siacs/conversations/ui/OnPresenceSelected.java
new file mode 100644
index 00000000..7e424b2e
--- /dev/null
+++ b/src/eu/siacs/conversations/ui/OnPresenceSelected.java
@@ -0,0 +1,5 @@
+package eu.siacs.conversations.ui;
+
+public interface OnPresenceSelected {
+ public void onPresenceSelected(boolean success, String presence);
+}
diff --git a/src/eu/siacs/conversations/ui/ShareWithActivity.java b/src/eu/siacs/conversations/ui/ShareWithActivity.java
index 51bad721..1bc9fc46 100644
--- a/src/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -81,7 +81,10 @@ public class ShareWithActivity extends XmppActivity {
}
});
for(final Conversation conversation : convList) {
- View view = createContactView(conversation.getName(useSubject), conversation.getLatestMessage().getBody().trim(), UIHelper.getContactPicture(conversation.getContact(),conversation.getName(useSubject), 90,this.getApplicationContext()));
+ View view = createContactView(conversation.getName(useSubject),
+ conversation.getLatestMessage().getBody().trim(),
+ UIHelper.getContactPicture(conversation, 48,
+ this.getApplicationContext(), false));
view.setOnClickListener(new OnClickListener() {
@Override
@@ -115,7 +118,8 @@ public class ShareWithActivity extends XmppActivity {
for(int i = 0; i < contactsList.size(); ++i) {
final Contact con = contactsList.get(i);
- View view = createContactView(con.getDisplayName(), con.getJid(), UIHelper.getContactPicture(con,null, 90,this.getApplicationContext()));
+ View view = createContactView(con.getDisplayName(), con.getJid(),
+ UIHelper.getContactPicture(con, 48, this.getApplicationContext(), false));
view.setOnClickListener(new OnClickListener() {
@Override
diff --git a/src/eu/siacs/conversations/utils/ExceptionHelper.java b/src/eu/siacs/conversations/utils/ExceptionHelper.java
index c6e857c0..f9cd375e 100644
--- a/src/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -34,6 +34,18 @@ public class ExceptionHelper {
if (neverSend) {
return;
}
+ List<Account> accounts = service.getAccounts();
+ Account account = null;
+ for(int i = 0; i < accounts.size(); ++i) {
+ if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) {
+ account = accounts.get(i);
+ break;
+ }
+ }
+ if (account==null) {
+ return;
+ }
+ final Account finalAccount = account;
FileInputStream file = context.openFileInput("stacktrace.txt");
InputStreamReader inputStreamReader = new InputStreamReader(
file);
@@ -54,20 +66,11 @@ public class ExceptionHelper {
@Override
public void onClick(DialogInterface dialog, int which) {
- List<Account> accounts = service.getAccounts();
- Account account = null;
- for(int i = 0; i < accounts.size(); ++i) {
- if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) {
- account = accounts.get(i);
- break;
- }
- }
- if (account!=null) {
- Log.d("xmppService","using account="+account.getJid()+" to send in stack trace");
- Conversation conversation = service.findOrCreateConversation(account, "bugs@siacs.eu", false);
+
+ Log.d("xmppService","using account="+finalAccount.getJid()+" to send in stack trace");
+ Conversation conversation = service.findOrCreateConversation(finalAccount, "bugs@siacs.eu", false);
Message message = new Message(conversation, stacktrace.toString(), Message.ENCRYPTION_NONE);
service.sendMessage(message, null);
- }
}
});
builder.setNegativeButton(context.getText(R.string.send_never),new OnClickListener() {
diff --git a/src/eu/siacs/conversations/utils/MessageParser.java b/src/eu/siacs/conversations/utils/MessageParser.java
index 936d0e9a..568386d5 100644
--- a/src/eu/siacs/conversations/utils/MessageParser.java
+++ b/src/eu/siacs/conversations/utils/MessageParser.java
@@ -149,19 +149,9 @@ public class MessageParser {
return new Message(conversation,fullJid, message.findChild("body").getContent(), Message.ENCRYPTION_NONE,status);
}
- public static Message parseError(MessagePacket packet, Account account, XmppConnectionService service) {
-
- String[] fromParts = packet.getFrom().split("/");
- Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
- Element error = packet.findChild("error");
- String errorName = error.getChildren().get(0).getName();
- String displayError;
- if (errorName.equals("service-unavailable")) {
- displayError = "Contact is offline and does not have offline storage";
- } else {
- displayError = errorName.replace("-", " ");
- }
- return new Message(conversation, packet.getFrom(), displayError, Message.ENCRYPTION_NONE, Message.STATUS_ERROR);
+ public static void parseError(MessagePacket packet, Account account, XmppConnectionService service) {
+ String[] fromParts = packet.getFrom().split("/");
+ service.markMessage(account, fromParts[0], packet.getId(), Message.STATUS_SEND_FAILED);
}
public static String getPgpBody(MessagePacket packet) {
diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java
index 5c652afe..6355e378 100644
--- a/src/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/eu/siacs/conversations/utils/PhoneHelper.java
@@ -13,18 +13,19 @@ import android.os.Bundle;
import android.os.Looper;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Profile;
+import android.provider.MediaStore;
public class PhoneHelper {
-
- public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) {
- if (Looper.myLooper()==null) {
+
+ 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,
+
+ final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.PHOTO_THUMBNAIL_URI,
ContactsContract.Data.LOOKUP_KEY,
@@ -35,7 +36,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);
@@ -55,14 +56,14 @@ public class PhoneHelper {
"photouri",
cursor.getString(cursor
.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
- contact.putString("lookup",cursor.getString(cursor
+ contact.putString("lookup", cursor.getString(cursor
.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
phoneContacts.put(
cursor.getString(cursor
.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
contact);
}
- if (listener!=null) {
+ if (listener != null) {
listener.onPhoneContactsLoaded(phoneContacts);
}
mLooper.quit();
@@ -71,18 +72,18 @@ public class PhoneHelper {
mCursorLoader.startLoading();
}
- public static Uri getSefliUri(Activity activity) {
+ public static Uri getSefliUri(Context context) {
String[] mProjection = new String[] { Profile._ID,
Profile.PHOTO_THUMBNAIL_URI };
- Cursor mProfileCursor = activity.getContentResolver().query(
+ Cursor mProfileCursor = context.getContentResolver().query(
Profile.CONTENT_URI, mProjection, null, null, null);
- if (mProfileCursor.getCount()==0) {
+ if (mProfileCursor.getCount() == 0) {
return null;
} else {
mProfileCursor.moveToFirst();
String uri = mProfileCursor.getString(1);
- if (uri==null) {
+ if (uri == null) {
return null;
} else {
return Uri.parse(uri);
diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java
index 73a0494b..f058b9ee 100644
--- a/src/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/eu/siacs/conversations/utils/UIHelper.java
@@ -12,6 +12,8 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.MucOptions;
+import eu.siacs.conversations.entities.MucOptions.User;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
@@ -31,6 +33,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.Typeface;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Contacts;
@@ -38,6 +41,7 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.InboxStyle;
import android.support.v4.app.TaskStackBuilder;
import android.text.Html;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,6 +50,10 @@ import android.widget.QuickContactBadge;
import android.widget.TextView;
public class UIHelper {
+ private static final int BG_COLOR = 0xFF181818;
+ private static final int FG_COLOR = 0xFFE5E5E5;
+ private static final int TRANSPARENT = 0x00000000;
+
public static String readableTimeDifference(long time) {
if (time == 0) {
return "just now";
@@ -65,49 +73,235 @@ public class UIHelper {
}
}
- private static Bitmap getUnknownContactPicture(String name, int size) {
- String firstLetter = (name.length() > 0) ? name.substring(0, 1).toUpperCase(Locale.US) : " ";
+ public static int getRealPx(int dp, Context context) {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ return ((int) (dp * metrics.density));
+ }
+ private static int getNameColor(String name) {
int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713,
0xFFe92727 };
-
int color = holoColors[Math.abs(name.toLowerCase(Locale.getDefault()).hashCode()) % holoColors.length];
+ return color;
+ }
+ private static Bitmap getUnknownContactPicture(String[] names, int size, int bgColor, int fgColor) {
+ int tiles = (names.length > 4)? 4 :
+ (names.length < 1)? 1 :
+ names.length;
Bitmap bitmap = Bitmap
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
- bitmap.eraseColor(color);
+ String[] letters = new String[tiles];
+ int[] colors = new int[tiles];
+ if (names.length < 1) {
+ letters[0] = "?";
+ colors[0] = 0xFFe92727;
+ } else {
+ for(int i = 0; i < tiles; ++i) {
+ letters[i] = (names[i].length() > 0) ?
+ names[i].substring(0, 1).toUpperCase(Locale.US) : " ";
+ colors[i] = getNameColor(names[i]);
+ }
- Paint paint = new Paint();
- paint.setColor(0xffe5e5e5);
- paint.setTextSize((float) (size * 0.9));
- paint.setAntiAlias(true);
- Rect rect = new Rect();
- paint.getTextBounds(firstLetter, 0, 1, rect);
- float width = paint.measureText(firstLetter);
- canvas.drawText(firstLetter, (size / 2) - (width / 2), (size / 2)
- + (rect.height() / 2), paint);
+ if (names.length > 4) {
+ letters[3] = "\u2026"; // Unicode ellipsis
+ colors[3] = 0xFF444444;
+ }
+ }
+ Paint textPaint = new Paint(), tilePaint = new Paint();
+ textPaint.setColor(fgColor);
+ textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+ Rect rect, left, right, topLeft, bottomLeft, topRight, bottomRight;
+ float width;
+
+ switch(tiles) {
+ case 1:
+ bitmap.eraseColor(colors[0]);
+
+ textPaint.setTextSize((float) (size * 0.8));
+ textPaint.setAntiAlias(true);
+ rect = new Rect();
+ textPaint.getTextBounds(letters[0], 0, 1, rect);
+ width = textPaint.measureText(letters[0]);
+ canvas.drawText(letters[0], (size / 2) - (width / 2), (size / 2)
+ + (rect.height() / 2), textPaint);
+ break;
+
+ case 2:
+ bitmap.eraseColor(bgColor);
+
+ tilePaint.setColor(colors[0]);
+ left = new Rect(0, 0, (size/2)-1, size);
+ canvas.drawRect(left, tilePaint);
+
+ tilePaint.setColor(colors[1]);
+ right = new Rect((size/2)+1, 0, size, size);
+ canvas.drawRect(right, tilePaint);
+
+ textPaint.setTextSize((float) (size * 0.8*0.5));
+ textPaint.setAntiAlias(true);
+ rect = new Rect();
+ textPaint.getTextBounds(letters[0], 0, 1, rect);
+ width = textPaint.measureText(letters[0]);
+ canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2)
+ + (rect.height() / 2), textPaint);
+ textPaint.getTextBounds(letters[1], 0, 1, rect);
+ width = textPaint.measureText(letters[1]);
+ canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 2)
+ + (rect.height() / 2), textPaint);
+ break;
+
+ case 3:
+ bitmap.eraseColor(bgColor);
+
+ tilePaint.setColor(colors[0]);
+ left = new Rect(0, 0, (size/2)-1, size);
+ canvas.drawRect(left, tilePaint);
+
+ tilePaint.setColor(colors[1]);
+ topRight = new Rect((size/2)+1, 0, size, (size/2 - 1));
+ canvas.drawRect(topRight, tilePaint);
+
+ tilePaint.setColor(colors[2]);
+ bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size);
+ canvas.drawRect(bottomRight, tilePaint);
+
+ textPaint.setTextSize((float) (size * 0.8*0.5));
+ textPaint.setAntiAlias(true);
+ rect = new Rect();
+
+ textPaint.getTextBounds(letters[0], 0, 1, rect);
+ width = textPaint.measureText(letters[0]);
+ canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 2)
+ + (rect.height() / 2), textPaint);
+
+ textPaint.getTextBounds(letters[1], 0, 1, rect);
+ width = textPaint.measureText(letters[1]);
+ canvas.drawText(letters[1], (3 * size / 4) - (width / 2), (size / 4)
+ + (rect.height() / 2), textPaint);
+
+ textPaint.getTextBounds(letters[2], 0, 1, rect);
+ width = textPaint.measureText(letters[2]);
+ canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (3* size / 4)
+ + (rect.height() / 2), textPaint);
+ break;
+
+ case 4:
+ bitmap.eraseColor(bgColor);
+
+ tilePaint.setColor(colors[0]);
+ topLeft = new Rect(0, 0, (size/2)-1, (size/2)-1);
+ canvas.drawRect(topLeft, tilePaint);
+
+ tilePaint.setColor(colors[1]);
+ bottomLeft = new Rect(0, (size/2)+1, (size/2)-1, size);
+ canvas.drawRect(bottomLeft, tilePaint);
+ tilePaint.setColor(colors[2]);
+ topRight = new Rect((size/2)+1, 0, size, (size/2 - 1));
+ canvas.drawRect(topRight, tilePaint);
+
+ tilePaint.setColor(colors[3]);
+ bottomRight = new Rect((size/2)+1, (size/2 + 1), size, size);
+ canvas.drawRect(bottomRight, tilePaint);
+
+ textPaint.setTextSize((float) (size * 0.8*0.5));
+ textPaint.setAntiAlias(true);
+ rect = new Rect();
+
+ textPaint.getTextBounds(letters[0], 0, 1, rect);
+ width = textPaint.measureText(letters[0]);
+ canvas.drawText(letters[0], (size / 4) - (width / 2), (size / 4)
+ + (rect.height() / 2), textPaint);
+
+ textPaint.getTextBounds(letters[1], 0, 1, rect);
+ width = textPaint.measureText(letters[1]);
+ canvas.drawText(letters[1], (size / 4) - (width / 2), (3* size / 4)
+ + (rect.height() / 2), textPaint);
+
+ textPaint.getTextBounds(letters[2], 0, 1, rect);
+ width = textPaint.measureText(letters[2]);
+ canvas.drawText(letters[2], (3 * size / 4) - (width / 2), (size / 4)
+ + (rect.height() / 2), textPaint);
+
+ textPaint.getTextBounds(letters[3], 0, 1, rect);
+ width = textPaint.measureText(letters[3]);
+ canvas.drawText(letters[3], (3 * size / 4) - (width / 2), (3* size / 4)
+ + (rect.height() / 2), textPaint);
+ break;
+ }
return bitmap;
}
-
- public static Bitmap getContactPicture(Contact contact, String fallback, int size, Context context) {
- if (contact==null) {
- return getUnknownContactPicture(fallback, size);
+
+ private static Bitmap getUnknownContactPicture(String[] names, int size) {
+ return getUnknownContactPicture(names, size, UIHelper.BG_COLOR, UIHelper.FG_COLOR);
+ }
+
+ private static Bitmap getMucContactPicture(Conversation conversation, int size, int bgColor, int fgColor) {
+ List<User> members = conversation.getMucOptions().getUsers();
+ if (members.size() == 0) {
+ return getUnknownContactPicture(new String[]{conversation.getName(false)}, size, bgColor, fgColor);
+ }
+ String[] names = new String[members.size()+1];
+ names[0] = conversation.getMucOptions().getNick();
+ for(int i = 0; i < members.size(); ++i) {
+ names[i+1] = members.get(i).getName();
+ }
+
+ return getUnknownContactPicture(names, size, bgColor, fgColor);
+ }
+
+ public static Bitmap getContactPicture(Conversation conversation, int dpSize, Context context, boolean notification) {
+ if(conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (conversation.getContact() != null){
+ return getContactPicture(conversation.getContact(), dpSize,
+ context, notification);
+ } else {
+ return getContactPicture(conversation.getName(false), dpSize,
+ context, notification);
+ }
+ } else{
+ int fgColor = UIHelper.FG_COLOR,
+ bgColor = (notification) ?
+ UIHelper.BG_COLOR : UIHelper.TRANSPARENT;
+
+ return getMucContactPicture(conversation, getRealPx(dpSize, context),
+ bgColor, fgColor);
}
+ }
+
+ public static Bitmap getContactPicture(Contact contact, int dpSize, Context context, boolean notification) {
+ int fgColor = UIHelper.FG_COLOR,
+ bgColor = (notification) ?
+ UIHelper.BG_COLOR : UIHelper.TRANSPARENT;
+
String uri = contact.getProfilePhoto();
if (uri==null) {
- return getUnknownContactPicture(contact.getDisplayName(), size);
+ return getContactPicture(contact.getDisplayName(), dpSize,
+ context, notification);
}
try {
- Bitmap bm = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(Uri.parse(uri)));
- return Bitmap.createScaledBitmap(bm, size, size, false);
+ Bitmap bm = BitmapFactory.decodeStream(context.getContentResolver()
+ .openInputStream(Uri.parse(uri)));
+ return Bitmap.createScaledBitmap(bm, getRealPx(dpSize, context),
+ getRealPx(dpSize, context), false);
} catch (FileNotFoundException e) {
- return getUnknownContactPicture(contact.getDisplayName(), size);
+ return getContactPicture(contact.getDisplayName(), dpSize,
+ context, notification);
}
}
+ public static Bitmap getContactPicture(String name, int dpSize, Context context, boolean notification) {
+ int fgColor = UIHelper.FG_COLOR,
+ bgColor = (notification) ?
+ UIHelper.BG_COLOR : UIHelper.TRANSPARENT;
+
+ return getUnknownContactPicture(new String[]{name}, getRealPx(dpSize, context),
+ bgColor, fgColor);
+ }
+
public static Bitmap getErrorPicture(int size) {
Bitmap bitmap = Bitmap
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
@@ -203,7 +397,6 @@ public class UIHelper {
}
String ringtone = preferences.getString("notification_ringtone", null);
- Resources res = context.getResources();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
context);
if (unread.size() == 0) {
@@ -212,8 +405,8 @@ public class UIHelper {
} else if (unread.size() == 1) {
Conversation conversation = unread.get(0);
targetUuid = conversation.getUuid();
- mBuilder.setLargeIcon(UIHelper.getContactPicture(conversation.getContact(), conversation.getName(useSubject), (int) res
- .getDimension(android.R.dimen.notification_large_icon_width), context));
+ mBuilder.setLargeIcon(UIHelper.getContactPicture(conversation, 64,
+ context, true));
mBuilder.setContentTitle(conversation.getName(useSubject));
if (notify) {
mBuilder.setTicker(conversation.getLatestMessage().getBody().trim());
@@ -255,12 +448,15 @@ public class UIHelper {
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
}
+ if ((currentCon!=null)&&(notify)) {
+ targetUuid=currentCon.getUuid();
+ }
if (unread.size() != 0) {
mBuilder.setSmallIcon(R.drawable.notification);
if (notify) {
if (vibrate) {
int dat = 70;
- long[] pattern = {0,3*dat,dat,dat,dat,3*dat,dat,dat};
+ long[] pattern = {0,3*dat,dat,dat};
mBuilder.setVibrate(pattern);
}
mBuilder.setLights(0xffffffff, 2000, 4000);
@@ -307,23 +503,13 @@ public class UIHelper {
}
public static void prepareContactBadge(final Activity activity,
- QuickContactBadge badge, final Contact contact) {
+ QuickContactBadge badge, final Contact contact, Context context) {
if (contact.getSystemAccount() != null) {
String[] systemAccount = contact.getSystemAccount().split("#");
long id = Long.parseLong(systemAccount[0]);
badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
-
- if (contact.getProfilePhoto() != null) {
- badge.setImageURI(Uri.parse(contact.getProfilePhoto()));
- } else {
- badge.setImageBitmap(UIHelper.getUnknownContactPicture(
- contact.getDisplayName(), 400));
- }
- } else {
- badge.setImageBitmap(UIHelper.getUnknownContactPicture(
- contact.getDisplayName(), 400));
}
-
+ badge.setImageBitmap(UIHelper.getContactPicture(contact, 72, context, false));
}
public static AlertDialog getVerifyFingerprintDialog(
@@ -359,20 +545,20 @@ public class UIHelper {
return builder.create();
}
- public static Bitmap getSelfContactPicture(Account account, int size, boolean showPhoneSelfContactPicture, Activity activity) {
+ public static Bitmap getSelfContactPicture(Account account, int size, boolean showPhoneSelfContactPicture, Context context) {
if (showPhoneSelfContactPicture) {
- Uri selfiUri = PhoneHelper.getSefliUri(activity);
+ Uri selfiUri = PhoneHelper.getSefliUri(context);
if (selfiUri != null) {
try {
- return BitmapFactory.decodeStream(activity
+ return BitmapFactory.decodeStream(context
.getContentResolver().openInputStream(selfiUri));
} catch (FileNotFoundException e) {
- return getUnknownContactPicture(account.getJid(), size);
+ return getContactPicture(account.getJid(), size, context, false);
}
}
- return getUnknownContactPicture(account.getJid(), size);
+ return getContactPicture(account.getJid(), size, context, false);
} else {
- return getUnknownContactPicture(account.getJid(), size);
+ return getContactPicture(account.getJid(), size, context, false);
}
}
}
diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java
index 2f1d7ad8..ce1d10ce 100644
--- a/src/eu/siacs/conversations/xml/Element.java
+++ b/src/eu/siacs/conversations/xml/Element.java
@@ -139,4 +139,8 @@ public class Element {
content = content.replace("'","&apos;");
return content;
}
+
+ public void clearChildren() {
+ this.children.clear();
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/PacketReceived.java b/src/eu/siacs/conversations/xmpp/PacketReceived.java
index d9f42459..d4502d73 100644
--- a/src/eu/siacs/conversations/xmpp/PacketReceived.java
+++ b/src/eu/siacs/conversations/xmpp/PacketReceived.java
@@ -1,5 +1,5 @@
package eu.siacs.conversations.xmpp;
-abstract interface PacketReceived {
+public abstract interface PacketReceived {
}
diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java
index 1ca7cd8b..adb27ec8 100644
--- a/src/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -16,9 +16,13 @@ import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
@@ -44,11 +48,12 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Tag;
import eu.siacs.conversations.xml.TagWriter;
import eu.siacs.conversations.xml.XmlReader;
+import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
-import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
@@ -70,8 +75,9 @@ public class XmppConnection implements Runnable {
private boolean shouldBind = true;
private boolean shouldAuthenticate = true;
private Element streamFeatures;
- private HashSet<String> discoFeatures = new HashSet<String>();
- private List<String> discoItems = new ArrayList<String>();
+ private HashMap<String, List<String>> disco = new HashMap<String, List<String>>();
+
+ private HashSet<String> pendingSubscriptions = new HashSet<String>();
private String streamId = null;
private int smVersion = 3;
@@ -644,8 +650,8 @@ public class XmppConnection implements Runnable {
tagWriter.writeStanzaAsync(enable);
}
sendInitialPresence();
- sendServiceDiscoveryInfo();
- sendServiceDiscoveryItems();
+ sendServiceDiscoveryInfo(account.getServer());
+ sendServiceDiscoveryItems(account.getServer());
if (bindListener !=null) {
bindListener.onBind(account);
}
@@ -654,42 +660,53 @@ public class XmppConnection implements Runnable {
});
}
- private void sendServiceDiscoveryInfo() {
+ private void sendServiceDiscoveryInfo(final String server) {
IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
- iq.setTo(account.getServer());
+ iq.setTo(server);
iq.query("http://jabber.org/protocol/disco#info");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- List<Element> elements = packet.query().getChildren();
- for (int i = 0; i < elements.size(); ++i) {
- if (elements.get(i).getName().equals("feature")) {
- discoFeatures.add(elements.get(i).getAttribute(
- "var"));
- }
+ List<Element> elements = packet.query().getChildren();
+ List<String> features = new ArrayList<String>();
+ for (int i = 0; i < elements.size(); ++i) {
+ if (elements.get(i).getName().equals("feature")) {
+ features.add(elements.get(i).getAttribute(
+ "var"));
}
- if (discoFeatures.contains("urn:xmpp:carbons:2")) {
- sendEnableCarbons();
+ }
+ disco.put(server, features);
+
+ if (account.getServer().equals(server)) {
+ enableAdvancedStreamFeatures();
}
}
});
}
- private void sendServiceDiscoveryItems() {
+
+ private void enableAdvancedStreamFeatures() {
+ if (hasFeaturesCarbon()) {
+ sendEnableCarbons();
+ }
+ }
+
+ private void sendServiceDiscoveryItems(final String server) {
IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
- iq.setTo(account.getServer());
+ iq.setTo(server);
iq.query("http://jabber.org/protocol/disco#items");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- List<Element> elements = packet.query().getChildren();
- for (int i = 0; i < elements.size(); ++i) {
- if (elements.get(i).getName().equals("item")) {
- discoItems.add(elements.get(i).getAttribute(
- "jid"));
- }
+ List<Element> elements = packet.query().getChildren();
+ for (int i = 0; i < elements.size(); ++i) {
+ if (elements.get(i).getName().equals("item")) {
+ String jid = elements.get(i).getAttribute(
+ "jid");
+ sendServiceDiscoveryInfo(jid);
}
+ }
}
});
}
@@ -732,8 +749,10 @@ public class XmppConnection implements Runnable {
}
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
- String id = nextRandomId();
- packet.setAttribute("id", id);
+ if (packet.getId()==null) {
+ String id = nextRandomId();
+ packet.setAttribute("id", id);
+ }
packet.setFrom(account.getFullJid());
this.sendPacket(packet, callback);
}
@@ -850,7 +869,26 @@ public class XmppConnection implements Runnable {
}
public boolean hasFeaturesCarbon() {
- return discoFeatures.contains("urn:xmpp:carbons:2");
+ return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2");
+ }
+
+ public boolean hasDiscoFeature(String server, String feature) {
+ if (!disco.containsKey(server)) {
+ return false;
+ }
+ return disco.get(server).contains(feature);
+ }
+
+ public String findDiscoItemByFeature(String feature) {
+ Iterator<Entry<String, List<String>>> it = this.disco.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, List<String>> pairs = it.next();
+ if (pairs.getValue().contains(feature)) {
+ return pairs.getKey();
+ }
+ it.remove();
+ }
+ return null;
}
public void r() {
@@ -866,15 +904,15 @@ public class XmppConnection implements Runnable {
}
public String getMucServer() {
- for(int i = 0; i < discoItems.size(); ++i) {
- if (discoItems.get(i).contains("conference.")) {
- return discoItems.get(i);
- } else if (discoItems.get(i).contains("conf.")) {
- return discoItems.get(i);
- } else if (discoItems.get(i).contains("muc.")) {
- return discoItems.get(i);
- }
- }
- return null;
+ return findDiscoItemByFeature("http://jabber.org/protocol/muc");
+ }
+
+ public boolean hasPendingSubscription(String jid) {
+ return this.pendingSubscriptions.contains(jid);
+ }
+
+ public void addPendingSubscription(String jid) {
+ Log.d(LOGTAG,"adding "+jid+" to pending subscriptions");
+ this.pendingSubscriptions.add(jid);
}
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java
new file mode 100644
index 00000000..80ffeaaa
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java
@@ -0,0 +1,138 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.xml.Element;
+
+public class JingleCandidate {
+
+ public static int TYPE_UNKNOWN;
+ public static int TYPE_DIRECT = 0;
+ public static int TYPE_PROXY = 1;
+
+ private boolean ours;
+ private boolean usedByCounterpart = false;
+ private String cid;
+ private String host;
+ private int port;
+ private int type;
+ private String jid;
+ private int priority;
+
+ public JingleCandidate(String cid,boolean ours) {
+ this.ours = ours;
+ this.cid = cid;
+ }
+
+ public String getCid() {
+ return cid;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getHost() {
+ return this.host;
+ }
+
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ public String getJid() {
+ return this.jid;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public int getPort() {
+ return this.port;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public void setType(String type) {
+ if ("proxy".equals(type)) {
+ this.type = TYPE_PROXY;
+ } else if ("direct".equals(type)) {
+ this.type = TYPE_DIRECT;
+ } else {
+ this.type = TYPE_UNKNOWN;
+ }
+ }
+
+ public void setPriority(int i) {
+ this.priority = i;
+ }
+
+ public int getPriority() {
+ return this.priority;
+ }
+
+ public boolean equals(JingleCandidate other) {
+ return this.getCid().equals(other.getCid());
+ }
+
+ public boolean equalValues(JingleCandidate other) {
+ return other.getHost().equals(this.getHost())&&(other.getPort()==this.getPort());
+ }
+
+ public boolean isOurs() {
+ return ours;
+ }
+
+ public int getType() {
+ return this.type;
+ }
+
+ public static List<JingleCandidate> parse(List<Element> canditates) {
+ List<JingleCandidate> parsedCandidates = new ArrayList<JingleCandidate>();
+ for(Element c : canditates) {
+ parsedCandidates.add(JingleCandidate.parse(c));
+ }
+ return parsedCandidates;
+ }
+
+ public static JingleCandidate parse(Element candidate) {
+ JingleCandidate parsedCandidate = new JingleCandidate(candidate.getAttribute("cid"), false);
+ parsedCandidate.setHost(candidate.getAttribute("host"));
+ parsedCandidate.setJid(candidate.getAttribute("jid"));
+ parsedCandidate.setType(candidate.getAttribute("type"));
+ parsedCandidate.setPriority(Integer.parseInt(candidate.getAttribute("priority")));
+ parsedCandidate.setPort(Integer.parseInt(candidate.getAttribute("port")));
+ return parsedCandidate;
+ }
+
+ public Element toElement() {
+ Element element = new Element("candidate");
+ element.setAttribute("cid", this.getCid());
+ element.setAttribute("host", this.getHost());
+ element.setAttribute("port", ""+this.getPort());
+ element.setAttribute("jid", this.getJid());
+ element.setAttribute("priority",""+this.getPriority());
+ if (this.getType()==TYPE_DIRECT) {
+ element.setAttribute("type","direct");
+ } else if (this.getType()==TYPE_PROXY) {
+ element.setAttribute("type","proxy");
+ }
+ return element;
+ }
+
+ public void flagAsUsedByCounterpart() {
+ this.usedByCounterpart = true;
+ }
+
+ public boolean isUsedByCounterpart() {
+ return this.usedByCounterpart;
+ }
+
+ public String toString() {
+ return this.getHost()+":"+this.getPort()+" (prio="+this.getPriority()+")";
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
new file mode 100644
index 00000000..a7fd367d
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -0,0 +1,494 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import android.util.Log;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class JingleConnection {
+
+ private JingleConnectionManager mJingleConnectionManager;
+ private XmppConnectionService mXmppConnectionService;
+
+ public static final int STATUS_INITIATED = 0;
+ public static final int STATUS_ACCEPTED = 1;
+ public static final int STATUS_TERMINATED = 2;
+ public static final int STATUS_CANCELED = 3;
+ public static final int STATUS_FINISHED = 4;
+ public static final int STATUS_TRANSMITTING = 5;
+ public static final int STATUS_FAILED = 99;
+
+ private int status = -1;
+ private Message message;
+ private String sessionId;
+ private Account account;
+ private String initiator;
+ private String responder;
+ private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
+ private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
+
+ private String transportId;
+ private Element fileOffer;
+ private JingleFile file = null;
+
+ private boolean receivedCandidateError = false;
+
+ private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE_ERROR) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ status = STATUS_FAILED;
+ }
+ }
+ };
+
+ public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
+ this.mJingleConnectionManager = mJingleConnectionManager;
+ this.mXmppConnectionService = mJingleConnectionManager.getXmppConnectionService();
+ }
+
+ public String getSessionId() {
+ return this.sessionId;
+ }
+
+ public String getAccountJid() {
+ return this.account.getFullJid();
+ }
+
+ public String getCounterPart() {
+ return this.message.getCounterpart();
+ }
+
+ public void deliverPacket(JinglePacket packet) {
+
+ if (packet.isAction("session-terminate")) {
+ Reason reason = packet.getReason();
+ if (reason!=null) {
+ if (reason.hasChild("cancel")) {
+ this.cancel();
+ } else if (reason.hasChild("success")) {
+ this.finish();
+ }
+ } else {
+ Log.d("xmppService","remote terminated for no reason");
+ this.cancel();
+ }
+ } else if (packet.isAction("session-accept")) {
+ accept(packet);
+ } else if (packet.isAction("transport-info")) {
+ transportInfo(packet);
+ } else {
+ Log.d("xmppService","packet arrived in connection. action was "+packet.getAction());
+ }
+ }
+
+ public void init(Message message) {
+ this.message = message;
+ this.account = message.getConversation().getAccount();
+ this.initiator = this.account.getFullJid();
+ this.responder = this.message.getCounterpart();
+ this.sessionId = this.mJingleConnectionManager.nextRandomId();
+ if (this.candidates.size() > 0) {
+ this.sendInitRequest();
+ } else {
+ this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
+
+ @Override
+ public void onPrimaryCandidateFound(boolean success, JingleCandidate candidate) {
+ if (success) {
+ mergeCandidate(candidate);
+ }
+ openOurCandidates();
+ sendInitRequest();
+ }
+ });
+ }
+
+ }
+
+ public void init(Account account, JinglePacket packet) {
+ this.status = STATUS_INITIATED;
+ Conversation conversation = this.mXmppConnectionService.findOrCreateConversation(account, packet.getFrom().split("/")[0], false);
+ this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE);
+ this.message.setType(Message.TYPE_IMAGE);
+ this.message.setStatus(Message.STATUS_RECIEVING);
+ String[] fromParts = packet.getFrom().split("/");
+ this.message.setPresence(fromParts[1]);
+ this.account = account;
+ this.initiator = packet.getFrom();
+ this.responder = this.account.getFullJid();
+ this.sessionId = packet.getSessionId();
+ Content content = packet.getJingleContent();
+ this.transportId = content.getTransportId();
+ this.mergeCandidates(JingleCandidate.parse(content.getCanditates()));
+ this.fileOffer = packet.getJingleContent().getFileOffer();
+ if (fileOffer!=null) {
+ this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
+ Element fileSize = fileOffer.findChild("size");
+ Element fileName = fileOffer.findChild("name");
+ this.file.setExpectedSize(Long.parseLong(fileSize.getContent()));
+ conversation.getMessages().add(message);
+ this.mXmppConnectionService.databaseBackend.createMessage(message);
+ if (this.mXmppConnectionService.convChangedListener!=null) {
+ this.mXmppConnectionService.convChangedListener.onConversationListChanged();
+ }
+ if (this.file.getExpectedSize()>=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
+ Log.d("xmppService","auto accepting file from "+packet.getFrom());
+ this.sendAccept();
+ } else {
+ Log.d("xmppService","not auto accepting new file offer with size: "+this.file.getExpectedSize()+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
+ }
+ } else {
+ Log.d("xmppService","no file offer was attached. aborting");
+ }
+ }
+
+ private void sendInitRequest() {
+ JinglePacket packet = this.bootstrapPacket("session-initiate");
+ Content content = new Content();
+ if (message.getType() == Message.TYPE_IMAGE) {
+ content.setAttribute("creator", "initiator");
+ content.setAttribute("name", "a-file-offer");
+ this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
+ content.setFileOffer(this.file);
+ this.transportId = this.mJingleConnectionManager.nextRandomId();
+ content.setCandidates(this.transportId,getCandidatesAsElements());
+ packet.setContent(content);
+ Log.d("xmppService",packet.toString());
+ account.getXmppConnection().sendIqPacket(packet, this.responseListener);
+ this.status = STATUS_INITIATED;
+ }
+ }
+
+ private List<Element> getCandidatesAsElements() {
+ List<Element> elements = new ArrayList<Element>();
+ for(JingleCandidate c : this.candidates) {
+ elements.add(c.toElement());
+ }
+ return elements;
+ }
+
+ private void sendAccept() {
+ this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
+
+ @Override
+ public void onPrimaryCandidateFound(boolean success, JingleCandidate candidate) {
+ Content content = new Content();
+ content.setFileOffer(fileOffer);
+ if (success) {
+ if (!equalCandidateExists(candidate)) {
+ mergeCandidate(candidate);
+ }
+ }
+ openOurCandidates();
+ content.setCandidates(transportId, getCandidatesAsElements());
+ JinglePacket packet = bootstrapPacket("session-accept");
+ packet.setContent(content);
+ account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() != IqPacket.TYPE_ERROR) {
+ status = STATUS_ACCEPTED;
+ connectNextCandidate();
+ }
+ }
+ });
+ }
+ });
+
+ }
+
+ private JinglePacket bootstrapPacket(String action) {
+ JinglePacket packet = new JinglePacket();
+ packet.setAction(action);
+ packet.setFrom(account.getFullJid());
+ packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases;
+ packet.setSessionId(this.sessionId);
+ packet.setInitiator(this.initiator);
+ return packet;
+ }
+
+ private void accept(JinglePacket packet) {
+ Log.d("xmppService","session-accept: "+packet.toString());
+ Content content = packet.getJingleContent();
+ mergeCandidates(JingleCandidate.parse(content.getCanditates()));
+ this.status = STATUS_ACCEPTED;
+ this.connectNextCandidate();
+ IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
+ account.getXmppConnection().sendIqPacket(response, null);
+ }
+
+ private void transportInfo(JinglePacket packet) {
+ Content content = packet.getJingleContent();
+ String cid = content.getUsedCandidate();
+ IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
+ if (cid!=null) {
+ Log.d("xmppService","candidate used by counterpart:"+cid);
+ JingleCandidate candidate = getCandidate(cid);
+ candidate.flagAsUsedByCounterpart();
+ if (status == STATUS_ACCEPTED) {
+ this.connect();
+ } else {
+ Log.d("xmppService","ignoring because file is already in transmission");
+ }
+ } else if (content.hasCandidateError()) {
+ Log.d("xmppService","received candidate error");
+ this.receivedCandidateError = true;
+ if (status == STATUS_ACCEPTED) {
+ this.connect();
+ }
+ }
+ account.getXmppConnection().sendIqPacket(response, null);
+ }
+
+ private void connect() {
+ final SocksConnection connection = chooseConnection();
+ this.status = STATUS_TRANSMITTING;
+ final OnFileTransmitted callback = new OnFileTransmitted() {
+
+ @Override
+ public void onFileTransmitted(JingleFile file) {
+ if (responder.equals(account.getFullJid())) {
+ sendSuccess();
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND);
+ }
+ Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
+ }
+ };
+ if (connection.isProxy()&&(connection.getCandidate().isOurs())) {
+ Log.d("xmppService","candidate "+connection.getCandidate().getCid()+" was our proxy and needs activation");
+ IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
+ activation.setTo(connection.getCandidate().getJid());
+ activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
+ activation.query().addChild("activate").setContent(this.getCounterPart());
+ this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Log.d("xmppService","activation result: "+packet.toString());
+ if (initiator.equals(account.getFullJid())) {
+ Log.d("xmppService","we were initiating. sending file");
+ connection.send(file,callback);
+ } else {
+ connection.receive(file,callback);
+ Log.d("xmppService","we were responding. receiving file");
+ }
+ }
+ });
+ } else {
+ if (initiator.equals(account.getFullJid())) {
+ Log.d("xmppService","we were initiating. sending file");
+ connection.send(file,callback);
+ } else {
+ Log.d("xmppService","we were responding. receiving file");
+ connection.receive(file,callback);
+ }
+ }
+ }
+
+ private SocksConnection chooseConnection() {
+ Log.d("xmppService","choosing connection from "+this.connections.size()+" possibilties");
+ SocksConnection connection = null;
+ Iterator<Entry<String, SocksConnection>> it = this.connections.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, SocksConnection> pairs = it.next();
+ SocksConnection currentConnection = pairs.getValue();
+ Log.d("xmppService","comparing candidate: "+currentConnection.getCandidate().toString());
+ if (currentConnection.isEstablished()&&(currentConnection.getCandidate().isUsedByCounterpart()||(!currentConnection.getCandidate().isOurs()))) {
+ Log.d("xmppService","is usable");
+ if (connection==null) {
+ connection = currentConnection;
+ } else {
+ if (connection.getCandidate().getPriority()<currentConnection.getCandidate().getPriority()) {
+ connection = currentConnection;
+ } else if (connection.getCandidate().getPriority()==currentConnection.getCandidate().getPriority()) {
+ Log.d("xmppService","found two candidates with same priority");
+ if (initiator.equals(account.getFullJid())) {
+ if (currentConnection.getCandidate().isOurs()) {
+ connection = currentConnection;
+ }
+ } else {
+ if (!currentConnection.getCandidate().isOurs()) {
+ connection = currentConnection;
+ }
+ }
+ }
+ }
+ }
+ it.remove();
+ }
+ Log.d("xmppService","chose candidate: "+connection.getCandidate().getHost());
+ return connection;
+ }
+
+ private void sendSuccess() {
+ JinglePacket packet = bootstrapPacket("session-terminate");
+ Reason reason = new Reason();
+ reason.addChild("success");
+ packet.setReason(reason);
+ Log.d("xmppService","sending success. "+packet.toString());
+ this.account.getXmppConnection().sendIqPacket(packet, responseListener);
+ this.disconnect();
+ this.status = STATUS_FINISHED;
+ this.mXmppConnectionService.markMessage(this.message, Message.STATUS_RECIEVED);
+ }
+
+ private void finish() {
+ this.status = STATUS_FINISHED;
+ this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND);
+ this.disconnect();
+ }
+
+ public void cancel() {
+ this.disconnect();
+ this.status = STATUS_CANCELED;
+ this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_REJECTED);
+ }
+
+ private void openOurCandidates() {
+ for(JingleCandidate candidate : this.candidates) {
+ if (candidate.isOurs()) {
+ final SocksConnection socksConnection = new SocksConnection(this,candidate);
+ connections.put(candidate.getCid(), socksConnection);
+ socksConnection.connect(new OnSocksConnection() {
+
+ @Override
+ public void failed() {
+ Log.d("xmppService","connection to our candidate failed");
+ }
+
+ @Override
+ public void established() {
+ Log.d("xmppService","connection to our candidate was successful");
+ }
+ });
+ }
+ }
+ }
+
+ private void connectNextCandidate() {
+ for(JingleCandidate candidate : this.candidates) {
+ if ((!connections.containsKey(candidate.getCid())&&(!candidate.isOurs()))) {
+ this.connectWithCandidate(candidate);
+ return;
+ }
+ }
+ this.sendCandidateError();
+ }
+
+ private void connectWithCandidate(final JingleCandidate candidate) {
+ final SocksConnection socksConnection = new SocksConnection(this,candidate);
+ connections.put(candidate.getCid(), socksConnection);
+ socksConnection.connect(new OnSocksConnection() {
+
+ @Override
+ public void failed() {
+ connectNextCandidate();
+ }
+
+ @Override
+ public void established() {
+ sendCandidateUsed(candidate.getCid());
+ if ((receivedCandidateError)&&(status == STATUS_ACCEPTED)) {
+ Log.d("xmppService","received candidate error before. trying to connect");
+ connect();
+ }
+ }
+ });
+ }
+
+ private void disconnect() {
+ Iterator<Entry<String, SocksConnection>> it = this.connections.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, SocksConnection> pairs = it.next();
+ pairs.getValue().disconnect();
+ it.remove();
+ }
+ }
+
+ private void sendCandidateUsed(final String cid) {
+ JinglePacket packet = bootstrapPacket("transport-info");
+ Content content = new Content();
+ //TODO: put these into actual variables
+ content.setAttribute("creator", "initiator");
+ content.setAttribute("name", "a-file-offer");
+ content.setUsedCandidate(this.transportId, cid);
+ packet.setContent(content);
+ Log.d("xmppService","send using candidate: "+cid);
+ this.account.getXmppConnection().sendIqPacket(packet,responseListener);
+ }
+
+ private void sendCandidateError() {
+ JinglePacket packet = bootstrapPacket("transport-info");
+ Content content = new Content();
+ //TODO: put these into actual variables
+ content.setAttribute("creator", "initiator");
+ content.setAttribute("name", "a-file-offer");
+ content.setCandidateError(this.transportId);
+ packet.setContent(content);
+ Log.d("xmppService","send candidate error");
+ this.account.getXmppConnection().sendIqPacket(packet,responseListener);
+ }
+
+ public String getInitiator() {
+ return this.initiator;
+ }
+
+ public String getResponder() {
+ return this.responder;
+ }
+
+ public int getStatus() {
+ return this.status;
+ }
+
+ private boolean equalCandidateExists(JingleCandidate candidate) {
+ for(JingleCandidate c : this.candidates) {
+ if (c.equalValues(candidate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void mergeCandidate(JingleCandidate candidate) {
+ for(JingleCandidate c : this.candidates) {
+ if (c.equals(candidate)) {
+ return;
+ }
+ }
+ this.candidates.add(candidate);
+ }
+
+ private void mergeCandidates(List<JingleCandidate> candidates) {
+ for(JingleCandidate c : candidates) {
+ mergeCandidate(c);
+ }
+ }
+
+ private JingleCandidate getCandidate(String cid) {
+ for(JingleCandidate c : this.candidates) {
+ if (c.getCid().equals(cid)) {
+ return c;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
new file mode 100644
index 00000000..44af51a3
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -0,0 +1,130 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import android.util.Log;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class JingleConnectionManager {
+
+ private XmppConnectionService xmppConnectionService;
+
+ private List<JingleConnection> connections = new ArrayList<JingleConnection>(); // make
+ // concurrent
+
+ private ConcurrentHashMap<String, JingleCandidate> primaryCandidates = new ConcurrentHashMap<String, JingleCandidate>();
+
+ private SecureRandom random = new SecureRandom();
+
+ public JingleConnectionManager(XmppConnectionService service) {
+ this.xmppConnectionService = service;
+ }
+
+ public void deliverPacket(Account account, JinglePacket packet) {
+ if (packet.isAction("session-initiate")) {
+ JingleConnection connection = new JingleConnection(this);
+ connection.init(account,packet);
+ connections.add(connection);
+ } else {
+ for (JingleConnection connection : connections) {
+ if (connection.getAccountJid().equals(account.getFullJid()) && connection
+ .getSessionId().equals(packet.getSessionId()) && connection
+ .getCounterPart().equals(packet.getFrom())) {
+ connection.deliverPacket(packet);
+ return;
+ }
+ }
+ Log.d("xmppService","delivering packet failed "+packet.toString());
+ }
+ }
+
+ public JingleConnection createNewConnection(Message message) {
+ JingleConnection connection = new JingleConnection(this);
+ connection.init(message);
+ connections.add(connection);
+ return connection;
+ }
+
+ public JingleConnection createNewConnection(JinglePacket packet) {
+ JingleConnection connection = new JingleConnection(this);
+ connections.add(connection);
+ return connection;
+ }
+
+ public XmppConnectionService getXmppConnectionService() {
+ return this.xmppConnectionService;
+ }
+
+ public void getPrimaryCandidate(Account account,
+ final OnPrimaryCandidateFound listener) {
+ if (!this.primaryCandidates.containsKey(account.getJid())) {
+ String xmlns = "http://jabber.org/protocol/bytestreams";
+ final String proxy = account.getXmppConnection()
+ .findDiscoItemByFeature(xmlns);
+ if (proxy != null) {
+ IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
+ iq.setTo(proxy);
+ iq.query(xmlns);
+ account.getXmppConnection().sendIqPacket(iq,
+ new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account,
+ IqPacket packet) {
+ Element streamhost = packet
+ .query()
+ .findChild("streamhost",
+ "http://jabber.org/protocol/bytestreams");
+ if (streamhost != null) {
+ Log.d("xmppService", "streamhost found "
+ + streamhost.toString());
+ JingleCandidate candidate = new JingleCandidate(nextRandomId(),true);
+ candidate.setHost(streamhost.getAttribute("host"));
+ candidate.setPort(Integer.parseInt(streamhost.getAttribute("port")));
+ candidate.setType(JingleCandidate.TYPE_PROXY);
+ candidate.setJid(proxy);
+ candidate.setPriority(655360+65535);
+ primaryCandidates.put(account.getJid(),
+ candidate);
+ listener.onPrimaryCandidateFound(true,
+ candidate);
+ } else {
+ listener.onPrimaryCandidateFound(false,
+ null);
+ }
+ }
+ });
+ } else {
+ listener.onPrimaryCandidateFound(false, null);
+ }
+
+ } else {
+ listener.onPrimaryCandidateFound(true,
+ this.primaryCandidates.get(account.getJid()));
+ }
+ }
+
+ public String nextRandomId() {
+ return new BigInteger(50, random).toString(32);
+ }
+
+ public long getAutoAcceptFileSize() {
+ String config = this.xmppConnectionService.getPreferences().getString("auto_accept_file_size", "0");
+ try {
+ return Long.parseLong(config);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
new file mode 100644
index 00000000..21cbd716
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
@@ -0,0 +1,35 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.io.File;
+
+public class JingleFile extends File {
+
+ private static final long serialVersionUID = 2247012619505115863L;
+
+ private long expectedSize = 0;
+ private String sha1sum;
+
+ public JingleFile(String path) {
+ super(path);
+ }
+
+ public long getSize() {
+ return super.length();
+ }
+
+ public long getExpectedSize() {
+ return this.expectedSize;
+ }
+
+ public void setExpectedSize(long size) {
+ this.expectedSize = size;
+ }
+
+ public String getSha1Sum() {
+ return this.sha1sum;
+ }
+
+ public void setSha1Sum(String sum) {
+ this.sha1sum = sum;
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmitted.java b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmitted.java
new file mode 100644
index 00000000..fd5fd2f7
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmitted.java
@@ -0,0 +1,5 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+public interface OnFileTransmitted {
+ public void onFileTransmitted(JingleFile file);
+}
diff --git a/src/eu/siacs/conversations/xmpp/OnJinglePacketReceived.java b/src/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java
index 6705e309..2aaf62a1 100644
--- a/src/eu/siacs/conversations/xmpp/OnJinglePacketReceived.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java
@@ -1,7 +1,8 @@
-package eu.siacs.conversations.xmpp;
+package eu.siacs.conversations.xmpp.jingle;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.stanzas.jingle.JinglePacket;
+import eu.siacs.conversations.xmpp.PacketReceived;
+import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
public interface OnJinglePacketReceived extends PacketReceived {
public void onJinglePacketReceived(Account account, JinglePacket packet);
diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java
new file mode 100644
index 00000000..b91a90ff
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/OnPrimaryCandidateFound.java
@@ -0,0 +1,5 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+public interface OnPrimaryCandidateFound {
+ public void onPrimaryCandidateFound(boolean success, JingleCandidate canditate);
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java
new file mode 100644
index 00000000..88771997
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/OnSocksConnection.java
@@ -0,0 +1,6 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+public interface OnSocksConnection {
+ public void failed();
+ public void established();
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java
new file mode 100644
index 00000000..197b9424
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java
@@ -0,0 +1,207 @@
+package eu.siacs.conversations.xmpp.jingle;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xml.Element;
+
+import android.util.Log;
+import android.widget.Button;
+
+public class SocksConnection {
+ private JingleCandidate candidate;
+ private String destination;
+ private OutputStream outputStream;
+ private InputStream inputStream;
+ private boolean isEstablished = false;
+ protected Socket socket;
+
+ public SocksConnection(JingleConnection jingleConnection, JingleCandidate candidate) {
+ this.candidate = candidate;
+ try {
+ MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
+ StringBuilder destBuilder = new StringBuilder();
+ destBuilder.append(jingleConnection.getSessionId());
+ if (candidate.isOurs()) {
+ destBuilder.append(jingleConnection.getAccountJid());
+ destBuilder.append(jingleConnection.getCounterPart());
+ } else {
+ destBuilder.append(jingleConnection.getCounterPart());
+ destBuilder.append(jingleConnection.getAccountJid());
+ }
+ mDigest.reset();
+ this.destination = CryptoHelper.bytesToHex(mDigest
+ .digest(destBuilder.toString().getBytes()));
+ } catch (NoSuchAlgorithmException e) {
+
+ }
+ }
+
+ public void connect(final OnSocksConnection callback) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ socket = new Socket(candidate.getHost(), candidate.getPort());
+ inputStream = socket.getInputStream();
+ outputStream = socket.getOutputStream();
+ byte[] login = { 0x05, 0x01, 0x00 };
+ byte[] expectedReply = { 0x05, 0x00 };
+ byte[] reply = new byte[2];
+ outputStream.write(login);
+ inputStream.read(reply);
+ if (Arrays.equals(reply, expectedReply)) {
+ String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
+ + '\u0028' + destination + '\u0000' + '\u0000';
+ outputStream.write(connect.getBytes());
+ byte[] result = new byte[2];
+ inputStream.read(result);
+ int status = result[1];
+ if (status == 0) {
+ Log.d("xmppService", "established connection with "+candidate.getHost()+":"+candidate.getPort()+ "/" + destination);
+ isEstablished = true;
+ callback.established();
+ } else {
+ callback.failed();
+ }
+ } else {
+ socket.close();
+ callback.failed();
+ }
+ } catch (UnknownHostException e) {
+ callback.failed();
+ } catch (IOException e) {
+ callback.failed();
+ }
+ }
+ }).start();
+
+ }
+
+ public void send(final JingleFile file, final OnFileTransmitted callback) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ FileInputStream fileInputStream = null;
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ fileInputStream = new FileInputStream(file);
+ int count;
+ byte[] buffer = new byte[8192];
+ while ((count = fileInputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, count);
+ digest.update(buffer, 0, count);
+ }
+ outputStream.flush();
+ file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+ if (callback!=null) {
+ callback.onFileTransmitted(file);
+ }
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ try {
+ if (fileInputStream != null) {
+ fileInputStream.close();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+
+ }
+
+ public void receive(final JingleFile file, final OnFileTransmitted callback) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ inputStream.skip(45);
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ long remainingSize = file.getExpectedSize();
+ byte[] buffer = new byte[8192];
+ int count = buffer.length;
+ while(remainingSize > 0) {
+ Log.d("xmppService","remaning size:"+remainingSize);
+ if (remainingSize<=count) {
+ count = (int) remainingSize;
+ }
+ count = inputStream.read(buffer, 0, count);
+ if (count==-1) {
+ Log.d("xmppService","end of stream");
+ } else {
+ fileOutputStream.write(buffer, 0, count);
+ digest.update(buffer, 0, count);
+ remainingSize-=count;
+ }
+ }
+ fileOutputStream.flush();
+ fileOutputStream.close();
+ file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+ Log.d("xmppService","transmitted filename was: "+file.getAbsolutePath());
+ callback.onFileTransmitted(file);
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+
+ public boolean isProxy() {
+ return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
+ }
+
+ public void disconnect() {
+ if (this.socket!=null) {
+ try {
+ this.socket.close();
+ Log.d("xmppService","cloesd socket with "+candidate.getHost()+":"+candidate.getPort());
+ } catch (IOException e) {
+ Log.d("xmppService","error closing socket with "+candidate.getHost()+":"+candidate.getPort());
+ }
+ }
+ }
+
+ public boolean isEstablished() {
+ return this.isEstablished;
+ }
+
+ public JingleCandidate getCandidate() {
+ return this.candidate;
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
new file mode 100644
index 00000000..3cd30251
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
@@ -0,0 +1,124 @@
+package eu.siacs.conversations.xmpp.jingle.stanzas;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.jingle.JingleFile;
+
+public class Content extends Element {
+ private Content(String name) {
+ super(name);
+ }
+
+ public Content() {
+ super("content");
+ }
+
+ public void setFileOffer(JingleFile actualFile) {
+ Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+ Element offer = description.addChild("offer");
+ Element file = offer.addChild("file");
+ file.addChild("size").setContent(""+actualFile.getSize());
+ file.addChild("name").setContent(actualFile.getName());
+ }
+
+ public Element getFileOffer() {
+ Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+ if (description==null) {
+ return null;
+ }
+ Element offer = description.findChild("offer");
+ if (offer==null) {
+ return null;
+ }
+ return offer.findChild("file");
+ }
+
+ public void setCandidates(String transportId, List<Element> canditates) {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ }
+ transport.setAttribute("sid", transportId);
+ transport.clearChildren();
+ for(Element canditate : canditates) {
+ transport.addChild(canditate);
+ }
+ }
+
+ public List<Element> getCanditates() {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ return new ArrayList<Element>();
+ } else {
+ return transport.getChildren();
+ }
+ }
+
+ public String getTransportId() {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ return null;
+ }
+ return transport.getAttribute("sid");
+ }
+
+ public String getUsedCandidate() {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ return null;
+ }
+ Element usedCandidate = transport.findChild("candidate-used");
+ if (usedCandidate==null) {
+ return null;
+ } else {
+ return usedCandidate.getAttribute("cid");
+ }
+ }
+
+ public boolean hasCandidateError() {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ return false;
+ }
+ return transport.hasChild("candidate-error");
+ }
+
+ public void setUsedCandidate(String transportId, String cid) {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ }
+ transport.setAttribute("sid", transportId);
+ transport.clearChildren();
+ Element usedCandidate = transport.addChild("candidate-used");
+ usedCandidate.setAttribute("cid",cid);
+ }
+
+ public void addCandidate(Element candidate) {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ }
+ transport.addChild(candidate);
+ }
+
+ public void setFileOffer(Element fileOffer) {
+ Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+ if (description==null) {
+ description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
+ }
+ description.addChild(fileOffer);
+ }
+
+ public void setCandidateError(String transportId) {
+ Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ if (transport==null) {
+ transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+ }
+ transport.setAttribute("sid", transportId);
+ transport.clearChildren();
+ transport.addChild("candidate-error");
+ }
+}
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java
index 51c60d1f..55700609 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/jingle/JinglePacket.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java
@@ -1,4 +1,4 @@
-package eu.siacs.conversations.xmpp.stanzas.jingle;
+package eu.siacs.conversations.xmpp.jingle.stanzas;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -6,6 +6,7 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class JinglePacket extends IqPacket {
Content content = null;
Reason reason = null;
+ Element jingle = new Element("jingle");
@Override
public Element addChild(Element child) {
@@ -22,27 +23,36 @@ public class JinglePacket extends IqPacket {
this.reason.setChildren(reasonElement.getChildren());
this.reason.setAttributes(reasonElement.getAttributes());
}
- this.build();
- this.findChild("jingle").setAttributes(child.getAttributes());
+ this.jingle.setAttributes(child.getAttributes());
}
return child;
}
public JinglePacket setContent(Content content) {
this.content = content;
- this.build();
return this;
}
+ public Content getJingleContent() {
+ if (this.content==null) {
+ this.content = new Content();
+ }
+ return this.content;
+ }
+
public JinglePacket setReason(Reason reason) {
this.reason = reason;
- this.build();
return this;
}
+ public Reason getReason() {
+ return this.reason;
+ }
+
private void build() {
this.children.clear();
- Element jingle = addChild("jingle", "urn:xmpp:jingle:1");
+ this.jingle.clearChildren();
+ this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1");
if (this.content!=null) {
jingle.addChild(this.content);
}
@@ -50,5 +60,36 @@ public class JinglePacket extends IqPacket {
jingle.addChild(this.reason);
}
this.children.add(jingle);
+ this.setAttribute("type", "set");
+ }
+
+ public String getSessionId() {
+ return this.jingle.getAttribute("sid");
+ }
+
+ public void setSessionId(String sid) {
+ this.jingle.setAttribute("sid", sid);
+ }
+
+ @Override
+ public String toString() {
+ this.build();
+ return super.toString();
+ }
+
+ public void setAction(String action) {
+ this.jingle.setAttribute("action", action);
+ }
+
+ public String getAction() {
+ return this.jingle.getAttribute("action");
+ }
+
+ public void setInitiator(String initiator) {
+ this.jingle.setAttribute("initiator", initiator);
+ }
+
+ public boolean isAction(String action) {
+ return action.equalsIgnoreCase(this.getAction());
}
}
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Reason.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java
index 35b81655..195e0db7 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Reason.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java
@@ -1,4 +1,4 @@
-package eu.siacs.conversations.xmpp.stanzas.jingle;
+package eu.siacs.conversations.xmpp.jingle.stanzas;
import eu.siacs.conversations.xml.Element;
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
index 3ab3b6c3..9e288454 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
+++ b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
@@ -63,5 +63,13 @@ public class IqPacket extends AbstractStanza {
return 1000;
}
}
+
+ public IqPacket generateRespone(int type) {
+ IqPacket packet = new IqPacket(type);
+ packet.setFrom(this.getTo());
+ packet.setTo(this.getFrom());
+ packet.setId(this.getId());
+ return packet;
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java b/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java
deleted file mode 100644
index ebd212b8..00000000
--- a/src/eu/siacs/conversations/xmpp/stanzas/jingle/Content.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.jingle;
-
-import eu.siacs.conversations.xml.Element;
-
-public class Content extends Element {
- private Content(String name) {
- super(name);
- }
-
- public Content() {
- super("content");
- }
-}