aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eu/siacs/conversations/crypto/PgpEngine.java165
-rw-r--r--src/eu/siacs/conversations/entities/Account.java11
-rw-r--r--src/eu/siacs/conversations/entities/Contact.java167
-rw-r--r--src/eu/siacs/conversations/entities/Conversation.java112
-rw-r--r--src/eu/siacs/conversations/entities/Message.java31
-rw-r--r--src/eu/siacs/conversations/entities/MucOptions.java63
-rw-r--r--src/eu/siacs/conversations/entities/Presences.java47
-rw-r--r--src/eu/siacs/conversations/entities/Roster.java74
-rw-r--r--src/eu/siacs/conversations/parser/AbstractParser.java53
-rw-r--r--src/eu/siacs/conversations/parser/MessageParser.java223
-rw-r--r--src/eu/siacs/conversations/parser/PresenceParser.java104
-rw-r--r--src/eu/siacs/conversations/persistance/DatabaseBackend.java201
-rw-r--r--src/eu/siacs/conversations/persistance/FileBackend.java89
-rw-r--r--src/eu/siacs/conversations/services/ImageProvider.java106
-rw-r--r--src/eu/siacs/conversations/services/XmppConnectionService.java958
-rw-r--r--src/eu/siacs/conversations/ui/ContactDetailsActivity.java155
-rw-r--r--src/eu/siacs/conversations/ui/ContactsActivity.java106
-rw-r--r--src/eu/siacs/conversations/ui/ConversationActivity.java741
-rw-r--r--src/eu/siacs/conversations/ui/ConversationFragment.java513
-rw-r--r--src/eu/siacs/conversations/ui/EditAccount.java34
-rw-r--r--src/eu/siacs/conversations/ui/ManageAccountActivity.java95
-rw-r--r--src/eu/siacs/conversations/ui/MucDetailsActivity.java41
-rw-r--r--src/eu/siacs/conversations/ui/OnPresenceSelected.java3
-rw-r--r--src/eu/siacs/conversations/ui/OnRosterFetchedListener.java9
-rw-r--r--src/eu/siacs/conversations/ui/ShareWithActivity.java138
-rw-r--r--src/eu/siacs/conversations/ui/UiCallback.java8
-rw-r--r--src/eu/siacs/conversations/ui/XmppActivity.java256
-rw-r--r--src/eu/siacs/conversations/utils/CryptoHelper.java25
-rw-r--r--src/eu/siacs/conversations/utils/DNSHelper.java2
-rw-r--r--src/eu/siacs/conversations/utils/ExceptionHelper.java2
-rw-r--r--src/eu/siacs/conversations/utils/MessageParser.java162
-rw-r--r--src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java4
-rw-r--r--src/eu/siacs/conversations/utils/PRNGFixes.java326
-rw-r--r--src/eu/siacs/conversations/utils/PhoneHelper.java23
-rw-r--r--src/eu/siacs/conversations/utils/UIHelper.java252
-rw-r--r--src/eu/siacs/conversations/utils/Validator.java2
-rw-r--r--src/eu/siacs/conversations/xml/Element.java65
-rw-r--r--src/eu/siacs/conversations/xml/XmlReader.java7
-rw-r--r--src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java7
-rw-r--r--src/eu/siacs/conversations/xmpp/XmppConnection.java374
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java24
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleFile.java32
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java9
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java30
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java70
-rw-r--r--src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java8
-rw-r--r--src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java1
-rw-r--r--src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java4
48 files changed, 3589 insertions, 2343 deletions
diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java
index 48750e24..c0d8ca07 100644
--- a/src/eu/siacs/conversations/crypto/PgpEngine.java
+++ b/src/eu/siacs/conversations/crypto/PgpEngine.java
@@ -17,6 +17,7 @@ import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.UiCallback;
@@ -35,17 +36,19 @@ public class PgpEngine {
this.mXmppConnectionService = service;
}
- public void decrypt(final Message message, final UiCallback callback) {
- Log.d("xmppService","decrypting message "+message.getUuid());
+ public void decrypt(final Message message,
+ final UiCallback<Message> callback) {
+ Log.d("xmppService", "decrypting message " + message.getUuid());
Intent params = new Intent();
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
.getConversation().getAccount().getJid());
if (message.getType() == Message.TYPE_TEXT) {
- InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
+ InputStream is = new ByteArrayInputStream(message.getBody()
+ .getBytes());
final OutputStream os = new ByteArrayOutputStream();
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
+
@Override
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
@@ -53,14 +56,15 @@ public class PgpEngine {
case OpenPgpApi.RESULT_CODE_SUCCESS:
message.setBody(os.toString());
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- callback.success();
+ callback.success(message);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, message);
return;
default:
return;
@@ -69,13 +73,15 @@ public class PgpEngine {
});
} else if (message.getType() == Message.TYPE_IMAGE) {
try {
- final JingleFile inputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, false);
- final JingleFile outputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message,true);
+ final JingleFile inputFile = this.mXmppConnectionService
+ .getFileBackend().getJingleFile(message, false);
+ final JingleFile outputFile = this.mXmppConnectionService
+ .getFileBackend().getJingleFile(message, true);
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
+
@Override
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
@@ -83,21 +89,27 @@ public class PgpEngine {
case OpenPgpApi.RESULT_CODE_SUCCESS:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(outputFile.getAbsolutePath(),options);
+ BitmapFactory.decodeFile(
+ outputFile.getAbsolutePath(), options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
- message.setBody(""+outputFile.getSize()+","+imageWidth+","+imageHeight);
+ message.setBody("" + outputFile.getSize() + ","
+ + imageWidth + "," + imageHeight);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- PgpEngine.this.mXmppConnectionService.updateMessage(message);
- PgpEngine.this.mXmppConnectionService.updateUi(message.getConversation(), false);
- callback.success();
+ PgpEngine.this.mXmppConnectionService
+ .updateMessage(message);
+ PgpEngine.this.mXmppConnectionService.updateUi(
+ message.getConversation(), false);
+ callback.success(message);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ callback.userInputRequried(
+ (PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, message);
return;
default:
return;
@@ -105,28 +117,38 @@ public class PgpEngine {
}
});
} catch (FileNotFoundException e) {
- callback.error(R.string.error_decrypting_file);
+ callback.error(R.string.error_decrypting_file, message);
} catch (IOException e) {
- callback.error(R.string.error_decrypting_file);
+ callback.error(R.string.error_decrypting_file, message);
}
-
+
}
}
- public void encrypt(final Message message,final UiCallback callback) {
- long[] keys = { message.getConversation().getContact().getPgpKeyId() };
+ public void encrypt(final Message message,
+ final UiCallback<Message> callback) {
+
Intent params = new Intent();
params.setAction(OpenPgpApi.ACTION_ENCRYPT);
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message.getConversation().getAccount().getJid());
-
+ if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
+ long[] keys = { message.getConversation().getContact()
+ .getPgpKeyId() };
+ params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
+ } else {
+ params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
+ .getMucOptions().getPgpKeyIds());
+ }
+ params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
+ .getConversation().getAccount().getJid());
+
if (message.getType() == Message.TYPE_TEXT) {
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-
- InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
+
+ InputStream is = new ByteArrayInputStream(message.getBody()
+ .getBytes());
final OutputStream os = new ByteArrayOutputStream();
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
+
@Override
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
@@ -137,53 +159,59 @@ public class PgpEngine {
for (int i = 3; i < lines.length - 1; ++i) {
encryptedMessageBody.append(lines[i].trim());
}
- message.setEncryptedBody(encryptedMessageBody.toString());
- callback.success();
+ message.setEncryptedBody(encryptedMessageBody
+ .toString());
+ callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, message);
break;
}
}
});
} else if (message.getType() == Message.TYPE_IMAGE) {
try {
- JingleFile inputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, true);
- JingleFile outputFile = this.mXmppConnectionService.getFileBackend().getJingleFile(message, false);
+ JingleFile inputFile = this.mXmppConnectionService
+ .getFileBackend().getJingleFile(message, true);
+ JingleFile outputFile = this.mXmppConnectionService
+ .getFileBackend().getJingleFile(message, false);
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
-
+
@Override
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success();
+ callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ callback.userInputRequried(
+ (PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, message);
break;
}
}
});
} catch (FileNotFoundException e) {
- Log.d("xmppService","file not found: "+e.getMessage());
+ Log.d("xmppService", "file not found: " + e.getMessage());
} catch (IOException e) {
- Log.d("xmppService","io exception during file encrypt");
+ Log.d("xmppService", "io exception during file encrypt");
}
}
}
-
+
public long fetchKeyId(Account account, String status, String signature) {
if ((signature == null) || (api == null)) {
return 0;
@@ -221,18 +249,20 @@ public class PgpEngine {
return 0;
}
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- Log.d("xmppService","openpgp user interaction requeried");
return 0;
case OpenPgpApi.RESULT_CODE_ERROR:
- Log.d("xmppService","openpgp error: "+((OpenPgpError) result
- .getParcelableExtra(OpenPgpApi.RESULT_ERROR)).getMessage());
+ Log.d("xmppService",
+ "openpgp error: "
+ + ((OpenPgpError) result
+ .getParcelableExtra(OpenPgpApi.RESULT_ERROR))
+ .getMessage());
return 0;
}
return 0;
}
public void generateSignature(final Account account, String status,
- final UiCallback callback) {
+ final UiCallback<Account> callback) {
Intent params = new Intent();
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
params.setAction(OpenPgpApi.ACTION_SIGN);
@@ -251,51 +281,66 @@ public class PgpEngine {
signatureBuilder.append(lines[i].trim());
}
account.setKey("pgp_signature", signatureBuilder.toString());
- callback.success();
+ callback.success(account);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ account);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, account);
return;
}
}
});
}
-
- public void hasKey(Contact contact, final UiCallback callback) {
+
+ public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
Intent params = new Intent();
params.setAction(OpenPgpApi.ACTION_GET_KEY);
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount().getJid());
+ params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
+ .getJid());
api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
-
+
@Override
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success();
+ callback.success(contact);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT));
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ contact);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error);
+ callback.error(R.string.openpgp_error, contact);
return;
}
}
});
}
-
+
public PendingIntent getIntentForKey(Contact contact) {
Intent params = new Intent();
params.setAction(OpenPgpApi.ACTION_GET_KEY);
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount().getJid());
+ params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
+ .getJid());
+ Intent result = api.executeApi(params, null, null);
+ return (PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+ }
+
+ public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_GET_KEY);
+ params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
+ params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
Intent result = api.executeApi(params, null, null);
- return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+ return (PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
}
}
diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java
index 35870aaa..b9c87eac 100644
--- a/src/eu/siacs/conversations/entities/Account.java
+++ b/src/eu/siacs/conversations/entities/Account.java
@@ -65,6 +65,8 @@ public class Account extends AbstractEntity{
private String otrFingerprint;
+ private Roster roster = null;
+
public Account() {
this.uuid = "0";
}
@@ -135,7 +137,7 @@ public class Account extends AbstractEntity{
}
public boolean hasErrorStatus() {
- return getStatus() > STATUS_NO_INTERNET;
+ return getStatus() > STATUS_NO_INTERNET && (getXmppConnection().getAttempt() >= 2);
}
public void setResource(String resource) {
@@ -287,4 +289,11 @@ public class Account extends AbstractEntity{
return null;
}
}
+
+ public Roster getRoster() {
+ if (this.roster==null) {
+ this.roster = new Roster(this);
+ }
+ return this.roster;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java
index 599aa8de..a0047cdf 100644
--- a/src/eu/siacs/conversations/entities/Contact.java
+++ b/src/eu/siacs/conversations/entities/Contact.java
@@ -1,8 +1,6 @@
package eu.siacs.conversations.entities;
-import java.io.Serializable;
import java.util.HashSet;
-import java.util.Hashtable;
import java.util.Set;
import org.json.JSONArray;
@@ -10,27 +8,24 @@ import org.json.JSONException;
import org.json.JSONObject;
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;
+public class Contact {
public static final String TABLENAME = "contacts";
- public static final String DISPLAYNAME = "name";
+ public static final String SYSTEMNAME = "systemname";
+ public static final String SERVERNAME = "servername";
public static final String JID = "jid";
- public static final String SUBSCRIPTION = "subscription";
+ public static final String OPTIONS = "options";
public static final String SYSTEMACCOUNT = "systemaccount";
public static final String PHOTOURI = "photouri";
public static final String KEYS = "pgpkey";
- public static final String PRESENCES = "presences";
public static final String ACCOUNT = "accountUuid";
protected String accountUuid;
- protected String displayName;
+ protected String systemName;
+ protected String serverName;
protected String jid;
protected int subscription = 0;
protected String systemAccount;
@@ -41,27 +36,15 @@ public class Contact extends AbstractEntity implements Serializable {
protected Account account;
protected boolean inRoster = true;
+
+ public Lastseen lastseen = new Lastseen();
- public Contact(Account account, String displayName, String jid,
- String photoUri) {
- if (account == null) {
- this.accountUuid = null;
- } else {
- this.accountUuid = account.getUuid();
- }
- this.account = account;
- this.displayName = displayName;
- this.jid = jid;
- this.photoUri = photoUri;
- this.uuid = java.util.UUID.randomUUID().toString();
- }
-
- public Contact(String uuid, String account, String displayName, String jid,
- int subscription, String photoUri, String systemAccount,
- String keys, String presences) {
- this.uuid = uuid;
+ public Contact(String account, String systemName, String serverName,
+ String jid, int subscription, String photoUri,
+ String systemAccount, String keys) {
this.accountUuid = account;
- this.displayName = displayName;
+ this.systemName = systemName;
+ this.serverName = serverName;
this.jid = jid;
this.subscription = subscription;
this.photoUri = photoUri;
@@ -74,11 +57,20 @@ public class Contact extends AbstractEntity implements Serializable {
} catch (JSONException e) {
this.keys = new JSONObject();
}
- this.presences = Presences.fromJsonString(presences);
+ }
+
+ public Contact(String jid) {
+ this.jid = jid;
}
public String getDisplayName() {
- return this.displayName;
+ if (this.systemName != null) {
+ return this.systemName;
+ } else if (this.serverName != null) {
+ return this.serverName;
+ } else {
+ return this.jid.split("@")[0];
+ }
}
public String getProfilePhoto() {
@@ -90,35 +82,32 @@ public class Contact extends AbstractEntity implements Serializable {
}
public boolean match(String needle) {
- return (jid.toLowerCase().contains(needle.toLowerCase()) || (displayName
+ return (jid.toLowerCase().contains(needle.toLowerCase()) || (getDisplayName()
.toLowerCase().contains(needle.toLowerCase())));
}
- @Override
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
- values.put(UUID, uuid);
values.put(ACCOUNT, accountUuid);
- values.put(DISPLAYNAME, displayName);
+ values.put(SYSTEMNAME, systemName);
+ values.put(SERVERNAME, serverName);
values.put(JID, jid);
- values.put(SUBSCRIPTION, subscription);
+ values.put(OPTIONS, subscription);
values.put(SYSTEMACCOUNT, systemAccount);
values.put(PHOTOURI, photoUri);
values.put(KEYS, keys.toString());
- values.put(PRESENCES, presences.toJsonString());
return values;
}
public static Contact fromCursor(Cursor cursor) {
- return new Contact(cursor.getString(cursor.getColumnIndex(UUID)),
- cursor.getString(cursor.getColumnIndex(ACCOUNT)),
- cursor.getString(cursor.getColumnIndex(DISPLAYNAME)),
+ return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
+ cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
+ cursor.getString(cursor.getColumnIndex(SERVERNAME)),
cursor.getString(cursor.getColumnIndex(JID)),
- cursor.getInt(cursor.getColumnIndex(SUBSCRIPTION)),
+ cursor.getInt(cursor.getColumnIndex(OPTIONS)),
cursor.getString(cursor.getColumnIndex(PHOTOURI)),
cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
- cursor.getString(cursor.getColumnIndex(KEYS)),
- cursor.getString(cursor.getColumnIndex(PRESENCES)));
+ cursor.getString(cursor.getColumnIndex(KEYS)));
}
public int getSubscription() {
@@ -138,10 +127,6 @@ public class Contact extends AbstractEntity implements Serializable {
return this.account;
}
- public void setUuid(String uuid) {
- this.uuid = uuid;
- }
-
public boolean couldBeMuc() {
String[] split = this.getJid().split("@");
if (split.length != 2) {
@@ -153,15 +138,17 @@ public class Contact extends AbstractEntity implements Serializable {
} else {
return (domainParts[0].equals("conf")
|| domainParts[0].equals("conference")
+ || domainParts[0].equals("room")
|| domainParts[0].equals("muc")
+ || domainParts[0].equals("chat")
|| domainParts[0].equals("sala")
|| domainParts[0].equals("salas"));
}
}
}
- public Hashtable<String, Integer> getPresences() {
- return this.presences.getPresences();
+ public Presences getPresences() {
+ return this.presences;
}
public void updatePresence(String resource, int status) {
@@ -188,8 +175,12 @@ public class Contact extends AbstractEntity implements Serializable {
this.photoUri = uri;
}
- public void setDisplayName(String name) {
- this.displayName = name;
+ public void setServerName(String serverName) {
+ this.serverName = serverName;
+ }
+
+ public void setSystemName(String systemName) {
+ this.systemName = systemName;
}
public String getSystemAccount() {
@@ -249,58 +240,76 @@ public class Contact extends AbstractEntity implements Serializable {
}
}
- public void setSubscriptionOption(int option) {
+ public void setOption(int option) {
this.subscription |= 1 << option;
}
- public void resetSubscriptionOption(int option) {
+ public void resetOption(int option) {
this.subscription &= ~(1 << option);
}
- public boolean getSubscriptionOption(int option) {
+ public boolean getOption(int option) {
return ((this.subscription & (1 << option)) != 0);
}
+ public boolean showInRoster() {
+ return (this.getOption(Contact.Options.IN_ROSTER) && (!this
+ .getOption(Contact.Options.DIRTY_DELETE)))
+ || (this.getOption(Contact.Options.DIRTY_PUSH));
+ }
+
public void parseSubscriptionFromElement(Element item) {
String ask = item.getAttribute("ask");
String subscription = item.getAttribute("subscription");
if (subscription != null) {
if (subscription.equals("to")) {
- this.resetSubscriptionOption(Contact.Subscription.FROM);
- this.setSubscriptionOption(Contact.Subscription.TO);
+ this.resetOption(Contact.Options.FROM);
+ this.setOption(Contact.Options.TO);
} else if (subscription.equals("from")) {
- this.resetSubscriptionOption(Contact.Subscription.TO);
- this.setSubscriptionOption(Contact.Subscription.FROM);
+ this.resetOption(Contact.Options.TO);
+ this.setOption(Contact.Options.FROM);
} else if (subscription.equals("both")) {
- this.setSubscriptionOption(Contact.Subscription.TO);
- this.setSubscriptionOption(Contact.Subscription.FROM);
+ this.setOption(Contact.Options.TO);
+ this.setOption(Contact.Options.FROM);
+ } else if (subscription.equals("none")) {
+ this.resetOption(Contact.Options.FROM);
+ this.resetOption(Contact.Options.TO);
}
}
- if ((ask != null) && (ask.equals("subscribe"))) {
- this.setSubscriptionOption(Contact.Subscription.ASKING);
- } else {
- this.resetSubscriptionOption(Contact.Subscription.ASKING);
+ // do NOT override asking if pending push request
+ if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
+ if ((ask != null) && (ask.equals("subscribe"))) {
+ this.setOption(Contact.Options.ASKING);
+ } else {
+ this.resetOption(Contact.Options.ASKING);
+ }
+ }
+ }
+
+ public Element asElement() {
+ Element item = new Element("item");
+ item.setAttribute("jid", this.jid);
+ if (this.serverName != null) {
+ item.setAttribute("name", this.serverName);
}
+ return item;
}
- public class Subscription {
+ public class Options {
public static final int TO = 0;
public static final int FROM = 1;
public static final int ASKING = 2;
- public static final int PREEMPTIVE_GRANT = 4;
- }
-
- public void flagAsNotInRoster() {
- this.inRoster = false;
- }
-
- public boolean isInRoster() {
- return this.inRoster;
- }
-
- public String getAccountUuid() {
- return this.accountUuid;
+ public static final int PREEMPTIVE_GRANT = 3;
+ public static final int IN_ROSTER = 4;
+ public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
+ public static final int DIRTY_PUSH = 6;
+ public static final int DIRTY_DELETE = 7;
+ }
+
+ public class Lastseen {
+ public long time = 0;
+ public String presence = null;
}
}
diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java
index 5674f84a..640d89e9 100644
--- a/src/eu/siacs/conversations/entities/Conversation.java
+++ b/src/eu/siacs/conversations/entities/Conversation.java
@@ -4,13 +4,14 @@ import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
import java.util.List;
+import eu.siacs.conversations.services.XmppConnectionService;
+
import net.java.otr4j.OtrException;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus;
-
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -44,12 +45,11 @@ 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;
- private transient Contact contact;
private transient SessionImpl otrSession;
@@ -60,6 +60,10 @@ public class Conversation extends AbstractEntity {
private transient MucOptions mucOptions = null;
+ private transient String latestMarkableMessageId;
+
+ private byte[] symmetricKey;
+
public Conversation(String name, Account account, String contactJid,
int mode) {
this(java.util.UUID.randomUUID().toString(), name, null, account
@@ -101,15 +105,26 @@ public class Conversation extends AbstractEntity {
}
public void markRead() {
- if (this.messages == null)
+ if (this.messages == null) {
return;
+ }
for (int i = this.messages.size() - 1; i >= 0; --i) {
- if (messages.get(i).isRead())
- return;
+ if (messages.get(i).isRead()) {
+ break;
+ }
this.messages.get(i).markRead();
}
}
+ public void markRead(XmppConnectionService service) {
+ markRead();
+ if (service.confirmMessages() && this.latestMarkableMessageId != null) {
+ service.sendConfirmMessage(getAccount(), getContactJid(),
+ this.latestMarkableMessageId);
+ this.latestMarkableMessageId = null;
+ }
+ }
+
public Message getLatestMessage() {
if ((this.messages == null) || (this.messages.size() == 0)) {
Message message = new Message(this, "", Message.ENCRYPTION_NONE);
@@ -127,21 +142,16 @@ public class Conversation extends AbstractEntity {
}
public String getName(boolean useSubject) {
- if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null) && useSubject) {
+ if ((getMode() == MODE_MULTI) && (getMucOptions().getSubject() != null)
+ && useSubject) {
return getMucOptions().getSubject();
- } else if (this.contact != null) {
- return this.contact.getDisplayName();
} else {
- return this.name;
+ return this.getContact().getDisplayName();
}
}
public String getProfilePhotoString() {
- if (this.contact == null) {
- return null;
- } else {
- return this.contact.getProfilePhoto();
- }
+ return this.getContact().getProfilePhoto();
}
public String getAccountUuid() {
@@ -153,14 +163,7 @@ public class Conversation extends AbstractEntity {
}
public Contact getContact() {
- return this.contact;
- }
-
- public void setContact(Contact contact) {
- this.contact = contact;
- if (contact != null) {
- this.contactUuid = contact.getUuid();
- }
+ return this.account.getRoster().getContact(this.contactJid);
}
public void setAccount(Account account) {
@@ -222,14 +225,15 @@ public class Conversation extends AbstractEntity {
this.mode = mode;
}
- public SessionImpl startOtrSession(Context context, String presence, boolean sendStart) {
+ public SessionImpl startOtrSession(Context context, String presence,
+ boolean sendStart) {
if (this.otrSession != null) {
return this.otrSession;
} else {
SessionID sessionId = new SessionID(this.getContactJid(), presence,
"xmpp");
- this.otrSession = new SessionImpl(sessionId, getAccount().getOtrEngine(
- context));
+ this.otrSession = new SessionImpl(sessionId, getAccount()
+ .getOtrEngine(context));
try {
if (sendStart) {
this.otrSession.startSession();
@@ -240,13 +244,13 @@ public class Conversation extends AbstractEntity {
return null;
}
}
-
+
}
public SessionImpl getOtrSession() {
return this.otrSession;
}
-
+
public void resetOtrSession() {
this.otrSession = null;
}
@@ -260,21 +264,14 @@ public class Conversation extends AbstractEntity {
} catch (OtrException e) {
this.resetOtrSession();
}
+ } else {
+ this.resetOtrSession();
}
}
}
public boolean hasValidOtrSession() {
- if (this.otrSession == null) {
- return false;
- } else {
- String foreignPresence = this.otrSession.getSessionID().getUserID();
- if (!getContact().getPresences().containsKey(foreignPresence)) {
- this.resetOtrSession();
- return false;
- }
- return true;
- }
+ return this.otrSession != null;
}
public String getOtrFingerprint() {
@@ -298,7 +295,7 @@ public class Conversation extends AbstractEntity {
public synchronized MucOptions getMucOptions() {
if (this.mucOptions == null) {
- this.mucOptions = new MucOptions();
+ this.mucOptions = new MucOptions(this.getAccount());
}
this.mucOptions.setConversation(this);
return this.mucOptions;
@@ -311,40 +308,59 @@ 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;
}
-
+
public int getLatestEncryption() {
int latestEncryption = this.getLatestMessage().getEncryption();
- if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
+ if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
+ || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
return Message.ENCRYPTION_PGP;
} else {
return latestEncryption;
}
}
-
+
public int getNextEncryption() {
if (this.nextMessageEncryption == -1) {
return this.getLatestEncryption();
}
return this.nextMessageEncryption;
}
-
+
public void setNextEncryption(int encryption) {
this.nextMessageEncryption = encryption;
}
-
+
public String getNextMessage() {
- return this.nextMessage;
+ if (this.nextMessage == null) {
+ return "";
+ } else {
+ return this.nextMessage;
+ }
}
-
+
public void setNextMessage(String message) {
this.nextMessage = message;
}
+
+ public void setLatestMarkableMessageId(String id) {
+ if (id != null) {
+ this.latestMarkableMessageId = id;
+ }
+ }
+
+ public void setSymmetricKey(byte[] key) {
+ this.symmetricKey = key;
+ }
+
+ public byte[] getSymmetricKey() {
+ return this.symmetricKey;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java
index 33f7a8d4..1e82fe6a 100644
--- a/src/eu/siacs/conversations/entities/Message.java
+++ b/src/eu/siacs/conversations/entities/Message.java
@@ -19,7 +19,10 @@ public class Message extends AbstractEntity {
public static final int STATUS_SEND = 2;
public static final int STATUS_SEND_FAILED = 3;
public static final int STATUS_SEND_REJECTED = 4;
+ public static final int STATUS_WAITING = 5;
public static final int STATUS_OFFERED = 6;
+ public static final int STATUS_SEND_RECEIVED = 7;
+ public static final int STATUS_SEND_DISPLAYED = 8;
public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1;
@@ -29,6 +32,8 @@ public class Message extends AbstractEntity {
public static final int TYPE_TEXT = 0;
public static final int TYPE_IMAGE = 1;
+ public static final int TYPE_AUDIO = 2;
+ public static final int TYPE_STATUS = 3;
public static String CONVERSATION = "conversationUuid";
public static String COUNTERPART = "counterpart";
@@ -51,6 +56,10 @@ public class Message extends AbstractEntity {
protected transient Conversation conversation = null;
protected transient JingleConnection jingleConnection = null;
+
+ private Message() {
+
+ }
public Message(Conversation conversation, String body, int encryption) {
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
@@ -192,7 +201,20 @@ public class Message extends AbstractEntity {
}
public void setPresence(String presence) {
- this.counterpart = this.counterpart.split("/")[0] + "/" + presence;
+ if (presence == null) {
+ this.counterpart = this.counterpart.split("/")[0];
+ } else {
+ this.counterpart = this.counterpart.split("/")[0] + "/" + presence;
+ }
+ }
+
+ public String getPresence() {
+ String[] counterparts = this.counterpart.split("/");
+ if (counterparts.length == 2) {
+ return counterparts[1];
+ } else {
+ return null;
+ }
}
public void setJingleConnection(JingleConnection connection) {
@@ -202,4 +224,11 @@ public class Message extends AbstractEntity {
public JingleConnection getJingleConnection() {
return this.jingleConnection;
}
+
+ public static Message createStatusMessage(Conversation conversation) {
+ Message message = new Message();
+ message.setType(Message.TYPE_STATUS);
+ message.setConversation(conversation);
+ return message;
+ }
}
diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java
index fbca8340..0f8e3565 100644
--- a/src/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/eu/siacs/conversations/entities/MucOptions.java
@@ -3,11 +3,10 @@ package eu.siacs.conversations.entities;
import java.util.ArrayList;
import java.util.List;
-import eu.siacs.conversations.entities.MucOptions.User;
+import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
import android.annotation.SuppressLint;
-import android.util.Log;
@SuppressLint("DefaultLocale")
public class MucOptions {
@@ -31,6 +30,7 @@ public class MucOptions {
private int role;
private int affiliation;
private String name;
+ private long pgpKeyId = 0;
public String getName() {
return name;
@@ -70,7 +70,15 @@ public class MucOptions {
this.affiliation = AFFILIATION_NONE;
}
}
+ public void setPgpKeyId(long id) {
+ this.pgpKeyId = id;
+ }
+
+ public long getPgpKeyId() {
+ return this.pgpKeyId;
+ }
}
+ private Account account;
private ArrayList<User> users = new ArrayList<User>();
private Conversation conversation;
private boolean isOnline = false;
@@ -80,6 +88,9 @@ public class MucOptions {
private User self = new User();
private String subject = null;
+ public MucOptions(Account account) {
+ this.account = account;
+ }
public void deleteUser(String name) {
for(int i = 0; i < users.size(); ++i) {
@@ -100,7 +111,7 @@ public class MucOptions {
users.add(user);
}
- public void processPacket(PresencePacket packet) {
+ public void processPacket(PresencePacket packet, PgpEngine pgp) {
String[] fromParts = packet.getFrom().split("/");
if (fromParts.length>=2) {
String name = fromParts[1];
@@ -119,6 +130,20 @@ public class MucOptions {
} else {
addUser(user);
}
+ if (pgp != null) {
+ Element x = packet.findChild("x",
+ "jabber:x:signed");
+ if (x != null) {
+ Element status = packet.findChild("status");
+ String msg;
+ if (status != null) {
+ msg = status.getContent();
+ } else {
+ msg = "";
+ }
+ user.setPgpKeyId(pgp.fetchKeyId(account,msg, x.getContent()));
+ }
+ }
} else if (type.equals("unavailable")) {
if (name.equals(getNick())) {
Element item = packet.findChild("x","http://jabber.org/protocol/muc#user").findChild("item");
@@ -211,4 +236,36 @@ public class MucOptions {
public void flagAboutToRename() {
this.aboutToRename = true;
}
+
+ public long[] getPgpKeyIds() {
+ List<Long> ids = new ArrayList<Long>();
+ for(User user : getUsers()) {
+ if(user.getPgpKeyId()!=0) {
+ ids.add(user.getPgpKeyId());
+ }
+ }
+ long[] primitivLongArray = new long[ids.size()];
+ for(int i = 0; i < ids.size(); ++i) {
+ primitivLongArray[i] = ids.get(i);
+ }
+ return primitivLongArray;
+ }
+
+ public boolean pgpKeysInUse() {
+ for(User user : getUsers()) {
+ if (user.getPgpKeyId()!=0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean everybodyHasKeys() {
+ for(User user : getUsers()) {
+ if (user.getPgpKeyId()==0) {
+ return false;
+ }
+ }
+ return true;
+ }
} \ No newline at end of file
diff --git a/src/eu/siacs/conversations/entities/Presences.java b/src/eu/siacs/conversations/entities/Presences.java
index acbaafca..77891648 100644
--- a/src/eu/siacs/conversations/entities/Presences.java
+++ b/src/eu/siacs/conversations/entities/Presences.java
@@ -4,10 +4,6 @@ import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import eu.siacs.conversations.xml.Element;
public class Presences {
@@ -47,39 +43,6 @@ public class Presences {
return status;
}
- public String toJsonString() {
- JSONArray json = new JSONArray();
- Iterator<Entry<String, Integer>> it = presences.entrySet().iterator();
-
- while (it.hasNext()) {
- Entry<String, Integer> entry = it.next();
- JSONObject jObj = new JSONObject();
- try {
- jObj.put("resource", entry.getKey());
- jObj.put("status", entry.getValue());
- } catch (JSONException e) {
-
- }
- json.put(jObj);
- }
- return json.toString();
- }
-
- public static Presences fromJsonString(String jsonString) {
- Presences presences = new Presences();
- try {
- JSONArray json = new JSONArray(jsonString);
- for (int i = 0; i < json.length(); ++i) {
- JSONObject jObj = json.getJSONObject(i);
- presences.updatePresence(jObj.getString("resource"),
- jObj.getInt("status"));
- }
- } catch (JSONException e1) {
-
- }
- return presences;
- }
-
public static int parseShow(Element show) {
if (show == null) {
return Presences.ONLINE;
@@ -99,4 +62,14 @@ public class Presences {
public int size() {
return presences.size();
}
+
+ public String[] asStringArray() {
+ final String[] presencesArray = new String[presences.size()];
+ presences.keySet().toArray(presencesArray);
+ return presencesArray;
+ }
+
+ public boolean has(String presence) {
+ return presences.containsKey(presence);
+ }
}
diff --git a/src/eu/siacs/conversations/entities/Roster.java b/src/eu/siacs/conversations/entities/Roster.java
new file mode 100644
index 00000000..c1e40dbc
--- /dev/null
+++ b/src/eu/siacs/conversations/entities/Roster.java
@@ -0,0 +1,74 @@
+package eu.siacs.conversations.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Roster {
+ Account account;
+ ConcurrentHashMap<String, Contact> contacts = new ConcurrentHashMap<String, Contact>();
+ private String version = null;
+
+ public Roster(Account account) {
+ this.account = account;
+ }
+
+ public boolean hasContact(String jid) {
+ String cleanJid = jid.split("/")[0];
+ return contacts.containsKey(cleanJid);
+ }
+
+ public Contact getContact(String jid) {
+ String cleanJid = jid.split("/")[0];
+ if (contacts.containsKey(cleanJid)) {
+ return contacts.get(cleanJid);
+ } else {
+ Contact contact = new Contact(cleanJid);
+ contact.setAccount(account);
+ contacts.put(cleanJid, contact);
+ return contact;
+ }
+ }
+
+ public void clearPresences() {
+ for(Contact contact : getContacts()) {
+ contact.clearPresences();
+ }
+ }
+
+ public void markAllAsNotInRoster() {
+ for(Contact contact : getContacts()) {
+ contact.resetOption(Contact.Options.IN_ROSTER);
+ }
+ }
+
+ public void clearSystemAccounts() {
+ for(Contact contact : getContacts()) {
+ contact.setPhotoUri(null);
+ contact.setSystemName(null);
+ contact.setSystemAccount(null);
+ }
+ }
+
+ public List<Contact> getContacts() {
+ return new ArrayList<Contact>(this.contacts.values());
+ }
+
+ public void initContact(Contact contact) {
+ contact.setAccount(account);
+ contact.setOption(Contact.Options.IN_ROSTER);
+ contacts.put(contact.getJid(),contact);
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public Account getAccount() {
+ return this.account;
+ }
+}
diff --git a/src/eu/siacs/conversations/parser/AbstractParser.java b/src/eu/siacs/conversations/parser/AbstractParser.java
new file mode 100644
index 00000000..2bd839f1
--- /dev/null
+++ b/src/eu/siacs/conversations/parser/AbstractParser.java
@@ -0,0 +1,53 @@
+package eu.siacs.conversations.parser;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Element;
+
+public abstract class AbstractParser {
+
+ protected XmppConnectionService mXmppConnectionService;
+
+ protected AbstractParser(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ protected long getTimestamp(Element packet) {
+ if (packet.hasChild("delay")) {
+ try {
+ String stamp = packet.findChild("delay").getAttribute(
+ "stamp");
+ stamp = stamp.replace("Z", "+0000");
+ Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
+ .parse(stamp);
+ return date.getTime();
+ } catch (ParseException e) {
+ return System.currentTimeMillis();
+ }
+ } else {
+ return System.currentTimeMillis();
+ }
+ }
+
+ protected void updateLastseen(Element packet, Account account, boolean presenceOverwrite) {
+ String[] fromParts = packet.getAttribute("from").split("/");
+ String from = fromParts[0];
+ String presence = null;
+ if (fromParts.length >= 2) {
+ presence = fromParts[1];
+ }
+ Contact contact = account.getRoster().getContact(from);
+ long timestamp = getTimestamp(packet);
+ if (timestamp >= contact.lastseen.time) {
+ contact.lastseen.time = timestamp;
+ if ((presence!=null)&&(presenceOverwrite)) {
+ contact.lastseen.presence = presence;
+ }
+ }
+ }
+}
diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java
new file mode 100644
index 00000000..a435d055
--- /dev/null
+++ b/src/eu/siacs/conversations/parser/MessageParser.java
@@ -0,0 +1,223 @@
+package eu.siacs.conversations.parser;
+
+import android.util.Log;
+import net.java.otr4j.session.Session;
+import net.java.otr4j.session.SessionStatus;
+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.utils.CryptoHelper;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+
+public class MessageParser extends AbstractParser {
+
+ public MessageParser(XmppConnectionService service) {
+ super(service);
+ }
+
+ public Message parseChat(MessagePacket packet, Account account) {
+ String[] fromParts = packet.getFrom().split("/");
+ Conversation conversation = mXmppConnectionService
+ .findOrCreateConversation(account, fromParts[0], false);
+ conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
+ updateLastseen(packet, account,true);
+ String pgpBody = getPgpBody(packet);
+ if (pgpBody != null) {
+ return new Message(conversation, packet.getFrom(), pgpBody,
+ Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED);
+ } else {
+ return new Message(conversation, packet.getFrom(),
+ packet.getBody(), Message.ENCRYPTION_NONE,
+ Message.STATUS_RECIEVED);
+ }
+ }
+
+ public Message parseOtrChat(MessagePacket packet, Account account) {
+ boolean properlyAddressed = (packet.getTo().split("/").length == 2)
+ || (account.countPresences() == 1);
+ String[] fromParts = packet.getFrom().split("/");
+ Conversation conversation = mXmppConnectionService
+ .findOrCreateConversation(account, fromParts[0], false);
+ updateLastseen(packet, account,true);
+ String body = packet.getBody();
+ if (!conversation.hasValidOtrSession()) {
+ if (properlyAddressed) {
+ conversation.startOtrSession(
+ mXmppConnectionService.getApplicationContext(),
+ fromParts[1], false);
+ } else {
+ return null;
+ }
+ } else {
+ String foreignPresence = conversation.getOtrSession()
+ .getSessionID().getUserID();
+ if (!foreignPresence.equals(fromParts[1])) {
+ conversation.resetOtrSession();
+ if (properlyAddressed) {
+ conversation.startOtrSession(
+ mXmppConnectionService.getApplicationContext(),
+ fromParts[1], false);
+ } else {
+ return null;
+ }
+ }
+ }
+ try {
+ Session otrSession = conversation.getOtrSession();
+ SessionStatus before = otrSession.getSessionStatus();
+ body = otrSession.transformReceiving(body);
+ SessionStatus after = otrSession.getSessionStatus();
+ if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
+ mXmppConnectionService.onOtrSessionEstablished(conversation);
+ } else if ((before != after) && (after == SessionStatus.FINISHED)) {
+ conversation.resetOtrSession();
+ }
+ if ((body == null) || (body.isEmpty())) {
+ return null;
+ }
+ if (body.startsWith(CryptoHelper.FILETRANSFER)) {
+ String key = body.substring(CryptoHelper.FILETRANSFER.length());
+ conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
+ Log.d("xmppService","new symmetric key: "+CryptoHelper.bytesToHex(conversation.getSymmetricKey()));
+ return null;
+ }
+ conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
+ Message finishedMessage = new Message(conversation, packet.getFrom(), body,
+ Message.ENCRYPTION_OTR, Message.STATUS_RECIEVED);
+ finishedMessage.setTime(getTimestamp(packet));
+ return finishedMessage;
+ } catch (Exception e) {
+ conversation.resetOtrSession();
+ return null;
+ }
+ }
+
+ public Message parseGroupchat(MessagePacket packet, Account account) {
+ int status;
+ String[] fromParts = packet.getFrom().split("/");
+ Conversation conversation = mXmppConnectionService
+ .findOrCreateConversation(account, fromParts[0], true);
+ if (packet.hasChild("subject")) {
+ conversation.getMucOptions().setSubject(
+ packet.findChild("subject").getContent());
+ mXmppConnectionService.updateUi(conversation, false);
+ return null;
+ }
+ if ((fromParts.length == 1)) {
+ return null;
+ }
+ String counterPart = fromParts[1];
+ if (counterPart.equals(conversation.getMucOptions().getNick())) {
+ if (mXmppConnectionService.markMessage(conversation,
+ packet.getId(), Message.STATUS_SEND)) {
+ return null;
+ } else {
+ status = Message.STATUS_SEND;
+ }
+ } else {
+ status = Message.STATUS_RECIEVED;
+ }
+ String pgpBody = getPgpBody(packet);
+ conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
+ Message finishedMessage;
+ if (pgpBody == null) {
+ finishedMessage = new Message(conversation, counterPart, packet.getBody(),
+ Message.ENCRYPTION_NONE, status);
+ } else {
+ finishedMessage= new Message(conversation, counterPart, pgpBody,
+ Message.ENCRYPTION_PGP, status);
+ }
+ finishedMessage.setTime(getTimestamp(packet));
+ return finishedMessage;
+ }
+
+ public Message parseCarbonMessage(MessagePacket packet, Account account) {
+ int status;
+ String fullJid;
+ Element forwarded;
+ if (packet.hasChild("received")) {
+ forwarded = packet.findChild("received").findChild("forwarded");
+ status = Message.STATUS_RECIEVED;
+ } else if (packet.hasChild("sent")) {
+ forwarded = packet.findChild("sent").findChild("forwarded");
+ status = Message.STATUS_SEND;
+ } else {
+ return null;
+ }
+ if (forwarded == null) {
+ return null;
+ }
+ Element message = forwarded.findChild("message");
+ if ((message == null) || (!message.hasChild("body")))
+ return null; // either malformed or boring
+ if (status == Message.STATUS_RECIEVED) {
+ fullJid = message.getAttribute("from");
+ updateLastseen(message, account,true);
+ } else {
+ fullJid = message.getAttribute("to");
+ }
+ String[] parts = fullJid.split("/");
+ Conversation conversation = mXmppConnectionService
+ .findOrCreateConversation(account, parts[0], false);
+ conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
+ String pgpBody = getPgpBody(message);
+ Message finishedMessage;
+ if (pgpBody != null) {
+ finishedMessage = new Message(conversation, fullJid, pgpBody,Message.ENCRYPTION_PGP, status);
+ } else {
+ String body = message.findChild("body").getContent();
+ finishedMessage= new Message(conversation, fullJid, body,Message.ENCRYPTION_NONE, status);
+ }
+ finishedMessage.setTime(getTimestamp(message));
+ return finishedMessage;
+ }
+
+ public void parseError(MessagePacket packet, Account account) {
+ String[] fromParts = packet.getFrom().split("/");
+ mXmppConnectionService.markMessage(account, fromParts[0],
+ packet.getId(), Message.STATUS_SEND_FAILED);
+ }
+
+ public void parseNormal(MessagePacket packet, Account account) {
+ if (packet.hasChild("displayed","urn:xmpp:chat-markers:0")) {
+ String id = packet.findChild("displayed","urn:xmpp:chat-markers:0").getAttribute("id");
+ String[] fromParts = packet.getFrom().split("/");
+ updateLastseen(packet, account,true);
+ mXmppConnectionService.markMessage(account,fromParts[0], id, Message.STATUS_SEND_DISPLAYED);
+ } else if (packet.hasChild("received","urn:xmpp:chat-markers:0")) {
+ String id = packet.findChild("received","urn:xmpp:chat-markers:0").getAttribute("id");
+ String[] fromParts = packet.getFrom().split("/");
+ updateLastseen(packet, account,false);
+ mXmppConnectionService.markMessage(account,fromParts[0], id, Message.STATUS_SEND_RECEIVED);
+ } else if (packet.hasChild("x")) {
+ Element x = packet.findChild("x");
+ if (x.hasChild("invite")) {
+ Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, packet.getFrom(),
+ true);
+ mXmppConnectionService.updateUi(conversation, false);
+ }
+
+ }
+ }
+
+ private String getPgpBody(Element message) {
+ Element child = message.findChild("x", "jabber:x:encrypted");
+ if (child == null) {
+ return null;
+ } else {
+ return child.getContent();
+ }
+ }
+
+ private String getMarkableMessageId(Element message) {
+ if (message.hasChild("markable", "urn:xmpp:chat-markers:0")) {
+ return message.getAttribute("id");
+ } else {
+ return null;
+ }
+ }
+
+
+}
diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java
new file mode 100644
index 00000000..2003d4cd
--- /dev/null
+++ b/src/eu/siacs/conversations/parser/PresenceParser.java
@@ -0,0 +1,104 @@
+package eu.siacs.conversations.parser;
+
+import eu.siacs.conversations.crypto.PgpEngine;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+
+public class PresenceParser extends AbstractParser {
+
+ public PresenceParser(XmppConnectionService service) {
+ super(service);
+ }
+
+ public void parseConferencePresence(PresencePacket packet, Account account) {
+ PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine();
+ if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
+ Conversation muc = mXmppConnectionService.findMuc(packet
+ .getAttribute("from").split("/")[0], account);
+ if (muc != null) {
+ muc.getMucOptions().processPacket(packet, mPgpEngine);
+ }
+ } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
+ Conversation muc = mXmppConnectionService.findMuc(packet
+ .getAttribute("from").split("/")[0], account);
+ if (muc != null) {
+ int error = muc.getMucOptions().getError();
+ muc.getMucOptions().processPacket(packet, mPgpEngine);
+ if (muc.getMucOptions().getError() != error) {
+ mXmppConnectionService.updateUi(muc, false);
+ }
+ }
+ }
+ }
+
+ public void parseContactPresence(PresencePacket packet, Account account) {
+ String[] fromParts = packet.getAttribute("from").split("/");
+ String type = packet.getAttribute("type");
+ if (fromParts[0].equals(account.getJid())) {
+ if (fromParts.length == 2) {
+ if (type == null) {
+ account.updatePresence(fromParts[1],
+ Presences.parseShow(packet.findChild("show")));
+ } else if (type.equals("unavailable")) {
+ account.removePresence(fromParts[1]);
+ }
+ }
+
+ } else {
+ Contact contact = account.getRoster().getContact(packet.getFrom());
+ if (type == null) {
+ if (fromParts.length == 2) {
+ int sizeBefore = contact.getPresences().size();
+ contact.updatePresence(fromParts[1],
+ Presences.parseShow(packet.findChild("show")));
+ PgpEngine pgp = mXmppConnectionService.getPgpEngine();
+ if (pgp != null) {
+ Element x = packet.findChild("x", "jabber:x:signed");
+ if (x != null) {
+ Element status = packet.findChild("status");
+ String msg;
+ if (status != null) {
+ msg = status.getContent();
+ } else {
+ msg = "";
+ }
+ contact.setPgpKeyId(pgp.fetchKeyId(account, msg,
+ x.getContent()));
+ }
+ }
+ boolean online = sizeBefore < contact.getPresences().size();
+ updateLastseen(packet, account,true);
+ mXmppConnectionService.onContactStatusChanged
+ .onContactStatusChanged(contact,online);
+ }
+ } else if (type.equals("unavailable")) {
+ if (fromParts.length != 2) {
+ contact.clearPresences();
+ } else {
+ contact.removePresence(fromParts[1]);
+ }
+ mXmppConnectionService.onContactStatusChanged
+ .onContactStatusChanged(contact,false);
+ } else if (type.equals("subscribe")) {
+ if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
+ mXmppConnectionService.sendPresenceUpdatesTo(contact);
+ contact.setOption(Contact.Options.FROM);
+ contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
+ if ((contact.getOption(Contact.Options.ASKING))
+ && (!contact.getOption(Contact.Options.TO))) {
+ mXmppConnectionService
+ .requestPresenceUpdatesFrom(contact);
+ }
+ } else {
+ contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
index 5a34dac6..fbf45d25 100644
--- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -2,20 +2,17 @@ package eu.siacs.conversations.persistance;
import java.util.ArrayList;
import java.util.List;
-import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
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.Presences;
-import android.content.ContentValues;
+import eu.siacs.conversations.entities.Roster;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Bundle;
import android.util.Log;
public class DatabaseBackend extends SQLiteOpenHelper {
@@ -23,7 +20,15 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 3;
+ private static final int DATABASE_VERSION = 5;
+
+ private static String CREATE_CONTATCS_STATEMENT = "create table "
+ + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " + Contact.SERVERNAME + " TEXT, "
+ + Contact.SYSTEMNAME + " TEXT," + Contact.JID + " TEXT,"
+ + Contact.KEYS + " TEXT," + Contact.PHOTOURI + " TEXT,"
+ + Contact.OPTIONS + " NUMBER," + Contact.SYSTEMACCOUNT
+ + " NUMBER, " + "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
+ + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, UNIQUE("+Contact.ACCOUNT+", "+Contact.JID+") ON CONFLICT REPLACE);";
public DatabaseBackend(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -36,7 +41,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
- + " NUMBER, "+Account.KEYS+" TEXT)");
+ + " NUMBER, " + Account.KEYS + " TEXT)");
db.execSQL("create table " + Conversation.TABLENAME + " ("
+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
+ " TEXT, " + Conversation.CONTACT + " TEXT, "
@@ -50,31 +55,28 @@ 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," +Message.TYPE +" NUMBER, FOREIGN KEY("
- + Message.CONVERSATION + ") REFERENCES "
- + Conversation.TABLENAME + "(" + Conversation.UUID
- + ") ON DELETE CASCADE);");
- db.execSQL("create table " + Contact.TABLENAME + "(" + Contact.UUID
- + " TEXT PRIMARY KEY, " + Contact.ACCOUNT + " TEXT, "
- + Contact.DISPLAYNAME + " TEXT," + Contact.JID + " TEXT,"
- + Contact.PRESENCES + " TEXT, " + Contact.KEYS
- + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.SUBSCRIPTION
- + " NUMBER," + Contact.SYSTEMACCOUNT + " NUMBER, "
- + "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
- + Account.TABLENAME + "(" + Account.UUID
- + ") ON DELETE CASCADE);");
+ + " NUMBER, " + Message.STATUS + " NUMBER," + Message.TYPE
+ + " NUMBER, FOREIGN KEY(" + Message.CONVERSATION
+ + ") REFERENCES " + Conversation.TABLENAME + "("
+ + Conversation.UUID + ") ON DELETE CASCADE);");
+
+ db.execSQL(CREATE_CONTATCS_STATEMENT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2 && newVersion >= 2) {
- // enable compression by default.
- db.execSQL("update " + Account.TABLENAME
- + " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
+ 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");;
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.TYPE + " NUMBER");
+ }
+ if (oldVersion < 5 && newVersion >= 5) {
+ db.execSQL("DROP TABLE "+Contact.TABLENAME);
+ db.execSQL(CREATE_CONTATCS_STATEMENT);
+ db.execSQL("UPDATE "+Account.TABLENAME+ " SET "+Account.ROSTERVERSION+" = NULL");
}
}
@@ -99,7 +101,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
SQLiteDatabase db = this.getWritableDatabase();
db.insert(Account.TABLENAME, null, account.getContentValues());
}
-
+
public void createContact(Contact contact) {
SQLiteDatabase db = this.getWritableDatabase();
db.insert(Contact.TABLENAME, null, contact.getContentValues());
@@ -126,14 +128,26 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
return list;
}
+
+ public List<Message> getMessages(Conversation conversations, int limit) {
+ return getMessages(conversations, limit,-1);
+ }
- public List<Message> getMessages(Conversation conversation, int limit) {
+ public List<Message> getMessages(Conversation conversation, int limit, long timestamp) {
List<Message> list = new CopyOnWriteArrayList<Message>();
SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = { conversation.getUuid() };
- Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
- + "=?", selectionArgs, null, null, Message.TIME_SENT + " DESC",
- String.valueOf(limit));
+ Cursor cursor;
+ if (timestamp==-1) {
+ String[] selectionArgs = { conversation.getUuid() };
+ cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=?", selectionArgs, null, null, Message.TIME_SENT + " DESC",
+ String.valueOf(limit));
+ } else {
+ String[] selectionArgs = { conversation.getUuid() , ""+timestamp};
+ cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=? and "+Message.TIME_SENT+"<?", selectionArgs, null, null, Message.TIME_SENT + " DESC",
+ String.valueOf(limit));
+ }
if (cursor.getCount() > 0) {
cursor.moveToLast();
do {
@@ -145,10 +159,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public Conversation findConversation(Account account, String contactJid) {
SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = { account.getUuid(), contactJid+"%" };
+ String[] selectionArgs = { account.getUuid(), contactJid + "%" };
Cursor cursor = db.query(Conversation.TABLENAME, null,
- Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID + " like ?",
- selectionArgs, null, null, null);
+ Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID
+ + " like ?", selectionArgs, null, null, null);
if (cursor.getCount() == 0)
return null;
cursor.moveToFirst();
@@ -200,87 +214,32 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
+ "=?", args);
}
-
- public void updateContact(Contact contact, boolean updatePresences) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = { contact.getUuid() };
- ContentValues values = contact.getContentValues();
- if (!updatePresences) {
- values.remove(Contact.PRESENCES);
- } else {
- values.remove(Contact.DISPLAYNAME);
- values.remove(Contact.PHOTOURI);
- values.remove(Contact.SYSTEMACCOUNT);
- }
- db.update(Contact.TABLENAME, contact.getContentValues(), Contact.UUID
- + "=?", args);
- }
-
- public void clearPresences(Account account) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = { account.getUuid() };
- ContentValues values = new ContentValues();
- values.put(Contact.PRESENCES,"[]");
- db.update(Contact.TABLENAME, values, Contact.ACCOUNT
- + "=?", args);
- }
-
- public void mergeContacts(List<Contact> contacts) {
- SQLiteDatabase db = this.getWritableDatabase();
- for (int i = 0; i < contacts.size(); i++) {
- Contact contact = contacts.get(i);
- String[] columns = {Contact.UUID, Contact.PRESENCES};
- String[] args = {contact.getAccount().getUuid(), contact.getJid()};
- Cursor cursor = db.query(Contact.TABLENAME, columns,Contact.ACCOUNT+"=? AND "+Contact.JID+"=?", args, null, null, null);
- if (cursor.getCount()>=1) {
- cursor.moveToFirst();
- contact.setUuid(cursor.getString(0));
- updateContact(contact,false);
- } else {
- contact.setUuid(UUID.randomUUID().toString());
- createContact(contact);
- }
- }
- }
- public List<Contact> getContactsByAccount(Account account) {
- List<Contact> list = new ArrayList<Contact>();
+ public void readRoster(Roster roster) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor;
- if (account==null) {
- cursor = db.query(Contact.TABLENAME, null, null, null, null,
- null, null);
- } else {
- String args[] = {account.getUuid()};
- cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT+"=?", args, null,
- null, null);
- }
+ String args[] = { roster.getAccount().getUuid() };
+ cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?",
+ args, null, null, null);
while (cursor.moveToNext()) {
- list.add(Contact.fromCursor(cursor));
+ roster.initContact(Contact.fromCursor(cursor));
}
- return list;
}
- public List<Contact> getContacts(String where) {
- List<Contact> list = new ArrayList<Contact>();
- SQLiteDatabase db = this.getReadableDatabase();
- Cursor cursor = db.query(Contact.TABLENAME, null, where, null, null, null, null);
- while (cursor.moveToNext()) {
- list.add(Contact.fromCursor(cursor));
+ public void writeRoster(Roster roster) {
+ Account account = roster.getAccount();
+ SQLiteDatabase db = this.getWritableDatabase();
+ for(Contact contact : roster.getContacts()) {
+ if (contact.getOption(Contact.Options.IN_ROSTER)) {
+ db.insert(Contact.TABLENAME, null, contact.getContentValues());
+ } else {
+ String where = Contact.ACCOUNT + "=? AND "+Contact.JID+"=?";
+ String[] whereArgs = {account.getUuid(), contact.getJid()};
+ db.delete(Contact.TABLENAME, where, whereArgs);
+ }
}
- return list;
- }
-
- public Contact findContact(Account account, String jid) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = { account.getUuid(), jid };
- Cursor cursor = db.query(Contact.TABLENAME, null,
- Contact.ACCOUNT + "=? AND " + Contact.JID + "=?",
- selectionArgs, null, null, null);
- if (cursor.getCount() == 0)
- return null;
- cursor.moveToFirst();
- return Contact.fromCursor(cursor);
+ account.setRosterVersion(roster.getVersion());
+ updateAccount(account);
}
public void deleteMessage(Message message) {
@@ -288,34 +247,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String[] args = { message.getUuid() };
db.delete(Message.TABLENAME, Message.UUID + "=?", args);
}
-
+
public void deleteMessagesInConversation(Conversation conversation) {
SQLiteDatabase db = this.getWritableDatabase();
String[] args = { conversation.getUuid() };
db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
}
- public void deleteContact(Contact contact) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = { contact.getUuid() };
- db.delete(Contact.TABLENAME, Contact.UUID + "=?", args);
- }
-
- public Contact getContact(String uuid) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = { uuid };
- Cursor cursor = db.query(Contact.TABLENAME, null, Contact.UUID + "=?", args, null, null, null);
- if (cursor.getCount() == 0) {
- return null;
- }
- cursor.moveToFirst();
- return Contact.fromCursor(cursor);
- }
-
public Conversation findConversationByUuid(String conversationUuid) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = { conversationUuid };
- Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.UUID + "=?", selectionArgs, null, null, null);
+ Cursor cursor = db.query(Conversation.TABLENAME, null,
+ Conversation.UUID + "=?", selectionArgs, null, null, null);
if (cursor.getCount() == 0) {
return null;
}
@@ -326,18 +269,20 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public Message findMessageByUuid(String messageUuid) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = { messageUuid };
- Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?", selectionArgs, null, null, null);
+ Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
+ selectionArgs, null, null, null);
if (cursor.getCount() == 0) {
return null;
}
cursor.moveToFirst();
return Message.fromCursor(cursor);
}
-
+
public Account findAccountByUuid(String accountUuid) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = { accountUuid };
- Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", selectionArgs, null, null, null);
+ Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
+ selectionArgs, null, null, null);
if (cursor.getCount() == 0) {
return null;
}
diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java
index becb1ee3..ca6360b9 100644
--- a/src/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/eu/siacs/conversations/persistance/FileBackend.java
@@ -1,12 +1,12 @@
package eu.siacs.conversations.persistance;
import java.io.File;
+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.lang.ref.WeakReference;
import android.content.Context;
import android.graphics.Bitmap;
@@ -14,14 +14,9 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.util.LruCache;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.jingle.JingleFile;
public class FileBackend {
@@ -43,11 +38,11 @@ public class FileBackend {
};
}
-
+
public LruCache<String, Bitmap> getThumbnailCache() {
return thumbnailCache;
}
-
+
public JingleFile getJingleFile(Message message) {
return getJingleFile(message, true);
}
@@ -58,10 +53,14 @@ public class FileBackend {
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
+ conversation.getContactJid();
String filename;
- if ((decrypted)||(message.getEncryption() == Message.ENCRYPTION_NONE)) {
+ if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
filename = message.getUuid() + ".webp";
} else {
- filename = message.getUuid() + ".webp.pgp";
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ filename = message.getUuid() + ".webp";
+ } else {
+ filename = message.getUuid() + ".webp.pgp";
+ }
}
return new JingleFile(path + "/" + filename);
}
@@ -86,37 +85,64 @@ public class FileBackend {
return originalBitmap;
}
}
+
+ public JingleFile copyImageToPrivateStorage(Message message, Uri image) throws ImageCopyException {
+ return this.copyImageToPrivateStorage(message, image,0);
+ }
- public JingleFile copyImageToPrivateStorage(Message message, Uri image) {
+ private JingleFile copyImageToPrivateStorage(Message message, Uri image, int sampleSize)
+ throws ImageCopyException {
try {
- Log.d("xmppService","copying file: "+image.toString()+ " to internal storage");
- InputStream is = context.getContentResolver()
- .openInputStream(image);
+ InputStream is;
+ if (image != null) {
+ is = context.getContentResolver().openInputStream(image);
+ } else {
+ is = new FileInputStream(getIncomingFile());
+ }
JingleFile file = getJingleFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
- OutputStream os = new FileOutputStream(file);
- Bitmap originalBitmap = BitmapFactory.decodeStream(is);
+ Bitmap originalBitmap;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ int inSampleSize = (int) Math.pow(2, sampleSize);
+ Log.d("xmppService","reading bitmap with sample size "+inSampleSize);
+ options.inSampleSize = inSampleSize;
+ originalBitmap = BitmapFactory.decodeStream(is, null, options);
is.close();
+ if (originalBitmap == null) {
+ throw new ImageCopyException(R.string.error_not_an_image_file);
+ }
+ if (image == null) {
+ getIncomingFile().delete();
+ }
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
+ OutputStream os = new FileOutputStream(file);
boolean success = scalledBitmap.compress(
Bitmap.CompressFormat.WEBP, 75, os);
if (!success) {
- return null;
+ throw new ImageCopyException(R.string.error_compressing_image);
}
os.flush();
os.close();
long size = file.getSize();
int width = scalledBitmap.getWidth();
int height = scalledBitmap.getHeight();
- message.setBody(""+size+","+width+","+height);
+ message.setBody("" + size + "," + width + "," + height);
return file;
} catch (FileNotFoundException e) {
- return null;
+ throw new ImageCopyException(R.string.error_file_not_found);
} catch (IOException e) {
- return null;
+ throw new ImageCopyException(R.string.error_io_exception);
} catch (SecurityException e) {
- return null;
+ throw new ImageCopyException(
+ R.string.error_security_exception_during_image_copy);
+ } catch (OutOfMemoryError e) {
+ ++sampleSize;
+ if (sampleSize<=3) {
+ return copyImageToPrivateStorage(message, image, sampleSize);
+ } else {
+ throw new ImageCopyException(R.string.error_out_of_memory);
+ }
}
}
@@ -128,7 +154,7 @@ public class FileBackend {
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
throws FileNotFoundException {
Bitmap thumbnail = thumbnailCache.get(message.getUuid());
- if ((thumbnail == null)&&(!cacheOnly)) {
+ if ((thumbnail == null) && (!cacheOnly)) {
Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
.getAbsolutePath());
if (fullsize == null) {
@@ -160,4 +186,21 @@ public class FileBackend {
}
f.delete();
}
+
+ public File getIncomingFile() {
+ return new File(context.getFilesDir().getAbsolutePath() + "/incoming");
+ }
+
+ public class ImageCopyException extends Exception {
+ private static final long serialVersionUID = -1010013599132881427L;
+ private int resId;
+
+ public ImageCopyException(int resId) {
+ this.resId = resId;
+ }
+
+ public int getResId() {
+ return resId;
+ }
+ }
}
diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java
index c1bae661..80cfbd95 100644
--- a/src/eu/siacs/conversations/services/ImageProvider.java
+++ b/src/eu/siacs/conversations/services/ImageProvider.java
@@ -2,13 +2,13 @@ package eu.siacs.conversations.services;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
-
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
@@ -21,46 +21,62 @@ public class ImageProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
- DatabaseBackend databaseBackend = DatabaseBackend
- .getInstance(getContext());
+ ParcelFileDescriptor pfd;
FileBackend fileBackend = new FileBackend(getContext());
- String uuids = uri.getPath();
- Log.d("xmppService", "uuids = " + uuids);
- if (uuids == null) {
- throw new FileNotFoundException();
- }
- String[] uuidsSplited = uuids.split("/");
- if (uuidsSplited.length != 3) {
+ if ("r".equals(mode)) {
+ DatabaseBackend databaseBackend = DatabaseBackend
+ .getInstance(getContext());
+ String uuids = uri.getPath();
+ Log.d("xmppService", "uuids = " + uuids+" mode="+mode);
+ if (uuids == null) {
+ throw new FileNotFoundException();
+ }
+ String[] uuidsSplited = uuids.split("/");
+ if (uuidsSplited.length != 3) {
+ throw new FileNotFoundException();
+ }
+ String conversationUuid = uuidsSplited[1];
+ String messageUuid = uuidsSplited[2].split("\\.")[0];
+
+ Log.d("xmppService","messageUuid="+messageUuid);
+
+ Conversation conversation = databaseBackend
+ .findConversationByUuid(conversationUuid);
+ if (conversation == null) {
+ throw new FileNotFoundException("conversation " + conversationUuid
+ + " could not be found");
+ }
+ Message message = databaseBackend.findMessageByUuid(messageUuid);
+ if (message == null) {
+ throw new FileNotFoundException("message " + messageUuid
+ + " could not be found");
+ }
+
+ Account account = databaseBackend.findAccountByUuid(conversation
+ .getAccountUuid());
+ if (account == null) {
+ throw new FileNotFoundException("account "
+ + conversation.getAccountUuid() + " cound not be found");
+ }
+ message.setConversation(conversation);
+ conversation.setAccount(account);
+
+ File file = fileBackend.getJingleFile(message);
+ pfd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ return pfd;
+ } else if ("w".equals(mode)){
+ File file = fileBackend.getIncomingFile();
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ throw new FileNotFoundException();
+ }
+ pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
+ return pfd;
+ } else {
throw new FileNotFoundException();
}
- String conversationUuid = uuidsSplited[1];
- String messageUuid = uuidsSplited[2];
-
- Conversation conversation = databaseBackend
- .findConversationByUuid(conversationUuid);
- if (conversation == null) {
- throw new FileNotFoundException("conversation " + conversationUuid
- + " could not be found");
- }
- Message message = databaseBackend.findMessageByUuid(messageUuid);
- if (message == null) {
- throw new FileNotFoundException("message " + messageUuid
- + " could not be found");
- }
-
- Account account = databaseBackend.findAccountByUuid(conversation
- .getAccountUuid());
- if (account == null) {
- throw new FileNotFoundException("account "
- + conversation.getAccountUuid() + " cound not be found");
- }
- message.setConversation(conversation);
- conversation.setAccount(account);
-
- File file = fileBackend.getJingleFile(message);
- ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_READ_ONLY);
- return pfd;
}
@Override
@@ -93,5 +109,17 @@ public class ImageProvider extends ContentProvider {
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
+
+ public static Uri getContentUri(Message message) {
+ return Uri
+ .parse("content://eu.siacs.conversations.images/"
+ + message.getConversationUuid()
+ + "/"
+ + message.getUuid()
+ + ".webp");
+ }
-}
+ public static Uri getIncomingContentUri() {
+ return Uri.parse("content://eu.siacs.conversations.images/incoming");
+ }
+} \ No newline at end of file
diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java
index d2742997..505d09e5 100644
--- a/src/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/eu/siacs/conversations/services/XmppConnectionService.java
@@ -1,23 +1,18 @@
package eu.siacs.conversations.services;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.security.SecureRandom;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
-import java.util.Random;
-import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
-import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -26,20 +21,22 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.parser.MessageParser;
+import eu.siacs.conversations.parser.PresenceParser;
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;
-import eu.siacs.conversations.ui.OnRosterFetchedListener;
import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.ExceptionHelper;
-import eu.siacs.conversations.utils.MessageParser;
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
+import eu.siacs.conversations.utils.PRNGFixes;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
+import eu.siacs.conversations.xmpp.OnContactStatusChanged;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
@@ -47,12 +44,12 @@ 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.JingleFile;
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 android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -60,7 +57,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.ContentObserver;
-import android.database.DatabaseUtils;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -88,6 +84,11 @@ public class XmppConnectionService extends Service {
private static final int CONNECT_TIMEOUT = 60;
private static final long CARBON_GRACE_PERIOD = 60000L;
+ private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+
+ private MessageParser mMessageParser = new MessageParser(this);
+ private PresenceParser mPresenceParser = new PresenceParser(this);
+
private List<Account> accounts;
private List<Conversation> conversations = null;
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
@@ -97,13 +98,26 @@ public class XmppConnectionService extends Service {
private int convChangedListenerCount = 0;
private OnAccountListChangedListener accountChangedListener = null;
private OnTLSExceptionReceived tlsException = null;
+ public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
+
+ @Override
+ public void onContactStatusChanged(Contact contact, boolean online) {
+ Conversation conversation = findActiveConversation(contact);
+ if (conversation != null) {
+ conversation.endOtrIfNeeded();
+ if (online && (contact.getPresences().size() == 1)) {
+ sendUnsendMessages(conversation);
+ }
+ }
+ }
+ };
public void setOnTLSExceptionReceivedListener(
OnTLSExceptionReceived listener) {
tlsException = listener;
}
- private Random mRandom = new Random(System.currentTimeMillis());
+ private SecureRandom mRandom;
private long lastCarbonMessageReceived = -CARBON_GRACE_PERIOD;
@@ -111,13 +125,13 @@ public class XmppConnectionService extends Service {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- Log.d(LOGTAG, "contact list has changed");
- mergePhoneContactsWithRoster(null);
+ Intent intent = new Intent(getApplicationContext(),
+ XmppConnectionService.class);
+ intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
+ startService(intent);
}
};
- private XmppConnectionService service = this;
-
private final IBinder mBinder = new XmppConnectionBinder();
private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
@@ -132,26 +146,19 @@ public class XmppConnectionService extends Service {
}
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.getBody() != null)
+ if ((packet.getBody() != null)
&& (packet.getBody().startsWith("?OTR"))) {
- message = MessageParser.parseOtrChat(packet, account,
- service);
+ message = mMessageParser.parseOtrChat(packet, account);
if (message != null) {
message.markUnread();
}
} else if (packet.hasChild("body")) {
- message = MessageParser.parsePlainTextChat(packet, account,
- service);
+ message = mMessageParser.parseChat(packet, account);
message.markUnread();
} else if (packet.hasChild("received")
|| (packet.hasChild("sent"))) {
- message = MessageParser.parseCarbonMessage(packet, account,
- service);
+ message = mMessageParser
+ .parseCarbonMessage(packet, account);
if (message != null) {
if (message.getStatus() == Message.STATUS_SEND) {
lastCarbonMessageReceived = SystemClock
@@ -165,8 +172,7 @@ public class XmppConnectionService extends Service {
}
} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
- message = MessageParser
- .parseGroupchat(packet, account, service);
+ message = mMessageParser.parseGroupchat(packet, account);
if (message != null) {
if (message.getStatus() == Message.STATUS_RECIEVED) {
message.markUnread();
@@ -176,38 +182,31 @@ public class XmppConnectionService extends Service {
}
}
} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
- MessageParser.parseError(packet, account, service);
+ mMessageParser.parseError(packet, account);
return;
} else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
- if (packet.hasChild("x")) {
- Element x = packet.findChild("x");
- if (x.hasChild("invite")) {
- findOrCreateConversation(account, packet.getFrom(),
- true);
- if (convChangedListener != null) {
- convChangedListener.onConversationListChanged();
- }
- Log.d(LOGTAG,
- "invitation received to " + packet.getFrom());
- }
-
- } else {
- // Log.d(LOGTAG, "unparsed message " + packet.toString());
- }
+ mMessageParser.parseNormal(packet, account);
}
if ((message == null) || (message.getBody() == null)) {
return;
}
- if (packet.hasChild("delay")) {
- try {
- String stamp = packet.findChild("delay").getAttribute(
- "stamp");
- stamp = stamp.replace("Z", "+0000");
- Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
- .parse(stamp);
- message.setTime(date.getTime());
- } catch (ParseException e) {
- Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
+ if ((confirmMessages()) && ((packet.getId() != null))) {
+ MessagePacket receivedPacket = new MessagePacket();
+ receivedPacket.setType(MessagePacket.TYPE_NORMAL);
+ receivedPacket.setTo(message.getCounterpart());
+ receivedPacket.setFrom(account.getFullJid());
+ if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
+ Element received = receivedPacket.addChild("received",
+ "urn:xmpp:chat-markers:0");
+ received.setAttribute("id", packet.getId());
+ account.getXmppConnection().sendMessagePacket(
+ receivedPacket);
+ } else if (packet.hasChild("request", "urn:xmpp:receipts")) {
+ Element received = receivedPacket.addChild("received",
+ "urn:xmpp:receipts");
+ received.setAttribute("id", packet.getId());
+ account.getXmppConnection().sendMessagePacket(
+ receivedPacket);
}
}
Conversation conversation = message.getConversation();
@@ -234,9 +233,11 @@ public class XmppConnectionService extends Service {
List<Conversation> conversations = getConversations();
for (int i = 0; i < conversations.size(); ++i) {
if (conversations.get(i).getAccount() == account) {
+ conversations.get(i).endOtrIfNeeded();
sendUnsendMessages(conversations.get(i));
}
}
+ syncDirtyContacts(account);
scheduleWakeupCall(PING_MAX_INTERVAL, true);
} else if (account.getStatus() == Account.STATUS_OFFLINE) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
@@ -247,10 +248,18 @@ public class XmppConnectionService extends Service {
} else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
databaseBackend.updateAccount(account);
reconnectAccount(account, true);
- } else {
- UIHelper.showErrorNotification(getApplicationContext(),
- getAccounts());
+ } else if ((account.getStatus() != Account.STATUS_CONNECTING)
+ && (account.getStatus() != Account.STATUS_NO_INTERNET)) {
+ int next = account.getXmppConnection().getTimeToNextAttempt();
+ Log.d(LOGTAG, account.getJid()
+ + ": error connecting account. try again in " + next
+ + "s for the "
+ + (account.getXmppConnection().getAttempt() + 1)
+ + " time");
+ scheduleWakeupCall(next, false);
}
+ UIHelper.showErrorNotification(getApplicationContext(),
+ getAccounts());
}
};
@@ -260,116 +269,11 @@ public class XmppConnectionService extends Service {
public void onPresencePacketReceived(final Account account,
PresencePacket packet) {
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
- Conversation muc = findMuc(
- packet.getAttribute("from").split("/")[0], account);
- if (muc != null) {
- muc.getMucOptions().processPacket(packet);
- } else {
- Log.d(LOGTAG, account.getJid()
- + ": could not find muc for received muc package "
- + packet.toString());
- }
+ mPresenceParser.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
- Conversation muc = findMuc(
- packet.getAttribute("from").split("/")[0], account);
- if (muc != null) {
- Log.d(LOGTAG,
- account.getJid() + ": reading muc status packet "
- + packet.toString());
- int error = muc.getMucOptions().getError();
- muc.getMucOptions().processPacket(packet);
- if ((muc.getMucOptions().getError() != error)
- && (convChangedListener != null)) {
- Log.d(LOGTAG, "muc error status changed");
- convChangedListener.onConversationListChanged();
- }
- }
+ mPresenceParser.parseConferencePresence(packet, account);
} else {
- String[] fromParts = packet.getAttribute("from").split("/");
- String type = packet.getAttribute("type");
- if (fromParts[0].equals(account.getJid())) {
- if (fromParts.length == 2) {
- if (type == null) {
- account.updatePresence(fromParts[1], Presences
- .parseShow(packet.findChild("show")));
- } else if (type.equals("unavailable")) {
- account.removePresence(fromParts[1]);
- }
- }
-
- } else {
- Contact contact = findContact(account, fromParts[0]);
- if (contact == null) {
- if ("subscribe".equals(type)) {
- account.getXmppConnection().addPendingSubscription(
- fromParts[0]);
- } else {
- // Log.d(LOGTAG,packet.getFrom()+
- // " could not be found");
- }
- return;
- }
- if (type == null) {
- if (fromParts.length == 2) {
- contact.updatePresence(fromParts[1], Presences
- .parseShow(packet.findChild("show")));
- PgpEngine pgp = getPgpEngine();
- if (pgp != null) {
- Element x = packet.findChild("x",
- "jabber:x:signed");
- if (x != null) {
- Element status = packet.findChild("status");
- String msg;
- if (status != null) {
- msg = status.getContent();
- } else {
- msg = "";
- }
- contact.setPgpKeyId(pgp.fetchKeyId(account,msg,x.getContent()));
- Log.d("xmppService",account.getJid()+": fetched key id for "+contact.getJid()+" was:"+contact.getPgpKeyId());
- }
- }
- replaceContactInConversation(account,
- contact.getJid(), contact);
- databaseBackend.updateContact(contact, true);
- } else {
- // Log.d(LOGTAG,"presence without resource "+packet.toString());
- }
- } else if (type.equals("unavailable")) {
- if (fromParts.length != 2) {
- contact.clearPresences();
- } else {
- contact.removePresence(fromParts[1]);
- }
- replaceContactInConversation(account, contact.getJid(),
- contact);
- databaseBackend.updateContact(contact, true);
- } 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);
- replaceContactInConversation(account,
- contact.getJid(), contact);
- databaseBackend.updateContact(contact, false);
- if ((contact
- .getSubscriptionOption(Contact.Subscription.ASKING))
- && (!contact
- .getSubscriptionOption(Contact.Subscription.TO))) {
- requestPresenceUpdatesFrom(contact);
- }
- } else {
- account.getXmppConnection().addPendingSubscription(
- fromParts[0]);
- }
- } else {
- // Log.d(LOGTAG, packet.toString());
- }
- }
+ mPresenceParser.parseContactPresence(packet, account);
}
}
};
@@ -378,14 +282,13 @@ public class XmppConnectionService extends Service {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.hasChild("query","jabber:iq:roster")) {
+ if (packet.hasChild("query", "jabber:iq:roster")) {
String from = packet.getFrom();
- if ((from==null)||(from.equals(account.getJid()))) {
+ if ((from == null) || (from.equals(account.getJid()))) {
Element query = packet.findChild("query");
processRosterItems(account, query);
- mergePhoneContactsWithRoster(null);
} else {
- Log.d(LOGTAG,"unauthorized roster push from: "+from);
+ Log.d(LOGTAG, "unauthorized roster push from: " + from);
}
} else if (packet
.hasChild("open", "http://jabber.org/protocol/ibb")
@@ -393,20 +296,34 @@ public class XmppConnectionService extends Service {
.hasChild("data", "http://jabber.org/protocol/ibb")) {
XmppConnectionService.this.mJingleConnectionManager
.deliverIbbPacket(account, packet);
- } else if (packet.hasChild("query","http://jabber.org/protocol/disco#info")) {
- IqPacket iqResponse = packet.generateRespone(IqPacket.TYPE_RESULT);
- Element query = iqResponse.addChild("query", "http://jabber.org/protocol/disco#info");
- query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:1");
- query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:apps:file-transfer:3");
- query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:transports:s5b:1");
- query.addChild("feature").setAttribute("var", "urn:xmpp:jingle:transports:ibb:1");
+ } else if (packet.hasChild("query",
+ "http://jabber.org/protocol/disco#info")) {
+ IqPacket iqResponse = packet
+ .generateRespone(IqPacket.TYPE_RESULT);
+ Element query = iqResponse.addChild("query",
+ "http://jabber.org/protocol/disco#info");
+ query.addChild("feature").setAttribute("var",
+ "urn:xmpp:jingle:1");
+ query.addChild("feature").setAttribute("var",
+ "urn:xmpp:jingle:apps:file-transfer:3");
+ query.addChild("feature").setAttribute("var",
+ "urn:xmpp:jingle:transports:s5b:1");
+ query.addChild("feature").setAttribute("var",
+ "urn:xmpp:jingle:transports:ibb:1");
+ if (confirmMessages()) {
+ query.addChild("feature").setAttribute("var",
+ "urn:xmpp:receipts");
+ }
account.getXmppConnection().sendIqPacket(iqResponse, null);
} else {
- if ((packet.getType() == IqPacket.TYPE_GET)||(packet.getType() == IqPacket.TYPE_SET)) {
- IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR);
+ if ((packet.getType() == IqPacket.TYPE_GET)
+ || (packet.getType() == IqPacket.TYPE_SET)) {
+ IqPacket response = packet
+ .generateRespone(IqPacket.TYPE_ERROR);
Element error = response.addChild("error");
- error.setAttribute("type","cancel");
- error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas");
+ error.setAttribute("type", "cancel");
+ error.addChild("feature-not-implemented",
+ "urn:ietf:params:xml:ns:xmpp-stanzas");
account.getXmppConnection().sendIqPacket(response, null);
}
}
@@ -433,7 +350,7 @@ public class XmppConnectionService extends Service {
if (this.mPgpEngine == null) {
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
getApplicationContext(),
- pgpServiceConnection.getService()),this);
+ pgpServiceConnection.getService()), this);
}
return mPgpEngine;
} else {
@@ -446,12 +363,14 @@ public class XmppConnectionService extends Service {
return this.fileBackend;
}
- public Message attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) {
+ public Message attachImageToConversation(final Conversation conversation,
+ final Uri uri, final UiCallback<Message> callback) {
final Message message;
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "",Message.ENCRYPTION_DECRYPTED);
+ message = new Message(conversation, "",
+ Message.ENCRYPTION_DECRYPTED);
} else {
- message = new Message(conversation, "", Message.ENCRYPTION_NONE);
+ message = new Message(conversation, "", conversation.getNextEncryption());
}
message.setPresence(conversation.getNextPresence());
message.setType(Message.TYPE_IMAGE);
@@ -460,22 +379,22 @@ public class XmppConnectionService extends Service {
@Override
public void run() {
- JingleFile file = getFileBackend().copyImageToPrivateStorage(message, uri);
- if (file==null) {
- callback.error(R.string.error_copying_image_file);
- } else {
+ try {
+ getFileBackend().copyImageToPrivateStorage(message, uri);
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
getPgpEngine().encrypt(message, callback);
} else {
- callback.success();
+ callback.success(message);
}
+ } catch (FileBackend.ImageCopyException e) {
+ callback.error(e.getResId(), message);
}
}
}).start();
return message;
}
-
- protected Conversation findMuc(String name, Account account) {
+
+ public Conversation findMuc(String name, Account account) {
for (Conversation conversation : this.conversations) {
if (conversation.getContactJid().split("/")[0].equals(name)
&& (conversation.getAccount() == account)) {
@@ -488,51 +407,28 @@ public class XmppConnectionService extends Service {
private void processRosterItems(Account account, Element elements) {
String version = elements.getAttribute("ver");
if (version != null) {
- account.setRosterVersion(version);
- databaseBackend.updateAccount(account);
+ account.getRoster().setVersion(version);
}
for (Element item : elements.getChildren()) {
if (item.getName().equals("item")) {
String jid = item.getAttribute("jid");
+ String name = item.getAttribute("name");
String subscription = item.getAttribute("subscription");
- Contact contact = databaseBackend.findContact(account, jid);
- if (contact == null) {
- if (!subscription.equals("remove")) {
- String name = item.getAttribute("name");
- if (name == null) {
- name = jid.split("@")[0];
- }
- contact = new Contact(account, name, jid, null);
- contact.parseSubscriptionFromElement(item);
- databaseBackend.createContact(contact);
- }
+ Contact contact = account.getRoster().getContact(jid);
+ if (!contact.getOption(Contact.Options.DIRTY_PUSH)) {
+ contact.setServerName(name);
+ }
+ if (subscription.equals("remove")) {
+ contact.resetOption(Contact.Options.IN_ROSTER);
+ contact.resetOption(Contact.Options.DIRTY_DELETE);
} else {
- if (subscription.equals("remove")) {
- databaseBackend.deleteContact(contact);
- replaceContactInConversation(account, contact.getJid(),
- null);
- } else {
- contact.parseSubscriptionFromElement(item);
- databaseBackend.updateContact(contact, false);
- replaceContactInConversation(account, contact.getJid(),
- contact);
- }
+ contact.setOption(Contact.Options.IN_ROSTER);
+ contact.parseSubscriptionFromElement(item);
}
}
}
}
- private void replaceContactInConversation(Account account, String jid,
- Contact contact) {
- List<Conversation> conversations = getConversations();
- for (Conversation c : conversations) {
- if (c.getContactJid().equals(jid) && (c.getAccount() == account)) {
- c.setContact(contact);
- break;
- }
- }
- }
-
public class XmppConnectionBinder extends Binder {
public XmppConnectionService getService() {
return XmppConnectionService.this;
@@ -541,8 +437,16 @@ public class XmppConnectionService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ if ((intent != null)
+ && (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
+ mergePhoneContactsWithRoster();
+ return START_STICKY;
+ } else if ((intent != null)
+ && (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) {
+ logoutAndSave();
+ return START_NOT_STICKY;
+ }
this.wakeLock.acquire();
- // Log.d(LOGTAG,"calling start service. caller was:"+intent.getAction());
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
@@ -563,8 +467,6 @@ public class XmppConnectionService extends Service {
statusListener.onStatusChanged(account);
}
}
-
- // TODO 3 remaining cases
if (account.getStatus() == Account.STATUS_ONLINE) {
long lastReceived = account.getXmppConnection().lastPaketReceived;
long lastSent = account.getXmppConnection().lastPingSent;
@@ -592,15 +494,9 @@ public class XmppConnectionService extends Service {
+ ": time out during connect reconnecting");
reconnectAccount(account, true);
} else {
- Log.d(LOGTAG,
- "seconds since last connect:"
- + ((SystemClock.elapsedRealtime() - account
- .getXmppConnection().lastConnect) / 1000));
- Log.d(LOGTAG,
- account.getJid() + ": status="
- + account.getStatus());
- // TODO notify user of ssl cert problem or auth problem
- // or what ever
+ if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
+ reconnectAccount(account, true);
+ }
}
// in any case. reschedule wakup call
this.scheduleWakeupCall(PING_MAX_INTERVAL, true);
@@ -616,14 +512,21 @@ public class XmppConnectionService extends Service {
return START_STICKY;
}
+ @SuppressLint("TrulyRandom")
@Override
public void onCreate() {
ExceptionHelper.init(getApplicationContext());
+ PRNGFixes.apply();
+ this.mRandom = new SecureRandom();
this.databaseBackend = DatabaseBackend
.getInstance(getApplicationContext());
this.fileBackend = new FileBackend(getApplicationContext());
this.accounts = databaseBackend.getAccounts();
+ for (Account account : this.accounts) {
+ this.databaseBackend.readRoster(account.getRoster());
+ }
+ this.mergePhoneContactsWithRoster();
this.getConversations();
getContentResolver().registerContentObserver(
@@ -639,13 +542,30 @@ public class XmppConnectionService extends Service {
@Override
public void onDestroy() {
- Log.d(LOGTAG,"stopping service");
super.onDestroy();
+ this.logoutAndSave();
+ }
+
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ this.logoutAndSave();
+ }
+
+ private void logoutAndSave() {
for (Account account : accounts) {
+ databaseBackend.writeRoster(account.getRoster());
if (account.getXmppConnection() != null) {
- disconnect(account, true);
+ disconnect(account, false);
}
}
+ Context context = getApplicationContext();
+ AlarmManager alarmManager = (AlarmManager) context
+ .getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(context, EventReceiver.class);
+ alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
+ Log.d(LOGTAG, "good bye");
+ stopSelf();
}
protected void scheduleWakeupCall(int seconds, boolean ping) {
@@ -663,7 +583,6 @@ public class XmppConnectionService extends Service {
this.pingIntent, 0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
timeToWake, pendingPingIntent);
- // Log.d(LOGTAG,"schedule ping in "+seconds+" seconds");
} else {
long scheduledTime = this.pingIntent.getLongExtra("time", 0);
if (scheduledTime < SystemClock.elapsedRealtime()
@@ -674,7 +593,6 @@ public class XmppConnectionService extends Service {
context, 0, this.pingIntent, 0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
timeToWake, pendingPingIntent);
- // Log.d(LOGTAG,"reschedule old ping to ping in "+seconds+" seconds");
}
}
} else {
@@ -692,7 +610,7 @@ public class XmppConnectionService extends Service {
SharedPreferences sharedPref = getPreferences();
account.setResource(sharedPref.getString("resource", "mobile")
.toLowerCase(Locale.getDefault()));
- XmppConnection connection = new XmppConnection(account, this.pm);
+ XmppConnection connection = new XmppConnection(account, this);
connection.setOnMessagePacketReceivedListener(this.messageListener);
connection.setOnStatusChangedListener(this.statusListener);
connection.setOnPresencePacketReceivedListener(this.presenceListener);
@@ -715,12 +633,11 @@ public class XmppConnectionService extends Service {
connection.setOnBindListener(new OnBindListener() {
@Override
- public void onBind(Account account) {
- databaseBackend.clearPresences(account);
+ public void onBind(final Account account) {
+ account.getRoster().clearPresences();
account.clearPresences(); // self presences
- if (account.getXmppConnection().hasFeatureRosterManagment()) {
- updateRoster(account, null);
- }
+ fetchRosterFromServer(account);
+ sendPresence(account);
connectMultiModeConversations(account);
if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
@@ -730,73 +647,86 @@ public class XmppConnectionService extends Service {
return connection;
}
- synchronized public void sendMessage(Message message, String presence) {
+ synchronized public void sendMessage(Message message) {
Account account = message.getConversation().getAccount();
Conversation conv = message.getConversation();
MessagePacket packet = null;
- boolean saveInDb = false;
- boolean addToConversation = false;
+ boolean saveInDb = true;
boolean send = false;
if (account.getStatus() == Account.STATUS_ONLINE) {
if (message.getType() == Message.TYPE_IMAGE) {
- mJingleConnectionManager.createNewConnection(message);
+ if (message.getPresence() != null) {
+ mJingleConnectionManager.createNewConnection(message);
+ } else {
+ message.setStatus(Message.STATUS_WAITING);
+ }
} else {
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- if (!conv.hasValidOtrSession()) {
+ if (!conv.hasValidOtrSession()
+ && (message.getPresence() != null)) {
// starting otr session. messages will be send later
- conv.startOtrSession(getApplicationContext(), presence,
- true);
- } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
+ conv.startOtrSession(getApplicationContext(),
+ message.getPresence(), true);
+ } else if (conv.hasValidOtrSession()
+ && 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);
+ } else if (message.getPresence() == null) {
+ message.setStatus(Message.STATUS_WAITING);
}
- saveInDb = true;
- addToConversation = true;
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
message.getConversation().endOtrIfNeeded();
- packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT);
- packet.setFrom(message.getConversation().getAccount()
- .getFullJid());
- packet.setTo(message.getCounterpart());
+ packet = prepareMessagePacket(account, message, null);
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;
}
}
} else {
- // account is offline
- saveInDb = true;
- addToConversation = true;
+ message.setStatus(Message.STATUS_WAITING);
+ if (message.getType() == Message.TYPE_TEXT) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ String pgpBody = message.getEncryptedBody();
+ String decryptedBody = message.getBody();
+ message.setBody(pgpBody);
+ databaseBackend.createMessage(message);
+ saveInDb = false;
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ message.setBody(decryptedBody);
+ } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ if (conv.hasValidOtrSession()) {
+ message.setPresence(conv.getOtrSession().getSessionID()
+ .getUserID());
+ } else if (!conv.hasValidOtrSession()
+ && message.getPresence() != null) {
+ conv.startOtrSession(getApplicationContext(),
+ message.getPresence(), false);
+ }
+ }
+ }
}
if (saveInDb) {
databaseBackend.createMessage(message);
}
- if (addToConversation) {
- conv.getMessages().add(message);
- if (convChangedListener != null) {
- convChangedListener.onConversationListChanged();
- }
+ conv.getMessages().add(message);
+ if (convChangedListener != null) {
+ convChangedListener.onConversationListChanged();
}
if ((send) && (packet != null)) {
account.getXmppConnection().sendMessagePacket(packet);
@@ -806,20 +736,68 @@ public class XmppConnectionService extends Service {
private void sendUnsendMessages(Conversation conversation) {
for (int i = 0; i < conversation.getMessages().size(); ++i) {
- if ((conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND)
- && (conversation.getMessages().get(i).getEncryption() == Message.ENCRYPTION_NONE)) {
- Message message = conversation.getMessages().get(i);
- MessagePacket packet = prepareMessagePacket(
- conversation.getAccount(), message, null);
- conversation.getAccount().getXmppConnection()
- .sendMessagePacket(packet);
- message.setStatus(Message.STATUS_SEND);
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- databaseBackend.updateMessage(message);
- } else {
- databaseBackend.deleteMessage(message);
- conversation.getMessages().remove(i);
- i--;
+ int status = conversation.getMessages().get(i).getStatus();
+ if (status == Message.STATUS_WAITING) {
+ resendMessage(conversation.getMessages().get(i));
+ }
+ }
+ }
+
+ private void resendMessage(Message message) {
+ Account account = message.getConversation().getAccount();
+ if (message.getType() == Message.TYPE_TEXT) {
+ MessagePacket packet = null;
+ if (message.getEncryption() == Message.ENCRYPTION_NONE) {
+ packet = prepareMessagePacket(account, message, null);
+ } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ packet = prepareMessagePacket(account, message, null);
+ packet.setBody("This is an XEP-0027 encryted message");
+ if (message.getEncryptedBody() == null) {
+ markMessage(message, Message.STATUS_SEND_FAILED);
+ return;
+ }
+ packet.addChild("x", "jabber:x:encrypted").setContent(
+ message.getEncryptedBody());
+ } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ packet = prepareMessagePacket(account, message, null);
+ packet.setBody("This is an XEP-0027 encryted message");
+ packet.addChild("x", "jabber:x:encrypted").setContent(
+ message.getBody());
+ } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ Presences presences = message.getConversation().getContact()
+ .getPresences();
+ if (!message.getConversation().hasValidOtrSession()) {
+ if ((message.getPresence() != null)
+ && (presences.has(message.getPresence()))) {
+ message.getConversation().startOtrSession(
+ getApplicationContext(), message.getPresence(),
+ true);
+ } else {
+ if (presences.size() == 1) {
+ String presence = presences.asStringArray()[0];
+ message.getConversation().startOtrSession(
+ getApplicationContext(), presence, true);
+ }
+ }
+ }
+ }
+ if (packet != null) {
+ account.getXmppConnection().sendMessagePacket(packet);
+ markMessage(message, Message.STATUS_SEND);
+ }
+ } else if (message.getType() == Message.TYPE_IMAGE) {
+ Presences presences = message.getConversation().getContact()
+ .getPresences();
+ if ((message.getPresence() != null)
+ && (presences.has(message.getPresence()))) {
+ markMessage(message, Message.STATUS_OFFERED);
+ mJingleConnectionManager.createNewConnection(message);
+ } else {
+ if (presences.size() == 1) {
+ String presence = presences.asStringArray()[0];
+ message.setPresence(presence);
+ markMessage(message, Message.STATUS_OFFERED);
+ mJingleConnectionManager.createNewConnection(message);
}
}
}
@@ -830,6 +808,7 @@ public class XmppConnectionService extends Service {
MessagePacket packet = new MessagePacket();
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setFrom(account.getFullJid());
if (otrSession != null) {
try {
packet.setBody(otrSession.transformSending(message
@@ -844,12 +823,11 @@ public class XmppConnectionService extends Service {
packet.addChild("no-copy", "urn:xmpp:hints");
packet.setTo(otrSession.getSessionID().getAccountID() + "/"
+ otrSession.getSessionID().getUserID());
- packet.setFrom(account.getFullJid());
} else {
packet.setBody(message.getBody());
packet.setTo(message.getCounterpart());
- packet.setFrom(account.getJid());
}
+ packet.addChild("markable", "urn:xmpp:chat-markers:0");
} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
packet.setType(MessagePacket.TYPE_GROUPCHAT);
packet.setBody(message.getBody());
@@ -860,27 +838,7 @@ public class XmppConnectionService extends Service {
return packet;
}
- private void getRoster(Account account,
- final OnRosterFetchedListener listener) {
- List<Contact> contacts = databaseBackend.getContactsByAccount(account);
- for (int i = 0; i < contacts.size(); ++i) {
- contacts.get(i).setAccount(account);
- }
- if (listener != null) {
- listener.onRosterFetched(contacts);
- }
- }
-
- public List<Contact> getRoster(Account account) {
- List<Contact> contacts = databaseBackend.getContactsByAccount(account);
- for (int i = 0; i < contacts.size(); ++i) {
- contacts.get(i).setAccount(account);
- }
- return contacts;
- }
-
- public void updateRoster(final Account account,
- final OnRosterFetchedListener listener) {
+ public void fetchRosterFromServer(Account account) {
IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
if (!"".equals(account.getRosterVersion())) {
Log.d(LOGTAG, account.getJid() + ": fetching roster version "
@@ -898,62 +856,26 @@ public class XmppConnectionService extends Service {
IqPacket packet) {
Element roster = packet.findChild("query");
if (roster != null) {
- Log.d(LOGTAG, account.getJid()
- + ": processing roster");
+ account.getRoster().markAllAsNotInRoster();
processRosterItems(account, roster);
- StringBuilder mWhere = new StringBuilder();
- mWhere.append("jid NOT IN(");
- List<Element> items = roster.getChildren();
- for (int i = 0; i < items.size(); ++i) {
- mWhere.append(DatabaseUtils
- .sqlEscapeString(items.get(i)
- .getAttribute("jid")));
- if (i != items.size() - 1) {
- mWhere.append(",");
- }
- }
- mWhere.append(") and accountUuid = \"");
- mWhere.append(account.getUuid());
- mWhere.append("\"");
- List<Contact> contactsToDelete = databaseBackend
- .getContacts(mWhere.toString());
- for (Contact contact : contactsToDelete) {
- databaseBackend.deleteContact(contact);
- replaceContactInConversation(account,
- contact.getJid(), null);
- }
-
- } else {
- Log.d(LOGTAG, account.getJid()
- + ": empty roster returend");
}
- mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
-
- @Override
- public void phoneContactsMerged() {
- if (listener != null) {
- getRoster(account, listener);
- }
- }
- });
}
});
}
- public void mergePhoneContactsWithRoster(
- final OnPhoneContactsMerged listener) {
+ private void mergePhoneContactsWithRoster() {
PhoneHelper.loadPhoneContacts(getApplicationContext(),
new OnPhoneContactsLoadedListener() {
@Override
- public void onPhoneContactsLoaded(
- Hashtable<String, Bundle> phoneContacts) {
- List<Contact> contacts = databaseBackend
- .getContactsByAccount(null);
- for (int i = 0; i < contacts.size(); ++i) {
- Contact contact = contacts.get(i);
- if (phoneContacts.containsKey(contact.getJid())) {
- Bundle phoneContact = phoneContacts.get(contact
- .getJid());
+ public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
+ for (Account account : accounts) {
+ account.getRoster().clearSystemAccounts();
+ }
+ for (Bundle phoneContact : phoneContacts) {
+ for (Account account : accounts) {
+ String jid = phoneContact.getString("jid");
+ Contact contact = account.getRoster()
+ .getContact(jid);
String systemAccount = phoneContact
.getInt("phoneid")
+ "#"
@@ -961,28 +883,10 @@ public class XmppConnectionService extends Service {
contact.setSystemAccount(systemAccount);
contact.setPhotoUri(phoneContact
.getString("photouri"));
- contact.setDisplayName(phoneContact
+ contact.setSystemName(phoneContact
.getString("displayname"));
- databaseBackend.updateContact(contact, false);
- replaceContactInConversation(
- contact.getAccount(), contact.getJid(),
- contact);
- } else {
- if ((contact.getSystemAccount() != null)
- || (contact.getProfilePhoto() != null)) {
- contact.setSystemAccount(null);
- contact.setPhotoUri(null);
- databaseBackend.updateContact(contact,
- false);
- replaceContactInConversation(
- contact.getAccount(),
- contact.getJid(), contact);
- }
}
}
- if (listener != null) {
- listener.phoneContactsMerged();
- }
}
});
}
@@ -998,30 +902,47 @@ public class XmppConnectionService extends Service {
for (Conversation conv : this.conversations) {
Account account = accountLookupTable.get(conv.getAccountUuid());
conv.setAccount(account);
- conv.setContact(findContact(account, conv.getContactJid()));
conv.setMessages(databaseBackend.getMessages(conv, 50));
}
}
Collections.sort(this.conversations, new Comparator<Conversation>() {
@Override
public int compare(Conversation lhs, Conversation rhs) {
- return (int) (rhs.getLatestMessage().getTimeSent() - lhs
- .getLatestMessage().getTimeSent());
+ Message left = lhs.getLatestMessage();
+ Message right = rhs.getLatestMessage();
+ if (left.getTimeSent() > right.getTimeSent()) {
+ return -1;
+ } else if (left.getTimeSent() < right.getTimeSent()) {
+ return 1;
+ } else {
+ return 0;
+ }
}
});
return this.conversations;
}
+ public List<Message> getMoreMessages(Conversation conversation,
+ long timestamp) {
+ List<Message> messages = databaseBackend.getMessages(conversation, 50,
+ timestamp);
+ for (Message message : messages) {
+ message.setConversation(conversation);
+ }
+ return messages;
+ }
+
public List<Account> getAccounts() {
return this.accounts;
}
- public Contact findContact(Account account, String jid) {
- Contact contact = databaseBackend.findContact(account, jid);
- if (contact != null) {
- contact.setAccount(account);
+ public Conversation findActiveConversation(Contact contact) {
+ for (Conversation conversation : this.getConversations()) {
+ if (conversation.getContact() == contact) {
+ return conversation;
+ }
}
- return contact;
+ return null;
}
public Conversation findOrCreateConversation(Account account, String jid,
@@ -1045,11 +966,9 @@ public class XmppConnectionService extends Service {
conversation.setMessages(databaseBackend.getMessages(conversation,
50));
this.databaseBackend.updateConversation(conversation);
- conversation.setContact(findContact(account,
- conversation.getContactJid()));
} else {
String conversationName;
- Contact contact = findContact(account, jid);
+ Contact contact = account.getRoster().getContact(jid);
if (contact != null) {
conversationName = contact.getDisplayName();
} else {
@@ -1062,7 +981,6 @@ public class XmppConnectionService extends Service {
conversation = new Conversation(conversationName, account, jid,
Conversation.MODE_SINGLE);
}
- conversation.setContact(contact);
this.databaseBackend.createConversation(conversation);
}
this.conversations.add(conversation);
@@ -1110,23 +1028,14 @@ public class XmppConnectionService extends Service {
accountChangedListener.onAccountListChangedListener();
}
- public void deleteContact(Contact contact) {
- IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
- Element query = iq.query("jabber:iq:roster");
- query.addChild("item").setAttribute("jid", contact.getJid())
- .setAttribute("subscription", "remove");
- contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
- replaceContactInConversation(contact.getAccount(), contact.getJid(),
- null);
- databaseBackend.deleteContact(contact);
- }
-
public void updateAccount(Account account) {
this.statusListener.onStatusChanged(account);
databaseBackend.updateAccount(account);
reconnectAccount(account, false);
- if (accountChangedListener != null)
+ if (accountChangedListener != null) {
accountChangedListener.onAccountListChangedListener();
+ }
+ UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
}
public void deleteAccount(Account account) {
@@ -1135,8 +1044,10 @@ public class XmppConnectionService extends Service {
}
databaseBackend.deleteAccount(account);
this.accounts.remove(account);
- if (accountChangedListener != null)
+ if (accountChangedListener != null) {
accountChangedListener.onAccountListChangedListener();
+ }
+ UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
}
public void setOnConversationListChangedListener(
@@ -1173,6 +1084,7 @@ public class XmppConnectionService extends Service {
}
public void joinMuc(Conversation conversation) {
+ Account account = conversation.getAccount();
String[] mucParts = conversation.getContactJid().split("/");
String muc;
String nick;
@@ -1181,20 +1093,24 @@ public class XmppConnectionService extends Service {
nick = mucParts[1];
} else {
muc = mucParts[0];
- nick = conversation.getAccount().getUsername();
+ nick = account.getUsername();
}
PresencePacket packet = new PresencePacket();
packet.setAttribute("to", muc + "/" + nick);
Element x = new Element("x");
x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
+ String sig = account.getPgpSignature();
+ if (sig != null) {
+ packet.addChild("status").setContent("online");
+ packet.addChild("x", "jabber:x:signed").setContent(sig);
+ }
if (conversation.getMessages().size() != 0) {
long lastMsgTime = conversation.getLatestMessage().getTimeSent();
long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
x.addChild("history").setAttribute("seconds", diff + "");
}
packet.addChild(x);
- conversation.getAccount().getXmppConnection()
- .sendPresencePacket(packet);
+ account.getXmppConnection().sendPresencePacket(packet);
}
private OnRenameListener renameListener = null;
@@ -1206,6 +1122,7 @@ public class XmppConnectionService extends Service {
public void renameInMuc(final Conversation conversation, final String nick) {
final MucOptions options = conversation.getMucOptions();
if (options.online()) {
+ Account account = conversation.getAccount();
options.setOnRenameListener(new OnRenameListener() {
@Override
@@ -1214,8 +1131,8 @@ public class XmppConnectionService extends Service {
renameListener.onRename(success);
}
if (success) {
- String jid = conversation.getContactJid().split("/")[0] + "/"
- + nick;
+ String jid = conversation.getContactJid().split("/")[0]
+ + "/" + nick;
conversation.setContactJid(jid);
databaseBackend.updateConversation(conversation);
}
@@ -1227,8 +1144,13 @@ public class XmppConnectionService extends Service {
conversation.getContactJid().split("/")[0] + "/" + nick);
packet.setAttribute("from", conversation.getAccount().getFullJid());
- conversation.getAccount().getXmppConnection()
- .sendPresencePacket(packet, null);
+ String sig = account.getPgpSignature();
+ if (sig != null) {
+ packet.addChild("status").setContent("online");
+ packet.addChild("x", "jabber:x:signed").setContent(sig);
+ }
+
+ account.getXmppConnection().sendPresencePacket(packet, null);
} else {
String jid = conversation.getContactJid().split("/")[0] + "/"
+ nick;
@@ -1277,96 +1199,153 @@ public class XmppConnectionService extends Service {
return mBinder;
}
- public void updateContact(Contact contact) {
- databaseBackend.updateContact(contact, false);
- replaceContactInConversation(contact.getAccount(), contact.getJid(),
- contact);
- }
-
public void updateMessage(Message message) {
databaseBackend.updateMessage(message);
}
+ protected void syncDirtyContacts(Account account) {
+ for (Contact contact : account.getRoster().getContacts()) {
+ if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
+ pushContactToServer(contact);
+ }
+ if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
+ Log.d(LOGTAG, "dirty delete");
+ deleteContactOnServer(contact);
+ }
+ }
+ }
+
public void createContact(Contact contact) {
SharedPreferences sharedPref = getPreferences();
boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
if (autoGrant) {
- contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
- contact.setSubscriptionOption(Contact.Subscription.ASKING);
+ contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
+ contact.setOption(Contact.Options.ASKING);
+ }
+ pushContactToServer(contact);
+ }
+
+ public void onOtrSessionEstablished(Conversation conversation) {
+ Account account = conversation.getAccount();
+ List<Message> messages = conversation.getMessages();
+ Session otrSession = conversation.getOtrSession();
+ Log.d(LOGTAG, account.getJid() + " otr session established with "
+ + conversation.getContactJid() + "/"
+ + otrSession.getSessionID().getUserID());
+ for (int i = 0; i < messages.size(); ++i) {
+ Message msg = messages.get(i);
+ if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING)
+ && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
+ MessagePacket outPacket = prepareMessagePacket(account, msg,
+ otrSession);
+ msg.setStatus(Message.STATUS_SEND);
+ databaseBackend.updateMessage(msg);
+ account.getXmppConnection().sendMessagePacket(outPacket);
+ }
}
- databaseBackend.createContact(contact);
- IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
- Element query = new Element("query");
- query.setAttribute("xmlns", "jabber:iq:roster");
- Element item = new Element("item");
- item.setAttribute("jid", contact.getJid());
- item.setAttribute("name", contact.getJid());
- query.addChild(item);
- iq.addChild(query);
+ updateUi(conversation, false);
+ }
+
+ public boolean renewSymmetricKey(Conversation conversation) {
+ Account account = conversation.getAccount();
+ byte[] symmetricKey = new byte[32];
+ this.mRandom.nextBytes(symmetricKey);
+ Session otrSession = conversation.getOtrSession();
+ if (otrSession!=null) {
+ MessagePacket packet = new MessagePacket();
+ packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setFrom(account.getFullJid());
+ packet.addChild("private", "urn:xmpp:carbons:2");
+ packet.addChild("no-copy", "urn:xmpp:hints");
+ packet.setTo(otrSession.getSessionID().getAccountID() + "/"
+ + otrSession.getSessionID().getUserID());
+ try {
+ packet.setBody(otrSession.transformSending(CryptoHelper.FILETRANSFER+CryptoHelper.bytesToHex(symmetricKey)));
+ account.getXmppConnection().sendMessagePacket(packet);
+ conversation.setSymmetricKey(symmetricKey);
+ return true;
+ } catch (OtrException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public void pushContactToServer(Contact contact) {
+ contact.resetOption(Contact.Options.DIRTY_DELETE);
Account account = contact.getAccount();
- account.getXmppConnection().sendIqPacket(iq, null);
- if (autoGrant) {
- requestPresenceUpdatesFrom(contact);
- if (account.getXmppConnection().hasPendingSubscription(
- contact.getJid())) {
+ if (account.getStatus() == Account.STATUS_ONLINE) {
+ IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
+ iq.query("jabber:iq:roster").addChild(contact.asElement());
+ account.getXmppConnection().sendIqPacket(iq, null);
+ if (contact.getOption(Contact.Options.ASKING)) {
+ requestPresenceUpdatesFrom(contact);
+ }
+ if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
Log.d("xmppService", "contact had pending subscription");
sendPresenceUpdatesTo(contact);
}
+ contact.resetOption(Contact.Options.DIRTY_PUSH);
+ } else {
+ contact.setOption(Contact.Options.DIRTY_PUSH);
+ }
+ }
+
+ public void deleteContactOnServer(Contact contact) {
+ contact.resetOption(Contact.Options.DIRTY_PUSH);
+ Account account = contact.getAccount();
+ if (account.getStatus() == Account.STATUS_ONLINE) {
+ IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
+ Element item = iq.query("jabber:iq:roster").addChild("item");
+ item.setAttribute("jid", contact.getJid());
+ item.setAttribute("subscription", "remove");
+ account.getXmppConnection().sendIqPacket(iq, null);
+ contact.resetOption(Contact.Options.DIRTY_DELETE);
+ } else {
+ contact.setOption(Contact.Options.DIRTY_DELETE);
}
- replaceContactInConversation(contact.getAccount(), contact.getJid(),
- contact);
}
public void requestPresenceUpdatesFrom(Contact contact) {
- // Requesting a Subscription type=subscribe
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", "subscribe");
packet.setAttribute("to", contact.getJid());
packet.setAttribute("from", contact.getAccount().getJid());
- Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet);
}
public void stopPresenceUpdatesFrom(Contact contact) {
- // Unsubscribing type='unsubscribe'
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", "unsubscribe");
packet.setAttribute("to", contact.getJid());
packet.setAttribute("from", contact.getAccount().getJid());
- Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet);
}
public void stopPresenceUpdatesTo(Contact contact) {
- // Canceling a Subscription type=unsubscribed
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", "unsubscribed");
packet.setAttribute("to", contact.getJid());
packet.setAttribute("from", contact.getAccount().getJid());
- Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet);
}
public void sendPresenceUpdatesTo(Contact contact) {
- // type='subscribed'
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", "subscribed");
packet.setAttribute("to", contact.getJid());
packet.setAttribute("from", contact.getAccount().getJid());
- Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet);
}
- public void sendPgpPresence(Account account, String signature) {
+ public void sendPresence(Account account) {
PresencePacket packet = new PresencePacket();
packet.setAttribute("from", account.getFullJid());
- Element status = new Element("status");
- status.setContent("online");
- packet.addChild(status);
- Element x = new Element("x");
- x.setAttribute("xmlns", "jabber:x:signed");
- x.setContent(signature);
- packet.addChild(x);
+ String sig = account.getPgpSignature();
+ if (sig != null) {
+ packet.addChild("status").setContent("online");
+ packet.addChild("x", "jabber:x:signed").setContent(sig);
+ }
account.getXmppConnection().sendPresencePacket(packet);
}
@@ -1374,23 +1353,10 @@ public class XmppConnectionService extends Service {
this.databaseBackend.updateConversation(conversation);
}
- public Contact findContact(String uuid) {
- Contact contact = this.databaseBackend.getContact(uuid);
- if (contact != null) {
- for (Account account : getAccounts()) {
- if (contact.getAccountUuid().equals(account.getUuid())) {
- contact.setAccount(account);
- }
- }
- }
- return contact;
- }
-
public void removeOnTLSExceptionReceivedListener() {
this.tlsException = null;
}
- // TODO dont let thread sleep but schedule wake up
public void reconnectAccount(final Account account, final boolean force) {
new Thread(new Runnable() {
@@ -1447,21 +1413,24 @@ public class XmppConnectionService extends Service {
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 markMessage(conversation, uuid, status);
+ }
+ }
+ return false;
+ }
+
+ public boolean markMessage(Conversation conversation, String uuid,
+ int status) {
+ for (Message message : conversation.getMessages()) {
+ if (message.getUuid().equals(uuid)) {
+ markMessage(message, status);
+ return true;
}
}
- return marked;
+ return false;
}
public void markMessage(Message message, int status) {
@@ -1476,12 +1445,49 @@ public class XmppConnectionService extends Service {
return PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
}
-
+
+ public boolean confirmMessages() {
+ return getPreferences().getBoolean("confirm_messages", true);
+ }
+
public void updateUi(Conversation conversation, boolean notify) {
if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
} else {
- UIHelper.updateNotification(getApplicationContext(), getConversations(), conversation, notify);
+ UIHelper.updateNotification(getApplicationContext(),
+ getConversations(), conversation, notify);
+ }
+ }
+
+ public Account findAccountByJid(String accountJid) {
+ for (Account account : this.accounts) {
+ if (account.getJid().equals(accountJid)) {
+ return account;
+ }
}
+ return null;
+ }
+
+ public void markRead(Conversation conversation) {
+ conversation.markRead(this);
+ }
+
+ public void sendConfirmMessage(Account account, String to, String id) {
+ MessagePacket receivedPacket = new MessagePacket();
+ receivedPacket.setType(MessagePacket.TYPE_NORMAL);
+ receivedPacket.setTo(to);
+ receivedPacket.setFrom(account.getFullJid());
+ Element received = receivedPacket.addChild("displayed",
+ "urn:xmpp:chat-markers:0");
+ received.setAttribute("id", id);
+ account.getXmppConnection().sendMessagePacket(receivedPacket);
+ }
+
+ public SecureRandom getRNG() {
+ return this.mRandom;
+ }
+
+ public PowerManager getPowerManager() {
+ return this.pm;
}
}
diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 06179bc6..d89c35f1 100644
--- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -1,8 +1,6 @@
package eu.siacs.conversations.ui;
-import java.math.BigInteger;
import java.util.Iterator;
-import java.util.Locale;
import org.openintents.openpgp.util.OpenPgpUtils;
@@ -17,7 +15,6 @@ import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -31,6 +28,7 @@ import android.widget.TextView;
import android.widget.Toast;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
+import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.utils.UIHelper;
@@ -40,14 +38,17 @@ public class ContactDetailsActivity extends XmppActivity {
protected ContactDetailsActivity activity = this;
- private String uuid;
private Contact contact;
-
+
+ private String accountJid;
+ private String contactJid;
+
private EditText name;
- private TextView contactJid;
- private TextView accountJid;
+ private TextView contactJidTv;
+ private TextView accountJidTv;
private TextView status;
private TextView askAgain;
+ private TextView lastseen;
private CheckBox send;
private CheckBox receive;
private QuickContactBadge badge;
@@ -56,7 +57,7 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(DialogInterface dialog, int which) {
- activity.xmppConnectionService.deleteContact(contact);
+ activity.xmppConnectionService.deleteContactOnServer(contact);
activity.finish();
}
};
@@ -65,8 +66,8 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(DialogInterface dialog, int which) {
- contact.setDisplayName(name.getText().toString());
- activity.xmppConnectionService.updateContact(contact);
+ contact.setServerName(name.getText().toString());
+ activity.xmppConnectionService.pushContactToServer(contact);
populateView();
}
};
@@ -89,11 +90,10 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setTitle("Add to phone book");
- builder.setMessage("Do you want to add " + contact.getJid()
- + " to your phones contact list?");
- builder.setNegativeButton("Cancel", null);
- builder.setPositiveButton("Add", addToPhonebook);
+ builder.setTitle(getString(R.string.action_add_phone_book));
+ builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid()));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.add), addToPhonebook);
builder.create().show();
}
};
@@ -104,13 +104,15 @@ public class ContactDetailsActivity extends XmppActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
- this.uuid = getIntent().getExtras().getString("uuid");
+ this.accountJid = getIntent().getExtras().getString("account");
+ this.contactJid = getIntent().getExtras().getString("contact");
}
setContentView(R.layout.activity_contact_details);
- contactJid = (TextView) findViewById(R.id.details_contactjid);
- accountJid = (TextView) findViewById(R.id.details_account);
+ contactJidTv = (TextView) findViewById(R.id.details_contactjid);
+ accountJidTv = (TextView) findViewById(R.id.details_account);
status = (TextView) findViewById(R.id.details_contactstatus);
+ lastseen = (TextView) findViewById(R.id.details_lastseen);
send = (CheckBox) findViewById(R.id.details_send_presence);
receive = (CheckBox) findViewById(R.id.details_receive_presence);
askAgain = (TextView) findViewById(R.id.ask_again);
@@ -124,17 +126,17 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setNegativeButton("Cancel", null);
+ builder.setNegativeButton(getString(R.string.cancel), null);
switch (menuItem.getItemId()) {
case android.R.id.home:
finish();
break;
case R.id.action_delete_contact:
- builder.setTitle("Delete from roster")
+ builder.setTitle(getString(R.string.action_delete_contact))
.setMessage(
getString(R.string.remove_contact_text,
contact.getJid()))
- .setPositiveButton("Delete", removeFromRoster).create()
+ .setPositiveButton(getString(R.string.delete), removeFromRoster).create()
.show();
break;
case R.id.action_edit_contact:
@@ -145,7 +147,7 @@ public class ContactDetailsActivity extends XmppActivity {
name = (EditText) view.findViewById(R.id.editText1);
name.setText(contact.getDisplayName());
builder.setView(view).setTitle(contact.getJid())
- .setPositiveButton("Edit", editContactNameListener)
+ .setPositiveButton(getString(R.string.edit), editContactNameListener)
.create().show();
} else {
@@ -170,18 +172,18 @@ public class ContactDetailsActivity extends XmppActivity {
private void populateView() {
setTitle(contact.getDisplayName());
- if (contact.getSubscriptionOption(Contact.Subscription.FROM)) {
+ if (contact.getOption(Contact.Options.FROM)) {
send.setChecked(true);
} else {
send.setText(R.string.preemptively_grant);
if (contact
- .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
+ .getOption(Contact.Options.PREEMPTIVE_GRANT)) {
send.setChecked(true);
} else {
send.setChecked(false);
}
}
- if (contact.getSubscriptionOption(Contact.Subscription.TO)) {
+ if (contact.getOption(Contact.Options.TO)) {
receive.setChecked(true);
} else {
receive.setText(R.string.ask_for_presence_updates);
@@ -190,54 +192,57 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onClick(View v) {
- Toast.makeText(getApplicationContext(), "Asked for presence updates",Toast.LENGTH_SHORT).show();
+ Toast.makeText(getApplicationContext(), getString(R.string.asked_for_presence_updates),
+ Toast.LENGTH_SHORT).show();
xmppConnectionService.requestPresenceUpdatesFrom(contact);
}
});
- if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) {
+ if (contact.getOption(Contact.Options.ASKING)) {
receive.setChecked(true);
} else {
receive.setChecked(false);
}
}
+
+ lastseen.setText(UIHelper.lastseen(getApplicationContext(),contact.lastseen.time));
switch (contact.getMostAvailableStatus()) {
case Presences.CHAT:
- status.setText("free to chat");
+ status.setText(R.string.contact_status_free_to_chat);
status.setTextColor(0xFF83b600);
break;
case Presences.ONLINE:
- status.setText("online");
+ status.setText(R.string.contact_status_online);
status.setTextColor(0xFF83b600);
break;
case Presences.AWAY:
- status.setText("away");
+ status.setText(R.string.contact_status_away);
status.setTextColor(0xFFffa713);
break;
case Presences.XA:
- status.setText("extended away");
+ status.setText(R.string.contact_status_extended_away);
status.setTextColor(0xFFffa713);
break;
case Presences.DND:
- status.setText("do not disturb");
+ status.setText(R.string.contact_status_do_not_disturb);
status.setTextColor(0xFFe92727);
break;
case Presences.OFFLINE:
- status.setText("offline");
+ status.setText(R.string.contact_status_offline);
status.setTextColor(0xFFe92727);
break;
default:
- status.setText("offline");
+ status.setText(R.string.contact_status_offline);
status.setTextColor(0xFFe92727);
break;
}
if (contact.getPresences().size() > 1) {
- contactJid.setText(contact.getJid()+" ("+contact.getPresences().size()+")");
+ contactJidTv.setText(contact.getJid()+" ("+contact.getPresences().size()+")");
} else {
- contactJid.setText(contact.getJid());
+ contactJidTv.setText(contact.getJid());
}
- accountJid.setText(contact.getAccount().getJid());
+ accountJidTv.setText(contact.getAccount().getJid());
UIHelper.prepareContactBadge(this, badge, contact, getApplicationContext());
@@ -286,65 +291,83 @@ public class ContactDetailsActivity extends XmppActivity {
@Override
public void onBackendConnected() {
- if (uuid != null) {
- this.contact = xmppConnectionService.findContact(uuid);
- if (this.contact != null) {
- populateView();
+ if ((accountJid != null)&&(contactJid != null)) {
+ Account account = xmppConnectionService.findAccountByJid(accountJid);
+ if (account==null) {
+ return;
}
+ this.contact = account.getRoster().getContact(contactJid);
+ populateView();
}
}
@Override
protected void onStop() {
super.onStop();
- boolean needsUpdating = false;
- if (contact.getSubscriptionOption(Contact.Subscription.FROM)) {
+ boolean updated = false;
+ boolean online = contact.getAccount().getStatus() == Account.STATUS_ONLINE;
+ if (contact.getOption(Contact.Options.FROM)) {
if (!send.isChecked()) {
- contact.resetSubscriptionOption(Contact.Subscription.FROM);
- contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
- activity.xmppConnectionService.stopPresenceUpdatesTo(contact);
- needsUpdating = true;
+ if (online) {
+ contact.resetOption(Contact.Options.FROM);
+ contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
+ activity.xmppConnectionService.stopPresenceUpdatesTo(contact);
+ }
+ updated = true;
}
} else {
if (contact
- .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
+ .getOption(Contact.Options.PREEMPTIVE_GRANT)) {
if (!send.isChecked()) {
- contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
- needsUpdating = true;
+ if (online) {
+ contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
+ }
+ updated = true;
}
} else {
if (send.isChecked()) {
- contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
- needsUpdating = true;
+ if (online) {
+ contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
+ }
+ updated = true;
}
}
}
- if (contact.getSubscriptionOption(Contact.Subscription.TO)) {
+ if (contact.getOption(Contact.Options.TO)) {
if (!receive.isChecked()) {
- contact.resetSubscriptionOption(Contact.Subscription.TO);
- activity.xmppConnectionService.stopPresenceUpdatesFrom(contact);
- needsUpdating = true;
+ if (online) {
+ contact.resetOption(Contact.Options.TO);
+ activity.xmppConnectionService.stopPresenceUpdatesFrom(contact);
+ }
+ updated = true;
}
} else {
- if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) {
+ if (contact.getOption(Contact.Options.ASKING)) {
if (!receive.isChecked()) {
- contact.resetSubscriptionOption(Contact.Subscription.ASKING);
- activity.xmppConnectionService
+ if (online) {
+ contact.resetOption(Contact.Options.ASKING);
+ activity.xmppConnectionService
.stopPresenceUpdatesFrom(contact);
- needsUpdating = true;
+ }
+ updated = true;
}
} else {
if (receive.isChecked()) {
- contact.setSubscriptionOption(Contact.Subscription.ASKING);
- activity.xmppConnectionService
+ if (online) {
+ contact.setOption(Contact.Options.ASKING);
+ activity.xmppConnectionService
.requestPresenceUpdatesFrom(contact);
- needsUpdating = true;
+ }
+ updated = true;
}
}
}
- if (needsUpdating) {
- Toast.makeText(getApplicationContext(), "Subscription updated", Toast.LENGTH_SHORT).show();
- activity.xmppConnectionService.updateContact(contact);
+ if (updated) {
+ if (online) {
+ Toast.makeText(getApplicationContext(), getString(R.string.subscription_updated), Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(getApplicationContext(), getString(R.string.subscription_not_updated_offline), Toast.LENGTH_SHORT).show();
+ }
}
}
diff --git a/src/eu/siacs/conversations/ui/ContactsActivity.java b/src/eu/siacs/conversations/ui/ContactsActivity.java
index e403450a..4e9c8af6 100644
--- a/src/eu/siacs/conversations/ui/ContactsActivity.java
+++ b/src/eu/siacs/conversations/ui/ContactsActivity.java
@@ -18,7 +18,6 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextWatcher;
-import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
@@ -34,13 +33,11 @@ import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
-import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.ImageView;
import android.widget.Toast;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
@@ -130,8 +127,10 @@ public class ContactsActivity extends XmppActivity {
Intent intent = new Intent(getApplicationContext(),
ContactDetailsActivity.class);
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
- intent.putExtra("uuid", selectedContacts.get(0).getUuid());
+ intent.putExtra("account", selectedContacts.get(0).getAccount().getJid());
+ intent.putExtra("contact",selectedContacts.get(0).getJid());
startActivity(intent);
+ finish();
break;
case R.id.action_invite:
invite();
@@ -187,7 +186,7 @@ public class ContactsActivity extends XmppActivity {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.account_offline));
builder.setMessage(getString(R.string.cant_invite_while_offline));
- builder.setNegativeButton("OK", null);
+ builder.setNegativeButton(getString(R.string.ok), null);
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.create().show();
return false;
@@ -238,7 +237,7 @@ public class ContactsActivity extends XmppActivity {
@Override
public void onClick(DialogInterface dialog, int which) {
- String mucName = CryptoHelper.randomMucName();
+ String mucName = CryptoHelper.randomMucName(xmppConnectionService.getRNG());
String serverName = account.getXmppConnection()
.getMucServer();
String jid = mucName + "@" + serverName;
@@ -259,7 +258,7 @@ public class ContactsActivity extends XmppActivity {
conversation, subject.toString());
xmppConnectionService.inviteToConference(conversation,
selectedContacts);
- switchToConversation(conversation, null);
+ switchToConversation(conversation, null,false);
}
});
builder.create().show();
@@ -270,7 +269,7 @@ public class ContactsActivity extends XmppActivity {
aggregatedContacts.clear();
for (Contact contact : rosterContacts) {
- if (contact.match(searchString))
+ if (contact.match(searchString)&&(contact.showInRoster()))
aggregatedContacts.add(contact);
}
@@ -287,16 +286,15 @@ public class ContactsActivity extends XmppActivity {
if (aggregatedContacts.size() == 0) {
if (Validator.isValidJid(searchString)) {
- String name = searchString.split("@")[0];
- Contact newContact = new Contact(null, name, searchString, null);
- newContact.flagAsNotInRoster();
+ Contact newContact = new Contact(searchString);
+ newContact.resetOption(Contact.Options.IN_ROSTER);
aggregatedContacts.add(newContact);
- contactsHeader.setText("Create new contact");
+ contactsHeader.setText(getString(R.string.new_contact));
} else {
- contactsHeader.setText("Contacts");
+ contactsHeader.setText(getString(R.string.contacts));
}
} else {
- contactsHeader.setText("Contacts");
+ contactsHeader.setText(getString(R.string.contacts));
}
contactsAdapter.notifyDataSetChanged();
@@ -429,17 +427,19 @@ public class ContactsActivity extends XmppActivity {
}
AlertDialog.Builder accountChooser = new AlertDialog.Builder(this);
- accountChooser.setTitle("Choose account");
+ accountChooser.setTitle(getString(R.string.choose_account));
accountChooser.setItems(accountList, listener);
return accountChooser.create();
}
public void showIsMucDialogIfNeeded(final Contact clickedContact) {
- if (clickedContact.couldBeMuc()) {
+ if (isMuc(clickedContact)) {
+ startConversation(clickedContact,clickedContact.getAccount(), true);
+ } else if (clickedContact.couldBeMuc()) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
- dialog.setTitle("Multi User Conference");
- dialog.setMessage("Are you trying to join a conference?");
- dialog.setPositiveButton("Yes", new OnClickListener() {
+ dialog.setTitle(getString(R.string.multi_user_conference));
+ dialog.setMessage(getString(R.string.trying_join_conference));
+ dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -447,7 +447,7 @@ public class ContactsActivity extends XmppActivity {
clickedContact.getAccount(), true);
}
});
- dialog.setNegativeButton("No", new OnClickListener() {
+ dialog.setNegativeButton(getString(R.string.no), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -461,15 +461,26 @@ public class ContactsActivity extends XmppActivity {
false);
}
}
+
+ private boolean isMuc(Contact contact) {
+ ArrayList<String> mucServers = new ArrayList<String>();
+ for(Account account : accounts) {
+ if (account.getXmppConnection()!=null) {
+ mucServers.add(account.getXmppConnection().getMucServer());
+ }
+ }
+ String server = contact.getJid().split("@")[1];
+ return mucServers.contains(server);
+ }
public void startConversation(Contact contact, Account account, boolean muc) {
- if (!contact.isInRoster()&&(!muc)) {
+ if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) {
xmppConnectionService.createContact(contact);
}
Conversation conversation = xmppConnectionService
.findOrCreateConversation(account, contact.getJid(), muc);
- switchToConversation(conversation, null);
+ switchToConversation(conversation, null,false);
}
@Override
@@ -496,7 +507,7 @@ public class ContactsActivity extends XmppActivity {
.findOrCreateConversation(
accounts.get(which), finalJid,
false);
- switchToConversation(conversation, null);
+ switchToConversation(conversation, null,false);
finish();
}
}).show();
@@ -504,7 +515,7 @@ public class ContactsActivity extends XmppActivity {
Conversation conversation = xmppConnectionService
.findOrCreateConversation(this.accounts.get(0),
jid, false);
- switchToConversation(conversation, null);
+ switchToConversation(conversation, null,false);
finish();
}
}
@@ -515,9 +526,10 @@ public class ContactsActivity extends XmppActivity {
getActionBar().setHomeButtonEnabled(false);
}
this.rosterContacts.clear();
- for (int i = 0; i < accounts.size(); ++i) {
- rosterContacts.addAll(xmppConnectionService.getRoster(accounts
- .get(i)));
+ for(Account account : accounts) {
+ if (account.getStatus() != Account.STATUS_DISABLED) {
+ rosterContacts.addAll(account.getRoster().getContacts());
+ }
}
updateAggregatedContacts();
}
@@ -532,52 +544,12 @@ public class ContactsActivity extends XmppActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.action_refresh_contacts:
- refreshContacts();
- break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
- private void refreshContacts() {
- final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1);
- final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search);
- final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header);
- final ListView contactList = (ListView) findViewById(R.id.contactList);
- searchBar.setVisibility(View.GONE);
- contactsHeader.setVisibility(View.GONE);
- contactList.setVisibility(View.GONE);
- progress.setVisibility(View.VISIBLE);
- this.accounts = xmppConnectionService.getAccounts();
- this.rosterContacts.clear();
- for (int i = 0; i < accounts.size(); ++i) {
- if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) {
- xmppConnectionService.updateRoster(accounts.get(i),
- new OnRosterFetchedListener() {
-
- @Override
- public void onRosterFetched(
- final List<Contact> roster) {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- rosterContacts.addAll(roster);
- progress.setVisibility(View.GONE);
- searchBar.setVisibility(View.VISIBLE);
- contactList.setVisibility(View.VISIBLE);
- contactList.setVisibility(View.VISIBLE);
- updateAggregatedContacts();
- }
- });
- }
- });
- }
- }
- }
-
@Override
public void onActionModeStarted(ActionMode mode) {
super.onActionModeStarted(mode);
diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java
index 88728245..4e264df7 100644
--- a/src/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/eu/siacs/conversations/ui/ConversationActivity.java
@@ -3,22 +3,20 @@ package eu.siacs.conversations.ui;
import java.io.FileNotFoundException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Hashtable;
import java.util.List;
-import org.openintents.openpgp.OpenPgpError;
-
import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.ImageProvider;
import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.UIHelper;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.provider.MediaStore;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.app.PendingIntent;
@@ -65,21 +63,25 @@ public class ConversationActivity extends XmppActivity {
public static final int REQUEST_SEND_MESSAGE = 0x75441;
public static final int REQUEST_DECRYPT_PGP = 0x76783;
private static final int REQUEST_ATTACH_FILE_DIALOG = 0x48502;
+ private static final int REQUEST_IMAGE_CAPTURE = 0x33788;
+ private static final int REQUEST_RECORD_AUDIO = 0x46189;
private static final int REQUEST_SEND_PGP_IMAGE = 0x53883;
- private static final int REQUEST_ATTACH_FILE = 0x73824;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x378018;
+ private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x92734;
+ private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x84123;
+ private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x75291;
+
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 boolean showLastseen = false;
private ArrayAdapter<Conversation> listAdapter;
-
- public Message pendingMessage = null;
private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
@@ -108,7 +110,7 @@ public class ConversationActivity extends XmppActivity {
});
}
};
-
+
protected ConversationActivity activity = this;
private DisplayMetrics metrics;
private Toast prepareImageToast;
@@ -121,6 +123,10 @@ public class ConversationActivity extends XmppActivity {
return this.selectedConversation;
}
+ public void setSelectedConversation(Conversation conversation) {
+ this.selectedConversation = conversation;
+ }
+
public ListView getConversationListView() {
return this.listView;
}
@@ -137,7 +143,7 @@ public class ConversationActivity extends XmppActivity {
protected void onCreate(Bundle savedInstanceState) {
metrics = getResources().getDisplayMetrics();
-
+
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_conversations_overview);
@@ -173,15 +179,18 @@ public class ConversationActivity extends XmppActivity {
convName.setText(conv.getName(useSubject));
TextView convLastMsg = (TextView) view
.findViewById(R.id.conversation_lastmsg);
- ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
-
+ ImageView imagePreview = (ImageView) view
+ .findViewById(R.id.conversation_lastimage);
+
Message latestMessage = conv.getLatestMessage();
-
+
if (latestMessage.getType() == Message.TYPE_TEXT) {
- if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP)&&(latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
+ if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP)
+ && (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
convLastMsg.setText(conv.getLatestMessage().getBody());
} else {
- convLastMsg.setText(getText(R.string.encrypted_message_received));
+ convLastMsg
+ .setText(getText(R.string.encrypted_message_received));
}
convLastMsg.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
@@ -194,16 +203,16 @@ public class ConversationActivity extends XmppActivity {
convLastMsg.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) {
- convLastMsg.setText(getText(R.string.image_offered_for_download));
+ convLastMsg
+ .setText(getText(R.string.image_offered_for_download));
} else if (latestMessage.getStatus() == Message.STATUS_RECIEVING) {
- convLastMsg.setText(getText(R.string.receiving_image));
+ convLastMsg
+ .setText(getText(R.string.receiving_image));
} else {
convLastMsg.setText("");
}
}
}
-
-
if (!conv.isRead()) {
convName.setTypeface(null, Typeface.BOLD);
@@ -214,14 +223,14 @@ public class ConversationActivity extends XmppActivity {
}
((TextView) view.findViewById(R.id.conversation_lastupdate))
- .setText(UIHelper.readableTimeDifference(conv
- .getLatestMessage().getTimeSent()));
+ .setText(UIHelper.readableTimeDifference(getContext(),
+ conv.getLatestMessage().getTimeSent()));
ImageView profilePicture = (ImageView) view
.findViewById(R.id.conversation_image);
- profilePicture.setImageBitmap(UIHelper.getContactPicture(
- conv, 56, activity.getApplicationContext(), false));
-
+ profilePicture.setImageBitmap(UIHelper.getContactPicture(conv,
+ 56, activity.getApplicationContext(), false));
+
return view;
}
@@ -235,8 +244,8 @@ public class ConversationActivity extends XmppActivity {
public void onItemClick(AdapterView<?> arg0, View clickedView,
int position, long arg3) {
paneShouldBeOpen = false;
- if (selectedConversation != conversationList.get(position)) {
- selectedConversation = conversationList.get(position);
+ if (getSelectedConversation() != conversationList.get(position)) {
+ setSelectedConversation(conversationList.get(position));
swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
} else {
spl.closePane();
@@ -253,6 +262,7 @@ public class ConversationActivity extends XmppActivity {
public void onPanelOpened(View arg0) {
paneShouldBeOpen = true;
getActionBar().setDisplayHomeAsUpEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
getActionBar().setTitle(R.string.app_name);
invalidateOptionsMenu();
hideKeyboard();
@@ -264,11 +274,13 @@ public class ConversationActivity extends XmppActivity {
if ((conversationList.size() > 0)
&& (getSelectedConversation() != null)) {
getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeButtonEnabled(true);
getActionBar().setTitle(
getSelectedConversation().getName(useSubject));
invalidateOptionsMenu();
if (!getSelectedConversation().isRead()) {
- getSelectedConversation().markRead();
+ xmppConnectionService
+ .markRead(getSelectedConversation());
UIHelper.updateNotification(getApplicationContext(),
getConversationList(), null, false);
listView.invalidateViews();
@@ -296,7 +308,8 @@ public class ConversationActivity extends XmppActivity {
MenuItem menuInviteContacts = (MenuItem) menu
.findItem(R.id.action_invite);
MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
- MenuItem menuClearHistory = (MenuItem) menu.findItem(R.id.action_clear_history);
+ MenuItem menuClearHistory = (MenuItem) menu
+ .findItem(R.id.action_clear_history);
if ((spl.isOpen() && (spl.isSlideable()))) {
menuArchive.setVisible(false);
@@ -310,112 +323,136 @@ public class ConversationActivity extends XmppActivity {
((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
.isSlideable());
if (this.getSelectedConversation() != null) {
+ if (this.getSelectedConversation().getLatestMessage()
+ .getEncryption() != Message.ENCRYPTION_NONE) {
+ menuSecure.setIcon(R.drawable.ic_action_secure);
+ }
if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
menuContactDetails.setVisible(false);
- menuSecure.setVisible(false);
menuAttach.setVisible(false);
} else {
menuMucDetails.setVisible(false);
menuInviteContacts.setVisible(false);
- if (this.getSelectedConversation().getLatestMessage()
- .getEncryption() != Message.ENCRYPTION_NONE) {
- menuSecure.setIcon(R.drawable.ic_action_secure);
- }
}
}
}
return true;
}
-
- private void attachFileDialog() {
+
+ private void selectPresenceToAttachFile(final int attachmentChoice) {
selectPresence(getSelectedConversation(), new OnPresenceSelected() {
-
+
@Override
- public void onPresenceSelected(boolean success, String presence) {
- if (success) {
+ public void onPresenceSelected() {
+ if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO) {
+ Intent takePictureIntent = new Intent(
+ MediaStore.ACTION_IMAGE_CAPTURE);
+ takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
+ ImageProvider.getIncomingContentUri());
+ if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(takePictureIntent,
+ REQUEST_IMAGE_CAPTURE);
+ }
+ } else if (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
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, REQUEST_ATTACH_FILE_DIALOG);
+ Intent chooser = Intent.createChooser(attachFileIntent,
+ getString(R.string.attach_file));
+ startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG);
+ } else if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
+ Intent intent = new Intent(
+ MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+ startActivityForResult(intent, REQUEST_RECORD_AUDIO);
}
}
-
- @Override
- public void onSendPlainTextInstead() {
-
- }
- },"file");
+ });
}
- private void attachFile() {
+ private void attachFile(final int attachmentChoice) {
final Conversation conversation = getSelectedConversation();
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
if (hasPgp()) {
- if (conversation.getContact().getPgpKeyId()!=0) {
- xmppConnectionService.getPgpEngine().hasKey(conversation.getContact(), new UiCallback() {
-
- @Override
- public void userInputRequried(PendingIntent pi) {
- ConversationActivity.this.runIntent(pi, REQUEST_ATTACH_FILE);
- }
-
- @Override
- public void success() {
- attachFileDialog();
- }
-
- @Override
- public void error(int error) {
- // TODO Auto-generated method stub
-
- }
- });
+ if (conversation.getContact().getPgpKeyId() != 0) {
+ xmppConnectionService.getPgpEngine().hasKey(
+ conversation.getContact(),
+ new UiCallback<Contact>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Contact contact) {
+ ConversationActivity.this.runIntent(pi,
+ attachmentChoice);
+ }
+
+ @Override
+ public void success(Contact contact) {
+ selectPresenceToAttachFile(attachmentChoice);
+ }
+
+ @Override
+ public void error(int error, Contact contact) {
+ displayErrorDialog(error);
+ }
+ });
} else {
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
.findFragmentByTag("conversation");
if (fragment != null) {
- fragment.showNoPGPKeyDialog(new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
- attachFileDialog();
- }
- });
+ fragment.showNoPGPKeyDialog(false,
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ selectPresenceToAttachFile(attachmentChoice);
+ }
+ });
}
}
+ } else {
+ showInstallPgpDialog();
}
} else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) {
- attachFileDialog();
+ selectPresenceToAttachFile(attachmentChoice);
} else {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ selectPresenceToAttachFile(attachmentChoice);
+ /*AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.otr_file_transfer));
builder.setMessage(getString(R.string.otr_file_transfer_msg));
builder.setNegativeButton(getString(R.string.cancel), null);
- if (conversation.getContact().getPgpKeyId()==0) {
- builder.setPositiveButton(getString(R.string.send_unencrypted), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
- attachFile();
- }
- });
+ if (conversation.getContact().getPgpKeyId() == 0) {
+ builder.setPositiveButton(getString(R.string.send_unencrypted),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ attachFile(attachmentChoice);
+ }
+ });
} else {
- builder.setPositiveButton(getString(R.string.use_pgp_encryption), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- conversation.setNextEncryption(Message.ENCRYPTION_PGP);
- attachFile();
- }
- });
+ builder.setPositiveButton(
+ getString(R.string.use_pgp_encryption),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_PGP);
+ attachFile(attachmentChoice);
+ }
+ });
}
- builder.create().show();
+ builder.create().show();*/
}
}
-
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -423,7 +460,29 @@ public class ConversationActivity extends XmppActivity {
spl.openPane();
break;
case R.id.action_attach_file:
- attachFile();
+ View menuAttachFile = findViewById(R.id.action_attach_file);
+ PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile);
+ attachFilePopup.inflate(R.menu.attachment_choices);
+ attachFilePopup
+ .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.attach_choose_picture:
+ attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
+ break;
+ case R.id.attach_take_picture:
+ attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
+ break;
+ case R.id.attach_record_voice:
+ attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
+ break;
+ }
+ return false;
+ }
+ });
+ attachFilePopup.show();
break;
case R.id.action_add:
startActivity(new Intent(this, ContactsActivity.class));
@@ -433,10 +492,12 @@ public class ConversationActivity extends XmppActivity {
break;
case R.id.action_contact_details:
Contact contact = this.getSelectedConversation().getContact();
- if (contact != null) {
+ if (contact.showInRoster()) {
Intent intent = new Intent(this, ContactDetailsActivity.class);
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
- intent.putExtra("uuid", contact.getUuid());
+ intent.putExtra("account", this.getSelectedConversation()
+ .getAccount().getJid());
+ intent.putExtra("contact", contact.getJid());
startActivity(intent);
} else {
showAddToRosterDialog(getSelectedConversation());
@@ -452,7 +513,7 @@ public class ConversationActivity extends XmppActivity {
Intent inviteIntent = new Intent(getApplicationContext(),
ContactsActivity.class);
inviteIntent.setAction("invite");
- inviteIntent.putExtra("uuid", selectedConversation.getUuid());
+ inviteIntent.putExtra("uuid", getSelectedConversation().getUuid());
startActivity(inviteIntent);
break;
case R.id.action_security:
@@ -468,25 +529,33 @@ public class ConversationActivity extends XmppActivity {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.encryption_choice_none:
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
item.setChecked(true);
break;
case R.id.encryption_choice_otr:
- conversation.setNextEncryption(Message.ENCRYPTION_OTR);
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_OTR);
item.setChecked(true);
break;
case R.id.encryption_choice_pgp:
if (hasPgp()) {
- if (conversation.getAccount().getKeys().has("pgp_signature")) {
- conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+ if (conversation.getAccount().getKeys()
+ .has("pgp_signature")) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_PGP);
item.setChecked(true);
} else {
- announcePgp(conversation.getAccount(),conversation);
+ announcePgp(conversation.getAccount(),
+ conversation);
}
+ } else {
+ showInstallPgpDialog();
}
break;
default:
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
break;
}
fragment.updateChatMsgHint();
@@ -494,14 +563,18 @@ public class ConversationActivity extends XmppActivity {
}
});
popup.inflate(R.menu.encryption_choices);
+ MenuItem otr = popup.getMenu().findItem(
+ R.id.encryption_choice_otr);
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ otr.setEnabled(false);
+ }
switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_NONE:
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);
+ otr.setChecked(true);
break;
case Message.ENCRYPTION_PGP:
popup.getMenu().findItem(R.id.encryption_choice_pgp)
@@ -524,36 +597,40 @@ public class ConversationActivity extends XmppActivity {
}
return super.onOptionsItemSelected(item);
}
-
+
private void endConversation(Conversation conversation) {
conversation.setStatus(Conversation.STATUS_ARCHIVED);
paneShouldBeOpen = true;
spl.openPane();
xmppConnectionService.archiveConversation(conversation);
if (conversationList.size() > 0) {
- selectedConversation = conversationList.get(0);
+ setSelectedConversation(conversationList.get(0));
} else {
- selectedConversation = null;
+ setSelectedConversation(null);
}
}
protected void clearHistoryDialog(final 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);
- final CheckBox endConversationCheckBox = (CheckBox) dialogView.findViewById(R.id.end_conversation_checkbox);
+ View dialogView = getLayoutInflater().inflate(
+ R.layout.dialog_clear_history, null);
+ final CheckBox endConversationCheckBox = (CheckBox) dialogView
+ .findViewById(R.id.end_conversation_checkbox);
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) {
- activity.xmppConnectionService.clearConversationHistory(conversation);
- if (endConversationCheckBox.isChecked()) {
- endConversation(conversation);
- }
- }
- });
+ builder.setPositiveButton(getString(R.string.delete_messages),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ activity.xmppConnectionService
+ .clearConversationHistory(conversation);
+ if (endConversationCheckBox.isChecked()) {
+ endConversation(conversation);
+ }
+ }
+ });
builder.create().show();
}
@@ -580,11 +657,30 @@ public class ConversationActivity extends XmppActivity {
}
@Override
+ protected void onNewIntent(Intent intent) {
+ if ((Intent.ACTION_VIEW.equals(intent.getAction()) && (VIEW_CONVERSATION
+ .equals(intent.getType())))) {
+ String convToView = (String) intent.getExtras().get(CONVERSATION);
+ updateConversationList();
+ for (int i = 0; i < conversationList.size(); ++i) {
+ if (conversationList.get(i).getUuid().equals(convToView)) {
+ setSelectedConversation(conversationList.get(i));
+ break;
+ }
+ }
+ paneShouldBeOpen = false;
+ String text = intent.getExtras().getString(TEXT, null);
+ swapConversationFragment().setText(text);
+ }
+ }
+
+ @Override
public void onStart() {
super.onStart();
SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this);
this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
+ this.showLastseen = preferences.getBoolean("show_last_seen", false);
if (this.xmppConnectionServiceBound) {
this.onBackendConnected();
}
@@ -619,7 +715,7 @@ public class ConversationActivity extends XmppActivity {
for (int i = 0; i < conversationList.size(); ++i) {
if (conversationList.get(i).getUuid().equals(convToView)) {
- selectedConversation = conversationList.get(i);
+ setSelectedConversation(conversationList.get(i));
}
}
paneShouldBeOpen = false;
@@ -642,7 +738,7 @@ public class ConversationActivity extends XmppActivity {
if (selectedFragment != null) {
selectedFragment.onBackendConnected();
} else {
- selectedConversation = conversationList.get(0);
+ setSelectedConversation(conversationList.get(0));
swapConversationFragment();
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
@@ -658,7 +754,8 @@ public class ConversationActivity extends XmppActivity {
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ protected void onActivityResult(int requestCode, int resultCode,
+ final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_DECRYPT_PGP) {
@@ -668,305 +765,213 @@ public class ConversationActivity extends XmppActivity {
selectedFragment.hidePgpPassphraseBox();
}
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
- prepareImageToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
- final Conversation conversation = getSelectedConversation();
- if (conversation.getNextEncryption() == Message.ENCRYPTION_NONE) {
- prepareImageToast.show();
- this.pendingMessage = xmppConnectionService.attachImageToConversation(conversation, data.getData(),new UiCallback() {
-
- @Override
- public void userInputRequried(PendingIntent pi) {
-
- }
-
- @Override
- public void success() {
- sendPendingImageMessage();
- }
-
- @Override
- public void error(int error) {
- pendingMessage = null;
- displayErrorDialog(error);
- }
- });
- } else if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- prepareImageToast.show();
- attachPgpFile(conversation,data.getData());
- } else {
- Log.d(LOGTAG,"unknown next message encryption: "+conversation.getNextEncryption());
- }
+ attachImageToConversation(getSelectedConversation(),
+ data.getData());
} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
-
- } else if (requestCode == REQUEST_ATTACH_FILE) {
- attachFile();
+
+ } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
+ attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
+ } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
+ attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
- announcePgp(getSelectedConversation().getAccount(),getSelectedConversation());
+ announcePgp(getSelectedConversation().getAccount(),
+ getSelectedConversation());
} else if (requestCode == REQUEST_ENCRYPT_MESSAGE) {
- encryptTextMessage();
+ // encryptTextMessage();
+ } else if (requestCode == REQUEST_IMAGE_CAPTURE) {
+ attachImageToConversation(getSelectedConversation(), null);
+ } else if (requestCode == REQUEST_RECORD_AUDIO) {
+ Log.d("xmppService", data.getData().toString());
+ attachAudioToConversation(getSelectedConversation(),
+ data.getData());
} else {
- Log.d(LOGTAG,"unknown result code:"+requestCode);
+ Log.d(LOGTAG, "unknown result code:" + requestCode);
}
}
}
-
- private void attachPgpFile(Conversation conversation, Uri uri) {
- pendingMessage = xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() {
-
- @Override
- public void userInputRequried(PendingIntent pi) {
- ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE);
- }
-
- @Override
- public void success() {
- sendPendingImageMessage();
- }
-
+
+ private void attachAudioToConversation(Conversation conversation, Uri uri) {
+
+ }
+
+ private void attachImageToConversation(Conversation conversation, Uri uri) {
+ prepareImageToast = Toast.makeText(getApplicationContext(),
+ getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ prepareImageToast.show();
+ xmppConnectionService.attachImageToConversation(conversation, uri,
+ new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Message object) {
+ hidePrepareImageToast();
+ ConversationActivity.this.runIntent(pi,
+ ConversationActivity.REQUEST_SEND_PGP_IMAGE);
+ }
+
+ @Override
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int error, Message message) {
+ hidePrepareImageToast();
+ displayErrorDialog(error);
+ }
+ });
+ }
+
+ private void hidePrepareImageToast() {
+ if (prepareImageToast != null) {
+ runOnUiThread(new Runnable() {
+
@Override
- public void error(int error) {
- pendingMessage = null;
- displayErrorDialog(error);
+ public void run() {
+ prepareImageToast.cancel();
}
});
+ }
}
- private void sendPendingImageMessage() {
- pendingMessage.getConversation().getMessages().add(pendingMessage);
- xmppConnectionService.databaseBackend.createMessage(pendingMessage);
- xmppConnectionService.sendMessage(pendingMessage, null);
- xmppConnectionService.updateUi(pendingMessage.getConversation(), false);
- pendingMessage = null;
- }
-
public void updateConversationList() {
conversationList.clear();
conversationList.addAll(xmppConnectionService.getConversations());
listView.invalidateViews();
}
-
- public void selectPresence(final Conversation conversation, final OnPresenceSelected listener, String reason) {
- Account account = conversation.getAccount();
- if (account.getStatus() != Account.STATUS_ONLINE) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.not_connected));
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- if ("otr".equals(reason)) {
- builder.setMessage(getString(R.string.you_are_offline,getString(R.string.otr_messages)));
- } else if ("file".equals(reason)) {
- builder.setMessage(getString(R.string.you_are_offline,getString(R.string.files)));
- } else {
- builder.setMessage(getString(R.string.you_are_offline_blank));
- }
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.manage_account), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- startActivity(new Intent(activity, ManageAccountActivity.class));
- }
- });
- builder.create().show();
- listener.onPresenceSelected(false, null);
+
+ public boolean showLastseen() {
+ if (getSelectedConversation() == null) {
+ return false;
} else {
- Contact contact = conversation.getContact();
- if (contact==null) {
- showAddToRosterDialog(conversation);
- listener.onPresenceSelected(false,null);
- } else {
- Hashtable<String, Integer> presences = contact.getPresences();
- if (presences.size() == 0) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.contact_offline));
- if ("otr".equals(reason)) {
- builder.setMessage(getString(R.string.contact_offline_otr));
- builder.setPositiveButton(getString(R.string.send_unencrypted), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- listener.onSendPlainTextInstead();
- }
- });
- } else if ("file".equals(reason)) {
- builder.setMessage(getString(R.string.contact_offline_file));
- }
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.create().show();
- 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();
- }
- }
+ return this.showLastseen
+ && getSelectedConversation().getMode() == Conversation.MODE_SINGLE;
}
}
-
- 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();
- }
-
public void runIntent(PendingIntent pi, int requestCode) {
try {
- this.startIntentSenderForResult(pi.getIntentSender(),requestCode, null, 0,
- 0, 0);
+ this.startIntentSenderForResult(pi.getIntentSender(), requestCode,
+ null, 0, 0, 0);
} catch (SendIntentException e1) {
- Log.d("xmppService","failed to start intent to send message");
+ Log.d("xmppService", "failed to start intent to send message");
}
}
-
-
+
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private Message message = null;
-
- public BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<ImageView>(imageView);
- }
-
- @Override
- protected Bitmap doInBackground(Message... params) {
- message = params[0];
- try {
- return xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288),false);
+ private final WeakReference<ImageView> imageViewReference;
+ private Message message = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<ImageView>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Message... params) {
+ message = params[0];
+ try {
+ return xmppConnectionService.getFileBackend().getThumbnail(
+ message, (int) (metrics.density * 288), false);
} catch (FileNotFoundException e) {
- Log.d("xmppService","file not found!");
+ Log.d("xmppService", "file not found!");
return null;
}
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (imageViewReference != null && bitmap != null) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
- }
- }
-
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (imageViewReference != null && bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
public void loadBitmap(Message message, ImageView imageView) {
Bitmap bm;
try {
- bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true);
+ bm = xmppConnectionService.getFileBackend().getThumbnail(message,
+ (int) (metrics.density * 288), true);
} catch (FileNotFoundException e) {
bm = null;
}
- if (bm!=null) {
+ if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
} else {
- if (cancelPotentialWork(message, imageView)) {
- imageView.setBackgroundColor(0xff333333);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable =
- new AsyncDrawable(getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- task.execute(message);
- }
+ if (cancelPotentialWork(message, imageView)) {
+ imageView.setBackgroundColor(0xff333333);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(
+ getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ task.execute(message);
+ }
}
}
-
- public static boolean cancelPotentialWork(Message message, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Message oldMessage = bitmapWorkerTask.message;
- if (oldMessage == null || message != oldMessage) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
+
+ public static boolean cancelPotentialWork(Message message,
+ ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Message oldMessage = bitmapWorkerTask.message;
+ if (oldMessage == null || message != oldMessage) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
- public AsyncDrawable(Resources res, Bitmap bitmap,
- BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference =
- new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
- }
+ public AsyncDrawable(Resources res, Bitmap bitmap,
+ BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
+ bitmapWorkerTask);
+ }
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
}
- public void encryptTextMessage() {
- xmppConnectionService.getPgpEngine().encrypt(this.pendingMessage, new UiCallback() {
+ public void encryptTextMessage(Message message) {
+ xmppConnectionService.getPgpEngine().encrypt(message,
+ new UiCallback<Message>() {
@Override
- public void userInputRequried(
- PendingIntent pi) {
- activity.runIntent(
- pi,
+ public void userInputRequried(PendingIntent pi,
+ Message message) {
+ activity.runIntent(pi,
ConversationActivity.REQUEST_SEND_MESSAGE);
}
@Override
- public void success() {
- xmppConnectionService.sendMessage(pendingMessage, null);
- pendingMessage = null;
- ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
- .findFragmentByTag("conversation");
- if (selectedFragment != null) {
- selectedFragment.clearInputField();
- }
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
}
@Override
- public void error(int error) {
+ public void error(int error, Message message) {
}
});
diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java
index 91a39ecc..2eac7ad0 100644
--- a/src/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/eu/siacs/conversations/ui/ConversationFragment.java
@@ -5,17 +5,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.Set;
-import org.openintents.openpgp.OpenPgpError;
-
import net.java.otr4j.session.SessionStatus;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
-import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
+import eu.siacs.conversations.services.ImageProvider;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jingle.JingleConnection;
@@ -30,17 +28,20 @@ import android.content.SharedPreferences;
import android.content.IntentSender.SendIntentException;
import android.graphics.Bitmap;
import android.graphics.Typeface;
-import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Selection;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
@@ -69,6 +70,7 @@ public class ConversationFragment extends Fragment {
protected Bitmap selfBitmap;
private boolean useSubject = true;
+ private boolean messagesLoaded = false;
private IntentSender askForPassphraseIntent = null;
@@ -93,7 +95,7 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- if (askForPassphraseIntent != null) {
+ if (activity.hasPgp() && askForPassphraseIntent != null) {
try {
getActivity().startIntentSenderForResult(
askForPassphraseIntent,
@@ -119,6 +121,33 @@ public class ConversationFragment extends Fragment {
startActivity(intent);
}
};
+
+ private OnScrollListener mOnScrollListener = new OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem,
+ int visibleItemCount, int totalItemCount) {
+ if (firstVisibleItem == 0 && messagesLoaded) {
+ long timestamp = messageList.get(0).getTimeSent();
+ messagesLoaded = false;
+ List<Message> messages = activity.xmppConnectionService
+ .getMoreMessages(conversation, timestamp);
+ messageList.addAll(0, messages);
+ messageListAdapter.notifyDataSetChanged();
+ if (messages.size() != 0) {
+ messagesLoaded = true;
+ }
+ messagesView.setSelectionFromTop(messages.size() + 1, 0);
+ }
+ }
+ };
+
private ConversationActivity activity;
public void hidePgpPassphraseBox() {
@@ -126,22 +155,18 @@ public class ConversationFragment extends Fragment {
}
public void updateChatMsgHint() {
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- chatMsg.setHint(getString(R.string.send_message_to_conference));
- } else {
- switch (conversation.getNextEncryption()) {
- case Message.ENCRYPTION_NONE:
- chatMsg.setHint(getString(R.string.send_plain_text_message));
- break;
- case Message.ENCRYPTION_OTR:
- chatMsg.setHint(getString(R.string.send_otr_message));
- break;
- case Message.ENCRYPTION_PGP:
- chatMsg.setHint(getString(R.string.send_pgp_message));
- break;
- default:
- break;
- }
+ switch (conversation.getNextEncryption()) {
+ case Message.ENCRYPTION_NONE:
+ chatMsg.setHint(getString(R.string.send_plain_text_message));
+ break;
+ case Message.ENCRYPTION_OTR:
+ chatMsg.setHint(getString(R.string.send_otr_message));
+ break;
+ case Message.ENCRYPTION_PGP:
+ chatMsg.setHint(getString(R.string.send_pgp_message));
+ break;
+ default:
+ break;
}
}
@@ -168,6 +193,8 @@ public class ConversationFragment extends Fragment {
mucErrorText = (TextView) view.findViewById(R.id.muc_error_msg);
messagesView = (ListView) view.findViewById(R.id.messages_view);
+ messagesView.setOnScrollListener(mOnScrollListener);
+ messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
.getApplicationContext(), R.layout.message_sent,
@@ -175,15 +202,18 @@ public class ConversationFragment extends Fragment {
private static final int SENT = 0;
private static final int RECIEVED = 1;
+ private static final int STATUS = 2;
@Override
public int getViewTypeCount() {
- return 2;
+ return 3;
}
@Override
public int getItemViewType(int position) {
- if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
+ if (getItem(position).getType() == Message.TYPE_STATUS) {
+ return STATUS;
+ } else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
return RECIEVED;
} else {
return SENT;
@@ -194,6 +224,8 @@ public class ConversationFragment extends Fragment {
String filesize = null;
String info = null;
boolean error = false;
+ boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
+ && message.getStatus() <= Message.STATUS_RECIEVED;
if (message.getType() == Message.TYPE_IMAGE) {
String[] fileParams = message.getBody().split(",");
try {
@@ -204,6 +236,9 @@ public class ConversationFragment extends Fragment {
}
}
switch (message.getStatus()) {
+ case Message.STATUS_WAITING:
+ info = getString(R.string.waiting);
+ break;
case Message.STATUS_UNSEND:
info = getString(R.string.sending);
break;
@@ -219,8 +254,7 @@ public class ConversationFragment extends Fragment {
error = true;
break;
default:
- if ((message.getConversation().getMode() == Conversation.MODE_MULTI)
- && (message.getStatus() <= Message.STATUS_RECIEVED)) {
+ if (multiReceived) {
info = message.getCounterpart();
}
break;
@@ -236,8 +270,8 @@ public class ConversationFragment extends Fragment {
viewHolder.indicator.setVisibility(View.VISIBLE);
}
- String formatedTime = UIHelper.readableTimeDifference(message
- .getTimeSent());
+ String formatedTime = UIHelper.readableTimeDifference(
+ getContext(), message.getTimeSent());
if (message.getStatus() <= Message.STATUS_RECIEVED) {
if ((filesize != null) && (info != null)) {
viewHolder.time.setText(filesize + " \u00B7 " + info);
@@ -254,8 +288,12 @@ public class ConversationFragment extends Fragment {
if ((filesize != null) && (info != null)) {
viewHolder.time.setText(filesize + " \u00B7 " + info);
} else if ((filesize == null) && (info != null)) {
- viewHolder.time.setText(info + " \u00B7 "
- + formatedTime);
+ if (error) {
+ viewHolder.time.setText(info + " \u00B7 "
+ + formatedTime);
+ } else {
+ viewHolder.time.setText(info);
+ }
} else if ((filesize != null) && (info == null)) {
viewHolder.time.setText(filesize + " \u00B7 "
+ formatedTime);
@@ -266,22 +304,28 @@ public class ConversationFragment extends Fragment {
}
private void displayInfoMessage(ViewHolder viewHolder, int r) {
- viewHolder.download_button.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getString(r));
viewHolder.messageBody.setTextColor(0xff33B5E5);
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
+ viewHolder.messageBody.setTextIsSelectable(false);
}
private void displayDecryptionFailed(ViewHolder viewHolder) {
- viewHolder.download_button.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody
.setText(getString(R.string.decryption_failed));
viewHolder.messageBody.setTextColor(0xFFe92727);
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setTextIsSelectable(false);
}
private void displayTextMessage(ViewHolder viewHolder, String text) {
@@ -297,6 +341,7 @@ public class ConversationFragment extends Fragment {
}
viewHolder.messageBody.setTextColor(0xff333333);
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setTextIsSelectable(true);
}
private void displayImageMessage(ViewHolder viewHolder,
@@ -329,18 +374,27 @@ public class ConversationFragment extends Fragment {
@Override
public void onClick(View v) {
- Uri uri = Uri
- .parse("content://eu.siacs.conversations.images/"
- + message.getConversationUuid()
- + "/"
- + message.getUuid());
- Log.d("xmppService",
- "staring intent with uri:" + uri.toString());
Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(uri, "image/*");
+ intent.setDataAndType(
+ ImageProvider.getContentUri(message), "image/*");
startActivity(intent);
}
});
+ viewHolder.image
+ .setOnLongClickListener(new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_STREAM,
+ ImageProvider.getContentUri(message));
+ shareIntent.setType("image/webp");
+ startActivity(Intent.createChooser(shareIntent,
+ getText(R.string.share_with)));
+ return true;
+ }
+ });
}
@Override
@@ -354,13 +408,26 @@ public class ConversationFragment extends Fragment {
case SENT:
view = (View) inflater.inflate(R.layout.message_sent,
null);
+ viewHolder.message_box = (LinearLayout) view
+ .findViewById(R.id.message_box);
viewHolder.contact_picture = (ImageView) view
.findViewById(R.id.message_photo);
viewHolder.contact_picture.setImageBitmap(selfBitmap);
+ viewHolder.indicator = (ImageView) view
+ .findViewById(R.id.security_indicator);
+ viewHolder.image = (ImageView) view
+ .findViewById(R.id.message_image);
+ viewHolder.messageBody = (TextView) view
+ .findViewById(R.id.message_body);
+ viewHolder.time = (TextView) view
+ .findViewById(R.id.message_time);
+ view.setTag(viewHolder);
break;
case RECIEVED:
view = (View) inflater.inflate(
R.layout.message_recieved, null);
+ viewHolder.message_box = (LinearLayout) view
+ .findViewById(R.id.message_box);
viewHolder.contact_picture = (ImageView) view
.findViewById(R.id.message_photo);
@@ -379,40 +446,61 @@ public class ConversationFragment extends Fragment {
.getApplicationContext()));
}
+ viewHolder.indicator = (ImageView) view
+ .findViewById(R.id.security_indicator);
+ viewHolder.image = (ImageView) view
+ .findViewById(R.id.message_image);
+ viewHolder.messageBody = (TextView) view
+ .findViewById(R.id.message_body);
+ viewHolder.time = (TextView) view
+ .findViewById(R.id.message_time);
+ view.setTag(viewHolder);
+ break;
+ case STATUS:
+ view = (View) inflater.inflate(R.layout.message_status,
+ null);
+ viewHolder.contact_picture = (ImageView) view
+ .findViewById(R.id.message_photo);
+ if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
+
+ viewHolder.contact_picture
+ .setImageBitmap(mBitmapCache.get(
+ item.getConversation().getName(
+ useSubject), item
+ .getConversation()
+ .getContact(),
+ getActivity()
+ .getApplicationContext()));
+ viewHolder.contact_picture.setAlpha(128);
+
+ }
break;
default:
viewHolder = null;
break;
}
- viewHolder.indicator = (ImageView) view
- .findViewById(R.id.security_indicator);
- viewHolder.image = (ImageView) view
- .findViewById(R.id.message_image);
- viewHolder.messageBody = (TextView) view
- .findViewById(R.id.message_body);
- viewHolder.time = (TextView) view
- .findViewById(R.id.message_time);
- view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
+ if (type == STATUS) {
+ return view;
+ }
+
if (type == RECIEVED) {
if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
- if (item.getCounterpart() != null) {
- viewHolder.contact_picture
- .setImageBitmap(mBitmapCache.get(item
- .getCounterpart(), null,
- getActivity()
- .getApplicationContext()));
- } else {
- viewHolder.contact_picture
- .setImageBitmap(mBitmapCache.get(
- item.getConversation().getName(
- useSubject), null,
- getActivity()
- .getApplicationContext()));
- }
+ viewHolder.contact_picture.setImageBitmap(mBitmapCache
+ .get(item.getCounterpart(), null, getActivity()
+ .getApplicationContext()));
+ viewHolder.contact_picture
+ .setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ highlightInConference(item
+ .getCounterpart());
+ }
+ });
}
}
@@ -439,7 +527,8 @@ public class ConversationFragment extends Fragment {
}
});
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
- || (item.getEncryption() == Message.ENCRYPTION_NONE)) {
+ || (item.getEncryption() == Message.ENCRYPTION_NONE)
+ || (item.getEncryption() == Message.ENCRYPTION_OTR)) {
displayImageMessage(viewHolder, item);
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
displayInfoMessage(viewHolder,
@@ -449,8 +538,21 @@ public class ConversationFragment extends Fragment {
}
} else {
if (item.getEncryption() == Message.ENCRYPTION_PGP) {
- displayInfoMessage(viewHolder,
- R.string.encrypted_message);
+ if (activity.hasPgp()) {
+ displayInfoMessage(viewHolder,
+ R.string.encrypted_message);
+ } else {
+ displayInfoMessage(viewHolder,
+ R.string.install_openkeychain);
+ viewHolder.message_box
+ .setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.showInstallPgpDialog();
+ }
+ });
+ }
} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
displayDecryptionFailed(viewHolder);
} else {
@@ -468,6 +570,18 @@ public class ConversationFragment extends Fragment {
return view;
}
+ protected void highlightInConference(String nick) {
+ String oldString = chatMsg.getText().toString().trim();
+ if (oldString.isEmpty()) {
+ chatMsg.setText(nick + ": ");
+ } else {
+ chatMsg.setText(oldString + " " + nick + " ");
+ }
+ int position = chatMsg.length();
+ Editable etext = chatMsg.getText();
+ Selection.setSelection(etext, position);
+ }
+
protected Bitmap findSelfPicture() {
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getActivity()
@@ -494,7 +608,7 @@ public class ConversationFragment extends Fragment {
@Override
public void onStop() {
super.onStop();
- if (this.conversation!=null) {
+ if (this.conversation != null) {
this.conversation.setNextMessage(chatMsg.getText().toString());
}
}
@@ -504,10 +618,16 @@ public class ConversationFragment extends Fragment {
if (this.conversation == null) {
return;
}
+ String oldString = conversation.getNextMessage().trim();
if (this.pastedText == null) {
- this.chatMsg.setText(conversation.getNextMessage());
+ this.chatMsg.setText(oldString);
} else {
- chatMsg.setText(conversation.getNextMessage() + " " + pastedText);
+
+ if (oldString.isEmpty()) {
+ chatMsg.setText(pastedText);
+ } else {
+ chatMsg.setText(oldString + " " + pastedText);
+ }
pastedText = null;
}
int position = chatMsg.length();
@@ -519,10 +639,10 @@ public class ConversationFragment extends Fragment {
if (!activity.shouldPaneBeOpen()) {
activity.getSlidingPaneLayout().closePane();
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getActionBar().setHomeButtonEnabled(true);
activity.getActionBar().setTitle(
conversation.getName(useSubject));
activity.invalidateOptionsMenu();
-
}
}
if (conversation.getMode() == Conversation.MODE_MULTI) {
@@ -555,50 +675,61 @@ public class ConversationFragment extends Fragment {
}
}
- private void decryptMessage(final Message message) {
+ private void decryptMessage(Message message) {
PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
if (engine != null) {
- engine.decrypt(message, new UiCallback() {
+ engine.decrypt(message, new UiCallback<Message>() {
@Override
- public void userInputRequried(PendingIntent pi) {
+ public void userInputRequried(PendingIntent pi, Message message) {
askForPassphraseIntent = pi.getIntentSender();
pgpInfo.setVisibility(View.VISIBLE);
}
@Override
- public void success() {
+ public void success(Message message) {
activity.xmppConnectionService.databaseBackend
.updateMessage(message);
updateMessages();
}
@Override
- public void error(int error) {
+ public void error(int error, Message message) {
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
// updateMessages();
}
});
} else {
- pgpInfo.setVisibility(View.VISIBLE);
+ pgpInfo.setVisibility(View.GONE);
}
}
public void updateMessages() {
- if (getView()==null) {
+ if (getView() == null) {
return;
}
ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
for (Message message : this.conversation.getMessages()) {
if ((message.getEncryption() == Message.ENCRYPTION_PGP)
- && (message.getStatus() == Message.STATUS_RECIEVED)) {
+ && ((message.getStatus() == Message.STATUS_RECIEVED) || (message
+ .getStatus() == Message.STATUS_SEND))) {
decryptMessage(message);
break;
}
}
- this.messageList.clear();
- this.messageList.addAll(this.conversation.getMessages());
+ if (this.conversation.getMessages().size() == 0) {
+ this.messageList.clear();
+ messagesLoaded = false;
+ } else {
+ for (Message message : this.conversation.getMessages()) {
+ if (!this.messageList.contains(message)) {
+ this.messageList.add(message);
+ }
+ }
+ messagesLoaded = true;
+ updateStatusMessages();
+ }
this.messageListAdapter.notifyDataSetChanged();
if (conversation.getMode() == Conversation.MODE_SINGLE) {
if (messageList.size() >= 1) {
@@ -616,11 +747,8 @@ public class ConversationFragment extends Fragment {
}
getActivity().invalidateOptionsMenu();
updateChatMsgHint();
- int size = this.messageList.size();
- if (size >= 1)
- messagesView.setSelection(size - 1);
if (!activity.shouldPaneBeOpen()) {
- conversation.markRead();
+ activity.xmppConnectionService.markRead(conversation);
// TODO update notifications
UIHelper.updateNotification(getActivity(),
activity.getConversationList(), null, false);
@@ -629,34 +757,61 @@ public class ConversationFragment extends Fragment {
}
}
+ private void messageSent() {
+ int size = this.messageList.size();
+ if (size >= 1) {
+ messagesView.setSelection(size - 1);
+ }
+ chatMsg.setText("");
+ }
+
+ protected void updateStatusMessages() {
+ boolean addedStatusMsg = false;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ for (int i = this.messageList.size() - 1; i >= 0; --i) {
+ if (addedStatusMsg) {
+ if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
+ this.messageList.remove(i);
+ --i;
+ }
+ } else {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
+ addedStatusMsg = true;
+ } else {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
+ this.messageList.add(i + 1,
+ Message.createStatusMessage(conversation));
+ addedStatusMsg = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
protected void makeFingerprintWarning(int latestEncryption) {
final LinearLayout fingerprintWarning = (LinearLayout) getView()
.findViewById(R.id.new_fingerprint);
- if (conversation.getContact() != null) {
- Set<String> knownFingerprints = conversation.getContact()
- .getOtrFingerprints();
- if ((latestEncryption == Message.ENCRYPTION_OTR)
- && (conversation.hasValidOtrSession()
- && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
- .contains(conversation.getOtrFingerprint())))) {
- fingerprintWarning.setVisibility(View.VISIBLE);
- TextView fingerprint = (TextView) getView().findViewById(
- R.id.otr_fingerprint);
- fingerprint.setText(conversation.getOtrFingerprint());
- fingerprintWarning.setOnClickListener(new OnClickListener() {
+ Set<String> knownFingerprints = conversation.getContact()
+ .getOtrFingerprints();
+ if ((latestEncryption == Message.ENCRYPTION_OTR)
+ && (conversation.hasValidOtrSession()
+ && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
+ .contains(conversation.getOtrFingerprint())))) {
+ fingerprintWarning.setVisibility(View.VISIBLE);
+ TextView fingerprint = (TextView) getView().findViewById(
+ R.id.otr_fingerprint);
+ fingerprint.setText(conversation.getOtrFingerprint());
+ fingerprintWarning.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- AlertDialog dialog = UIHelper
- .getVerifyFingerprintDialog(
- (ConversationActivity) getActivity(),
- conversation, fingerprintWarning);
- dialog.show();
- }
- });
- } else {
- fingerprintWarning.setVisibility(View.GONE);
- }
+ @Override
+ public void onClick(View v) {
+ AlertDialog dialog = UIHelper.getVerifyFingerprintDialog(
+ (ConversationActivity) getActivity(), conversation,
+ fingerprintWarning);
+ dialog.show();
+ }
+ });
} else {
fingerprintWarning.setVisibility(View.GONE);
}
@@ -664,97 +819,128 @@ public class ConversationFragment extends Fragment {
protected void sendPlainTextMessage(Message message) {
ConversationActivity activity = (ConversationActivity) getActivity();
- activity.xmppConnectionService.sendMessage(message, null);
- chatMsg.setText("");
+ activity.xmppConnectionService.sendMessage(message);
+ messageSent();
}
protected void sendPgpMessage(final Message message) {
- activity.pendingMessage = message;
final ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
final Contact contact = message.getConversation().getContact();
if (activity.hasPgp()) {
- if (contact.getPgpKeyId() != 0) {
- xmppService.getPgpEngine().hasKey(contact,
- new UiCallback() {
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (contact.getPgpKeyId() != 0) {
+ xmppService.getPgpEngine().hasKey(contact,
+ new UiCallback<Contact>() {
- @Override
- public void userInputRequried(PendingIntent pi) {
- activity.runIntent(
- pi,
- ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
- }
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Contact contact) {
+ activity.runIntent(
+ pi,
+ ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
+ }
- @Override
- public void success() {
- activity.encryptTextMessage();
- }
+ @Override
+ public void success(Contact contact) {
+ messageSent();
+ activity.encryptTextMessage(message);
+ }
- @Override
- public void error(int error) {
-
- }
- });
+ @Override
+ public void error(int error, Contact contact) {
- } else {
- showNoPGPKeyDialog(new DialogInterface.OnClickListener() {
+ }
+ });
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- conversation
- .setNextEncryption(Message.ENCRYPTION_NONE);
- message.setEncryption(Message.ENCRYPTION_NONE);
- xmppService.sendMessage(message, null);
- chatMsg.setText("");
+ } else {
+ showNoPGPKeyDialog(false,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+ });
+ }
+ } else {
+ if (conversation.getMucOptions().pgpKeysInUse()) {
+ if (!conversation.getMucOptions().everybodyHasKeys()) {
+ Toast warning = Toast
+ .makeText(getActivity(),
+ R.string.missing_public_keys,
+ Toast.LENGTH_LONG);
+ warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ warning.show();
}
- });
+ activity.encryptTextMessage(message);
+ messageSent();
+ } else {
+ showNoPGPKeyDialog(true,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+ });
+ }
}
+ } else {
+ activity.showInstallPgpDialog();
}
}
-
- public void showNoPGPKeyDialog(DialogInterface.OnClickListener listener) {
- AlertDialog.Builder builder = new AlertDialog.Builder(
- getActivity());
- builder.setTitle(getString(R.string.no_pgp_key));
+
+ public void showNoPGPKeyDialog(boolean plural,
+ DialogInterface.OnClickListener listener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getText(R.string.contact_has_no_pgp_key));
+ if (plural) {
+ builder.setTitle(getString(R.string.no_pgp_keys));
+ builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
+ } else {
+ builder.setTitle(getString(R.string.no_pgp_key));
+ builder.setMessage(getText(R.string.contact_has_no_pgp_key));
+ }
builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.send_unencrypted),listener);
+ builder.setPositiveButton(getString(R.string.send_unencrypted),
+ listener);
builder.create().show();
}
protected void sendOtrMessage(final Message message) {
- ConversationActivity activity = (ConversationActivity) getActivity();
+ final ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
if (conversation.hasValidOtrSession()) {
- activity.xmppConnectionService.sendMessage(message, null);
- chatMsg.setText("");
+ activity.xmppConnectionService.sendMessage(message);
+ messageSent();
} else {
activity.selectPresence(message.getConversation(),
new OnPresenceSelected() {
@Override
- public void onPresenceSelected(boolean success,
- String presence) {
- if (success) {
- xmppService.sendMessage(message, presence);
- chatMsg.setText("");
- }
+ public void onPresenceSelected() {
+ message.setPresence(conversation.getNextPresence());
+ xmppService.sendMessage(message);
+ messageSent();
}
-
- @Override
- public void onSendPlainTextInstead() {
- message.setEncryption(Message.ENCRYPTION_NONE);
- xmppService.sendMessage(message, null);
- chatMsg.setText("");
- }
- }, "otr");
+ });
}
}
private static class ViewHolder {
+ protected LinearLayout message_box;
protected Button download_button;
protected ImageView image;
protected ImageView indicator;
@@ -766,7 +952,6 @@ public class ConversationFragment extends Fragment {
private class BitmapCache {
private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
- private Bitmap error = null;
public Bitmap get(String name, Contact contact, Context context) {
if (bitmaps.containsKey(name)) {
diff --git a/src/eu/siacs/conversations/ui/EditAccount.java b/src/eu/siacs/conversations/ui/EditAccount.java
index 8b1c0fa6..e1bcaeb5 100644
--- a/src/eu/siacs/conversations/ui/EditAccount.java
+++ b/src/eu/siacs/conversations/ui/EditAccount.java
@@ -7,7 +7,6 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -51,27 +50,19 @@ public class EditAccount extends DialogFragment {
final CheckBox registerAccount = (CheckBox) view
.findViewById(R.id.edit_account_register_new);
- final String okButtonDesc;
-
if (account != null) {
jidText.setText(account.getJid());
password.setText(account.getPassword());
- Log.d("xmppService","mein debugger. account != null");
if (account.isOptionSet(Account.OPTION_REGISTER)) {
registerAccount.setChecked(true);
- builder.setTitle(getString(R.string.register_account));
- okButtonDesc = "Register";
passwordConfirm.setVisibility(View.VISIBLE);
passwordConfirm.setText(account.getPassword());
} else {
registerAccount.setVisibility(View.GONE);
- builder.setTitle("Edit account");
- okButtonDesc = "Edit";
}
- } else {
- builder.setTitle("Add account");
- okButtonDesc = "Add";
}
+ builder.setTitle(R.string.account_settings);
+
registerAccount
.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@@ -79,26 +70,19 @@ public class EditAccount extends DialogFragment {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
- AlertDialog d = (AlertDialog) getDialog();
- Button positiveButton = (Button) d
- .getButton(Dialog.BUTTON_POSITIVE);
if (isChecked) {
- d.setTitle(getString(R.string.register_account));
- positiveButton.setText("Register");
passwordConfirm.setVisibility(View.VISIBLE);
confirmPwDesc.setVisibility(View.VISIBLE);
} else {
- d.setTitle("Add account");
passwordConfirm.setVisibility(View.GONE);
- positiveButton.setText("Add");
confirmPwDesc.setVisibility(View.GONE);
}
}
});
builder.setView(view);
- builder.setNeutralButton("Cancel", null);
- builder.setPositiveButton(okButtonDesc, null);
+ builder.setNeutralButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.save), null);
return builder.create();
}
@@ -114,7 +98,9 @@ public class EditAccount extends DialogFragment {
String jid = jidEdit.getText().toString();
EditText passwordEdit = (EditText) d
.findViewById(R.id.account_password);
+ EditText passwordConfirmEdit = (EditText) d.findViewById(R.id.account_password_confirm2);
String password = passwordEdit.getText().toString();
+ String passwordConfirm = passwordConfirmEdit.getText().toString();
CheckBox register = (CheckBox) d.findViewById(R.id.edit_account_register_new);
String username;
String server;
@@ -123,9 +109,15 @@ public class EditAccount extends DialogFragment {
username = parts[0];
server = parts[1];
} else {
- jidEdit.setError("Invalid Jabber ID");
+ jidEdit.setError(getString(R.string.invalid_jid));
return;
}
+ if (register.isChecked()) {
+ if (!passwordConfirm.equals(password)) {
+ passwordConfirmEdit.setError(getString(R.string.passwords_do_not_match));
+ return;
+ }
+ }
if (account != null) {
account.setPassword(password);
account.setUsername(username);
diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java
index 0b7dac58..c52916a2 100644
--- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -3,25 +3,19 @@ package eu.siacs.conversations.ui;
import java.util.ArrayList;
import java.util.List;
-import org.openintents.openpgp.OpenPgpError;
-
import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.EditAccount.EditAccountListener;
import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
import eu.siacs.conversations.xmpp.XmppConnection;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
-import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.os.SystemClock;
-import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -73,7 +67,7 @@ public class ManageAccountActivity extends XmppActivity {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setTitle("Untrusted Certificate");
+ builder.setTitle(getString(R.string.account_status_error));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
View view = (View) getLayoutInflater().inflate(R.layout.cert_warning, null);
TextView sha = (TextView) view.findViewById(R.id.sha);
@@ -91,8 +85,8 @@ public class ManageAccountActivity extends XmppActivity {
hint.setText(getString(R.string.untrusted_cert_hint,account.getServer()));
sha.setText(humanReadableSha.toString());
builder.setView(view);
- builder.setNegativeButton("Don't connect", null);
- builder.setPositiveButton("Trust certificate", new OnClickListener() {
+ builder.setNegativeButton(getString(R.string.certif_no_trust), null);
+ builder.setPositiveButton(getString(R.string.certif_trust), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -130,55 +124,55 @@ public class ManageAccountActivity extends XmppActivity {
.findViewById(R.id.account_status);
switch (account.getStatus()) {
case Account.STATUS_DISABLED:
- statusView.setText("temporarily disabled");
+ statusView.setText(getString(R.string.account_status_disabled));
statusView.setTextColor(0xFF1da9da);
break;
case Account.STATUS_ONLINE:
- statusView.setText("online");
+ statusView.setText(getString(R.string.account_status_online));
statusView.setTextColor(0xFF83b600);
break;
case Account.STATUS_CONNECTING:
- statusView.setText("connecting\u2026");
+ statusView.setText(getString(R.string.account_status_connecting));
statusView.setTextColor(0xFF1da9da);
break;
case Account.STATUS_OFFLINE:
- statusView.setText("offline");
+ statusView.setText(getString(R.string.account_status_offline));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_UNAUTHORIZED:
- statusView.setText("unauthorized");
+ statusView.setText(getString(R.string.account_status_unauthorized));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_SERVER_NOT_FOUND:
- statusView.setText("server not found");
+ statusView.setText(getString(R.string.account_status_not_found));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_NO_INTERNET:
- statusView.setText("no internet");
+ statusView.setText(getString(R.string.account_status_no_internet));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_SERVER_REQUIRES_TLS:
- statusView.setText("server requires TLS");
+ statusView.setText(getString(R.string.account_status_requires_tls));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_TLS_ERROR:
- statusView.setText("untrusted cerficate");
+ statusView.setText(getString(R.string.account_status_error));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_REGISTRATION_FAILED:
- statusView.setText("registration failed");
+ statusView.setText(getString(R.string.account_status_regis_fail));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_REGISTRATION_CONFLICT:
- statusView.setText("username already in use");
+ statusView.setText(getString(R.string.account_status_regis_conflict));
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_REGISTRATION_SUCCESSFULL:
- statusView.setText("registration completed");
+ statusView.setText(getString(R.string.account_status_regis_success));
statusView.setTextColor(0xFF83b600);
break;
case Account.STATUS_REGISTRATION_NOT_SUPPORTED:
- statusView.setText("server does not support registration");
+ statusView.setText(getString(R.string.account_status_regis_not_sup));
statusView.setTextColor(0xFFe92727);
break;
default:
@@ -261,10 +255,10 @@ public class ManageAccountActivity extends XmppActivity {
mode.finish();
} else if (item.getItemId()==R.id.mgmt_account_delete) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setTitle("Are you sure?");
+ builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage("If you delete your account your entire conversation history will be lost");
- builder.setPositiveButton("Delete", new OnClickListener() {
+ builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
+ builder.setPositiveButton(getString(R.string.delete), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -273,12 +267,14 @@ public class ManageAccountActivity extends XmppActivity {
mode.finish();
}
});
- builder.setNegativeButton("Cancel",null);
+ builder.setNegativeButton(getString(R.string.cancel),null);
builder.create().show();
} else if (item.getItemId()==R.id.mgmt_account_announce_pgp) {
if (activity.hasPgp()) {
mode.finish();
announcePgp(selectedAccountForActionMode,null);
+ } else {
+ activity.showInstallPgpDialog();
}
} else if (item.getItemId() == R.id.mgmt_otr_key) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -293,7 +289,7 @@ public class ManageAccountActivity extends XmppActivity {
noFingerprintView.setVisibility(View.GONE);
}
builder.setView(view);
- builder.setPositiveButton("Done", null);
+ builder.setPositiveButton(getString(R.string.done), null);
builder.create().show();
} else if (item.getItemId() == R.id.mgmt_account_info) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -317,36 +313,36 @@ public class ManageAccountActivity extends XmppActivity {
pcks_received.setText(""+xmpp.getReceivedStanzas());
pcks_sent.setText(""+xmpp.getSentStanzas());
if (connectionAgeHours >= 2) {
- connection.setText(connectionAgeHours+" hours");
+ connection.setText(connectionAgeHours+" " + getString(R.string.hours));
} else {
- connection.setText(connectionAge+" mins");
+ connection.setText(connectionAge+" " + getString(R.string.mins));
}
if (xmpp.hasFeatureStreamManagment()) {
if (sessionAgeHours >= 2) {
- session.setText(sessionAgeHours+" hours");
+ session.setText(sessionAgeHours+" " + getString(R.string.hours));
} else {
- session.setText(sessionAge+" mins");
+ session.setText(sessionAge+" " + getString(R.string.mins));
}
- stream.setText("Yes");
+ stream.setText(getString(R.string.yes));
} else {
- stream.setText("No");
+ stream.setText(getString(R.string.no));
session.setText(connection.getText());
}
if (xmpp.hasFeaturesCarbon()) {
- carbon.setText("Yes");
+ carbon.setText(getString(R.string.yes));
} else {
- carbon.setText("No");
+ carbon.setText(getString(R.string.no));
}
if (xmpp.hasFeatureRosterManagment()) {
- roster.setText("Yes");
+ roster.setText(getString(R.string.yes));
} else {
- roster.setText("No");
+ roster.setText(getString(R.string.no));
}
builder.setView(view);
} else {
- builder.setMessage("Account is offline");
+ builder.setMessage(getString(R.string.mgmt_account_account_offline));
}
- builder.setPositiveButton("Hide", null);
+ builder.setPositiveButton(getString(R.string.hide), null);
builder.create().show();
}
return true;
@@ -380,6 +376,7 @@ public class ManageAccountActivity extends XmppActivity {
accountListViewAdapter.notifyDataSetChanged();
if ((this.accountList.size() == 0)&&(this.firstrun)) {
getActionBar().setDisplayHomeAsUpEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
addAccount();
this.firstrun = false;
}
@@ -403,6 +400,25 @@ public class ManageAccountActivity extends XmppActivity {
return super.onOptionsItemSelected(item);
}
+ @Override
+ public boolean onNavigateUp() {
+ if (xmppConnectionService.getConversations().size() == 0) {
+ Intent contactsIntent = new Intent(this, ContactsActivity.class);
+ contactsIntent.setFlags(
+ // if activity exists in stack, pop the stack and go back to it
+ Intent.FLAG_ACTIVITY_CLEAR_TOP |
+ // otherwise, make a new task for it
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ // don't use the new activity animation; finish animation runs instead
+ Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(contactsIntent);
+ finish();
+ return true;
+ } else {
+ return super.onNavigateUp();
+ }
+ }
+
private void editAccount(Account account) {
EditAccount dialog = new EditAccount();
dialog.setAccount(account);
@@ -429,6 +445,7 @@ public class ManageAccountActivity extends XmppActivity {
public void onAccountEdited(Account account) {
xmppConnectionService.createAccount(account);
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
+ activity.getActionBar().setHomeButtonEnabled(true);
}
});
dialog.show(getFragmentManager(), "add_account");
diff --git a/src/eu/siacs/conversations/ui/MucDetailsActivity.java b/src/eu/siacs/conversations/ui/MucDetailsActivity.java
index c6807c61..ee6709b7 100644
--- a/src/eu/siacs/conversations/ui/MucDetailsActivity.java
+++ b/src/eu/siacs/conversations/ui/MucDetailsActivity.java
@@ -3,14 +3,19 @@ package eu.siacs.conversations.ui;
import java.util.ArrayList;
import java.util.List;
+import org.openintents.openpgp.util.OpenPgpUtils;
+
import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.User;
import eu.siacs.conversations.utils.UIHelper;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -19,8 +24,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
@@ -153,11 +156,11 @@ public class MucDetailsActivity extends XmppActivity {
switch (self.getAffiliation()) {
case User.AFFILIATION_ADMIN:
mRoleAffiliaton.setText(getReadableRole(self.getRole())
- + " (Admin)");
+ + " (" + getString(R.string.admin) + ")");
break;
case User.AFFILIATION_OWNER:
mRoleAffiliaton.setText(getReadableRole(self.getRole())
- + " (Owner)");
+ + " (" + getString(R.string.owner) + ")");
break;
default:
mRoleAffiliaton
@@ -167,16 +170,36 @@ public class MucDetailsActivity extends XmppActivity {
}
this.users.clear();
this.users.addAll(conversation.getMucOptions().getUsers());
- //contactsAdapter.notifyDataSetChanged();
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
membersView.removeAllViews();
- for(User contact : conversation.getMucOptions().getUsers()) {
+ for(final User contact : conversation.getMucOptions().getUsers()) {
View view = (View) inflater.inflate(R.layout.contact, null);
-
- ((TextView) view.findViewById(R.id.contact_display_name))
- .setText(contact.getName());
+ TextView displayName = (TextView) view.findViewById(R.id.contact_display_name);
+ TextView key = (TextView) view.findViewById(R.id.key);
+ displayName.setText(contact.getName());
TextView role = (TextView) view.findViewById(R.id.contact_jid);
role.setText(getReadableRole(contact.getRole()));
+ if (contact.getPgpKeyId()!=0) {
+ key.setVisibility(View.VISIBLE);
+ key.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ PgpEngine pgp = xmppConnectionService.getPgpEngine();
+ if (pgp!=null) {
+ PendingIntent intent = pgp.getIntentForKey(conversation.getAccount(), contact.getPgpKeyId());
+ if (intent!=null) {
+ try {
+ startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
+ } catch (SendIntentException e) {
+
+ }
+ }
+ }
+ }
+ });
+ key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
+ }
ImageView imageView = (ImageView) view
.findViewById(R.id.contact_photo);
imageView.setImageBitmap(UIHelper.getContactPicture(contact.getName(), 48,this.getApplicationContext(), false));
diff --git a/src/eu/siacs/conversations/ui/OnPresenceSelected.java b/src/eu/siacs/conversations/ui/OnPresenceSelected.java
index b3a995dc..1c967224 100644
--- a/src/eu/siacs/conversations/ui/OnPresenceSelected.java
+++ b/src/eu/siacs/conversations/ui/OnPresenceSelected.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.ui;
public interface OnPresenceSelected {
- public void onPresenceSelected(boolean success, String presence);
- public void onSendPlainTextInstead();
+ public void onPresenceSelected();
}
diff --git a/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java b/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java
deleted file mode 100644
index d69ce35b..00000000
--- a/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import java.util.List;
-
-import eu.siacs.conversations.entities.Contact;
-
-public interface OnRosterFetchedListener {
- public void onRosterFetched(List<Contact> roster);
-}
diff --git a/src/eu/siacs/conversations/ui/ShareWithActivity.java b/src/eu/siacs/conversations/ui/ShareWithActivity.java
index 1bc9fc46..d4d23ddf 100644
--- a/src/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -11,10 +11,13 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.utils.UIHelper;
+import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -25,37 +28,49 @@ import android.widget.LinearLayout;
import android.widget.TextView;
public class ShareWithActivity extends XmppActivity {
-
+
private LinearLayout conversations;
private LinearLayout contacts;
+ private boolean isImage = false;
- private OnClickListener click = new OnClickListener() {
+ private UiCallback<Message> attachImageCallback = new UiCallback<Message>() {
@Override
- public void onClick(View v) {
+ public void userInputRequried(PendingIntent pi, Message object) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
// TODO Auto-generated method stub
}
};
-
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.share_with);
- setTitle("Share with Conversation");
-
+ setTitle(getString(R.string.title_activity_sharewith));
+
contacts = (LinearLayout) findViewById(R.id.contacts);
conversations = (LinearLayout) findViewById(R.id.conversations);
-
+
}
-
-
+
public View createContactView(String name, String msgTxt, Bitmap bm) {
View view = (View) getLayoutInflater().inflate(R.layout.contact, null);
view.setBackgroundResource(R.drawable.greybackground);
- TextView contactName =(TextView) view.findViewById(R.id.contact_display_name);
+ TextView contactName = (TextView) view
+ .findViewById(R.id.contact_display_name);
contactName.setText(name);
TextView msg = (TextView) view.findViewById(R.id.contact_jid);
msg.setText(msgTxt);
@@ -63,75 +78,100 @@ public class ShareWithActivity extends XmppActivity {
imageView.setImageBitmap(bm);
return view;
}
-
-
-
+
@Override
void onBackendConnected() {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ this.isImage = (getIntent().getType() != null && getIntent()
+ .getType().startsWith("image/"));
+ SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(this);
boolean useSubject = preferences.getBoolean("use_subject_in_muc", true);
-
- Set<String> displayedContacts = new HashSet<String>();
+
+ Set<Contact> displayedContacts = new HashSet<Contact>();
conversations.removeAllViews();
List<Conversation> convList = xmppConnectionService.getConversations();
Collections.sort(convList, new Comparator<Conversation>() {
@Override
public int compare(Conversation lhs, Conversation rhs) {
- return (int) (rhs.getLatestMessage().getTimeSent() - lhs.getLatestMessage().getTimeSent());
+ return (int) (rhs.getLatestMessage().getTimeSent() - lhs
+ .getLatestMessage().getTimeSent());
}
});
- for(final Conversation conversation : convList) {
- View view = createContactView(conversation.getName(useSubject),
- conversation.getLatestMessage().getBody().trim(),
- UIHelper.getContactPicture(conversation, 48,
- this.getApplicationContext(), false));
- view.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- String sharedText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- switchToConversation(conversation, sharedText);
- finish();
- }
- });
- conversations.addView(view);
- if (conversation.getContact() != null) {
- displayedContacts.add(conversation.getContact().getUuid());
+ for (final Conversation conversation : convList) {
+ if (!isImage || conversation.getMode() == Conversation.MODE_SINGLE) {
+ View view = createContactView(
+ conversation.getName(useSubject),
+ conversation.getLatestMessage().getBody().trim(),
+ UIHelper.getContactPicture(conversation, 48,
+ this.getApplicationContext(), false));
+ view.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ share(conversation);
+ }
+ });
+ conversations.addView(view);
+ displayedContacts.add(conversation.getContact());
}
}
contacts.removeAllViews();
- final List<Contact> contactsList = new ArrayList<Contact>();
- for(Account account : xmppConnectionService.getAccounts()) {
- for(final Contact contact : xmppConnectionService.getRoster(account)) {
- if (!displayedContacts.contains(contact.getUuid())) {
+ List<Contact> contactsList = new ArrayList<Contact>();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ for (Contact contact : account.getRoster().getContacts()) {
+ if (!displayedContacts.contains(contact)
+ && (contact.showInRoster())) {
contactsList.add(contact);
}
}
}
-
+
Collections.sort(contactsList, new Comparator<Contact>() {
@Override
public int compare(Contact lhs, Contact rhs) {
- return lhs.getDisplayName().compareToIgnoreCase(rhs.getDisplayName());
+ return lhs.getDisplayName().compareToIgnoreCase(
+ rhs.getDisplayName());
}
});
-
- for(int i = 0; i < contactsList.size(); ++i) {
+
+ for (int i = 0; i < contactsList.size(); ++i) {
final Contact con = contactsList.get(i);
- View view = createContactView(con.getDisplayName(), con.getJid(),
- UIHelper.getContactPicture(con, 48, this.getApplicationContext(), false));
+ View view = createContactView(
+ con.getDisplayName(),
+ con.getJid(),
+ UIHelper.getContactPicture(con, 48,
+ this.getApplicationContext(), false));
view.setOnClickListener(new OnClickListener() {
-
+
@Override
public void onClick(View v) {
- String sharedText = getIntent().getStringExtra(Intent.EXTRA_TEXT);
- Conversation conversation = xmppConnectionService.findOrCreateConversation(con.getAccount(), con.getJid(), false);
- switchToConversation(conversation, sharedText);
- finish();
+ Conversation conversation = xmppConnectionService
+ .findOrCreateConversation(con.getAccount(),
+ con.getJid(), false);
+ share(conversation);
}
});
contacts.addView(view);
}
}
+
+ private void share(final Conversation conversation) {
+ String sharedText = null;
+ if (isImage) {
+ final Uri uri = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ selectPresence(conversation, new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ ShareWithActivity.this.xmppConnectionService.attachImageToConversation(conversation, uri,attachImageCallback);
+ }
+ });
+
+ } else {
+ sharedText = getIntent().getStringExtra(
+ Intent.EXTRA_TEXT);
+ }
+ switchToConversation(conversation, sharedText, true);
+ finish();
+ }
}
diff --git a/src/eu/siacs/conversations/ui/UiCallback.java b/src/eu/siacs/conversations/ui/UiCallback.java
index f9273b96..05a869f7 100644
--- a/src/eu/siacs/conversations/ui/UiCallback.java
+++ b/src/eu/siacs/conversations/ui/UiCallback.java
@@ -2,8 +2,8 @@ package eu.siacs.conversations.ui;
import android.app.PendingIntent;
-public interface UiCallback {
- public void success();
- public void error(int errorCode);
- public void userInputRequried(PendingIntent pi);
+public interface UiCallback<T> {
+ public void success(T object);
+ public void error(int errorCode, T object);
+ public void userInputRequried(PendingIntent pi, T object);
}
diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java
index dc894ad5..c95cbfec 100644
--- a/src/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/eu/siacs/conversations/ui/XmppActivity.java
@@ -1,11 +1,11 @@
package eu.siacs.conversations.ui;
-import java.nio.channels.AlreadyConnectedException;
-
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
import eu.siacs.conversations.utils.ExceptionHelper;
@@ -29,15 +29,15 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
public abstract class XmppActivity extends Activity {
-
+
public static final int REQUEST_ANNOUNCE_PGP = 0x73731;
-
+
protected final static String LOGTAG = "xmppService";
-
+
public XmppConnectionService xmppConnectionService;
public boolean xmppConnectionServiceBound = false;
protected boolean handledViewIntent = false;
-
+
protected ServiceConnection mConnection = new ServiceConnection() {
@Override
@@ -53,7 +53,7 @@ public abstract class XmppActivity extends Activity {
xmppConnectionServiceBound = false;
}
};
-
+
@Override
protected void onStart() {
super.onStart();
@@ -61,14 +61,14 @@ public abstract class XmppActivity extends Activity {
connectToBackend();
}
}
-
+
public void connectToBackend() {
Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction("ui");
startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
-
+
@Override
protected void onStop() {
super.onStop();
@@ -77,7 +77,7 @@ public abstract class XmppActivity extends Activity {
xmppConnectionServiceBound = false;
}
}
-
+
protected void hideKeyboard() {
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -85,50 +85,52 @@ public abstract class XmppActivity extends Activity {
if (focus != null) {
- inputManager.hideSoftInputFromWindow(
- focus.getWindowToken(),
+ inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
-
+
public boolean hasPgp() {
- if (xmppConnectionService.getPgpEngine()!=null) {
- return true;
- } else {
- Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.openkeychain_required));
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getText(R.string.openkeychain_required_long));
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setNeutralButton(getString(R.string.restart), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (xmppConnectionServiceBound) {
- unbindService(mConnection);
- xmppConnectionServiceBound = false;
+ return xmppConnectionService.getPgpEngine() != null;
+ }
+
+ public void showInstallPgpDialog() {
+ Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.openkeychain_required));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getText(R.string.openkeychain_required_long));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setNeutralButton(getString(R.string.restart),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (xmppConnectionServiceBound) {
+ unbindService(mConnection);
+ xmppConnectionServiceBound = false;
+ }
+ stopService(new Intent(XmppActivity.this,
+ XmppConnectionService.class));
+ finish();
}
- stopService(new Intent(XmppActivity.this, XmppConnectionService.class));
- finish();
- }
- });
- builder.setPositiveButton(getString(R.string.install), new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Uri uri = Uri.parse("market://details?id=org.sufficientlysecure.keychain");
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivity(intent);
- finish();
- }
- });
- builder.create().show();
- return false;
- }
+ });
+ builder.setPositiveButton(getString(R.string.install),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Uri uri = Uri
+ .parse("market://details?id=org.sufficientlysecure.keychain");
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ finish();
+ }
+ });
+ builder.create().show();
}
-
+
abstract void onBackendConnected();
-
+
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
@@ -140,62 +142,76 @@ public abstract class XmppActivity extends Activity {
}
return super.onOptionsItemSelected(item);
}
-
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ExceptionHelper.init(getApplicationContext());
}
-
- public void switchToConversation(Conversation conversation, String text) {
+
+ public void switchToConversation(Conversation conversation, String text,
+ boolean newTask) {
Intent viewConversationIntent = new Intent(this,
ConversationActivity.class);
viewConversationIntent.setAction(Intent.ACTION_VIEW);
viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
conversation.getUuid());
- if (text!=null) {
+ if (text != null) {
viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
}
viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
- viewConversationIntent.setFlags(viewConversationIntent.getFlags()
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (newTask) {
+ viewConversationIntent.setFlags(viewConversationIntent.getFlags()
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ } else {
+ viewConversationIntent.setFlags(viewConversationIntent.getFlags()
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ }
startActivity(viewConversationIntent);
}
-
- protected void announcePgp(final Account account, final Conversation conversation) {
- xmppConnectionService.getPgpEngine().generateSignature(account, "online", new UiCallback() {
-
- @Override
- public void userInputRequried(PendingIntent pi) {
- try {
- startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
- } catch (SendIntentException e) {
- Log.d("xmppService","coulnd start intent for pgp anncouncment");
- }
- }
-
- @Override
- public void success() {
- xmppConnectionService.databaseBackend.updateAccount(account);
- xmppConnectionService.sendPgpPresence(account, account.getPgpSignature());
- if (conversation!=null) {
- conversation.setNextEncryption(Message.ENCRYPTION_PGP);
- }
- }
-
- @Override
- public void error(int error) {
- displayErrorDialog(error);
- }
- });
+
+ protected void announcePgp(Account account, final Conversation conversation) {
+ xmppConnectionService.getPgpEngine().generateSignature(account,
+ "online", new UiCallback<Account>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Account account) {
+ try {
+ startIntentSenderForResult(pi.getIntentSender(),
+ REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
+ } catch (SendIntentException e) {
+ Log.d("xmppService",
+ "coulnd start intent for pgp anncouncment");
+ }
+ }
+
+ @Override
+ public void success(Account account) {
+ xmppConnectionService.databaseBackend
+ .updateAccount(account);
+ xmppConnectionService.sendPresence(account);
+ if (conversation != null) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_PGP);
+ }
+ }
+
+ @Override
+ public void error(int error, Account account) {
+ displayErrorDialog(error);
+ }
+ });
}
-
+
protected void displayErrorDialog(final int errorCode) {
runOnUiThread(new Runnable() {
-
+
@Override
public void run() {
- AlertDialog.Builder builder = new AlertDialog.Builder(XmppActivity.this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ XmppActivity.this);
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setTitle(getString(R.string.error));
builder.setMessage(errorCode);
@@ -203,6 +219,78 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
});
-
+
+ }
+
+ protected 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 = conversation.getAccount();
+ Contact contact = account.getRoster().getContact(jid);
+ xmppConnectionService.createContact(contact);
+ }
+ });
+ builder.create().show();
+ }
+
+ public void selectPresence(final Conversation conversation,
+ final OnPresenceSelected listener) {
+ Contact contact = conversation.getContact();
+ if (contact == null) {
+ showAddToRosterDialog(conversation);
+ } else {
+ Presences presences = contact.getPresences();
+ if (presences.size() == 0) {
+ conversation.setNextPresence(null);
+ listener.onPresenceSelected();
+ } else if (presences.size() == 1) {
+ String presence = (String) presences.asStringArray()[0];
+ conversation.setNextPresence(presence);
+ listener.onPresenceSelected();
+ } else {
+ final StringBuilder presence = new StringBuilder();
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.choose_presence));
+ final String[] presencesArray = presences.asStringArray();
+ int preselectedPresence = 0;
+ for (int i = 0; i < presencesArray.length; ++i) {
+ if (presencesArray[i].equals(contact.lastseen.presence)) {
+ preselectedPresence = i;
+ break;
+ }
+ }
+ presence.append(presencesArray[preselectedPresence]);
+ builder.setSingleChoiceItems(presencesArray,
+ preselectedPresence,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ presence.delete(0, presence.length());
+ presence.append(presencesArray[which]);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ conversation.setNextPresence(presence.toString());
+ listener.onPresenceSelected();
+ }
+ });
+ builder.create().show();
+ }
+ }
}
}
diff --git a/src/eu/siacs/conversations/utils/CryptoHelper.java b/src/eu/siacs/conversations/utils/CryptoHelper.java
index 98dbea4d..a70b419e 100644
--- a/src/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/eu/siacs/conversations/utils/CryptoHelper.java
@@ -5,15 +5,14 @@ import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.Random;
+import java.util.Arrays;
import eu.siacs.conversations.entities.Account;
-
import android.util.Base64;
-import android.util.Log;
public class CryptoHelper {
- final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+ public static final String FILETRANSFER = "?FILETRANSFERv1:";
+ final protected static char[] hexArray = "0123456789abcdef".toCharArray();
final protected static char[] vowels = "aeiou".toCharArray();
final protected static char[] consonants = "bcdfghjklmnpqrstvwxyz"
.toCharArray();
@@ -25,7 +24,15 @@ public class CryptoHelper {
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
- return new String(hexChars).toLowerCase();
+ return new String(hexChars);
+ }
+
+ public static byte[] hexToBytes(String hexString) {
+ byte[] array = new BigInteger(hexString, 16).toByteArray();
+ if (array[0] == 0) {
+ array = Arrays.copyOfRange(array, 1, array.length);
+ }
+ return array;
}
public static String saslPlain(String username, String password) {
@@ -41,9 +48,8 @@ public class CryptoHelper {
return result;
}
- public static String saslDigestMd5(Account account, String challenge) {
+ public static String saslDigestMd5(Account account, String challenge, SecureRandom random) {
try {
- Random random = new SecureRandom();
String[] challengeParts = new String(Base64.decode(challenge,
Base64.DEFAULT)).split(",");
String nonce = "";
@@ -85,12 +91,11 @@ public class CryptoHelper {
}
}
- public static String randomMucName() {
- Random random = new SecureRandom();
+ public static String randomMucName(SecureRandom random) {
return randomWord(3, random) + "." + randomWord(7, random);
}
- protected static String randomWord(int lenght, Random random) {
+ protected static String randomWord(int lenght, SecureRandom random) {
StringBuilder builder = new StringBuilder(lenght);
for (int i = 0; i < lenght; ++i) {
if (i % 2 == 0) {
diff --git a/src/eu/siacs/conversations/utils/DNSHelper.java b/src/eu/siacs/conversations/utils/DNSHelper.java
index 94dd3b51..002e124f 100644
--- a/src/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/eu/siacs/conversations/utils/DNSHelper.java
@@ -52,7 +52,7 @@ public class DNSHelper {
DNSMessage message =
client.query(
qname,
- TYPE.ANY,
+ TYPE.SRV,
CLASS.IN,
dnsServer.getHostAddress());
diff --git a/src/eu/siacs/conversations/utils/ExceptionHelper.java b/src/eu/siacs/conversations/utils/ExceptionHelper.java
index f9cd375e..05b16240 100644
--- a/src/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -70,7 +70,7 @@ public class ExceptionHelper {
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);
+ service.sendMessage(message);
}
});
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
deleted file mode 100644
index 52b18f66..00000000
--- a/src/eu/siacs/conversations/utils/MessageParser.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import java.util.List;
-
-import net.java.otr4j.session.Session;
-import net.java.otr4j.session.SessionStatus;
-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.stanzas.MessagePacket;
-
-public class MessageParser {
-
- protected static final String LOGTAG = "xmppService";
-
- public static Message parsePlainTextChat(MessagePacket packet, Account account, XmppConnectionService service) {
- String[] fromParts = packet.getFrom().split("/");
- Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
- String body = packet.getBody();
- return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED);
- }
-
- public static Message parsePgpChat(String pgpBody, MessagePacket packet, Account account, XmppConnectionService service) {
- String[] fromParts = packet.getFrom().split("/");
- Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
- return new Message(conversation, packet.getFrom(), pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED);
- }
-
- public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) {
- boolean properlyAddressed = (packet.getTo().split("/").length == 2) || (account.countPresences() == 1);
- String[] fromParts = packet.getFrom().split("/");
- Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
- String body = packet.getBody();
- if (!conversation.hasValidOtrSession()) {
- if (properlyAddressed) {
- Log.d("xmppService","starting new otr session with "+packet.getFrom()+" because no valid otr session has been found");
- conversation.startOtrSession(service.getApplicationContext(), fromParts[1],false);
- } else {
- Log.d("xmppService",account.getJid()+": ignoring otr session with "+fromParts[0]);
- return null;
- }
- } else {
- String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
- if (!foreignPresence.equals(fromParts[1])) {
- conversation.resetOtrSession();
- if (properlyAddressed) {
- Log.d("xmppService","replacing otr session with "+packet.getFrom());
- conversation.startOtrSession(service.getApplicationContext(), fromParts[1],false);
- } else {
- return null;
- }
- }
- }
- try {
- Session otrSession = conversation.getOtrSession();
- SessionStatus before = otrSession
- .getSessionStatus();
- body = otrSession.transformReceiving(body);
- SessionStatus after = otrSession.getSessionStatus();
- if ((before != after)
- && (after == SessionStatus.ENCRYPTED)) {
- Log.d(LOGTAG, "otr session etablished");
- List<Message> messages = conversation
- .getMessages();
- for (int i = 0; i < messages.size(); ++i) {
- Message msg = messages.get(i);
- if ((msg.getStatus() == Message.STATUS_UNSEND)
- && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
- MessagePacket outPacket = service.prepareMessagePacket(
- account, msg, otrSession);
- msg.setStatus(Message.STATUS_SEND);
- service.databaseBackend.updateMessage(msg);
- account.getXmppConnection()
- .sendMessagePacket(outPacket);
- }
- }
- service.updateUi(conversation, false);
- } else if ((before != after) && (after == SessionStatus.FINISHED)) {
- conversation.resetOtrSession();
- Log.d(LOGTAG,"otr session stoped");
- }
- //isEmpty is a work around for some weird clients which send emtpty strings over otr
- if ((body == null)||(body.isEmpty())) {
- return null;
- }
- return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_OTR,Message.STATUS_RECIEVED);
- } catch (Exception e) {
- conversation.resetOtrSession();
- return null;
- }
- }
-
- public static Message parseGroupchat(MessagePacket packet, Account account, XmppConnectionService service) {
- int status;
- String[] fromParts = packet.getFrom().split("/");
- Conversation conversation = service.findOrCreateConversation(account, fromParts[0],true);
- if (packet.hasChild("subject")) {
- conversation.getMucOptions().setSubject(packet.findChild("subject").getContent());
- service.updateUi(conversation, false);
- return null;
- }
- if ((fromParts.length == 1)) {
- return null;
- }
- String counterPart = fromParts[1];
- if (counterPart.equals(conversation.getMucOptions().getNick())) {
- status = Message.STATUS_SEND;
- } else {
- status = Message.STATUS_RECIEVED;
- }
- return new Message(conversation, counterPart, packet.getBody(), Message.ENCRYPTION_NONE, status);
- }
-
- public static Message parseCarbonMessage(MessagePacket packet,
- Account account, XmppConnectionService service) {
- int status;
- String fullJid;
- Element forwarded;
- if (packet.hasChild("received")) {
- forwarded = packet.findChild("received").findChild(
- "forwarded");
- status = Message.STATUS_RECIEVED;
- } else if (packet.hasChild("sent")) {
- forwarded = packet.findChild("sent").findChild(
- "forwarded");
- status = Message.STATUS_SEND;
- } else {
- return null;
- }
- if (forwarded==null) {
- return null;
- }
- Element message = forwarded.findChild("message");
- if ((message == null) || (!message.hasChild("body")))
- return null; // either malformed or boring
- if (status == Message.STATUS_RECIEVED) {
- fullJid = message.getAttribute("from");
- } else {
- fullJid = message.getAttribute("to");
- }
- String[] parts = fullJid.split("/");
- Conversation conversation = service.findOrCreateConversation(account, parts[0],false);
- return new Message(conversation,fullJid, message.findChild("body").getContent(), Message.ENCRYPTION_NONE,status);
- }
-
- 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) {
- for(Element child : packet.getChildren()) {
- if (child.getName().equals("x")&&child.getAttribute("xmlns").equals("jabber:x:encrypted")) {
- return child.getContent();
- }
- }
- return null;
- }
-}
diff --git a/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java b/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
index fa8cea04..9a689768 100644
--- a/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
+++ b/src/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
@@ -1,9 +1,9 @@
package eu.siacs.conversations.utils;
-import java.util.Hashtable;
+import java.util.List;
import android.os.Bundle;
public interface OnPhoneContactsLoadedListener {
- public void onPhoneContactsLoaded(Hashtable<String, Bundle> phoneContacts);
+ public void onPhoneContactsLoaded(List<Bundle> phoneContacts);
}
diff --git a/src/eu/siacs/conversations/utils/PRNGFixes.java b/src/eu/siacs/conversations/utils/PRNGFixes.java
new file mode 100644
index 00000000..cf28ff30
--- /dev/null
+++ b/src/eu/siacs/conversations/utils/PRNGFixes.java
@@ -0,0 +1,326 @@
+package eu.siacs.conversations.utils;
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PRNGFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PRNGFixes() {}
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PRNGFixes.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java
index 6355e378..773312bb 100644
--- a/src/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/eu/siacs/conversations/utils/PhoneHelper.java
@@ -1,8 +1,8 @@
package eu.siacs.conversations.utils;
-import java.util.Hashtable;
+import java.util.ArrayList;
+import java.util.List;
-import android.app.Activity;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
@@ -10,21 +10,15 @@ import android.content.Loader.OnLoadCompleteListener;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Looper;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Profile;
-import android.provider.MediaStore;
public class PhoneHelper {
public static void loadPhoneContacts(Context context,
final OnPhoneContactsLoadedListener listener) {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- final Looper mLooper = Looper.myLooper();
- final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
-
+ final List<Bundle> phoneContacts = new ArrayList<Bundle>();
+
final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.PHOTO_THUMBNAIL_URI,
@@ -58,15 +52,14 @@ public class PhoneHelper {
.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
contact.putString("lookup", cursor.getString(cursor
.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
- phoneContacts.put(
- cursor.getString(cursor
- .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
- contact);
+
+ contact.putString("jid",cursor.getString(cursor
+ .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
+ phoneContacts.add(contact);
}
if (listener != null) {
listener.onPhoneContactsLoaded(phoneContacts);
}
- mLooper.quit();
}
});
mCursorLoader.startLoading();
diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java
index 8baa3c25..183d94f3 100644
--- a/src/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/eu/siacs/conversations/utils/UIHelper.java
@@ -1,18 +1,19 @@
package eu.siacs.conversations.utils;
import java.io.FileNotFoundException;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.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;
@@ -27,7 +28,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -38,11 +38,11 @@ import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Contacts;
import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.InboxStyle;
import android.support.v4.app.TaskStackBuilder;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
import android.text.Html;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
@@ -53,23 +53,56 @@ public class UIHelper {
private static final int BG_COLOR = 0xFF181818;
private static final int FG_COLOR = 0xFFE5E5E5;
private static final int TRANSPARENT = 0x00000000;
+ private static final int DATE_NO_YEAR_FLAGS = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
- public static String readableTimeDifference(long time) {
+ public static String readableTimeDifference(Context context, long time) {
if (time == 0) {
- return "just now";
+ return context.getString(R.string.just_now);
}
Date date = new Date(time);
long difference = (System.currentTimeMillis() - time) / 1000;
if (difference < 60) {
- return "just now";
- } else if (difference < 60 * 10) {
- return difference / 60 + " min ago";
- } else if (difference < 60 * 60 * 24) {
- SimpleDateFormat sdf = new SimpleDateFormat("HH:mm",Locale.US);
- return sdf.format(date);
+ return context.getString(R.string.just_now);
+ } else if (difference < 60 * 2) {
+ return context.getString(R.string.minute_ago);
+ } else if (difference < 60 * 15) {
+ return context.getString(R.string.minutes_ago,Math.round(difference/60.0));
+ } else if (today(date)) {
+ java.text.DateFormat df = DateFormat.getTimeFormat(context);
+ return df.format(date);
} else {
- SimpleDateFormat sdf = new SimpleDateFormat("MM/dd",Locale.US);
- return sdf.format(date);
+ return DateUtils.formatDateTime(context, date.getTime(), DATE_NO_YEAR_FLAGS);
+ }
+ }
+
+ private static boolean today(Date date) {
+ Calendar cal1 = Calendar.getInstance();
+ Calendar cal2 = Calendar.getInstance();
+ cal1.setTime(date);
+ cal2.setTimeInMillis(System.currentTimeMillis());
+ return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
+ cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
+ }
+
+ public static String lastseen(Context context, long time) {
+ if (time==0) {
+ return context.getString(R.string.never_seen);
+ }
+ long difference = (System.currentTimeMillis() - time) / 1000;
+ if (difference < 60) {
+ return context.getString(R.string.last_seen_now);
+ } else if (difference < 60 * 2) {
+ return context.getString(R.string.last_seen_min);
+ } else if (difference < 60 * 60) {
+ return context.getString(R.string.last_seen_mins,Math.round(difference/60.0));
+ } else if (difference < 60 * 60 * 2) {
+ return context.getString(R.string.last_seen_hour);
+ } else if (difference < 60 * 60 * 24) {
+ return context.getString(R.string.last_seen_hours,Math.round(difference/(60.0*60.0)));
+ } else if (difference < 60 * 60 * 48) {
+ return context.getString(R.string.last_seen_day);
+ } else {
+ return context.getString(R.string.last_seen_days,Math.round(difference/(60.0*60.0*24.0)));
}
}
@@ -81,8 +114,24 @@ public class UIHelper {
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;
+ return holoColors[Math.abs(name.toLowerCase(Locale.getDefault()).hashCode()) % holoColors.length];
+ }
+
+ private static void drawTile(Canvas canvas, String letter, int tileColor, int textColor, int left, int top, int right, int bottom) {
+ Paint tilePaint = new Paint(), textPaint = new Paint();
+ tilePaint.setColor(tileColor);
+ textPaint.setColor(textColor);
+ textPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+ textPaint.setTextSize((float) ((right - left) * 0.8));
+ Rect rect = new Rect();
+
+ canvas.drawRect(new Rect(left, top, right, bottom), tilePaint);
+ textPaint.getTextBounds(letter, 0, 1, rect);
+ float width = textPaint.measureText(letter);
+ canvas.drawText(letter,
+ (right+left)/2 - width/2,
+ (top+bottom)/2 + rect.height()/2,
+ textPaint);
}
private static Bitmap getUnknownContactPicture(String[] names, int size, int bgColor, int fgColor) {
@@ -110,135 +159,46 @@ public class UIHelper {
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;
+
+ bitmap.eraseColor(bgColor);
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);
+ drawTile(canvas, letters[0], colors[0], fgColor,
+ 0, 0, size, size);
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);
+ drawTile(canvas, letters[0], colors[0], fgColor,
+ 0, 0, size/2 - 1, size);
+ drawTile(canvas, letters[1], colors[1], fgColor,
+ size/2 + 1, 0, size, size);
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);
+ drawTile(canvas, letters[0], colors[0], fgColor,
+ 0, 0, size/2 - 1, size);
+ drawTile(canvas, letters[1], colors[1], fgColor,
+ size/2 + 1, 0, size, size/2 - 1);
+ drawTile(canvas, letters[2], colors[2], fgColor,
+ size/2 + 1, size/2 + 1, size, size);
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);
+ drawTile(canvas, letters[0], colors[0], fgColor,
+ 0, 0, size/2 - 1, size/2 - 1);
+ drawTile(canvas, letters[1], colors[1], fgColor,
+ 0, size/2 + 1, size/2 - 1, size);
+ drawTile(canvas, letters[2], colors[2], fgColor,
+ size/2 + 1, 0, size, size/2 - 1);
+ drawTile(canvas, letters[3], colors[3], fgColor,
+ size/2 + 1, size/2 + 1, size, size);
break;
}
- return bitmap;
- }
- private static Bitmap getUnknownContactPicture(String[] names, int size) {
- return getUnknownContactPicture(names, size, UIHelper.BG_COLOR, UIHelper.FG_COLOR);
+ return bitmap;
}
-
+
private static Bitmap getMucContactPicture(Conversation conversation, int size, int bgColor, int fgColor) {
List<User> members = conversation.getMucOptions().getUsers();
if (members.size() == 0) {
@@ -255,13 +215,8 @@ public class UIHelper {
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) ?
@@ -273,10 +228,6 @@ public class UIHelper {
}
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 getContactPicture(contact.getDisplayName(), dpSize,
@@ -340,6 +291,15 @@ public class UIHelper {
mNotificationManager.notify(1111, notification);
}
+ private static Pattern generateNickHighlightPattern(String nick) {
+ // We expect a word boundary, i.e. space or start of string, followed by the
+ // nick (matched in case-insensitive manner), followed by optional
+ // punctuation (for example "bob: i disagree" or "how are you alice?"),
+ // followed by another word boundary.
+ return Pattern.compile("\\b"+nick+"\\p{Punct}?\\b",
+ Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
+ }
+
public static void updateNotification(Context context,
List<Conversation> conversations, Conversation currentCon, boolean notify) {
NotificationManager mNotificationManager = (NotificationManager) context
@@ -360,7 +320,9 @@ public class UIHelper {
if ((currentCon != null) &&(currentCon.getMode() == Conversation.MODE_MULTI)&&(!alwaysNotify)) {
String nick = currentCon.getMucOptions().getNick();
- notify = currentCon.getLatestMessage().getBody().contains(nick);
+ Pattern highlight = generateNickHighlightPattern(nick);
+ Matcher m = highlight.matcher(currentCon.getLatestMessage().getBody());
+ notify = m.find();
}
List<Conversation> unread = new ArrayList<Conversation>();
@@ -412,7 +374,7 @@ public class UIHelper {
.bigText(bigText.toString()));
} else {
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
- style.setBigContentTitle(unread.size() + " unread Conversations");
+ style.setBigContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations));
StringBuilder names = new StringBuilder();
for (int i = 0; i < unread.size(); ++i) {
targetUuid = unread.get(i).getUuid();
@@ -424,7 +386,7 @@ public class UIHelper {
style.addLine(Html.fromHtml("<b>" + unread.get(i).getName(useSubject)
+ "</b> " + unread.get(i).getLatestMessage().getReadableBody(context)));
}
- mBuilder.setContentTitle(unread.size() + " unread Conversations");
+ mBuilder.setContentTitle(unread.size() + " " + context.getString(R.string.unread_conversations));
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
}
@@ -470,11 +432,13 @@ public class UIHelper {
private static boolean wasHighlighted(Conversation conversation) {
List<Message> messages = conversation.getMessages();
String nick = conversation.getMucOptions().getNick();
+ Pattern highlight = generateNickHighlightPattern(nick);
for(int i = messages.size() - 1; i >= 0; --i) {
if (messages.get(i).isRead()) {
break;
} else {
- if (messages.get(i).getBody().contains(nick)) {
+ Matcher m = highlight.matcher(messages.get(i).getBody());
+ if (m.find()) {
return true;
}
}
@@ -518,7 +482,7 @@ public class UIHelper {
public void onClick(DialogInterface dialog, int which) {
contact.addOtrFingerprint(conversation.getOtrFingerprint());
msg.setVisibility(View.GONE);
- activity.xmppConnectionService.updateContact(contact);
+ //activity.xmppConnectionService.updateContact(contact);
}
});
builder.setView(view);
diff --git a/src/eu/siacs/conversations/utils/Validator.java b/src/eu/siacs/conversations/utils/Validator.java
index fce953ae..51d8b25c 100644
--- a/src/eu/siacs/conversations/utils/Validator.java
+++ b/src/eu/siacs/conversations/utils/Validator.java
@@ -5,7 +5,7 @@ import java.util.regex.Pattern;
public class Validator {
public static final Pattern VALID_JID =
- Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
+ Pattern.compile("\\b^[A-Z0-9._%+-]+@([A-Z0-9.-]+\\.)?\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}\\b$|^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public static boolean isValidJid(String jid) {
Matcher matcher = VALID_JID.matcher(jid);
diff --git a/src/eu/siacs/conversations/xml/Element.java b/src/eu/siacs/conversations/xml/Element.java
index ce1d10ce..f8e070f7 100644
--- a/src/eu/siacs/conversations/xml/Element.java
+++ b/src/eu/siacs/conversations/xml/Element.java
@@ -9,24 +9,24 @@ public class Element {
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
protected String content;
protected List<Element> children = new ArrayList<Element>();
-
+
public Element(String name) {
this.name = name;
}
-
+
public Element addChild(Element child) {
this.content = null;
children.add(child);
return child;
}
-
+
public Element addChild(String name) {
this.content = null;
Element child = new Element(name);
children.add(child);
return child;
}
-
+
public Element addChild(String name, String xmlns) {
this.content = null;
Element child = new Element(name);
@@ -34,64 +34,65 @@ public class Element {
children.add(child);
return child;
}
-
+
public Element setContent(String content) {
this.content = content;
this.children.clear();
return this;
}
-
+
public Element findChild(String name) {
- for(Element child : this.children) {
+ for (Element child : this.children) {
if (child.getName().equals(name)) {
return child;
}
}
return null;
}
-
+
public Element findChild(String name, String xmlns) {
- for(Element child : this.children) {
- if (child.getName().equals(name)&&(child.getAttribute("xmlns").equals(xmlns))) {
+ for (Element child : this.children) {
+ if (child.getName().equals(name)
+ && (child.getAttribute("xmlns").equals(xmlns))) {
return child;
}
}
return null;
}
-
+
public boolean hasChild(String name) {
return findChild(name) != null;
}
-
+
public boolean hasChild(String name, String xmlns) {
return findChild(name, xmlns) != null;
}
-
-
-
+
public List<Element> getChildren() {
return this.children;
}
-
+
public Element setChildren(List<Element> children) {
this.children = children;
return this;
}
-
+
public String getContent() {
return content;
}
-
+
public Element setAttribute(String name, String value) {
- this.attributes.put(name, value);
+ if (name != null && value != null) {
+ this.attributes.put(name, value);
+ }
return this;
}
-
+
public Element setAttributes(Hashtable<String, String> attributes) {
this.attributes = attributes;
return this;
}
-
+
public String getAttribute(String name) {
if (this.attributes.containsKey(name)) {
return this.attributes.get(name);
@@ -99,14 +100,14 @@ public class Element {
return null;
}
}
-
+
public Hashtable<String, String> getAttributes() {
return this.attributes;
}
-
+
public String toString() {
StringBuilder elementOutput = new StringBuilder();
- if ((content==null)&&(children.size() == 0)) {
+ if ((content == null) && (children.size() == 0)) {
Tag emptyTag = Tag.empty(name);
emptyTag.setAtttributes(this.attributes);
elementOutput.append(emptyTag.toString());
@@ -114,10 +115,10 @@ public class Element {
Tag startTag = Tag.start(name);
startTag.setAtttributes(this.attributes);
elementOutput.append(startTag);
- if (content!=null) {
+ if (content != null) {
elementOutput.append(encodeEntities(content));
} else {
- for(Element child : children) {
+ for (Element child : children) {
elementOutput.append(child.toString());
}
}
@@ -130,13 +131,13 @@ public class Element {
public String getName() {
return name;
}
-
+
private String encodeEntities(String content) {
- content = content.replace("&","&amp;");
- content = content.replace("<","&lt;");
- content = content.replace(">","&gt;");
- content = content.replace("\"","&quot;");
- content = content.replace("'","&apos;");
+ content = content.replace("&", "&amp;");
+ content = content.replace("<", "&lt;");
+ content = content.replace(">", "&gt;");
+ content = content.replace("\"", "&quot;");
+ content = content.replace("'", "&apos;");
return content;
}
diff --git a/src/eu/siacs/conversations/xml/XmlReader.java b/src/eu/siacs/conversations/xml/XmlReader.java
index 0a82a5d8..b4b1647e 100644
--- a/src/eu/siacs/conversations/xml/XmlReader.java
+++ b/src/eu/siacs/conversations/xml/XmlReader.java
@@ -85,10 +85,8 @@ public class XmlReader {
public Element readElement(Tag currentTag) throws XmlPullParserException, IOException {
Element element = new Element(currentTag.getName());
- //Log.d(LOGTAG,"trying to read element "+element.getName());
element.setAttributes(currentTag.getAttributes());
Tag nextTag = this.readTag();
- //Log.d(LOGTAG,"next Tag is: "+nextTag.toString());
if(nextTag.isNo()) {
element.setContent(nextTag.getName());
nextTag = this.readTag();
@@ -96,15 +94,16 @@ public class XmlReader {
if (nextTag == null) {
throw new IOException("unterupted mid tag");
}
- //Log.d(LOGTAG,"reading till the end of "+element.getName());
while(!nextTag.isEnd(element.getName())) {
if (!nextTag.isNo()) {
Element child = this.readElement(nextTag);
element.addChild(child);
}
nextTag = this.readTag();
+ if (nextTag == null) {
+ throw new IOException("unterupted mid tag");
+ }
}
- //Log.d(LOGTAG,"return with element"+element);
return element;
}
}
diff --git a/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java b/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java
new file mode 100644
index 00000000..849e8e76
--- /dev/null
+++ b/src/eu/siacs/conversations/xmpp/OnContactStatusChanged.java
@@ -0,0 +1,7 @@
+package eu.siacs.conversations.xmpp;
+
+import eu.siacs.conversations.entities.Contact;
+
+public interface OnContactStatusChanged {
+ public void onContactStatusChanged(Contact contact, boolean online);
+}
diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java
index adb27ec8..2447b49b 100644
--- a/src/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -17,11 +17,9 @@ 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;
@@ -31,7 +29,6 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.os.Bundle;
@@ -40,6 +37,7 @@ import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.util.Log;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.DNSHelper;
import eu.siacs.conversations.utils.zlib.ZLibOutputStream;
@@ -66,7 +64,7 @@ public class XmppConnection implements Runnable {
private WakeLock wakeLock;
- private SecureRandom random = new SecureRandom();
+ private SecureRandom mRandom;
private Socket socket;
private XmlReader tagReader;
@@ -76,20 +74,20 @@ public class XmppConnection implements Runnable {
private boolean shouldAuthenticate = true;
private Element streamFeatures;
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;
-
+
private int stanzasReceived = 0;
private int stanzasSent = 0;
-
+
public long lastPaketReceived = 0;
public long lastPingSent = 0;
public long lastConnect = 0;
public long lastSessionStarted = 0;
+ private int attempt = 0;
+
private static final int PACKET_IQ = 0;
private static final int PACKET_MESSAGE = 1;
private static final int PACKET_PRESENCE = 2;
@@ -103,17 +101,25 @@ public class XmppConnection implements Runnable {
private OnTLSExceptionReceived tlsListener = null;
private OnBindListener bindListener = null;
- public XmppConnection(Account account, PowerManager pm) {
+ public XmppConnection(Account account, XmppConnectionService service) {
+ this.mRandom = service.getRNG();
this.account = account;
- this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,account.getJid());
+ this.wakeLock = service.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ account.getJid());
tagWriter = new TagWriter();
}
protected void changeStatus(int nextStatus) {
if (account.getStatus() != nextStatus) {
- if ((nextStatus == Account.STATUS_OFFLINE)&&(account.getStatus() != Account.STATUS_CONNECTING)&&(account.getStatus() != Account.STATUS_ONLINE)) {
+ if ((nextStatus == Account.STATUS_OFFLINE)
+ && (account.getStatus() != Account.STATUS_CONNECTING)
+ && (account.getStatus() != Account.STATUS_ONLINE)
+ && (account.getStatus() != Account.STATUS_DISABLED)) {
return;
}
+ if (nextStatus == Account.STATUS_ONLINE) {
+ this.attempt = 0;
+ }
account.setStatus(nextStatus);
if (statusListener != null) {
statusListener.onStatusChanged(account);
@@ -122,17 +128,19 @@ public class XmppConnection implements Runnable {
}
protected void connect() {
- Log.d(LOGTAG,account.getJid()+ ": connecting");
+ Log.d(LOGTAG, account.getJid() + ": connecting");
lastConnect = SystemClock.elapsedRealtime();
+ this.attempt++;
try {
- shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER);
+ shouldAuthenticate = shouldBind = !account
+ .isOptionSet(Account.OPTION_REGISTER);
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
packetCallbacks.clear();
this.changeStatus(Account.STATUS_CONNECTING);
Bundle namePort = DNSHelper.getSRVRecord(account.getServer());
if ("timeout".equals(namePort.getString("error"))) {
- Log.d(LOGTAG,account.getJid()+": dns timeout");
+ Log.d(LOGTAG, account.getJid() + ": dns timeout");
this.changeStatus(Account.STATUS_OFFLINE);
return;
}
@@ -142,12 +150,12 @@ public class XmppConnection implements Runnable {
if (srvRecordServer != null) {
if (srvIpServer != null) {
Log.d(LOGTAG, account.getJid() + ": using values from dns "
- + srvRecordServer + "[" + srvIpServer + "]:"
- + srvRecordPort);
+ + srvRecordServer + "[" + srvIpServer + "]:"
+ + srvRecordPort);
socket = new Socket(srvIpServer, srvRecordPort);
} else {
Log.d(LOGTAG, account.getJid() + ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort);
+ + srvRecordServer + ":" + srvRecordPort);
socket = new Socket(srvRecordServer, srvRecordPort);
}
} else {
@@ -227,56 +235,60 @@ public class XmppConnection implements Runnable {
} else if (nextTag.isStart("compressed")) {
switchOverToZLib(nextTag);
} else if (nextTag.isStart("success")) {
- Log.d(LOGTAG, account.getJid()
- + ": logged in");
+ Log.d(LOGTAG, account.getJid() + ": logged in");
tagReader.readTag();
tagReader.reset();
sendStartStream();
processStream(tagReader.readTag());
break;
} else if (nextTag.isStart("failure")) {
- Element failure = tagReader.readElement(nextTag);
+ tagReader.readElement(nextTag);
changeStatus(Account.STATUS_UNAUTHORIZED);
} else if (nextTag.isStart("challenge")) {
String challange = tagReader.readElement(nextTag).getContent();
Element response = new Element("response");
- response.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
- response.setContent(CryptoHelper.saslDigestMd5(account, challange));
+ response.setAttribute("xmlns",
+ "urn:ietf:params:xml:ns:xmpp-sasl");
+ response.setContent(CryptoHelper.saslDigestMd5(account,
+ challange,mRandom));
tagWriter.writeElement(response);
} else if (nextTag.isStart("enabled")) {
this.stanzasSent = 0;
Element enabled = tagReader.readElement(nextTag);
if ("true".equals(enabled.getAttribute("resume"))) {
this.streamId = enabled.getAttribute("id");
- Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled (resumable)");
+ Log.d(LOGTAG, account.getJid() + ": stream managment("
+ + smVersion + ") enabled (resumable)");
} else {
- Log.d(LOGTAG,account.getJid()+": stream managment("+smVersion+") enabled");
+ Log.d(LOGTAG, account.getJid() + ": stream managment("
+ + smVersion + ") enabled");
}
this.lastSessionStarted = SystemClock.elapsedRealtime();
this.stanzasReceived = 0;
RequestPacket r = new RequestPacket(smVersion);
tagWriter.writeStanzaAsync(r);
} else if (nextTag.isStart("resumed")) {
+ lastPaketReceived = SystemClock.elapsedRealtime();
+ Log.d(LOGTAG, account.getJid() + ": session resumed");
tagReader.readElement(nextTag);
sendPing();
changeStatus(Account.STATUS_ONLINE);
- Log.d(LOGTAG,account.getJid()+": session resumed");
} else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag);
- AckPacket ack = new AckPacket(this.stanzasReceived,smVersion);
- //Log.d(LOGTAG,ack.toString());
+ AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
+ // Log.d(LOGTAG,ack.toString());
tagWriter.writeStanzaAsync(ack);
} else if (nextTag.isStart("a")) {
Element ack = tagReader.readElement(nextTag);
lastPaketReceived = SystemClock.elapsedRealtime();
int serverSequence = Integer.parseInt(ack.getAttribute("h"));
- if (serverSequence>this.stanzasSent) {
+ if (serverSequence > this.stanzasSent) {
this.stanzasSent = serverSequence;
}
- //Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
+ // Log.d(LOGTAG,"server ack"+ack.toString()+" ("+this.stanzasSent+")");
} else if (nextTag.isStart("failed")) {
tagReader.readElement(nextTag);
- Log.d(LOGTAG,account.getJid()+": resumption failed");
+ Log.d(LOGTAG, account.getJid() + ": resumption failed");
streamId = null;
if (account.getStatus() != Account.STATUS_ONLINE) {
sendBindRequest();
@@ -319,16 +331,23 @@ public class XmppConnection implements Runnable {
}
element.setAttributes(currentTag.getAttributes());
Tag nextTag = tagReader.readTag();
+ if (nextTag==null) {
+ throw new IOException("interrupted mid tag");
+ }
while (!nextTag.isEnd(element.getName())) {
if (!nextTag.isNo()) {
Element child = tagReader.readElement(nextTag);
- if ((packetType == PACKET_IQ)&&("jingle".equals(child.getName()))) {
+ if ((packetType == PACKET_IQ)
+ && ("jingle".equals(child.getName()))) {
element = new JinglePacket();
element.setAttributes(currentTag.getAttributes());
}
element.addChild(child);
}
nextTag = tagReader.readTag();
+ if (nextTag==null) {
+ throw new IOException("interrupted mid tag");
+ }
}
++stanzasReceived;
lastPaketReceived = SystemClock.elapsedRealtime();
@@ -338,14 +357,15 @@ public class XmppConnection implements Runnable {
private void processIq(Tag currentTag) throws XmlPullParserException,
IOException {
IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
-
+
if (packet.getId() == null) {
- return; //an iq packet without id is definitely invalid
+ return; // an iq packet without id is definitely invalid
}
-
+
if (packet instanceof JinglePacket) {
- if (this.jingleListener !=null) {
- this.jingleListener.onJinglePacketReceived(account, (JinglePacket) packet);
+ if (this.jingleListener != null) {
+ this.jingleListener.onJinglePacketReceived(account,
+ (JinglePacket) packet);
}
} else {
if (packetCallbacks.containsKey(packet.getId())) {
@@ -353,7 +373,7 @@ public class XmppConnection implements Runnable {
((OnIqPacketReceived) packetCallbacks.get(packet.getId()))
.onIqPacketReceived(account, packet);
}
-
+
packetCallbacks.remove(packet.getId());
} else if (this.unregisteredIqListener != null) {
this.unregisteredIqListener.onIqPacketReceived(account, packet);
@@ -400,15 +420,18 @@ public class XmppConnection implements Runnable {
tagWriter.writeElement(compress);
}
- private void switchOverToZLib(Tag currentTag) throws XmlPullParserException,
- IOException, NoSuchAlgorithmException {
+ private void switchOverToZLib(Tag currentTag)
+ throws XmlPullParserException, IOException,
+ NoSuchAlgorithmException {
tagReader.readTag(); // read tag close
- tagWriter.setOutputStream(new ZLibOutputStream(tagWriter.getOutputStream()));
- tagReader.setInputStream(new ZLibInputStream(tagReader.getInputStream()));
+ tagWriter.setOutputStream(new ZLibOutputStream(tagWriter
+ .getOutputStream()));
+ tagReader
+ .setInputStream(new ZLibInputStream(tagReader.getInputStream()));
sendStartStream();
- Log.d(LOGTAG,account.getJid()+": compression enabled");
+ Log.d(LOGTAG, account.getJid() + ": compression enabled");
processStream(tagReader.readTag());
}
@@ -420,17 +443,14 @@ public class XmppConnection implements Runnable {
private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
IOException {
- Tag nextTag = tagReader.readTag(); // should be proceed end tag
+ tagReader.readTag();
try {
SSLContext sc = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- // Initialise the TMF as you normally would, for example:
- // tmf.in
try {
tmf.init((KeyStore) null);
} catch (KeyStoreException e1) {
- // TODO Auto-generated catch block
e1.printStackTrace();
}
@@ -454,13 +474,15 @@ public class XmppConnection implements Runnable {
if (e.getCause() instanceof CertPathValidatorException) {
String sha;
try {
- MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ MessageDigest sha1 = MessageDigest
+ .getInstance("SHA1");
sha1.update(chain[0].getEncoded());
sha = CryptoHelper.bytesToHex(sha1.digest());
if (!sha.equals(account.getSSLFingerprint())) {
changeStatus(Account.STATUS_TLS_ERROR);
- if (tlsListener!=null) {
- tlsListener.onTLSExceptionReceived(sha,account);
+ if (tlsListener != null) {
+ tlsListener.onTLSExceptionReceived(sha,
+ account);
}
throw new CertificateException();
}
@@ -483,12 +505,12 @@ public class XmppConnection implements Runnable {
sc.init(null, wrappedTrustManagers, null);
SSLSocketFactory factory = sc.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
- socket.getInetAddress().getHostAddress(), socket.getPort(),
- true);
+ socket.getInetAddress().getHostAddress(), socket.getPort(),
+ true);
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
- Log.d(LOGTAG,account.getJid()+": TLS connection established");
+ Log.d(LOGTAG, account.getJid() + ": TLS connection established");
processStream(tagReader.readTag());
sslSocket.close();
} catch (NoSuchAlgorithmException e1) {
@@ -509,7 +531,7 @@ public class XmppConnection implements Runnable {
auth.setContent(saslString);
tagWriter.writeElement(auth);
}
-
+
private void sendSaslAuthDigestMd5() throws IOException {
Element auth = new Element("auth");
auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
@@ -525,41 +547,47 @@ public class XmppConnection implements Runnable {
sendStartTLS();
} else if (compressionAvailable()) {
sendCompressionZlib();
- } else if (this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
- sendRegistryRequest();
- } else if (!this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) {
+ } else if (this.streamFeatures.hasChild("register")
+ && (account.isOptionSet(Account.OPTION_REGISTER))) {
+ sendRegistryRequest();
+ } else if (!this.streamFeatures.hasChild("register")
+ && (account.isOptionSet(Account.OPTION_REGISTER))) {
changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED);
disconnect(true);
} else if (this.streamFeatures.hasChild("mechanisms")
&& shouldAuthenticate) {
- List<String> mechanisms = extractMechanisms( streamFeatures.findChild("mechanisms"));
+ List<String> mechanisms = extractMechanisms(streamFeatures
+ .findChild("mechanisms"));
if (mechanisms.contains("PLAIN")) {
sendSaslAuthPlain();
} else if (mechanisms.contains("DIGEST-MD5")) {
sendSaslAuthDigestMd5();
}
- } else if (this.streamFeatures.hasChild("sm","urn:xmpp:sm:"+smVersion) && streamId != null) {
- ResumePacket resume = new ResumePacket(this.streamId,stanzasReceived,smVersion);
+ } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
+ + smVersion)
+ && streamId != null) {
+ ResumePacket resume = new ResumePacket(this.streamId,
+ stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
sendBindRequest();
- if (this.streamFeatures.hasChild("session")) {
- Log.d(LOGTAG,account.getJid()+": sending deprecated session");
- IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
- startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); //setContent("")
- this.sendIqPacket(startSession, null);
- }
}
}
private boolean compressionAvailable() {
- if (!this.streamFeatures.hasChild("compression", "http://jabber.org/features/compress")) return false;
- if (!ZLibOutputStream.SUPPORTED) return false;
- if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) return false;
+ if (!this.streamFeatures.hasChild("compression",
+ "http://jabber.org/features/compress"))
+ return false;
+ if (!ZLibOutputStream.SUPPORTED)
+ return false;
+ if (!account.isOptionSet(Account.OPTION_USECOMPRESSION))
+ return false;
- Element compression = this.streamFeatures.findChild("compression", "http://jabber.org/features/compress");
+ Element compression = this.streamFeatures.findChild("compression",
+ "http://jabber.org/features/compress");
for (Element child : compression.getChildren()) {
- if (!"method".equals(child.getName())) continue;
+ if (!"method".equals(child.getName()))
+ continue;
if ("zlib".equalsIgnoreCase(child.getContent())) {
return true;
@@ -569,8 +597,9 @@ public class XmppConnection implements Runnable {
}
private List<String> extractMechanisms(Element stream) {
- ArrayList<String> mechanisms = new ArrayList<String>(stream.getChildren().size());
- for(Element child : stream.getChildren()) {
+ ArrayList<String> mechanisms = new ArrayList<String>(stream
+ .getChildren().size());
+ for (Element child : stream.getChildren()) {
mechanisms.add(child.getContent());
}
return mechanisms;
@@ -581,28 +610,35 @@ public class XmppConnection implements Runnable {
register.query("jabber:iq:register");
register.setTo(account.getServer());
sendIqPacket(register, new OnIqPacketReceived() {
-
+
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
Element instructions = packet.query().findChild("instructions");
- if (packet.query().hasChild("username")&&(packet.query().hasChild("password"))) {
+ if (packet.query().hasChild("username")
+ && (packet.query().hasChild("password"))) {
IqPacket register = new IqPacket(IqPacket.TYPE_SET);
- Element username = new Element("username").setContent(account.getUsername());
- Element password = new Element("password").setContent(account.getPassword());
+ Element username = new Element("username")
+ .setContent(account.getUsername());
+ Element password = new Element("password")
+ .setContent(account.getPassword());
register.query("jabber:iq:register").addChild(username);
register.query().addChild(password);
sendIqPacket(register, new OnIqPacketReceived() {
-
+
@Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType()==IqPacket.TYPE_RESULT) {
- account.setOption(Account.OPTION_REGISTER, false);
+ public void onIqPacketReceived(Account account,
+ IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE_RESULT) {
+ account.setOption(Account.OPTION_REGISTER,
+ false);
changeStatus(Account.STATUS_REGISTRATION_SUCCESSFULL);
- } else if (packet.hasChild("error")&&(packet.findChild("error").hasChild("conflict"))){
+ } else if (packet.hasChild("error")
+ && (packet.findChild("error")
+ .hasChild("conflict"))) {
changeStatus(Account.STATUS_REGISTRATION_CONFLICT);
} else {
changeStatus(Account.STATUS_REGISTRATION_FAILED);
- Log.d(LOGTAG,packet.toString());
+ Log.d(LOGTAG, packet.toString());
}
disconnect(true);
}
@@ -610,54 +646,49 @@ public class XmppConnection implements Runnable {
} else {
changeStatus(Account.STATUS_REGISTRATION_FAILED);
disconnect(true);
- Log.d(LOGTAG,account.getJid()+": could not register. instructions are"+instructions.getContent());
+ Log.d(LOGTAG, account.getJid()
+ + ": could not register. instructions are"
+ + instructions.getContent());
}
}
});
}
- private void sendInitialPresence() {
- PresencePacket packet = new PresencePacket();
- packet.setAttribute("from", account.getFullJid());
- if (account.getKeys().has("pgp_signature")) {
- try {
- String signature = account.getKeys().getString("pgp_signature");
- packet.addChild("status").setContent("online");
- packet.addChild("x","jabber:x:signed").setContent(signature);
- } catch (JSONException e) {
- //
- }
- }
- this.sendPresencePacket(packet);
- }
-
private void sendBindRequest() throws IOException {
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
- iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind").addChild("resource").setContent(account.getResource());
- this.sendIqPacket(iq, new OnIqPacketReceived() {
+ iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
+ .addChild("resource").setContent(account.getResource());
+ this.sendUnboundIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
String resource = packet.findChild("bind").findChild("jid")
.getContent().split("/")[1];
account.setResource(resource);
- if (streamFeatures.hasChild("sm","urn:xmpp:sm:3")) {
+ if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) {
smVersion = 3;
EnablePacket enable = new EnablePacket(smVersion);
tagWriter.writeStanzaAsync(enable);
- } else if (streamFeatures.hasChild("sm","urn:xmpp:sm:2")) {
+ } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) {
smVersion = 2;
EnablePacket enable = new EnablePacket(smVersion);
tagWriter.writeStanzaAsync(enable);
}
- sendInitialPresence();
sendServiceDiscoveryInfo(account.getServer());
sendServiceDiscoveryItems(account.getServer());
- if (bindListener !=null) {
+ if (bindListener != null) {
bindListener.onBind(account);
}
+
changeStatus(Account.STATUS_ONLINE);
}
});
+ if (this.streamFeatures.hasChild("session")) {
+ Log.d(LOGTAG, account.getJid() + ": sending deprecated session");
+ IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
+ startSession.addChild("session",
+ "urn:ietf:params:xml:ns:xmpp-session");
+ this.sendUnboundIqPacket(startSession, null);
+ }
}
private void sendServiceDiscoveryInfo(final String server) {
@@ -672,25 +703,24 @@ public class XmppConnection implements Runnable {
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"));
+ features.add(elements.get(i).getAttribute("var"));
}
}
disco.put(server, features);
-
+
if (account.getServer().equals(server)) {
enableAdvancedStreamFeatures();
}
}
});
}
-
+
private void enableAdvancedStreamFeatures() {
if (hasFeaturesCarbon()) {
sendEnableCarbons();
}
}
-
+
private void sendServiceDiscoveryItems(final String server) {
IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
iq.setTo(server);
@@ -702,8 +732,7 @@ public class XmppConnection implements Runnable {
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");
+ String jid = elements.get(i).getAttribute("jid");
sendServiceDiscoveryInfo(jid);
}
}
@@ -713,7 +742,7 @@ public class XmppConnection implements Runnable {
private void sendEnableCarbons() {
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
- iq.addChild("enable","urn:xmpp:carbons:2");
+ iq.addChild("enable", "urn:xmpp:carbons:2");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
@@ -745,11 +774,11 @@ public class XmppConnection implements Runnable {
}
private String nextRandomId() {
- return new BigInteger(50, random).toString(32);
+ return new BigInteger(50, mRandom).toString(32);
}
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
- if (packet.getId()==null) {
+ if (packet.getId() == null) {
String id = nextRandomId();
packet.setAttribute("id", id);
}
@@ -757,6 +786,14 @@ public class XmppConnection implements Runnable {
this.sendPacket(packet, callback);
}
+ public void sendUnboundIqPacket(IqPacket packet, OnIqPacketReceived callback) {
+ if (packet.getId() == null) {
+ String id = nextRandomId();
+ packet.setAttribute("id", id);
+ }
+ this.sendPacket(packet, callback);
+ }
+
public void sendMessagePacket(MessagePacket packet) {
this.sendPacket(packet, null);
}
@@ -774,26 +811,27 @@ public class XmppConnection implements Runnable {
OnPresencePacketReceived callback) {
this.sendPacket(packet, callback);
}
-
- private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) {
+
+ private synchronized void sendPacket(final AbstractStanza packet,
+ PacketReceived callback) {
// TODO dont increment stanza count if packet = request packet or ack;
++stanzasSent;
tagWriter.writeStanzaAsync(packet);
if (callback != null) {
- if (packet.getId()==null) {
+ if (packet.getId() == null) {
packet.setId(nextRandomId());
}
packetCallbacks.put(packet.getId(), callback);
}
}
-
+
public void sendPing() {
if (streamFeatures.hasChild("sm")) {
tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
} else {
IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
iq.setFrom(account.getFullJid());
- iq.addChild("ping","urn:xmpp:ping");
+ iq.addChild("ping", "urn:xmpp:ping");
this.sendIqPacket(iq, null);
}
}
@@ -812,82 +850,95 @@ public class XmppConnection implements Runnable {
OnPresencePacketReceived listener) {
this.presenceListener = listener;
}
-
- public void setOnJinglePacketReceivedListener(OnJinglePacketReceived listener) {
+
+ public void setOnJinglePacketReceivedListener(
+ OnJinglePacketReceived listener) {
this.jingleListener = listener;
}
public void setOnStatusChangedListener(OnStatusChanged listener) {
this.statusListener = listener;
}
-
- public void setOnTLSExceptionReceivedListener(OnTLSExceptionReceived listener) {
+
+ public void setOnTLSExceptionReceivedListener(
+ OnTLSExceptionReceived listener) {
this.tlsListener = listener;
}
-
+
public void setOnBindListener(OnBindListener listener) {
this.bindListener = listener;
}
public void disconnect(boolean force) {
changeStatus(Account.STATUS_OFFLINE);
- Log.d(LOGTAG,"disconnecting");
+ Log.d(LOGTAG, "disconnecting");
try {
- if (force) {
+ if (force) {
socket.close();
return;
- }
- if (tagWriter.isActive()) {
- tagWriter.finish();
- while(!tagWriter.finished()) {
- //Log.d(LOGTAG,"not yet finished");
- Thread.sleep(100);
}
- tagWriter.writeTag(Tag.end("stream:stream"));
- }
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (tagWriter.isActive()) {
+ tagWriter.finish();
+ try {
+ while (!tagWriter.finished()) {
+ Log.d(LOGTAG, "not yet finished");
+ Thread.sleep(100);
+ }
+ tagWriter.writeTag(Tag.end("stream:stream"));
+ } catch (IOException e) {
+ Log.d(LOGTAG, "io exception during disconnect");
+ } catch (InterruptedException e) {
+ Log.d(LOGTAG, "interrupted");
+ }
+ }
+ }
+ }).start();
} catch (IOException e) {
- Log.d(LOGTAG,"io exception during disconnect");
- } catch (InterruptedException e) {
- Log.d(LOGTAG,"interupted while waiting for disconnect");
+ Log.d(LOGTAG, "io exception during disconnect");
}
}
-
+
public boolean hasFeatureRosterManagment() {
- if (this.streamFeatures==null) {
+ if (this.streamFeatures == null) {
return false;
} else {
return this.streamFeatures.hasChild("ver");
}
}
-
+
public boolean hasFeatureStreamManagment() {
- if (this.streamFeatures==null) {
+ if (this.streamFeatures == null) {
return false;
} else {
return this.streamFeatures.hasChild("sm");
}
}
-
+
public boolean hasFeaturesCarbon() {
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();
- }
+ 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)&&pairs.getValue().size()==1) {
+ return pairs.getKey();
+ }
+ it.remove();
+ }
return null;
}
@@ -898,7 +949,7 @@ public class XmppConnection implements Runnable {
public int getReceivedStanzas() {
return this.stanzasReceived;
}
-
+
public int getSentStanzas() {
return this.stanzasSent;
}
@@ -906,13 +957,14 @@ public class XmppConnection implements Runnable {
public String getMucServer() {
return findDiscoItemByFeature("http://jabber.org/protocol/muc");
}
-
- public boolean hasPendingSubscription(String jid) {
- return this.pendingSubscriptions.contains(jid);
+
+ public int getTimeToNextAttempt() {
+ int interval = (int) (25 * Math.pow(1.5, attempt));
+ int secondsSinceLast = (int) ((SystemClock.elapsedRealtime() - this.lastConnect) / 1000);
+ return interval - secondsSinceLast;
}
-
- public void addPendingSubscription(String jid) {
- Log.d(LOGTAG,"adding "+jid+" to pending subscriptions");
- this.pendingSubscriptions.add(jid);
+
+ public int getAttempt() {
+ return this.attempt;
}
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index 8f88688a..85110c38 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -9,11 +9,11 @@ import java.util.Map.Entry;
import android.graphics.BitmapFactory;
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.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
@@ -24,7 +24,7 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class JingleConnection {
private final String[] extensions = {"webp","jpeg","jpg","png"};
- private final String[] cryptoExtensions = {"pgp","gpg"};
+ private final String[] cryptoExtensions = {"pgp","gpg","otr"};
private JingleConnectionManager mJingleConnectionManager;
private XmppConnectionService mXmppConnectionService;
@@ -244,6 +244,7 @@ public class JingleConnection {
Element fileNameElement = fileOffer.findChild("name");
if (fileNameElement!=null) {
boolean supportedFile = false;
+ Log.d("xmppService","file offer: "+fileNameElement.getContent());
String[] filename = fileNameElement.getContent().toLowerCase().split("\\.");
if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) {
supportedFile = true;
@@ -251,7 +252,12 @@ public class JingleConnection {
if (filename.length == 3) {
if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) {
supportedFile = true;
- this.message.setEncryption(Message.ENCRYPTION_PGP);
+ if (filename[filename.length - 1].equals("otr")) {
+ Log.d("xmppService","receiving otr file");
+ this.message.setEncryption(Message.ENCRYPTION_OTR);
+ } else {
+ this.message.setEncryption(Message.ENCRYPTION_PGP);
+ }
}
}
}
@@ -269,6 +275,9 @@ public class JingleConnection {
this.mXmppConnectionService.updateUi(conversation, true);
}
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ this.file.setKey(conversation.getSymmetricKey());
+ }
this.file.setExpectedSize(size);
} else {
this.sendCancel();
@@ -287,7 +296,14 @@ public class JingleConnection {
if (message.getType() == Message.TYPE_IMAGE) {
content.setTransportId(this.transportId);
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
- content.setFileOffer(this.file);
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ Conversation conversation = this.message.getConversation();
+ this.mXmppConnectionService.renewSymmetricKey(conversation);
+ content.setFileOffer(this.file, true);
+ this.file.setKey(conversation.getSymmetricKey());
+ } else {
+ content.setFileOffer(this.file,false);
+ }
this.transportId = this.mJingleConnectionManager.nextRandomId();
content.setTransportId(this.transportId);
content.socks5transport().setChildren(getCandidatesAsElements());
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
index 21cbd716..3672351b 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java
@@ -1,6 +1,12 @@
package eu.siacs.conversations.xmpp.jingle;
import java.io.File;
+import java.security.Key;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import eu.siacs.conversations.utils.CryptoHelper;
+import android.util.Log;
public class JingleFile extends File {
@@ -8,6 +14,7 @@ public class JingleFile extends File {
private long expectedSize = 0;
private String sha1sum;
+ private Key aeskey;
public JingleFile(String path) {
super(path);
@@ -18,7 +25,11 @@ public class JingleFile extends File {
}
public long getExpectedSize() {
- return this.expectedSize;
+ if (this.aeskey!=null) {
+ return (this.expectedSize/16 + 1) * 16;
+ } else {
+ return this.expectedSize;
+ }
}
public void setExpectedSize(long size) {
@@ -32,4 +43,23 @@ public class JingleFile extends File {
public void setSha1Sum(String sum) {
this.sha1sum = sum;
}
+
+ public void setKey(byte[] key) {
+ if (key.length>=32) {
+ byte[] secretKey = new byte[32];
+ System.arraycopy(key, 0, secretKey, 0, 32);
+ this.aeskey = new SecretKeySpec(secretKey, "AES");
+ } else if (key.length>=16) {
+ byte[] secretKey = new byte[16];
+ System.arraycopy(key, 0, secretKey, 0, 16);
+ this.aeskey = new SecretKeySpec(secretKey, "AES");
+ } else {
+ Log.d("xmppService","weird key");
+ }
+ Log.d("xmppService","using aes key "+CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
+ }
+
+ public Key getKey() {
+ return this.aeskey;
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
index 8326fa39..a46e4a47 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -8,12 +8,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.util.Base64;
-import android.util.Log;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.PacketReceived;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class JingleInbandTransport extends JingleTransport {
@@ -39,7 +37,6 @@ public class JingleInbandTransport extends JingleTransport {
private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- Log.d("xmppService", "on ack received");
if (packet.getType() == IqPacket.TYPE_RESULT) {
sendNextBlock();
}
@@ -82,7 +79,6 @@ public class JingleInbandTransport extends JingleTransport {
public void receive(JingleFile file, OnFileTransmitted callback) {
this.onFileTransmitted = callback;
this.file = file;
- Log.d("xmppService", "receiving file over ibb");
try {
this.digest = MessageDigest.getInstance("SHA-1");
digest.reset();
@@ -101,7 +97,6 @@ public class JingleInbandTransport extends JingleTransport {
public void send(JingleFile file, OnFileTransmitted callback) {
this.onFileTransmitted = callback;
this.file = file;
- Log.d("xmppService", "sending file over ibb");
try {
this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset();
@@ -151,10 +146,8 @@ public class JingleInbandTransport extends JingleTransport {
this.fileOutputStream.write(buffer);
this.digest.update(buffer);
- Log.d("xmppService", "remaining file size:" + this.remainingSize);
if (this.remainingSize <= 0) {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
- Log.d("xmppService","file name: "+file.getAbsolutePath());
fileOutputStream.flush();
fileOutputStream.close();
this.onFileTransmitted.onFileTransmitted(file);
@@ -179,7 +172,7 @@ public class JingleInbandTransport extends JingleTransport {
this.account.getXmppConnection().sendIqPacket(
packet.generateRespone(IqPacket.TYPE_RESULT), null);
} else {
- Log.d("xmppServic","couldnt deliver payload "+packet.toString());
+ //TODO some sort of exception
}
}
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index ea5d7285..4fef0388 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
@@ -6,17 +6,15 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.ObjectInputStream.GetField;
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;
+import eu.siacs.conversations.utils.CryptoHelper;
public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate;
@@ -94,17 +92,20 @@ public class JingleSocks5Transport extends JingleTransport {
@Override
public void run() {
- FileInputStream fileInputStream = null;
+ InputStream fileInputStream = null;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- fileInputStream = new FileInputStream(file);
+ fileInputStream = getInputStream(file);
int count;
+ long txBytes = 0;
byte[] buffer = new byte[8192];
while ((count = fileInputStream.read(buffer)) > 0) {
+ txBytes += count;
outputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
}
+ Log.d("xmppService","txBytes="+txBytes);
outputStream.flush();
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
if (callback!=null) {
@@ -145,28 +146,26 @@ public class JingleSocks5Transport extends JingleTransport {
inputStream.skip(45);
file.getParentFile().mkdirs();
file.createNewFile();
- FileOutputStream fileOutputStream = new FileOutputStream(file);
+ OutputStream fileOutputStream = getOutputStream(file);
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
int count = buffer.length;
+ long rxBytes = 0;
while(remainingSize > 0) {
- Log.d("xmppService","remaning size:"+remainingSize);
- if (remainingSize<=count) {
- count = (int) remainingSize;
- }
- count = inputStream.read(buffer, 0, count);
+ count = inputStream.read(buffer);
if (count==-1) {
- Log.d("xmppService","end of stream");
+ Log.d("xmppService","read end");
} else {
+ rxBytes += count;
fileOutputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
remainingSize-=count;
}
}
+ Log.d("xmppService","rx bytes="+rxBytes);
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
@@ -194,9 +193,8 @@ public class JingleSocks5Transport extends JingleTransport {
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());
+
}
}
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
index 6e9482a9..5db4b715 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java
@@ -1,9 +1,77 @@
package eu.siacs.conversations.xmpp.jingle;
-import eu.siacs.conversations.xml.Element;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.CipherInputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+
+import android.util.Log;
public abstract class JingleTransport {
public abstract void connect(final OnTransportConnected callback);
public abstract void receive(final JingleFile file, final OnFileTransmitted callback);
public abstract void send(final JingleFile file, final OnFileTransmitted callback);
+ private byte[] iv = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0xf};
+
+ protected InputStream getInputStream(JingleFile file) throws FileNotFoundException {
+ if (file.getKey() == null) {
+ return new FileInputStream(file);
+ } else {
+ try {
+ IvParameterSpec ips = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, file.getKey(),ips);
+ Log.d("xmppService","opening encrypted input stream");
+ return new CipherInputStream(new FileInputStream(file), cipher);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d("xmppService","no such algo: "+e.getMessage());
+ return null;
+ } catch (NoSuchPaddingException e) {
+ Log.d("xmppService","no such padding: "+e.getMessage());
+ return null;
+ } catch (InvalidKeyException e) {
+ Log.d("xmppService","invalid key: "+e.getMessage());
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.d("xmppService","invavid iv:"+e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ protected OutputStream getOutputStream(JingleFile file) throws FileNotFoundException {
+ if (file.getKey() == null) {
+ return new FileOutputStream(file);
+ } else {
+ try {
+ IvParameterSpec ips = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, file.getKey(),ips);
+ Log.d("xmppService","opening encrypted output stream");
+ return new CipherOutputStream(new FileOutputStream(file), cipher);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d("xmppService","no such algo: "+e.getMessage());
+ return null;
+ } catch (NoSuchPaddingException e) {
+ Log.d("xmppService","no such padding: "+e.getMessage());
+ return null;
+ } catch (InvalidKeyException e) {
+ Log.d("xmppService","invalid key: "+e.getMessage());
+ return null;
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.d("xmppService","invavid iv:"+e.getMessage());
+ return null;
+ }
+ }
+ }
}
diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
index abede91a..494ff0d6 100644
--- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
+++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java
@@ -25,12 +25,16 @@ public class Content extends Element {
this.transportId = sid;
}
- public void setFileOffer(JingleFile actualFile) {
+ public void setFileOffer(JingleFile actualFile, boolean otr) {
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());
+ if (otr) {
+ file.addChild("name").setContent(actualFile.getName()+".otr");
+ } else {
+ file.addChild("name").setContent(actualFile.getName());
+ }
}
public Element getFileOffer() {
diff --git a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
index 48e5c0ec..1d4e44d1 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
+++ b/src/eu/siacs/conversations/xmpp/stanzas/IqPacket.java
@@ -69,7 +69,6 @@ public class IqPacket extends AbstractStanza {
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/MessagePacket.java b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
index 941bda4f..64a9edc3 100644
--- a/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
+++ b/src/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java
@@ -37,6 +37,10 @@ public class MessagePacket extends AbstractStanza {
case TYPE_GROUPCHAT:
this.setAttribute("type", "groupchat");
break;
+ case TYPE_UNKNOWN:
+ break;
+ case TYPE_NORMAL:
+ break;
default:
this.setAttribute("type","chat");
break;