diff options
author | iNPUTmice <daniel@gultsch.de> | 2014-10-30 11:20:35 +0100 |
---|---|---|
committer | iNPUTmice <daniel@gultsch.de> | 2014-10-30 11:20:35 +0100 |
commit | f2c1a80059af89caeb299a5cc02c401da6b4b138 (patch) | |
tree | c868409a94abdee41313e4509c8625701bfdd4ff /src/eu | |
parent | 15c05dc3c3dfe7eee82dac2f180e3505b503fe81 (diff) | |
parent | 7b4f3637db8c99d84fe3c825d583bfc0fa91fada (diff) |
Merge branch 'development'
Diffstat (limited to '')
52 files changed, 2517 insertions, 1457 deletions
diff --git a/src/eu/siacs/conversations/Config.java b/src/eu/siacs/conversations/Config.java index a11e1763..7dd5a799 100644 --- a/src/eu/siacs/conversations/Config.java +++ b/src/eu/siacs/conversations/Config.java @@ -10,7 +10,8 @@ public final class Config { public static final int PING_MIN_INTERVAL = 30; public static final int PING_TIMEOUT = 10; public static final int CONNECT_TIMEOUT = 90; - public static final int CARBON_GRACE_PERIOD = 120; + public static final int CARBON_GRACE_PERIOD = 60; + public static final int MINI_GRACE_PERIOD = 750; public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; diff --git a/src/eu/siacs/conversations/crypto/PgpEngine.java b/src/eu/siacs/conversations/crypto/PgpEngine.java index e7058a68..9a2b4a11 100644 --- a/src/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/eu/siacs/conversations/crypto/PgpEngine.java @@ -8,25 +8,24 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URL; -import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; -import eu.siacs.conversations.Config; 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.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; -import eu.siacs.conversations.xmpp.jingle.JingleFile; import android.app.PendingIntent; import android.content.Intent; import android.graphics.BitmapFactory; -import android.util.Log; +import android.net.Uri; public class PgpEngine { private OpenPgpApi api; @@ -39,7 +38,6 @@ public class PgpEngine { public void decrypt(final Message message, final UiCallback<Message> callback) { - Log.d(Config.LOGTAG, "decrypting message " + message.getUuid()); Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message @@ -60,6 +58,10 @@ public class PgpEngine { if (message.getEncryption() == Message.ENCRYPTION_PGP) { message.setBody(os.toString()); message.setEncryption(Message.ENCRYPTION_DECRYPTED); + if (message.trusted() && message.bodyContainsDownloadable()) { + mXmppConnectionService.getHttpConnectionManager() + .createNewConnection(message); + } callback.success(message); } } catch (IOException e) { @@ -74,9 +76,6 @@ public class PgpEngine { message); return; case OpenPgpApi.RESULT_CODE_ERROR: - OpenPgpError error = result - .getParcelableExtra(OpenPgpApi.RESULT_ERROR); - Log.d(Config.LOGTAG, error.getMessage()); callback.error(R.string.openpgp_error, message); return; default: @@ -86,10 +85,10 @@ 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 DownloadableFile inputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, false); + final DownloadableFile outputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, true); outputFile.createNewFile(); InputStream is = new FileInputStream(inputFile); OutputStream os = new FileOutputStream(outputFile); @@ -100,19 +99,32 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: + URL url = message.getImageParams().url; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile( outputFile.getAbsolutePath(), options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; - message.setBody(Long.toString(outputFile.getSize()) - + ',' + imageWidth + ',' + imageHeight); + if (url == null) { + message.setBody(Long.toString(outputFile + .getSize()) + + '|' + + imageWidth + + '|' + + imageHeight); + } else { + message.setBody(url.toString() + "|" + + Long.toString(outputFile.getSize()) + + '|' + imageWidth + '|' + imageHeight); + } message.setEncryption(Message.ENCRYPTION_DECRYPTED); PgpEngine.this.mXmppConnectionService .updateMessage(message); - PgpEngine.this.mXmppConnectionService - .updateConversationUi(); + inputFile.delete(); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(outputFile)); + mXmppConnectionService.sendBroadcast(intent); callback.success(message); return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -197,10 +209,10 @@ public class PgpEngine { }); } else if (message.getType() == Message.TYPE_IMAGE) { try { - JingleFile inputFile = this.mXmppConnectionService - .getFileBackend().getJingleFile(message, true); - JingleFile outputFile = this.mXmppConnectionService - .getFileBackend().getJingleFile(message, false); + DownloadableFile inputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, true); + DownloadableFile outputFile = this.mXmppConnectionService + .getFileBackend().getFile(message, false); outputFile.createNewFile(); InputStream is = new FileInputStream(inputFile); OutputStream os = new FileOutputStream(outputFile); @@ -226,9 +238,11 @@ public class PgpEngine { } }); } catch (FileNotFoundException e) { - Log.d(Config.LOGTAG, "file not found: " + e.getMessage()); + callback.error(R.string.openpgp_error, message); + return; } catch (IOException e) { - Log.d(Config.LOGTAG, "io exception during file encrypt"); + callback.error(R.string.openpgp_error, message); + return; } } } @@ -272,11 +286,6 @@ public class PgpEngine { case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: return 0; case OpenPgpApi.RESULT_CODE_ERROR: - Log.d(Config.LOGTAG, - "openpgp error: " - + ((OpenPgpError) result - .getParcelableExtra(OpenPgpApi.RESULT_ERROR)) - .getMessage()); return 0; } return 0; diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index eacd172c..80a9d62f 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -11,16 +11,14 @@ import net.java.otr4j.crypto.OtrCryptoException; import org.json.JSONException; import org.json.JSONObject; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OtrEngine; -import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.XmppConnection; import android.content.ContentValues; -import android.content.Context; import android.database.Cursor; -import android.graphics.Bitmap; +import android.os.SystemClock; public class Account extends AbstractEntity { @@ -64,16 +62,14 @@ public class Account extends AbstractEntity { protected boolean online = false; - transient OtrEngine otrEngine = null; - transient XmppConnection xmppConnection = null; - transient protected Presences presences = new Presences(); - + private OtrEngine otrEngine = null; + private XmppConnection xmppConnection = null; + private Presences presences = new Presences(); + private long mEndGracePeriod = 0L; private String otrFingerprint; - private Roster roster = null; private List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>(); - public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>(); public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>(); @@ -160,8 +156,12 @@ public class Account extends AbstractEntity { } public boolean hasErrorStatus() { - return getStatus() > STATUS_NO_INTERNET - && (getXmppConnection().getAttempt() >= 2); + if (getXmppConnection() == null) { + return false; + } else { + return getStatus() > STATUS_NO_INTERNET + && (getXmppConnection().getAttempt() >= 2); + } } public void setResource(String resource) { @@ -341,20 +341,6 @@ public class Account extends AbstractEntity { return false; } - public Bitmap getImage(Context context, int size) { - if (this.avatar != null) { - Bitmap bm = FileBackend.getAvatar(this.avatar, size, context); - if (bm == null) { - return UIHelper.getContactPicture(getJid(), size, context, - false); - } else { - return bm; - } - } else { - return UIHelper.getContactPicture(getJid(), size, context, false); - } - } - public boolean setAvatar(String filename) { if (this.avatar != null && this.avatar.equals(filename)) { return false; @@ -397,4 +383,17 @@ public class Account extends AbstractEntity { return R.string.account_status_unknown; } } + + public void activateGracePeriod() { + this.mEndGracePeriod = SystemClock.elapsedRealtime() + + (Config.CARBON_GRACE_PERIOD * 1000); + } + + public void deactivateGracePeriod() { + this.mEndGracePeriod = 0L; + } + + public boolean inGracePeriod() { + return SystemClock.elapsedRealtime() < this.mEndGracePeriod; + } } diff --git a/src/eu/siacs/conversations/entities/Bookmark.java b/src/eu/siacs/conversations/entities/Bookmark.java index 722fb6d9..dd9e805c 100644 --- a/src/eu/siacs/conversations/entities/Bookmark.java +++ b/src/eu/siacs/conversations/entities/Bookmark.java @@ -2,9 +2,6 @@ package eu.siacs.conversations.entities; import java.util.Locale; -import android.content.Context; -import android.graphics.Bitmap; -import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; public class Bookmark extends Element implements ListItem { @@ -120,21 +117,14 @@ public class Bookmark extends Element implements ListItem { return this.account; } - @Override - public Bitmap getImage(int dpSize, Context context) { - if (this.mJoinedConversation == null) { - return UIHelper.getContactPicture(getDisplayName(), dpSize, - context, false); - } else { - return UIHelper.getContactPicture(this.mJoinedConversation, dpSize, - context, false); - } - } - public void setConversation(Conversation conversation) { this.mJoinedConversation = conversation; } + public Conversation getConversation() { + return this.mJoinedConversation; + } + public String getName() { return this.getAttribute("name"); } diff --git a/src/eu/siacs/conversations/entities/Contact.java b/src/eu/siacs/conversations/entities/Contact.java index b1ebe662..60c31a42 100644 --- a/src/eu/siacs/conversations/entities/Contact.java +++ b/src/eu/siacs/conversations/entities/Contact.java @@ -8,13 +8,9 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; import android.content.ContentValues; -import android.content.Context; import android.database.Cursor; -import android.graphics.Bitmap; public class Contact implements ListItem { public static final String TABLENAME = "contacts"; @@ -330,20 +326,6 @@ public class Contact implements ListItem { } } - @Override - public Bitmap getImage(int size, Context context) { - if (this.avatar != null) { - Bitmap bm = FileBackend.getAvatar(avatar, size, context); - if (bm == null) { - return UIHelper.getContactPicture(this, size, context, false); - } else { - return bm; - } - } else { - return UIHelper.getContactPicture(this, size, context, false); - } - } - public boolean setAvatar(String filename) { if (this.avatar != null && this.avatar.equals(filename)) { return false; @@ -353,6 +335,10 @@ public class Contact implements ListItem { } } + public String getAvatar() { + return this.avatar; + } + public boolean deleteOtrFingerprint(String fingerprint) { boolean success = false; try { @@ -374,4 +360,8 @@ public class Contact implements ListItem { return false; } } + + public boolean trusted() { + return getOption(Options.FROM) && getOption(Options.TO); + } } diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index b4c99dc1..9d4c36db 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -1,14 +1,13 @@ package eu.siacs.conversations.entities; import java.security.interfaces.DSAPublicKey; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import org.json.JSONException; import org.json.JSONObject; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.utils.UIHelper; import net.java.otr4j.OtrException; import net.java.otr4j.crypto.OtrCryptoEngineImpl; @@ -17,9 +16,7 @@ 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; -import android.graphics.Bitmap; import android.os.SystemClock; public class Conversation extends AbstractEntity { @@ -57,8 +54,8 @@ public class Conversation extends AbstractEntity { private String nextPresence; - private transient CopyOnWriteArrayList<Message> messages = null; - private transient Account account = null; + protected ArrayList<Message> messages = new ArrayList<Message>(); + protected Account account = null; private transient SessionImpl otrSession; @@ -68,12 +65,10 @@ public class Conversation extends AbstractEntity { private transient MucOptions mucOptions = null; - //private transient String latestMarkableMessageId; + // private transient String latestMarkableMessageId; private byte[] symmetricKey; - private boolean otrSessionNeedsStarting = false; - private Bookmark bookmark; public Conversation(String name, Account account, String contactJid, @@ -106,17 +101,6 @@ public class Conversation extends AbstractEntity { } public List<Message> getMessages() { - if (messages == null) { - this.messages = new CopyOnWriteArrayList<Message>(); // prevent null - // pointer - } - - // populate with Conversation (this) - - for (Message msg : messages) { - msg.setConversation(this); - } - return messages; } @@ -142,8 +126,9 @@ public class Conversation extends AbstractEntity { if (this.messages == null) { return null; } - for(int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED && this.messages.get(i).markable) { + for (int i = this.messages.size() - 1; i >= 0; --i) { + if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED + && this.messages.get(i).markable) { if (this.messages.get(i).isRead()) { return null; } else { @@ -166,7 +151,7 @@ public class Conversation extends AbstractEntity { } } - public void setMessages(CopyOnWriteArrayList<Message> msgs) { + public void setMessages(ArrayList<Message> msgs) { this.messages = msgs; } @@ -263,10 +248,7 @@ public class Conversation extends AbstractEntity { try { if (sendStart) { this.otrSession.startSession(); - this.otrSessionNeedsStarting = false; return this.otrSession; - } else { - this.otrSessionNeedsStarting = true; } return this.otrSession; } catch (OtrException e) { @@ -282,12 +264,12 @@ public class Conversation extends AbstractEntity { public void resetOtrSession() { this.otrFingerprint = null; - this.otrSessionNeedsStarting = false; this.otrSession = null; } public void startOtrIfNeeded() { - if (this.otrSession != null && this.otrSessionNeedsStarting) { + if (this.otrSession != null + && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { try { this.otrSession.startSession(); } catch (OtrException e) { @@ -296,18 +278,23 @@ public class Conversation extends AbstractEntity { } } - public void endOtrIfNeeded() { + public boolean endOtrIfNeeded() { if (this.otrSession != null) { if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) { try { this.otrSession.endSession(); this.resetOtrSession(); + return true; } catch (OtrException e) { this.resetOtrSession(); + return false; } } else { this.resetOtrSession(); + return false; } + } else { + return false; } } @@ -339,9 +326,8 @@ public class Conversation extends AbstractEntity { public synchronized MucOptions getMucOptions() { if (this.mucOptions == null) { - this.mucOptions = new MucOptions(this.getAccount()); + this.mucOptions = new MucOptions(this); } - this.mucOptions.setConversation(this); return this.mucOptions; } @@ -438,14 +424,6 @@ public class Conversation extends AbstractEntity { return this.bookmark; } - public Bitmap getImage(Context context, int size) { - if (mode == MODE_SINGLE) { - return getContact().getImage(size, context); - } else { - return UIHelper.getContactPicture(this, size, context, false); - } - } - public boolean hasDuplicateMessage(Message message) { for (int i = this.getMessages().size() - 1; i >= 0; --i) { if (this.messages.get(i).equals(message)) { @@ -506,4 +484,17 @@ public class Conversation extends AbstractEntity { } } } + + public void add(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(message); + } + } + + public void addAll(int index, List<Message> messages) { + synchronized (this.messages) { + this.messages.addAll(index, messages); + } + } } diff --git a/src/eu/siacs/conversations/entities/Downloadable.java b/src/eu/siacs/conversations/entities/Downloadable.java index 8fb4977e..70516b20 100644 --- a/src/eu/siacs/conversations/entities/Downloadable.java +++ b/src/eu/siacs/conversations/entities/Downloadable.java @@ -1,5 +1,21 @@ package eu.siacs.conversations.entities; public interface Downloadable { - public void start(); + + public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" }; + public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" }; + + public static final int STATUS_UNKNOWN = 0x200; + public static final int STATUS_CHECKING = 0x201; + public static final int STATUS_FAILED = 0x202; + public static final int STATUS_OFFER = 0x203; + public static final int STATUS_DOWNLOADING = 0x204; + public static final int STATUS_DELETED = 0x205; + public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206; + + public boolean start(); + + public int getStatus(); + + public long getFileSize(); } diff --git a/src/eu/siacs/conversations/entities/DownloadableFile.java b/src/eu/siacs/conversations/entities/DownloadableFile.java new file mode 100644 index 00000000..1605c75b --- /dev/null +++ b/src/eu/siacs/conversations/entities/DownloadableFile.java @@ -0,0 +1,154 @@ +package eu.siacs.conversations.entities; + +import java.io.File; +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.Key; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import android.util.Log; + +public class DownloadableFile extends File { + + private static final long serialVersionUID = 2247012619505115863L; + + private long expectedSize = 0; + private String sha1sum; + private Key aeskey; + + private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; + + public DownloadableFile(String path) { + super(path); + } + + public long getSize() { + return super.length(); + } + + public long getExpectedSize() { + if (this.aeskey != null) { + if (this.expectedSize == 0) { + return 0; + } else { + return (this.expectedSize / 16 + 1) * 16; + } + } else { + return this.expectedSize; + } + } + + public void setExpectedSize(long size) { + this.expectedSize = size; + } + + public String getSha1Sum() { + return this.sha1sum; + } + + public void setSha1Sum(String sum) { + this.sha1sum = sum; + } + + public void setKey(byte[] key) { + if (key.length == 48) { + byte[] secretKey = new byte[32]; + byte[] iv = new byte[16]; + System.arraycopy(key, 0, iv, 0, 16); + System.arraycopy(key, 16, secretKey, 0, 32); + this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.iv = iv; + } else 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"); + } + } + + public Key getKey() { + return this.aeskey; + } + + public InputStream createInputStream() { + if (this.getKey() == null) { + try { + return new FileInputStream(this); + } catch (FileNotFoundException e) { + return null; + } + } else { + try { + IvParameterSpec ips = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new CipherInputStream(new FileInputStream(this), cipher); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); + return null; + } catch (NoSuchPaddingException e) { + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); + return null; + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); + return null; + } catch (InvalidAlgorithmParameterException e) { + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); + return null; + } catch (FileNotFoundException e) { + return null; + } + } + } + + public OutputStream createOutputStream() { + if (this.getKey() == null) { + try { + return new FileOutputStream(this); + } catch (FileNotFoundException e) { + return null; + } + } else { + try { + IvParameterSpec ips = new IvParameterSpec(this.iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(new FileOutputStream(this), + cipher); + } catch (NoSuchAlgorithmException e) { + Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); + return null; + } catch (NoSuchPaddingException e) { + Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); + return null; + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); + return null; + } catch (InvalidAlgorithmParameterException e) { + Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); + return null; + } catch (FileNotFoundException e) { + return null; + } + } + } +} diff --git a/src/eu/siacs/conversations/entities/ListItem.java b/src/eu/siacs/conversations/entities/ListItem.java index 19089b28..a1872d2f 100644 --- a/src/eu/siacs/conversations/entities/ListItem.java +++ b/src/eu/siacs/conversations/entities/ListItem.java @@ -1,12 +1,7 @@ package eu.siacs.conversations.entities; -import android.content.Context; -import android.graphics.Bitmap; - public interface ListItem extends Comparable<ListItem> { public String getDisplayName(); public String getJid(); - - public Bitmap getImage(int dpSize, Context context); } diff --git a/src/eu/siacs/conversations/entities/Message.java b/src/eu/siacs/conversations/entities/Message.java index 49482bbc..8a83c465 100644 --- a/src/eu/siacs/conversations/entities/Message.java +++ b/src/eu/siacs/conversations/entities/Message.java @@ -1,23 +1,21 @@ package eu.siacs.conversations.entities; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; + import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; import android.content.ContentValues; -import android.content.Context; import android.database.Cursor; public class Message extends AbstractEntity { public static final String TABLENAME = "messages"; - public static final int STATUS_RECEPTION_FAILED = -3; - public static final int STATUS_RECEIVED_OFFER = -2; - public static final int STATUS_RECEIVING = -1; public static final int STATUS_RECEIVED = 0; public static final int STATUS_UNSEND = 1; 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; @@ -61,6 +59,9 @@ public class Message extends AbstractEntity { protected Downloadable downloadable = null; public boolean markable = false; + private Message mNextMessage = null; + private Message mPreviousMessage = null; + private Message() { } @@ -131,14 +132,8 @@ public class Message extends AbstractEntity { if (this.trueCounterpart == null) { return null; } else { - Account account = this.conversation.getAccount(); - Contact contact = account.getRoster().getContact( - this.trueCounterpart); - if (contact.showInRoster()) { - return contact; - } else { - return null; - } + return this.conversation.getAccount().getRoster() + .getContactFromRoster(this.trueCounterpart); } } } @@ -147,22 +142,6 @@ public class Message extends AbstractEntity { return body; } - public String getReadableBody(Context context) { - if ((encryption == ENCRYPTION_PGP) && (type == TYPE_TEXT)) { - return context.getText(R.string.encrypted_message_received) - .toString(); - } else if ((encryption == ENCRYPTION_OTR) && (type == TYPE_IMAGE)) { - return context.getText(R.string.encrypted_image_received) - .toString(); - } else if (encryption == ENCRYPTION_DECRYPTION_FAILED) { - return context.getText(R.string.decryption_failed).toString(); - } else if (type == TYPE_IMAGE) { - return context.getText(R.string.image_file).toString(); - } else { - return body.trim(); - } - } - public long getTimeSent() { return timeSent; } @@ -301,21 +280,34 @@ public class Message extends AbstractEntity { } public Message next() { - int index = this.conversation.getMessages().indexOf(this); - if (index < 0 || index >= this.conversation.getMessages().size() - 1) { - return null; - } else { - return this.conversation.getMessages().get(index + 1); + if (this.mNextMessage == null) { + synchronized (this.conversation.messages) { + int index = this.conversation.messages.indexOf(this); + if (index < 0 + || index >= this.conversation.getMessages().size() - 1) { + this.mNextMessage = null; + } else { + this.mNextMessage = this.conversation.messages + .get(index + 1); + } + } } + return this.mNextMessage; } public Message prev() { - int index = this.conversation.getMessages().indexOf(this); - if (index <= 0 || index > this.conversation.getMessages().size()) { - return null; - } else { - return this.conversation.getMessages().get(index - 1); + if (this.mPreviousMessage == null) { + synchronized (this.conversation.messages) { + int index = this.conversation.messages.indexOf(this); + if (index <= 0 || index > this.conversation.messages.size()) { + this.mPreviousMessage = null; + } else { + this.mPreviousMessage = this.conversation.messages + .get(index - 1); + } + } } + return this.mPreviousMessage; } public boolean mergable(Message message) { @@ -323,6 +315,8 @@ public class Message extends AbstractEntity { return false; } return (message.getType() == Message.TYPE_TEXT + && this.getDownloadable() == null + && message.getDownloadable() == null && message.getEncryption() != Message.ENCRYPTION_PGP && this.getType() == message.getType() && this.getEncryption() == message.getEncryption() @@ -332,7 +326,9 @@ public class Message extends AbstractEntity { .getStatus() == Message.STATUS_SEND_RECEIVED) && (message .getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND || message - .getStatus() == Message.STATUS_SEND_DISPLAYED))))); + .getStatus() == Message.STATUS_SEND_DISPLAYED)))) + && !message.bodyContainsDownloadable() + && !this.bodyContainsDownloadable()); } public String getMergedBody() { @@ -369,4 +365,148 @@ public class Message extends AbstractEntity { return prev.mergable(this); } } + + public boolean trusted() { + Contact contact = this.getContact(); + return (status > STATUS_RECEIVED || (contact != null && contact.trusted())); + } + + public boolean bodyContainsDownloadable() { + try { + URL url = new URL(this.getBody()); + if (!url.getProtocol().equalsIgnoreCase("http") + && !url.getProtocol().equalsIgnoreCase("https")) { + return false; + } + if (url.getPath() == null) { + return false; + } + String[] pathParts = url.getPath().split("/"); + String filename; + if (pathParts.length > 0) { + filename = pathParts[pathParts.length - 1]; + } else { + filename = pathParts[0]; + } + String[] extensionParts = filename.split("\\."); + if (extensionParts.length == 2 + && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains( + extensionParts[extensionParts.length - 1])) { + return true; + } else if (extensionParts.length == 3 + && Arrays + .asList(Downloadable.VALID_CRYPTO_EXTENSIONS) + .contains(extensionParts[extensionParts.length - 1]) + && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains( + extensionParts[extensionParts.length - 2])) { + return true; + } else { + return false; + } + } catch (MalformedURLException e) { + return false; + } + } + + public ImageParams getImageParams() { + ImageParams params = getLegacyImageParams(); + if (params!=null) { + return params; + } + params = new ImageParams(); + if (this.downloadable != null) { + params.size = this.downloadable.getFileSize(); + } + if (body == null) { + return params; + } + String parts[] = body.split("\\|"); + if (parts.length == 1) { + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + params.origin = parts[0]; + try { + params.url = new URL(parts[0]); + } catch (MalformedURLException e1) { + params.url = null; + } + } + } else if (parts.length == 3) { + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + params.height = 0; + } + } else if (parts.length == 4) { + params.origin = parts[0]; + try { + params.url = new URL(parts[0]); + } catch (MalformedURLException e1) { + params.url = null; + } + try { + params.size = Long.parseLong(parts[1]); + } catch (NumberFormatException e) { + params.size = 0; + } + try { + params.width = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + params.width = 0; + } + try { + params.height = Integer.parseInt(parts[3]); + } catch (NumberFormatException e) { + params.height = 0; + } + } + return params; + } + + public ImageParams getLegacyImageParams() { + ImageParams params = new ImageParams(); + if (body == null) { + return params; + } + String parts[] = body.split(","); + if (parts.length == 3) { + try { + params.size = Long.parseLong(parts[0]); + } catch (NumberFormatException e) { + return null; + } + try { + params.width = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + return null; + } + try { + params.height = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + return null; + } + return params; + } else { + return null; + } + } + + public class ImageParams { + public URL url; + public long size = 0; + public int width = 0; + public int height = 0; + public String origin; + } } diff --git a/src/eu/siacs/conversations/entities/MucOptions.java b/src/eu/siacs/conversations/entities/MucOptions.java index 12ea4e93..d7407cd5 100644 --- a/src/eu/siacs/conversations/entities/MucOptions.java +++ b/src/eu/siacs/conversations/entities/MucOptions.java @@ -102,6 +102,10 @@ public class MucOptions { public long getPgpKeyId() { return this.pgpKeyId; } + + public Contact getContact() { + return account.getRoster().getContactFromRoster(getJid()); + } } private Account account; @@ -116,8 +120,9 @@ public class MucOptions { private String joinnick; private String password = null; - public MucOptions(Account account) { - this.account = account; + public MucOptions(Conversation conversation) { + this.account = conversation.getAccount(); + this.conversation = conversation; } public void deleteUser(String name) { @@ -253,10 +258,6 @@ public class MucOptions { this.joinnick = nick; } - public void setConversation(Conversation conversation) { - this.conversation = conversation; - } - public boolean online() { return this.isOnline; } @@ -361,4 +362,8 @@ public class MucOptions { conversation .setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password); } + + public Conversation getConversation() { + return this.conversation; + } }
\ No newline at end of file diff --git a/src/eu/siacs/conversations/entities/Roster.java b/src/eu/siacs/conversations/entities/Roster.java index b6908793..3267b15a 100644 --- a/src/eu/siacs/conversations/entities/Roster.java +++ b/src/eu/siacs/conversations/entities/Roster.java @@ -14,7 +14,10 @@ public class Roster { this.account = account; } - public Contact getContactAsShownInRoster(String jid) { + public Contact getContactFromRoster(String jid) { + if (jid == null) { + return null; + } String cleanJid = jid.split("/", 2)[0]; Contact contact = contacts.get(cleanJid); if (contact != null && contact.showInRoster()) { diff --git a/src/eu/siacs/conversations/http/HttpConnection.java b/src/eu/siacs/conversations/http/HttpConnection.java new file mode 100644 index 00000000..0810e167 --- /dev/null +++ b/src/eu/siacs/conversations/http/HttpConnection.java @@ -0,0 +1,270 @@ +package eu.siacs.conversations.http; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.conn.ssl.StrictHostnameVerifier; + +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.net.Uri; + +import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.CryptoHelper; + +public class HttpConnection implements Downloadable { + + private HttpConnectionManager mHttpConnectionManager; + private XmppConnectionService mXmppConnectionService; + + private URL mUrl; + private Message message; + private DownloadableFile file; + private int mStatus = Downloadable.STATUS_UNKNOWN; + private boolean acceptedAutomatically = false; + + public HttpConnection(HttpConnectionManager manager) { + this.mHttpConnectionManager = manager; + this.mXmppConnectionService = manager.getXmppConnectionService(); + } + + @Override + public boolean start() { + if (mXmppConnectionService.hasInternetConnection()) { + if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { + checkFileSize(true); + } else { + new Thread(new FileDownloader(true)).start(); + } + return true; + } else { + return false; + } + } + + public void init(Message message) { + this.message = message; + this.message.setDownloadable(this); + try { + mUrl = new URL(message.getBody()); + String path = mUrl.getPath(); + if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) { + this.message.setEncryption(Message.ENCRYPTION_PGP); + } else if (message.getEncryption() != Message.ENCRYPTION_OTR) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + this.file = mXmppConnectionService.getFileBackend().getFile( + message, false); + String reference = mUrl.getRef(); + if (reference != null && reference.length() == 96) { + this.file.setKey(CryptoHelper.hexToBytes(reference)); + } + + if (this.message.getEncryption() == Message.ENCRYPTION_OTR + && this.file.getKey() == null) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + checkFileSize(false); + } catch (MalformedURLException e) { + this.cancel(); + } + } + + private void checkFileSize(boolean interactive) { + new Thread(new FileSizeChecker(interactive)).start(); + } + + public void cancel() { + mHttpConnectionManager.finishConnection(this); + message.setDownloadable(null); + mXmppConnectionService.updateConversationUi(); + } + + private void finish() { + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); + message.setDownloadable(null); + mHttpConnectionManager.finishConnection(this); + mXmppConnectionService.updateConversationUi(); + if (acceptedAutomatically) { + mXmppConnectionService.getNotificationService().push(message); + } + } + + private void changeStatus(int status) { + this.mStatus = status; + mXmppConnectionService.updateConversationUi(); + } + + private void setupTrustManager(HttpsURLConnection connection, + boolean interactive) { + X509TrustManager trustManager; + HostnameVerifier hostnameVerifier; + if (interactive) { + trustManager = mXmppConnectionService.getMemorizingTrustManager(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager().wrapHostnameVerifier( + new StrictHostnameVerifier()); + } else { + trustManager = mXmppConnectionService.getMemorizingTrustManager() + .getNonInteractive(); + hostnameVerifier = mXmppConnectionService + .getMemorizingTrustManager() + .wrapHostnameVerifierNonInteractive( + new StrictHostnameVerifier()); + } + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new X509TrustManager[] { trustManager }, + mXmppConnectionService.getRNG()); + connection.setSSLSocketFactory(sc.getSocketFactory()); + connection.setHostnameVerifier(hostnameVerifier); + } catch (KeyManagementException e) { + return; + } catch (NoSuchAlgorithmException e) { + return; + } + } + + private class FileSizeChecker implements Runnable { + + private boolean interactive = false; + + public FileSizeChecker(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + long size; + try { + size = retrieveFileSize(); + } catch (SSLHandshakeException e) { + changeStatus(STATUS_OFFER_CHECK_FILESIZE); + HttpConnection.this.acceptedAutomatically = false; + HttpConnection.this.mXmppConnectionService.getNotificationService().push(message); + return; + } catch (IOException e) { + cancel(); + return; + } + file.setExpectedSize(size); + if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) { + HttpConnection.this.acceptedAutomatically = true; + new Thread(new FileDownloader(interactive)).start(); + } else { + changeStatus(STATUS_OFFER); + HttpConnection.this.acceptedAutomatically = false; + HttpConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + } + + private long retrieveFileSize() throws IOException, + SSLHandshakeException { + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl + .openConnection(); + connection.setRequestMethod("HEAD"); + if (connection instanceof HttpsURLConnection) { + setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + if (contentLength == null) { + throw new IOException(); + } + try { + return Long.parseLong(contentLength, 10); + } catch (NumberFormatException e) { + throw new IOException(); + } + } + + } + + private class FileDownloader implements Runnable { + + private boolean interactive = false; + + public FileDownloader(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + try { + changeStatus(STATUS_DOWNLOADING); + download(); + updateImageBounds(); + finish(); + } catch (SSLHandshakeException e) { + changeStatus(STATUS_OFFER); + } catch (IOException e) { + cancel(); + } + } + + private void download() throws SSLHandshakeException, IOException { + HttpURLConnection connection = (HttpURLConnection) mUrl + .openConnection(); + if (connection instanceof HttpsURLConnection) { + setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + BufferedInputStream is = new BufferedInputStream( + connection.getInputStream()); + OutputStream os = file.createOutputStream(); + int count = -1; + byte[] buffer = new byte[1024]; + while ((count = is.read(buffer)) != -1) { + os.write(buffer, 0, count); + } + os.flush(); + os.close(); + is.close(); + } + + private void updateImageBounds() { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + int imageHeight = options.outHeight; + int imageWidth = options.outWidth; + message.setBody(mUrl.toString() + "|" + file.getSize() + '|' + + imageWidth + '|' + imageHeight); + message.setType(Message.TYPE_IMAGE); + mXmppConnectionService.updateMessage(message); + } + + } + + @Override + public int getStatus() { + return this.mStatus; + } + + @Override + public long getFileSize() { + if (this.file != null) { + return this.file.getExpectedSize(); + } else { + return 0; + } + } +}
\ No newline at end of file diff --git a/src/eu/siacs/conversations/http/HttpConnectionManager.java b/src/eu/siacs/conversations/http/HttpConnectionManager.java new file mode 100644 index 00000000..9a2a2405 --- /dev/null +++ b/src/eu/siacs/conversations/http/HttpConnectionManager.java @@ -0,0 +1,28 @@ +package eu.siacs.conversations.http; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.AbstractConnectionManager; +import eu.siacs.conversations.services.XmppConnectionService; + +public class HttpConnectionManager extends AbstractConnectionManager { + + public HttpConnectionManager(XmppConnectionService service) { + super(service); + } + + private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>(); + + public HttpConnection createNewConnection(Message message) { + HttpConnection connection = new HttpConnection(this); + connection.init(message); + this.connections.add(connection); + return connection; + } + + public void finishConnection(HttpConnection connection) { + this.connections.remove(connection); + } +} diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index f8329037..383ac89a 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -256,7 +256,6 @@ public class MessageParser extends AbstractParser implements return null; } } - return finishedMessage; } @@ -348,10 +347,18 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.databaseBackend .updateAccount(account); } + mXmppConnectionService.getAvatarService().clear( + account); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateAccountUi(); } else { Contact contact = account.getRoster().getContact( from); contact.setAvatar(avatar.getFilename()); + mXmppConnectionService.getAvatarService().clear( + contact); + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); } } else { mXmppConnectionService.fetchAvatar(account, avatar); @@ -417,8 +424,7 @@ public class MessageParser extends AbstractParser implements message = this.parseCarbonMessage(packet, account); if (message != null) { if (message.getStatus() == Message.STATUS_SEND) { - mXmppConnectionService.getNotificationService() - .activateGracePeriod(); + account.activateGracePeriod(); notify = false; mXmppConnectionService.markRead( message.getConversation(), false); @@ -440,8 +446,7 @@ public class MessageParser extends AbstractParser implements } else { mXmppConnectionService.markRead(message.getConversation(), false); - mXmppConnectionService.getNotificationService() - .activateGracePeriod(); + account.activateGracePeriod(); notify = false; } } @@ -471,13 +476,26 @@ public class MessageParser extends AbstractParser implements } } Conversation conversation = message.getConversation(); - conversation.getMessages().add(message); + conversation.add(message); + + if (message.getStatus() == Message.STATUS_RECEIVED + && conversation.getOtrSession() != null + && !conversation.getOtrSession().getSessionID().getUserID() + .equals(message.getPresence())) { + conversation.endOtrIfNeeded(); + } + if (packet.getType() != MessagePacket.TYPE_ERROR) { if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) { mXmppConnectionService.databaseBackend.createMessage(message); } } + if (message.trusted() && message.bodyContainsDownloadable()) { + this.mXmppConnectionService.getHttpConnectionManager() + .createNewConnection(message); + notify = false; + } notify = notify && !conversation.isMuted(); if (notify) { mXmppConnectionService.getNotificationService().push(message); diff --git a/src/eu/siacs/conversations/parser/PresenceParser.java b/src/eu/siacs/conversations/parser/PresenceParser.java index 2c3a7dbc..4e90cda8 100644 --- a/src/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/eu/siacs/conversations/parser/PresenceParser.java @@ -29,6 +29,7 @@ public class PresenceParser extends AbstractParser implements if (before != muc.getMucOptions().online()) { mXmppConnectionService.updateConversationUi(); } + mXmppConnectionService.getAvatarService().clear(muc); } } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { Conversation muc = mXmppConnectionService.find(account, packet @@ -39,6 +40,7 @@ public class PresenceParser extends AbstractParser implements if (before != muc.getMucOptions().online()) { mXmppConnectionService.updateConversationUi(); } + mXmppConnectionService.getAvatarService().clear(muc); } } } @@ -58,8 +60,7 @@ public class PresenceParser extends AbstractParser implements Presences.parseShow(packet.findChild("show"))); } else if (type.equals("unavailable")) { account.removePresence(fromParts[1]); - mXmppConnectionService.getNotificationService() - .deactivateGracePeriod(); + account.deactivateGracePeriod(); } } } else { diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index 0991dd2d..12e5e251 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -11,6 +11,7 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Roster; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -151,14 +152,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { return list; } - public CopyOnWriteArrayList<Message> getMessages( - Conversation conversations, int limit) { + public ArrayList<Message> getMessages(Conversation conversations, int limit) { return getMessages(conversations, limit, -1); } - public CopyOnWriteArrayList<Message> getMessages(Conversation conversation, - int limit, long timestamp) { - CopyOnWriteArrayList<Message> list = new CopyOnWriteArrayList<Message>(); + public ArrayList<Message> getMessages(Conversation conversation, int limit, + long timestamp) { + ArrayList<Message> list = new ArrayList<Message>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; if (timestamp == -1) { @@ -177,7 +177,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() > 0) { cursor.moveToLast(); do { - list.add(Message.fromCursor(cursor)); + Message message = Message.fromCursor(cursor); + message.setConversation(conversation); + list.add(message); } while (cursor.moveToPrevious()); } return list; @@ -231,10 +233,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from " + Account.TABLENAME + " where not options & (1 <<1)", null); - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - return (count > 0); + try { + cursor.moveToFirst(); + int count = cursor.getInt(0); + cursor.close(); + return (count > 0); + } catch (SQLiteCantOpenDatabaseException e) { + return true; // better safe than sorry + } } @Override @@ -326,4 +332,22 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.moveToFirst(); return Account.fromCursor(cursor); } + + public List<Message> getImageMessages(Conversation conversation) { + ArrayList<Message> list = new ArrayList<Message>(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor; + String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) }; + cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null); + if (cursor.getCount() > 0) { + cursor.moveToLast(); + do { + Message message = Message.fromCursor(cursor); + message.setConversation(conversation); + list.add(message); + } while (cursor.moveToPrevious()); + } + return list; + } } diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index d86c0ee1..13daa27b 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/eu/siacs/conversations/persistance/FileBackend.java @@ -14,89 +14,47 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.RectF; -import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Base64; import android.util.Base64OutputStream; import android.util.Log; -import android.util.LruCache; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; -import eu.siacs.conversations.services.ImageProvider; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.jingle.JingleFile; +import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { private static int IMAGE_SIZE = 1920; - private Context context; - private LruCache<String, Bitmap> thumbnailCache; - private SimpleDateFormat imageDateFormat = new SimpleDateFormat( "yyyyMMdd_HHmmssSSS", Locale.US); - public FileBackend(Context context) { - this.context = context; - int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); - int cacheSize = maxMemory / 8; - thumbnailCache = new LruCache<String, Bitmap>(cacheSize) { - @Override - protected int sizeOf(String key, Bitmap bitmap) { - return bitmap.getByteCount() / 1024; - } - }; - - } - - public LruCache<String, Bitmap> getThumbnailCache() { - return thumbnailCache; - } + private XmppConnectionService mXmppConnectionService; - public JingleFile getJingleFileLegacy(Message message) { - return getJingleFileLegacy(message, true); - } - - public JingleFile getJingleFileLegacy(Message message, boolean decrypted) { - Conversation conversation = message.getConversation(); - String prefix = context.getFilesDir().getAbsolutePath(); - String path = prefix + "/" + conversation.getAccount().getJid() + "/" - + conversation.getContactJid(); - String filename; - if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { - filename = message.getUuid() + ".webp"; - } else { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - filename = message.getUuid() + ".webp"; - } else { - filename = message.getUuid() + ".webp.pgp"; - } - } - return new JingleFile(path + "/" + filename); + public FileBackend(XmppConnectionService service) { + this.mXmppConnectionService = service; } - public JingleFile getJingleFile(Message message) { - return getJingleFile(message, true); + public DownloadableFile getFile(Message message) { + return getFile(message, true); } - public JingleFile getJingleFile(Message message, boolean decrypted) { + public DownloadableFile getFile(Message message, boolean decrypted) { StringBuilder filename = new StringBuilder(); - filename.append(Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_PICTURES).getAbsolutePath()); - filename.append("/Conversations/"); + filename.append(getConversationsDirectory()); filename.append(message.getUuid()); if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { filename.append(".webp"); @@ -107,7 +65,13 @@ public class FileBackend { filename.append(".webp.pgp"); } } - return new JingleFile(filename.toString()); + return new DownloadableFile(filename.toString()); + } + + public static String getConversationsDirectory() { + return Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES).getAbsolutePath() + + "/Conversations/"; } public Bitmap resize(Bitmap originalBitmap, int size) { @@ -139,17 +103,17 @@ public class FileBackend { return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true); } - public JingleFile copyImageToPrivateStorage(Message message, Uri image) + public DownloadableFile copyImageToPrivateStorage(Message message, Uri image) throws ImageCopyException { return this.copyImageToPrivateStorage(message, image, 0); } - private JingleFile copyImageToPrivateStorage(Message message, Uri image, - int sampleSize) throws ImageCopyException { + private DownloadableFile copyImageToPrivateStorage(Message message, + Uri image, int sampleSize) throws ImageCopyException { try { - InputStream is = context.getContentResolver() + InputStream is = mXmppConnectionService.getContentResolver() .openInputStream(image); - JingleFile file = getJingleFile(message); + DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); file.createNewFile(); Bitmap originalBitmap; @@ -202,7 +166,7 @@ public class FileBackend { private int getRotation(Uri image) { if ("content".equals(image.getScheme())) { try { - Cursor cursor = context + Cursor cursor = mXmppConnectionService .getContentResolver() .query(image, new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, @@ -216,40 +180,26 @@ public class FileBackend { return -1; } } else { - ExifInterface exif; try { - exif = new ExifInterface(image.toString()); - if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("6")) { - return 90; - } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("8")) { - return 270; - } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) - .equalsIgnoreCase("3")) { - return 180; - } else { - return 0; - } - } catch (IOException e) { - return -1; + InputStream is = mXmppConnectionService.getContentResolver() + .openInputStream(image); + return ExifHelper.getOrientation(is); + } catch (FileNotFoundException e) { + return 0; } } } public Bitmap getImageFromMessage(Message message) { - return BitmapFactory.decodeFile(getJingleFile(message) - .getAbsolutePath()); + return BitmapFactory.decodeFile(getFile(message).getAbsolutePath()); } public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException { - Bitmap thumbnail = thumbnailCache.get(message.getUuid()); + Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get( + message.getUuid()); if ((thumbnail == null) && (!cacheOnly)) { - File file = getJingleFile(message); - if (!file.exists()) { - file = getJingleFileLegacy(message); - } + File file = getFile(message); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = calcSampleSize(file, size); Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(), @@ -258,32 +208,12 @@ public class FileBackend { throw new FileNotFoundException(); } thumbnail = resize(fullsize, size); - this.thumbnailCache.put(message.getUuid(), thumbnail); + this.mXmppConnectionService.getBitmapCache().put(message.getUuid(), + thumbnail); } return thumbnail; } - public void removeFiles(Conversation conversation) { - String prefix = context.getFilesDir().getAbsolutePath(); - String path = prefix + "/" + conversation.getAccount().getJid() + "/" - + conversation.getContactJid(); - File file = new File(path); - try { - this.deleteFile(file); - } catch (IOException e) { - Log.d(Config.LOGTAG, - "error deleting file: " + file.getAbsolutePath()); - } - } - - private void deleteFile(File f) throws IOException { - if (f.isDirectory()) { - for (File c : f.listFiles()) - deleteFile(c); - } - f.delete(); - } - public Uri getTakePhotoUri() { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(Environment @@ -328,7 +258,7 @@ public class FileBackend { } public boolean isAvatarCached(Avatar avatar) { - File file = new File(getAvatarPath(context, avatar.getFilename())); + File file = new File(getAvatarPath(avatar.getFilename())); return file.exists(); } @@ -336,7 +266,7 @@ public class FileBackend { if (isAvatarCached(avatar)) { return true; } - String filename = getAvatarPath(context, avatar.getFilename()); + String filename = getAvatarPath(avatar.getFilename()); File file = new File(filename + ".tmp"); file.getParentFile().mkdirs(); try { @@ -368,15 +298,20 @@ public class FileBackend { } } - public static String getAvatarPath(Context context, String avatar) { - return context.getFilesDir().getAbsolutePath() + "/avatars/" + avatar; + public String getAvatarPath(String avatar) { + return mXmppConnectionService.getFilesDir().getAbsolutePath() + + "/avatars/" + avatar; + } + + public Uri getAvatarUri(String avatar) { + return Uri.parse("file:" + getAvatarPath(avatar)); } public Bitmap cropCenterSquare(Uri image, int size) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = calcSampleSize(image, size); - InputStream is = context.getContentResolver() + InputStream is = mXmppConnectionService.getContentResolver() .openInputStream(image); Bitmap input = BitmapFactory.decodeStream(is, null, options); if (input == null) { @@ -393,7 +328,40 @@ public class FileBackend { } } - public static Bitmap cropCenterSquare(Bitmap input, int size) { + public Bitmap cropCenter(Uri image, int newHeight, int newWidth) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calcSampleSize(image, + Math.max(newHeight, newWidth)); + InputStream is = mXmppConnectionService.getContentResolver() + .openInputStream(image); + Bitmap source = BitmapFactory.decodeStream(is, null, options); + + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + float xScale = (float) newWidth / sourceWidth; + float yScale = (float) newHeight / sourceHeight; + float scale = Math.max(xScale, yScale); + float scaledWidth = scale * sourceWidth; + float scaledHeight = scale * sourceHeight; + float left = (newWidth - scaledWidth) / 2; + float top = (newHeight - scaledHeight) / 2; + + RectF targetRect = new RectF(left, top, left + scaledWidth, top + + scaledHeight); + Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, + source.getConfig()); + Canvas canvas = new Canvas(dest); + canvas.drawBitmap(source, null, targetRect, null); + + return dest; + } catch (FileNotFoundException e) { + return null; + } + + } + + public Bitmap cropCenterSquare(Bitmap input, int size) { int w = input.getWidth(); int h = input.getHeight(); @@ -415,7 +383,7 @@ public class FileBackend { throws FileNotFoundException { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(context.getContentResolver() + BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver() .openInputStream(image), null, options); return calcSampleSize(options, size); } @@ -445,12 +413,8 @@ public class FileBackend { } public Uri getJingleFileUri(Message message) { - File file = getJingleFile(message); - if (file.exists()) { - return Uri.parse("file://" + file.getAbsolutePath()); - } else { - return ImageProvider.getProviderUri(message); - } + File file = getFile(message); + return Uri.parse("file://" + file.getAbsolutePath()); } public class ImageCopyException extends Exception { @@ -466,12 +430,18 @@ public class FileBackend { } } - public static Bitmap getAvatar(String avatar, int size, Context context) { - Bitmap bm = BitmapFactory.decodeFile(FileBackend.getAvatarPath(context, - avatar)); + public Bitmap getAvatar(String avatar, int size) { + if (avatar == null) { + return null; + } + Bitmap bm = cropCenter(getAvatarUri(avatar), size, size); if (bm == null) { return null; } - return cropCenterSquare(bm, UIHelper.getRealPx(size, context)); + return bm; + } + + public boolean isFileAvailable(Message message) { + return getFile(message).exists(); } } diff --git a/src/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/eu/siacs/conversations/services/AbstractConnectionManager.java new file mode 100644 index 00000000..676a09c9 --- /dev/null +++ b/src/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -0,0 +1,23 @@ +package eu.siacs.conversations.services; + +public class AbstractConnectionManager { + protected XmppConnectionService mXmppConnectionService; + + public AbstractConnectionManager(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public XmppConnectionService getXmppConnectionService() { + return this.mXmppConnectionService; + } + + public long getAutoAcceptFileSize() { + String config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size", "524288"); + try { + return Long.parseLong(config); + } catch (NumberFormatException e) { + return 524288; + } + } +} diff --git a/src/eu/siacs/conversations/services/AvatarService.java b/src/eu/siacs/conversations/services/AvatarService.java new file mode 100644 index 00000000..bd27b555 --- /dev/null +++ b/src/eu/siacs/conversations/services/AvatarService.java @@ -0,0 +1,292 @@ +package eu.siacs.conversations.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Bookmark; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.MucOptions; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.net.Uri; + +public class AvatarService { + + private static final int FG_COLOR = 0xFFFAFAFA; + private static final int TRANSPARENT = 0x00000000; + + private static final String PREFIX_CONTACT = "contact"; + private static final String PREFIX_CONVERSATION = "conversation"; + private static final String PREFIX_ACCOUNT = "account"; + private static final String PREFIX_GENERIC = "generic"; + + private ArrayList<Integer> sizes = new ArrayList<Integer>(); + + protected XmppConnectionService mXmppConnectionService = null; + + public AvatarService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public Bitmap get(Contact contact, int size) { + final String KEY = key(contact, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar( + contact.getAvatar(), size); + if (avatar == null) { + if (contact.getProfilePhoto() != null) { + avatar = mXmppConnectionService.getFileBackend() + .cropCenterSquare(Uri.parse(contact.getProfilePhoto()), + size); + if (avatar == null) { + avatar = get(contact.getDisplayName(), size); + } + } else { + avatar = get(contact.getDisplayName(), size); + } + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Contact contact) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(contact, size)); + } + } + + private String key(Contact contact, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + contact.getAccount().getJid() + "_" + + contact.getJid() + "_" + String.valueOf(size); + } + + public Bitmap get(ListItem item, int size) { + if (item instanceof Contact) { + return get((Contact) item, size); + } else if (item instanceof Bookmark) { + Bookmark bookmark = (Bookmark) item; + if (bookmark.getConversation() != null) { + return get(bookmark.getConversation(), size); + } else { + return get(bookmark.getDisplayName(), size); + } + } else { + return get(item.getDisplayName(), size); + } + } + + public Bitmap get(Conversation conversation, int size) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + return get(conversation.getContact(), size); + } else { + return get(conversation.getMucOptions(), size); + } + } + + public void clear(Conversation conversation) { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + clear(conversation.getContact()); + } else { + clear(conversation.getMucOptions()); + } + } + + public Bitmap get(MucOptions mucOptions, int size) { + final String KEY = key(mucOptions, size); + Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null) { + return bitmap; + } + List<MucOptions.User> users = mucOptions.getUsers(); + int count = users.size(); + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + bitmap.eraseColor(TRANSPARENT); + + if (count == 0) { + String name = mucOptions.getConversation().getName(); + String letter = name.substring(0, 1); + int color = this.getColorForName(name); + drawTile(canvas, letter, color, 0, 0, size, size); + } else if (count == 1) { + drawTile(canvas, users.get(0), 0, 0, size, size); + } else if (count == 2) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size); + } else if (count == 3) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size, + size); + } else if (count == 4) { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size, + size); + } else { + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1); + drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size); + drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1); + drawTile(canvas, "\u2026", 0xFF202020, size / 2 + 1, size / 2 + 1, + size, size); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + public void clear(MucOptions options) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(options, size)); + } + } + + private String key(MucOptions options, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + + "_" + String.valueOf(size); + } + + public Bitmap get(Account account, int size) { + final String KEY = key(account, size); + Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null) { + return avatar; + } + avatar = mXmppConnectionService.getFileBackend().getAvatar( + account.getAvatar(), size); + if (avatar == null) { + avatar = get(account.getJid(), size); + } + mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + + public void clear(Account account) { + for (Integer size : sizes) { + this.mXmppConnectionService.getBitmapCache().remove( + key(account, size)); + } + } + + private String key(Account account, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_ACCOUNT + "_" + account.getUuid() + "_" + + String.valueOf(size); + } + + public Bitmap get(String name, int size) { + final String KEY = key(name, size); + Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY); + if (bitmap != null) { + return bitmap; + } + bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + String letter = name.substring(0, 1); + int color = this.getColorForName(name); + drawTile(canvas, letter, color, 0, 0, size, size); + mXmppConnectionService.getBitmapCache().put(KEY, bitmap); + return bitmap; + } + + private String key(String name, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); + } + + private void drawTile(Canvas canvas, String letter, int tileColor, + int left, int top, int right, int bottom) { + letter = letter.toUpperCase(Locale.getDefault()); + Paint tilePaint = new Paint(), textPaint = new Paint(); + tilePaint.setColor(tileColor); + textPaint.setFlags(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(FG_COLOR); + 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 void drawTile(Canvas canvas, MucOptions.User user, int left, + int top, int right, int bottom) { + Contact contact = user.getContact(); + if (contact != null) { + Uri uri = null; + if (contact.getAvatar() != null) { + uri = mXmppConnectionService.getFileBackend().getAvatarUri( + contact.getAvatar()); + } else if (contact.getProfilePhoto() != null) { + uri = Uri.parse(contact.getProfilePhoto()); + } + if (uri != null) { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } else { + String letter = user.getName().substring(0, 1); + int color = this.getColorForName(user.getName()); + drawTile(canvas, letter, color, left, top, right, bottom); + } + } + + private void drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, + int dstright, int dstbottom) { + Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); + canvas.drawBitmap(bm, null, dst, null); + } + + private int getColorForName(String name) { + int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, + 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, + 0xFF795548, 0xFF607d8b }; + return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)]; + } + +} diff --git a/src/eu/siacs/conversations/services/ImageProvider.java b/src/eu/siacs/conversations/services/ImageProvider.java deleted file mode 100644 index ac78a454..00000000 --- a/src/eu/siacs/conversations/services/ImageProvider.java +++ /dev/null @@ -1,109 +0,0 @@ -package eu.siacs.conversations.services; - -import java.io.File; -import java.io.FileNotFoundException; - -import eu.siacs.conversations.Config; -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; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -public class ImageProvider extends ContentProvider { - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) - throws FileNotFoundException { - ParcelFileDescriptor pfd; - FileBackend fileBackend = new FileBackend(getContext()); - if ("r".equals(mode)) { - DatabaseBackend databaseBackend = DatabaseBackend - .getInstance(getContext()); - String uuids = uri.getPath(); - Log.d(Config.LOGTAG, "uuids = " + uuids + " mode=" + mode); - if (uuids == null) { - throw new FileNotFoundException(); - } - String[] uuidsSplited = uuids.split("/", 2); - if (uuidsSplited.length != 3) { - throw new FileNotFoundException(); - } - String conversationUuid = uuidsSplited[1]; - String messageUuid = uuidsSplited[2].split("\\.")[0]; - - Log.d(Config.LOGTAG, "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.getJingleFileLegacy(message); - pfd = ParcelFileDescriptor.open(file, - ParcelFileDescriptor.MODE_READ_ONLY); - return pfd; - } else { - throw new FileNotFoundException(); - } - } - - @Override - public int delete(Uri arg0, String arg1, String[] arg2) { - return 0; - } - - @Override - public String getType(Uri arg0) { - return null; - } - - @Override - public Uri insert(Uri arg0, ContentValues arg1) { - return null; - } - - @Override - public boolean onCreate() { - return false; - } - - @Override - public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4) { - return null; - } - - @Override - public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { - return 0; - } - - public static Uri getProviderUri(Message message) { - return Uri.parse("content://eu.siacs.conversations.images/" - + message.getConversationUuid() + "/" + message.getUuid() - + ".webp"); - } -}
\ No newline at end of file diff --git a/src/eu/siacs/conversations/services/NotificationService.java b/src/eu/siacs/conversations/services/NotificationService.java index 41656707..7b2e16df 100644 --- a/src/eu/siacs/conversations/services/NotificationService.java +++ b/src/eu/siacs/conversations/services/NotificationService.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.services; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.regex.Matcher; @@ -11,70 +12,83 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.net.Uri; import android.os.PowerManager; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationCompat.BigPictureStyle; +import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.TaskStackBuilder; import android.text.Html; +import android.util.DisplayMetrics; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; public class NotificationService { private XmppConnectionService mXmppConnectionService; - private NotificationManager mNotificationManager; private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>(); - public int NOTIFICATION_ID = 0x2342; + public static int NOTIFICATION_ID = 0x2342; private Conversation mOpenConversation; private boolean mIsInForeground; - - private long mEndGracePeriod = 0L; + private long mLastNotification; public NotificationService(XmppConnectionService service) { this.mXmppConnectionService = service; - this.mNotificationManager = (NotificationManager) service - .getSystemService(Context.NOTIFICATION_SERVICE); } - public synchronized void push(Message message) { - + public void push(Message message) { PowerManager pm = (PowerManager) mXmppConnectionService .getSystemService(Context.POWER_SERVICE); boolean isScreenOn = pm.isScreenOn(); + if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; } - String conversationUuid = message.getConversationUuid(); - if (notifications.containsKey(conversationUuid)) { - notifications.get(conversationUuid).add(message); - } else { - ArrayList<Message> mList = new ArrayList<Message>(); - mList.add(message); - notifications.put(conversationUuid, mList); + synchronized (notifications) { + String conversationUuid = message.getConversationUuid(); + if (notifications.containsKey(conversationUuid)) { + notifications.get(conversationUuid).add(message); + } else { + ArrayList<Message> mList = new ArrayList<Message>(); + mList.add(message); + notifications.put(conversationUuid, mList); + } + Account account = message.getConversation().getAccount(); + updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) + && !account.inGracePeriod() + && !this.inMiniGracePeriod(account)); } - updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) - && !inGracePeriod()); + } public void clear() { - notifications.clear(); - updateNotification(false); + synchronized (notifications) { + notifications.clear(); + updateNotification(false); + } } public void clear(Conversation conversation) { - notifications.remove(conversation.getUuid()); - updateNotification(false); + synchronized (notifications) { + notifications.remove(conversation.getUuid()); + updateNotification(false); + } } private void updateNotification(boolean notify) { + NotificationManager notificationManager = (NotificationManager) mXmppConnectionService + .getSystemService(Context.NOTIFICATION_SERVICE); SharedPreferences preferences = mXmppConnectionService.getPreferences(); String ringtone = preferences.getString("notification_ringtone", null); @@ -82,76 +96,16 @@ public class NotificationService { true); if (notifications.size() == 0) { - mNotificationManager.cancel(NOTIFICATION_ID); + notificationManager.cancel(NOTIFICATION_ID); } else { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder( - mXmppConnectionService); - mBuilder.setSmallIcon(R.drawable.ic_notification); + if (notify) { + this.markLastNotification(); + } + Builder mBuilder; if (notifications.size() == 1) { - ArrayList<Message> messages = notifications.values().iterator() - .next(); - if (messages.size() >= 1) { - Conversation conversation = messages.get(0) - .getConversation(); - mBuilder.setLargeIcon(conversation.getImage( - mXmppConnectionService, 64)); - mBuilder.setContentTitle(conversation.getName()); - StringBuilder text = new StringBuilder(); - for (int i = 0; i < messages.size(); ++i) { - text.append(messages.get(i).getReadableBody( - mXmppConnectionService)); - if (i != messages.size() - 1) { - text.append("\n"); - } - } - mBuilder.setStyle(new NotificationCompat.BigTextStyle() - .bigText(text.toString())); - mBuilder.setContentText(messages.get(0).getReadableBody( - mXmppConnectionService)); - if (notify) { - mBuilder.setTicker(messages.get(messages.size() - 1) - .getReadableBody(mXmppConnectionService)); - } - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); - } else { - mNotificationManager.cancel(NOTIFICATION_ID); - return; - } + mBuilder = buildSingleConversations(notify); } else { - NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - style.setBigContentTitle(notifications.size() - + " " - + mXmppConnectionService - .getString(R.string.unread_conversations)); - StringBuilder names = new StringBuilder(); - Conversation conversation = null; - for (ArrayList<Message> messages : notifications.values()) { - if (messages.size() > 0) { - conversation = messages.get(0).getConversation(); - String name = conversation.getName(); - style.addLine(Html.fromHtml("<b>" - + name - + "</b> " - + messages.get(0).getReadableBody( - mXmppConnectionService))); - names.append(name); - names.append(", "); - } - } - if (names.length() >= 2) { - names.delete(names.length() - 2, names.length()); - } - mBuilder.setContentTitle(notifications.size() - + " " - + mXmppConnectionService - .getString(R.string.unread_conversations)); - mBuilder.setContentText(names.toString()); - mBuilder.setStyle(style); - if (conversation != null) { - mBuilder.setContentIntent(createContentIntent(conversation - .getUuid())); - } + mBuilder = buildMultipleConversation(); } if (notify) { if (vibrate) { @@ -163,12 +117,147 @@ public class NotificationService { mBuilder.setSound(Uri.parse(ringtone)); } } + mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); - if (!inGracePeriod()) { - mBuilder.setLights(0xffffffff, 2000, 4000); - } + mBuilder.setLights(0xffffffff, 2000, 4000); Notification notification = mBuilder.build(); - mNotificationManager.notify(NOTIFICATION_ID, notification); + notificationManager.notify(NOTIFICATION_ID, notification); + } + } + + private Builder buildMultipleConversation() { + Builder mBuilder = new NotificationCompat.Builder( + mXmppConnectionService); + NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + style.setBigContentTitle(notifications.size() + + " " + + mXmppConnectionService + .getString(R.string.unread_conversations)); + StringBuilder names = new StringBuilder(); + Conversation conversation = null; + for (ArrayList<Message> messages : notifications.values()) { + if (messages.size() > 0) { + conversation = messages.get(0).getConversation(); + String name = conversation.getName(); + style.addLine(Html.fromHtml("<b>" + name + "</b> " + + getReadableBody(messages.get(0)))); + names.append(name); + names.append(", "); + } + } + if (names.length() >= 2) { + names.delete(names.length() - 2, names.length()); + } + mBuilder.setContentTitle(notifications.size() + + " " + + mXmppConnectionService + .getString(R.string.unread_conversations)); + mBuilder.setContentText(names.toString()); + mBuilder.setStyle(style); + if (conversation != null) { + mBuilder.setContentIntent(createContentIntent(conversation + .getUuid())); + } + return mBuilder; + } + + private Builder buildSingleConversations(boolean notify) { + Builder mBuilder = new NotificationCompat.Builder( + mXmppConnectionService); + ArrayList<Message> messages = notifications.values().iterator().next(); + if (messages.size() >= 1) { + Conversation conversation = messages.get(0).getConversation(); + mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService() + .get(conversation, getPixel(64))); + mBuilder.setContentTitle(conversation.getName()); + Message message; + if ((message = getImage(messages)) != null) { + modifyForImage(mBuilder, message, messages, notify); + } else { + modifyForTextOnly(mBuilder, messages, notify); + } + mBuilder.setContentIntent(createContentIntent(conversation + .getUuid())); + } + return mBuilder; + + } + + private void modifyForImage(Builder builder, Message message, + ArrayList<Message> messages, boolean notify) { + try { + Bitmap bitmap = mXmppConnectionService.getFileBackend() + .getThumbnail(message, getPixel(288), false); + ArrayList<Message> tmp = new ArrayList<Message>(); + for (Message msg : messages) { + if (msg.getType() == Message.TYPE_TEXT + && msg.getDownloadable() == null) { + tmp.add(msg); + } + } + BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); + bigPictureStyle.bigPicture(bitmap); + if (tmp.size() > 0) { + bigPictureStyle.setSummaryText(getMergedBodies(tmp)); + builder.setContentText(getReadableBody(tmp.get(0))); + } else { + builder.setContentText(mXmppConnectionService.getString(R.string.image_file)); + } + builder.setStyle(bigPictureStyle); + } catch (FileNotFoundException e) { + modifyForTextOnly(builder, messages, notify); + } + } + + private void modifyForTextOnly(Builder builder, + ArrayList<Message> messages, boolean notify) { + builder.setStyle(new NotificationCompat.BigTextStyle() + .bigText(getMergedBodies(messages))); + builder.setContentText(getReadableBody(messages.get(0))); + if (notify) { + builder.setTicker(getReadableBody(messages.get(messages.size() - 1))); + } + } + + private Message getImage(ArrayList<Message> messages) { + for (Message message : messages) { + if (message.getType() == Message.TYPE_IMAGE + && message.getDownloadable() == null + && message.getEncryption() != Message.ENCRYPTION_PGP) { + return message; + } + } + return null; + } + + private String getMergedBodies(ArrayList<Message> messages) { + StringBuilder text = new StringBuilder(); + for (int i = 0; i < messages.size(); ++i) { + text.append(getReadableBody(messages.get(i))); + if (i != messages.size() - 1) { + text.append("\n"); + } + } + return text.toString(); + } + + private String getReadableBody(Message message) { + if (message.getDownloadable() != null + && (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message + .getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) { + return mXmppConnectionService.getText( + R.string.image_offered_for_download).toString(); + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + return mXmppConnectionService.getText( + R.string.encrypted_message_received).toString(); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + return mXmppConnectionService.getText(R.string.decryption_failed) + .toString(); + } else if (message.getType() == Message.TYPE_IMAGE) { + return mXmppConnectionService.getText(R.string.image_file) + .toString(); + } else { + return message.getBody().trim(); } } @@ -226,16 +315,19 @@ public class NotificationService { this.mIsInForeground = foreground; } - public void activateGracePeriod() { - this.mEndGracePeriod = SystemClock.elapsedRealtime() - + (Config.CARBON_GRACE_PERIOD * 1000); + private int getPixel(int dp) { + DisplayMetrics metrics = mXmppConnectionService.getResources() + .getDisplayMetrics(); + return ((int) (dp * metrics.density)); } - public void deactivateGracePeriod() { - this.mEndGracePeriod = 0L; + private void markLastNotification() { + this.mLastNotification = SystemClock.elapsedRealtime(); } - private boolean inGracePeriod() { - return SystemClock.elapsedRealtime() < this.mEndGracePeriod; + private boolean inMiniGracePeriod(Account account) { + int miniGrace = account.getStatus() == Account.STATUS_ONLINE ? Config.MINI_GRACE_PERIOD + : Config.MINI_GRACE_PERIOD * 2; + return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace); } } diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index e6297f4f..be73e07f 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -27,6 +27,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; @@ -34,6 +35,7 @@ import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.generator.MessageGenerator; import eu.siacs.conversations.generator.PresenceGenerator; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.parser.MessageParser; import eu.siacs.conversations.parser.PresenceParser; @@ -74,6 +76,7 @@ import android.net.NetworkInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.FileObserver; import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -81,11 +84,12 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.util.Log; +import android.util.LruCache; public class XmppConnectionService extends Service { public DatabaseBackend databaseBackend; - private FileBackend fileBackend; + private FileBackend fileBackend = new FileBackend(this); public long startDate; @@ -94,7 +98,8 @@ public class XmppConnectionService extends Service { private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService; + private NotificationService mNotificationService = new NotificationService( + this); private MessageParser mMessageParser = new MessageParser(this); private PresenceParser mPresenceParser = new PresenceParser(this); @@ -106,20 +111,27 @@ public class XmppConnectionService extends Service { private CopyOnWriteArrayList<Conversation> conversations = null; private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( this); + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( + this); + private AvatarService mAvatarService = new AvatarService(this); private OnConversationUpdate mOnConversationUpdate = null; - private int convChangedListenerCount = 0; + private Integer convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; - private int accountChangedListenerCount = 0; + private Integer accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; - private int rosterChangedListenerCount = 0; + private Integer rosterChangedListenerCount = 0; public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override public void onContactStatusChanged(Contact contact, boolean online) { Conversation conversation = find(getConversations(), contact); if (conversation != null) { - conversation.endOtrIfNeeded(); + if (online && contact.getPresences().size() > 1) { + conversation.endOtrIfNeeded(); + } else { + conversation.resetOtrSession(); + } if (online && (contact.getPresences().size() == 1)) { sendUnsendMessages(conversation); } @@ -140,6 +152,17 @@ public class XmppConnectionService extends Service { } }; + private FileObserver fileObserver = new FileObserver( + FileBackend.getConversationsDirectory()) { + + @Override + public void onEvent(int event, String path) { + if (event == FileObserver.DELETE) { + markFileDeleted(path.split("\\.")[0]); + } + } + }; + private final IBinder mBinder = new XmppConnectionBinder(); private OnStatusChanged statusListener = new OnStatusChanged() { @@ -252,6 +275,7 @@ public class XmppConnectionService extends Service { } } }; + private LruCache<String, Bitmap> mBitmapCache; public PgpEngine getPgpEngine() { if (pgpServiceConnection.isBound()) { @@ -271,6 +295,10 @@ public class XmppConnectionService extends Service { return this.fileBackend; } + public AvatarService getAvatarService() { + return this.mAvatarService; + } + public Message attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { final Message message; @@ -331,15 +359,10 @@ public class XmppConnectionService extends Service { } } this.wakeLock.acquire(); - ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - boolean isConnected = activeNetwork != null - && activeNetwork.isConnected(); for (Account account : accounts) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { - if (!isConnected) { + if (!hasInternetConnection()) { account.setStatus(Account.STATUS_NO_INTERNET); if (statusListener != null) { statusListener.onStatusChanged(account); @@ -398,6 +421,13 @@ public class XmppConnectionService extends Service { return START_STICKY; } + public boolean hasInternetConnection() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + return activeNetwork != null && activeNetwork.isConnected(); + } + @SuppressLint("TrulyRandom") @Override public void onCreate() { @@ -406,10 +436,18 @@ public class XmppConnectionService extends Service { this.mRandom = new SecureRandom(); this.mMemorizingTrustManager = new MemorizingTrustManager( getApplicationContext()); - this.mNotificationService = new NotificationService(this); + + int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + int cacheSize = maxMemory / 8; + this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; + this.databaseBackend = DatabaseBackend .getInstance(getApplicationContext()); - this.fileBackend = new FileBackend(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); for (Account account : this.accounts) { @@ -420,6 +458,7 @@ public class XmppConnectionService extends Service { getContentResolver().registerContentObserver( ContactsContract.Contacts.CONTENT_URI, true, contactObserver); + this.fileObserver.startWatching(); this.pgpServiceConnection = new OpenPgpServiceConnection( getApplicationContext(), "org.sufficientlysecure.keychain"); this.pgpServiceConnection.bindToService(); @@ -511,8 +550,9 @@ public class XmppConnectionService extends Service { return connection; } - synchronized public void sendMessage(Message message) { + public void sendMessage(Message message) { Account account = message.getConversation().getAccount(); + account.deactivateGracePeriod(); Conversation conv = message.getConversation(); MessagePacket packet = null; boolean saveInDb = true; @@ -531,13 +571,14 @@ public class XmppConnectionService extends Service { && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) { mJingleConnectionManager .createNewConnection(message); - } else if (message.getPresence() == null) { - message.setStatus(Message.STATUS_WAITING); } } else { mJingleConnectionManager.createNewConnection(message); } } else { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + conv.startOtrIfNeeded(); + } message.setStatus(Message.STATUS_WAITING); } } else { @@ -554,6 +595,7 @@ public class XmppConnectionService extends Service { send = true; } else if (message.getPresence() == null) { + conv.startOtrIfNeeded(); message.setStatus(Message.STATUS_WAITING); } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { @@ -596,7 +638,7 @@ public class XmppConnectionService extends Service { } } - conv.getMessages().add(message); + conv.add(message); if (saveInDb) { if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { @@ -776,6 +818,7 @@ public class XmppConnectionService extends Service { .getString("photouri")); contact.setSystemName(phoneContact .getString("displayname")); + getAvatarService().clear(contact); } } } @@ -794,12 +837,39 @@ public class XmppConnectionService extends Service { Account account = accountLookupTable.get(conv.getAccountUuid()); conv.setAccount(account); conv.setMessages(databaseBackend.getMessages(conv, 50)); + checkDeletedFiles(conv); } } - return this.conversations; } + private void checkDeletedFiles(Conversation conversation) { + for (Message message : conversation.getMessages()) { + if (message.getType() == Message.TYPE_IMAGE + && message.getEncryption() != Message.ENCRYPTION_PGP) { + if (!getFileBackend().isFileAvailable(message)) { + message.setDownloadable(new DeletedDownloadable()); + } + } + } + } + + private void markFileDeleted(String uuid) { + for (Conversation conversation : getConversations()) { + for (Message message : conversation.getMessages()) { + if (message.getType() == Message.TYPE_IMAGE + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getUuid().equals(uuid)) { + if (!getFileBackend().isFileAvailable(message)) { + message.setDownloadable(new DeletedDownloadable()); + updateConversationUi(); + } + return; + } + } + } + } + public void populateWithOrderedConversations(List<Conversation> list) { populateWithOrderedConversations(list, true); } @@ -838,7 +908,7 @@ public class XmppConnectionService extends Service { for (Message message : messages) { message.setConversation(conversation); } - conversation.getMessages().addAll(0, messages); + conversation.addAll(0, messages); return messages.size(); } @@ -858,9 +928,9 @@ public class XmppConnectionService extends Service { public Conversation find(List<Conversation> haystack, Account account, String jid) { for (Conversation conversation : haystack) { - if ((account == null || conversation.getAccount().equals(account)) + if ((account == null || conversation.getAccount() == account) && (conversation.getContactJid().split("/", 2)[0] - .equals(jid))) { + .equalsIgnoreCase(jid))) { return conversation; } } @@ -927,7 +997,6 @@ public class XmppConnectionService extends Service { public void clearConversationHistory(Conversation conversation) { this.databaseBackend.deleteMessagesInConversation(conversation); - this.fileBackend.removeFiles(conversation); conversation.getMessages().clear(); updateConversationUi(); } @@ -973,60 +1042,85 @@ public class XmppConnectionService extends Service { public void setOnConversationListChangedListener( OnConversationUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, + "ignoring setOnConversationListChangedListener"); + return; + } + synchronized (this.convChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnConversationUpdate = listener; + this.mNotificationService.setIsInForeground(true); + this.convChangedListenerCount++; } - this.mOnConversationUpdate = listener; - this.mNotificationService.setIsInForeground(true); - this.convChangedListenerCount++; } public void removeOnConversationListChangedListener() { - this.convChangedListenerCount--; - if (this.convChangedListenerCount == 0) { - this.mOnConversationUpdate = null; - this.mNotificationService.setIsInForeground(false); - if (checkListeners()) { - switchToBackground(); + synchronized (this.convChangedListenerCount) { + this.convChangedListenerCount--; + if (this.convChangedListenerCount <= 0) { + this.convChangedListenerCount = 0; + this.mOnConversationUpdate = null; + this.mNotificationService.setIsInForeground(false); + if (checkListeners()) { + switchToBackground(); + } } } } public void setOnAccountListChangedListener(OnAccountUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, "ignoring setOnAccountListChangedListener"); + return; + } + synchronized (this.accountChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnAccountUpdate = listener; + this.accountChangedListenerCount++; } - this.mOnAccountUpdate = listener; - this.accountChangedListenerCount++; } public void removeOnAccountListChangedListener() { - this.accountChangedListenerCount--; - if (this.accountChangedListenerCount == 0) { - this.mOnAccountUpdate = null; - if (checkListeners()) { - switchToBackground(); + synchronized (this.accountChangedListenerCount) { + this.accountChangedListenerCount--; + if (this.accountChangedListenerCount <= 0) { + this.mOnAccountUpdate = null; + this.accountChangedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } } } } public void setOnRosterUpdateListener(OnRosterUpdate listener) { - this.mNotificationService.deactivateGracePeriod(); - if (checkListeners()) { - switchToForeground(); + if (!isScreenOn()) { + Log.d(Config.LOGTAG, "ignoring setOnRosterUpdateListener"); + return; + } + synchronized (this.rosterChangedListenerCount) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnRosterUpdate = listener; + this.rosterChangedListenerCount++; } - this.mOnRosterUpdate = listener; - this.rosterChangedListenerCount++; } public void removeOnRosterUpdateListener() { - this.rosterChangedListenerCount--; - if (this.rosterChangedListenerCount == 0) { - this.mOnRosterUpdate = null; - if (checkListeners()) { - switchToBackground(); + synchronized (this.rosterChangedListenerCount) { + this.rosterChangedListenerCount--; + if (this.rosterChangedListenerCount <= 0) { + this.rosterChangedListenerCount = 0; + this.mOnRosterUpdate = null; + if (checkListeners()) { + switchToBackground(); + } } } } @@ -1042,11 +1136,10 @@ public class XmppConnectionService extends Service { XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().csi()) { connection.sendActive(); - Log.d(Config.LOGTAG, account.getJid() - + " sending csi//active"); } } } + Log.d(Config.LOGTAG, "app switched into foreground"); } private void switchToBackground() { @@ -1055,11 +1148,17 @@ public class XmppConnectionService extends Service { XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().csi()) { connection.sendInactive(); - Log.d(Config.LOGTAG, account.getJid() - + " sending csi//inactive"); } } } + this.mNotificationService.setIsInForeground(false); + Log.d(Config.LOGTAG, "app switched into background"); + } + + private boolean isScreenOn() { + PowerManager pm = (PowerManager) this + .getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); } public void connectMultiModeConversations(Account account) { @@ -1197,7 +1296,7 @@ public class XmppConnectionService extends Service { conversation.getMucOptions().setOffline(); conversation.deregisterWithBookmark(); Log.d(Config.LOGTAG, conversation.getAccount().getJid() - + " leaving muc " + conversation.getContactJid()); + + ": leaving muc " + conversation.getContactJid()); } else { account.pendingConferenceLeaves.add(conversation); } @@ -1214,7 +1313,11 @@ public class XmppConnectionService extends Service { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation); } else { - conversation.endOtrIfNeeded(); + if (conversation.endOtrIfNeeded()) { + Log.d(Config.LOGTAG, account.getJid() + + ": ended otr session with " + + conversation.getContactJid()); + } } } } @@ -1230,6 +1333,7 @@ public class XmppConnectionService extends Service { public void updateMessage(Message message) { databaseBackend.updateMessage(message); + updateConversationUi(); } protected void syncDirtyContacts(Account account) { @@ -1410,10 +1514,16 @@ public class XmppConnectionService extends Service { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } + getAvatarService().clear(account); + updateConversationUi(); + updateAccountUi(); } else { Contact contact = account.getRoster() .getContact(avatar.owner); contact.setAvatar(avatar.getFilename()); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); } if (callback != null) { callback.success(avatar); @@ -1463,6 +1573,7 @@ public class XmppConnectionService extends Service { if (account.setAvatar(avatar.getFilename())) { databaseBackend.updateAccount(account); } + getAvatarService().clear(account); callback.success(avatar); } else { fetchAvatar(account, avatar, callback); @@ -1675,6 +1786,10 @@ public class XmppConnectionService extends Service { return this.pm; } + public LruCache<String, Bitmap> getBitmapCache() { + return this.mBitmapCache; + } + public void replyWithNotAcceptable(Account account, MessagePacket packet) { if (account.getStatus() == Account.STATUS_ONLINE) { MessagePacket error = this.mMessageGenerator @@ -1779,8 +1894,7 @@ public class XmppConnectionService extends Service { ArrayList<Contact> contacts = new ArrayList<Contact>(); for (Account account : getAccounts()) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { - Contact contact = account.getRoster() - .getContactAsShownInRoster(jid); + Contact contact = account.getRoster().getContactFromRoster(jid); if (contact != null) { contacts.add(contact); } @@ -1792,4 +1906,44 @@ public class XmppConnectionService extends Service { public NotificationService getNotificationService() { return this.mNotificationService; } + + public HttpConnectionManager getHttpConnectionManager() { + return this.mHttpConnectionManager; + } + + private class DeletedDownloadable implements Downloadable { + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + return Downloadable.STATUS_DELETED; + } + + @Override + public long getFileSize() { + return 0; + } + + } + + public void resendFailedMessages(Message message) { + List<Message> messages = new ArrayList<Message>(); + Message current = message; + while(current.getStatus() == Message.STATUS_SEND_FAILED) { + messages.add(current); + if (current.mergable(current.next())) { + current = current.next(); + } else { + break; + } + } + for(Message msg: messages) { + markMessage(msg, Message.STATUS_WAITING); + this.resendMessage(msg); + } + } } diff --git a/src/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/eu/siacs/conversations/ui/ChooseContactActivity.java index 62a2cbe1..f14da352 100644 --- a/src/eu/siacs/conversations/ui/ChooseContactActivity.java +++ b/src/eu/siacs/conversations/ui/ChooseContactActivity.java @@ -113,7 +113,7 @@ public class ChooseContactActivity extends XmppActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.choose_contact, menu); - MenuItem menuSearchView = (MenuItem) menu.findItem(R.id.action_search); + MenuItem menuSearchView = menu.findItem(R.id.action_search); View mSearchView = menuSearchView.getActionView(); mSearchEditText = (EditText) mSearchView .findViewById(R.id.search_field); diff --git a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 04059d52..52687c81 100644 --- a/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -7,14 +7,12 @@ import org.openintents.openpgp.util.OpenPgpUtils; 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.MucOptions; import eu.siacs.conversations.entities.MucOptions.OnRenameListener; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate; -import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import android.app.PendingIntent; import android.content.Context; @@ -41,6 +39,7 @@ public class ConferenceDetailsActivity extends XmppActivity { private ImageButton mEditNickButton; private TextView mRoleAffiliaton; private TextView mFullJid; + private TextView mAccountJid; private LinearLayout membersView; private LinearLayout mMoreDetails; private Button mInviteButton; @@ -78,6 +77,7 @@ public class ConferenceDetailsActivity extends XmppActivity { mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button); mFullJid = (TextView) findViewById(R.id.muc_jabberid); membersView = (LinearLayout) findViewById(R.id.muc_members); + mAccountJid = (TextView) findViewById(R.id.details_account); mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details); mMoreDetails.setVisibility(View.GONE); mInviteButton = (Button) findViewById(R.id.invite); @@ -169,37 +169,38 @@ public class ConferenceDetailsActivity extends XmppActivity { } protected void registerListener() { - if (xmppConnectionServiceBound) { - xmppConnectionService - .setOnConversationListChangedListener(this.onConvChanged); - xmppConnectionService.setOnRenameListener(new OnRenameListener() { + xmppConnectionService + .setOnConversationListChangedListener(this.onConvChanged); + xmppConnectionService.setOnRenameListener(new OnRenameListener() { - @Override - public void onRename(final boolean success) { - runOnUiThread(new Runnable() { + @Override + public void onRename(final boolean success) { + runOnUiThread(new Runnable() { - @Override - public void run() { - populateView(); - if (success) { - Toast.makeText( - ConferenceDetailsActivity.this, - getString(R.string.your_nick_has_been_changed), - Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(ConferenceDetailsActivity.this, - getString(R.string.nick_in_use), - Toast.LENGTH_SHORT).show(); - } + @Override + public void run() { + populateView(); + if (success) { + Toast.makeText( + ConferenceDetailsActivity.this, + getString(R.string.your_nick_has_been_changed), + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ConferenceDetailsActivity.this, + getString(R.string.nick_in_use), + Toast.LENGTH_SHORT).show(); } - }); - } - }); - } + } + }); + } + }); } private void populateView() { - mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48)); + mAccountJid.setText(getString(R.string.using_account, conversation + .getAccount().getJid())); + mYourPhoto.setImageBitmap(avatarService().get( + conversation.getAccount(), getPixel(48))); setTitle(conversation.getName()); mFullJid.setText(conversation.getContactJid().split("/", 2)[0]); mYourNick.setText(conversation.getMucOptions().getActualNick()); @@ -225,9 +226,8 @@ public class ConferenceDetailsActivity extends XmppActivity { this.users.addAll(conversation.getMucOptions().getUsers()); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); - Account account = conversation.getAccount(); for (final User user : conversation.getMucOptions().getUsers()) { - View view = (View) inflater.inflate(R.layout.contact, membersView, + View view = inflater.inflate(R.layout.contact, membersView, false); TextView name = (TextView) view .findViewById(R.id.contact_display_name); @@ -245,22 +245,14 @@ public class ConferenceDetailsActivity extends XmppActivity { key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } Bitmap bm; - if (user.getJid() != null) { - Contact contact = account.getRoster().getContact(user.getJid()); - if (contact.showInRoster()) { - bm = contact.getImage(48, this); - name.setText(contact.getDisplayName()); - role.setText(user.getName() + " \u2022 " - + getReadableRole(user.getRole())); - } else { - bm = UIHelper.getContactPicture(user.getName(), 48, this, - false); - name.setText(user.getName()); - role.setText(getReadableRole(user.getRole())); - } + Contact contact = user.getContact(); + if (contact != null) { + bm = avatarService().get(contact, getPixel(48)); + name.setText(contact.getDisplayName()); + role.setText(user.getName() + " \u2022 " + + getReadableRole(user.getRole())); } else { - bm = UIHelper - .getContactPicture(user.getName(), 48, this, false); + bm = avatarService().get(user.getName(), getPixel(48)); name.setText(user.getName()); role.setText(getReadableRole(user.getRole())); } diff --git a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java index 9926e126..4c52c609 100644 --- a/src/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -56,7 +56,8 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(DialogInterface dialog, int which) { - ContactDetailsActivity.this.xmppConnectionService.deleteContactOnServer(contact); + ContactDetailsActivity.this.xmppConnectionService + .deleteContactOnServer(contact); ContactDetailsActivity.this.finish(); } }; @@ -78,7 +79,8 @@ public class ContactDetailsActivity extends XmppActivity { @Override public void onClick(View v) { - AlertDialog.Builder builder = new AlertDialog.Builder(ContactDetailsActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder( + ContactDetailsActivity.this); builder.setTitle(getString(R.string.action_add_phone_book)); builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid())); @@ -309,22 +311,21 @@ public class ContactDetailsActivity extends XmppActivity { } else { contactJidTv.setText(contact.getJid()); } - accountJidTv.setText(contact.getAccount().getJid()); - - UIHelper.prepareContactBadge(this, badge, contact, - getApplicationContext()); - + accountJidTv.setText(getString(R.string.using_account, contact + .getAccount().getJid())); + prepareContactBadge(badge, contact); if (contact.getSystemAccount() == null) { badge.setOnClickListener(onBadgeClick); } keys.removeAllViews(); + boolean hasKeys = false; LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); for (Iterator<String> iterator = contact.getOtrFingerprints() .iterator(); iterator.hasNext();) { + hasKeys = true; final String otrFingerprint = iterator.next(); - View view = (View) inflater.inflate(R.layout.contact_key, keys, - false); + View view = inflater.inflate(R.layout.contact_key, keys, false); TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); ImageButton remove = (ImageButton) view @@ -342,8 +343,8 @@ public class ContactDetailsActivity extends XmppActivity { }); } if (contact.getPgpKeyId() != 0) { - View view = (View) inflater.inflate(R.layout.contact_key, keys, - false); + hasKeys = true; + View view = inflater.inflate(R.layout.contact_key, keys, false); TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); keyType.setText("PGP Key ID"); @@ -370,6 +371,20 @@ public class ContactDetailsActivity extends XmppActivity { }); keys.addView(view); } + if (hasKeys) { + keys.setVisibility(View.VISIBLE); + } else { + keys.setVisibility(View.GONE); + } + } + + private void prepareContactBadge(QuickContactBadge badge, Contact contact) { + if (contact.getSystemAccount() != null) { + String[] systemAccount = contact.getSystemAccount().split("#"); + long id = Long.parseLong(systemAccount[0]); + badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1])); + } + badge.setImageBitmap(avatarService().get(contact, getPixel(72))); } protected void confirmToDeleteFingerprint(final String fingerprint) { diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java index ad1cd283..1d7364d6 100644 --- a/src/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -191,6 +191,7 @@ public class ConversationActivity extends XmppActivity implements xmppConnectionService.getNotificationService() .setOpenConversation(null); } + closeContextMenu(); } @Override @@ -222,7 +223,8 @@ public class ConversationActivity extends XmppActivity implements ab.setDisplayHomeAsUpEnabled(true); ab.setHomeButtonEnabled(true); if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE - || ConversationActivity.this.useSubjectToIdentifyConference()) { + || ConversationActivity.this + .useSubjectToIdentifyConference()) { ab.setTitle(getSelectedConversation().getName()); } else { ab.setTitle(getSelectedConversation().getContactJid() @@ -239,19 +241,16 @@ public class ConversationActivity extends XmppActivity implements @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.conversations, menu); - MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security); - MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive); - MenuItem menuMucDetails = (MenuItem) menu - .findItem(R.id.action_muc_details); - MenuItem menuContactDetails = (MenuItem) menu + MenuItem menuSecure = menu.findItem(R.id.action_security); + MenuItem menuArchive = menu.findItem(R.id.action_archive); + MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details); + MenuItem menuContactDetails = menu .findItem(R.id.action_contact_details); - MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file); - MenuItem menuClearHistory = (MenuItem) menu - .findItem(R.id.action_clear_history); - MenuItem menuAdd = (MenuItem) menu.findItem(R.id.action_add); - MenuItem menuInviteContact = (MenuItem) menu - .findItem(R.id.action_invite); - MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute); + MenuItem menuAttach = menu.findItem(R.id.action_attach_file); + MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); + MenuItem menuAdd = menu.findItem(R.id.action_add); + MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + MenuItem menuMute = menu.findItem(R.id.action_mute); if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) { @@ -604,8 +603,11 @@ public class ConversationActivity extends XmppActivity implements .beginTransaction(); transaction.replace(R.id.selected_conversation, selectedFragment, "conversation"); - - transaction.commitAllowingStateLoss(); + try { + transaction.commitAllowingStateLoss(); + } catch (IllegalStateException e) { + return selectedFragment; + } } return selectedFragment; } @@ -624,23 +626,10 @@ public class ConversationActivity extends XmppActivity implements @Override protected void onNewIntent(Intent intent) { if (xmppConnectionServiceBound) { - 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); + if (intent != null && VIEW_CONVERSATION.equals(intent.getType())) { + handleViewConversationIntent(intent); } } else { - handledViewIntent = false; setIntent(intent); } } @@ -690,6 +679,10 @@ public class ConversationActivity extends XmppActivity implements } else if (conversationList.size() <= 0) { startActivity(new Intent(this, StartConversationActivity.class)); finish(); + } else if (getIntent() != null + && VIEW_CONVERSATION.equals(getIntent().getType())) { + handleViewConversationIntent(getIntent()); + setIntent(null); } else if (mOpenConverstaion != null) { selectConversationByUuid(mOpenConverstaion); paneShouldBeOpen = mPanelOpen; @@ -698,14 +691,6 @@ public class ConversationActivity extends XmppActivity implements } swapConversationFragment(); mOpenConverstaion = null; - } else if (getIntent() != null - && VIEW_CONVERSATION.equals(getIntent().getType())) { - String uuid = (String) getIntent().getExtras().get(CONVERSATION); - String text = getIntent().getExtras().getString(TEXT, null); - selectConversationByUuid(uuid); - paneShouldBeOpen = false; - swapConversationFragment().setText(text); - setIntent(null); } else { showConversationsOverview(); ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager() @@ -727,6 +712,14 @@ public class ConversationActivity extends XmppActivity implements ExceptionHelper.checkForCrash(this, this.xmppConnectionService); } + private void handleViewConversationIntent(Intent intent) { + String uuid = (String) intent.getExtras().get(CONVERSATION); + String text = intent.getExtras().getString(TEXT, null); + selectConversationByUuid(uuid); + paneShouldBeOpen = false; + swapConversationFragment().setText(text); + } + private void selectConversationByUuid(String uuid) { for (int i = 0; i < conversationList.size(); ++i) { if (conversationList.get(i).getUuid().equals(uuid)) { @@ -736,11 +729,9 @@ public class ConversationActivity extends XmppActivity implements } public void registerListener() { - if (xmppConnectionServiceBound) { - xmppConnectionService.setOnConversationListChangedListener(this); - xmppConnectionService.setOnAccountListChangedListener(this); - xmppConnectionService.setOnRosterUpdateListener(this); - } + xmppConnectionService.setOnConversationListChangedListener(this); + xmppConnectionService.setOnAccountListChangedListener(this); + xmppConnectionService.setOnRosterUpdateListener(this); } @Override @@ -753,6 +744,7 @@ public class ConversationActivity extends XmppActivity implements .findFragmentByTag("conversation"); if (selectedFragment != null) { selectedFragment.hideSnackbar(); + selectedFragment.updateMessages(); } } else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) { pendingImageUri = data.getData(); @@ -786,6 +778,10 @@ public class ConversationActivity extends XmppActivity implements attachAudioToConversation(getSelectedConversation(), data.getData()); } + } else { + if (requestCode == REQUEST_IMAGE_CAPTURE) { + pendingImageUri = null; + } } } diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java index cdaa7152..20eeeb30 100644 --- a/src/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.ui; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import net.java.otr4j.session.SessionStatus; import eu.siacs.conversations.R; @@ -32,9 +33,12 @@ import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.text.Editable; import android.text.Selection; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -44,6 +48,8 @@ import android.widget.AbsListView.OnScrollListener; import android.widget.TextView.OnEditorActionListener; import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ListView; import android.widget.ImageButton; import android.widget.RelativeLayout; @@ -72,6 +78,9 @@ public class ConversationFragment extends Fragment { private IntentSender askForPassphraseIntent = null; + private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<Message>(); + private boolean mDecryptJobRunning = false; + private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { @Override @@ -189,6 +198,7 @@ public class ConversationFragment extends Fragment { }; private ConversationActivity activity; + private Message selectedMessage; private void sendMessage() { if (this.conversation == null) { @@ -322,9 +332,114 @@ public class ConversationFragment extends Fragment { }); messagesView.setAdapter(messageListAdapter); + registerForContextMenu(messagesView); + return view; } + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; + this.selectedMessage = this.messageList.get(acmi.position); + populateContextMenu(menu); + } + + private void populateContextMenu(ContextMenu menu) { + if (this.selectedMessage.getType() != Message.TYPE_STATUS) { + activity.getMenuInflater().inflate(R.menu.message_context, menu); + menu.setHeaderTitle(R.string.message_options); + MenuItem copyText = menu.findItem(R.id.copy_text); + MenuItem shareImage = menu.findItem(R.id.share_image); + MenuItem sendAgain = menu.findItem(R.id.send_again); + MenuItem copyUrl = menu.findItem(R.id.copy_url); + MenuItem downloadImage = menu.findItem(R.id.download_image); + if (this.selectedMessage.getType() != Message.TYPE_TEXT + || this.selectedMessage.getDownloadable() != null) { + copyText.setVisible(false); + } + if (this.selectedMessage.getType() != Message.TYPE_IMAGE + || this.selectedMessage.getDownloadable() != null) { + shareImage.setVisible(false); + } + if (this.selectedMessage.getStatus() != Message.STATUS_SEND_FAILED) { + sendAgain.setVisible(false); + } + if ((this.selectedMessage.getType() != Message.TYPE_IMAGE && this.selectedMessage + .getDownloadable() == null) + || this.selectedMessage.getImageParams().url == null) { + copyUrl.setVisible(false); + } + + if (this.selectedMessage.getType() != Message.TYPE_TEXT + || this.selectedMessage.getDownloadable() != null + || !this.selectedMessage.bodyContainsDownloadable()) { + downloadImage.setVisible(false); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.share_image: + shareImage(selectedMessage); + return true; + case R.id.copy_text: + copyText(selectedMessage); + return true; + case R.id.send_again: + resendMessage(selectedMessage); + return true; + case R.id.copy_url: + copyUrl(selectedMessage); + return true; + case R.id.download_image: + downloadImage(selectedMessage); + return true; + default: + return super.onContextItemSelected(item); + } + } + + private void shareImage(Message message) { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, + activity.xmppConnectionService.getFileBackend() + .getJingleFileUri(message)); + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + shareIntent.setType("image/webp"); + activity.startActivity(Intent.createChooser(shareIntent, + getText(R.string.share_with))); + } + + private void copyText(Message message) { + if (activity.copyTextToClipboard(message.getMergedBody(), + R.string.message_text)) { + Toast.makeText(activity, R.string.message_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } + + private void resendMessage(Message message) { + activity.xmppConnectionService.resendFailedMessages(message); + } + + private void copyUrl(Message message) { + if (activity.copyTextToClipboard( + message.getImageParams().url.toString(), R.string.image_url)) { + Toast.makeText(activity, R.string.url_copied_to_clipboard, + Toast.LENGTH_SHORT).show(); + } + } + + private void downloadImage(Message message) { + activity.xmppConnectionService.getHttpConnectionManager() + .createNewConnection(message); + } + protected void privateMessageWith(String counterpart) { this.mEditMessage.setText(""); this.conversation.setNextPresence(counterpart); @@ -356,6 +471,7 @@ public class ConversationFragment extends Fragment { @Override public void onStop() { + mDecryptJobRunning = false; super.onStop(); if (this.conversation != null) { this.conversation.setNextMessage(mEditMessage.getText().toString()); @@ -395,34 +511,6 @@ public class ConversationFragment extends Fragment { updateMessages(); } - private void decryptMessage(Message message) { - PgpEngine engine = activity.xmppConnectionService.getPgpEngine(); - if (engine != null) { - engine.decrypt(message, new UiCallback<Message>() { - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - askForPassphraseIntent = pi.getIntentSender(); - showSnackbar(R.string.openpgp_messages_found, - R.string.decrypt, clickToDecryptListener); - } - - @Override - public void success(Message message) { - activity.xmppConnectionService.databaseBackend - .updateMessage(message); - updateMessages(); - } - - @Override - public void error(int error, Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); - // updateMessages(); - } - }); - } - } - public void updateMessages() { if (getView() == null) { return; @@ -458,13 +546,16 @@ public class ConversationFragment extends Fragment { }); } for (Message message : this.conversation.getMessages()) { - if ((message.getEncryption() == Message.ENCRYPTION_PGP) - && ((message.getStatus() == Message.STATUS_RECEIVED) || (message - .getStatus() == Message.STATUS_SEND))) { - decryptMessage(message); - break; + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message + .getStatus() >= Message.STATUS_SEND) + && message.getDownloadable() == null) { + if (!mEncryptedMessages.contains(message)) { + mEncryptedMessages.add(message); + } } } + decryptNext(); this.messageList.clear(); if (this.conversation.getMessages().size() == 0) { messagesLoaded = false; @@ -476,7 +567,7 @@ public class ConversationFragment extends Fragment { this.messageListAdapter.notifyDataSetChanged(); if (conversation.getMode() == Conversation.MODE_SINGLE) { if (messageList.size() >= 1) { - makeFingerprintWarning(conversation.getLatestEncryption()); + makeFingerprintWarning(); } } else { if (!conversation.getMucOptions().online() @@ -522,6 +613,40 @@ public class ConversationFragment extends Fragment { } } + private void decryptNext() { + Message next = this.mEncryptedMessages.peek(); + PgpEngine engine = activity.xmppConnectionService.getPgpEngine(); + + if (next != null && engine != null && !mDecryptJobRunning) { + mDecryptJobRunning = true; + engine.decrypt(next, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + mDecryptJobRunning = false; + askForPassphraseIntent = pi.getIntentSender(); + showSnackbar(R.string.openpgp_messages_found, + R.string.decrypt, clickToDecryptListener); + } + + @Override + public void success(Message message) { + mDecryptJobRunning = false; + mEncryptedMessages.remove(); + activity.xmppConnectionService.updateMessage(message); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + mDecryptJobRunning = false; + mEncryptedMessages.remove(); + activity.xmppConnectionService.updateConversationUi(); + } + }); + } + } + private void messageSent() { int size = this.messageList.size(); messagesView.setSelection(size - 1); @@ -594,14 +719,13 @@ public class ConversationFragment extends Fragment { } } - protected void makeFingerprintWarning(int latestEncryption) { + protected void makeFingerprintWarning() { Set<String> knownFingerprints = conversation.getContact() .getOtrFingerprints(); - if ((latestEncryption == Message.ENCRYPTION_OTR) - && (conversation.hasValidOtrSession() + if (conversation.hasValidOtrSession() && (!conversation.isMuted()) && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints - .contains(conversation.getOtrFingerprint())))) { + .contains(conversation.getOtrFingerprint()))) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, new OnClickListener() { diff --git a/src/eu/siacs/conversations/ui/EditAccountActivity.java b/src/eu/siacs/conversations/ui/EditAccountActivity.java index 0ec38547..58ca49cc 100644 --- a/src/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1,8 +1,6 @@ package eu.siacs.conversations.ui; import android.app.PendingIntent; -import android.content.ClipData; -import android.content.ClipboardManager; import android.content.Intent; import android.os.Bundle; import android.text.Editable; @@ -17,6 +15,7 @@ import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import eu.siacs.conversations.R; @@ -43,7 +42,7 @@ public class EditAccountActivity extends XmppActivity { private TextView mServerInfoPep; private TextView mSessionEst; private TextView mOtrFingerprint; - private TextView mOtrFingerprintHeadline; + private RelativeLayout mOtrFingerprintBox; private ImageButton mOtrFingerprintToClipboardButton; private String jidToEdit; @@ -277,7 +276,7 @@ public class EditAccountActivity extends XmppActivity { this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint); - this.mOtrFingerprintHeadline = (TextView) findViewById(R.id.otr_fingerprint_headline); + this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box); this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard); this.mSaveButton = (Button) findViewById(R.id.save_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button); @@ -378,8 +377,7 @@ public class EditAccountActivity extends XmppActivity { final String fingerprint = this.mAccount .getOtrFingerprint(xmppConnectionService); if (fingerprint != null) { - this.mOtrFingerprintHeadline.setVisibility(View.VISIBLE); - this.mOtrFingerprint.setVisibility(View.VISIBLE); + this.mOtrFingerprintBox.setVisibility(View.VISIBLE); this.mOtrFingerprint.setText(fingerprint); this.mOtrFingerprintToClipboardButton .setVisibility(View.VISIBLE); @@ -389,7 +387,7 @@ public class EditAccountActivity extends XmppActivity { @Override public void onClick(View v) { - if (OtrFingerprintToClipBoard(fingerprint)) { + if (copyTextToClipboard(fingerprint,R.string.otr_fingerprint)) { Toast.makeText( EditAccountActivity.this, R.string.toast_message_otr_fingerprint, @@ -398,9 +396,7 @@ public class EditAccountActivity extends XmppActivity { } }); } else { - this.mOtrFingerprintToClipboardButton.setVisibility(View.GONE); - this.mOtrFingerprint.setVisibility(View.GONE); - this.mOtrFingerprintHeadline.setVisibility(View.GONE); + this.mOtrFingerprintBox.setVisibility(View.GONE); } } else { if (this.mAccount.errorStatus()) { @@ -411,15 +407,4 @@ public class EditAccountActivity extends XmppActivity { this.mStats.setVisibility(View.GONE); } } - - private boolean OtrFingerprintToClipBoard(String fingerprint) { - ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - String label = getResources().getString(R.string.otr_fingerprint); - if (mClipBoardManager != null) { - ClipData mClipData = ClipData.newPlainText(label, fingerprint); - mClipBoardManager.setPrimaryClip(mClipData); - return true; - } - return false; - } } diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java index afe9e06e..77f8b68a 100644 --- a/src/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -70,7 +70,8 @@ public class ManageAccountActivity extends XmppActivity { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); - ManageAccountActivity.this.getMenuInflater().inflate(R.menu.manageaccounts_context, menu); + ManageAccountActivity.this.getMenuInflater().inflate( + R.menu.manageaccounts_context, menu); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; this.selectedAccount = accountList.get(acmi.position); if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) { @@ -122,6 +123,7 @@ public class ManageAccountActivity extends XmppActivity { return true; case R.id.mgmt_account_announce_pgp: publishOpenPGPPublicKey(selectedAccount); + return true; default: return super.onContextItemSelected(item); } @@ -187,7 +189,8 @@ public class ManageAccountActivity extends XmppActivity { } private void deleteAccount(final Account account) { - AlertDialog.Builder builder = new AlertDialog.Builder(ManageAccountActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder( + ManageAccountActivity.this); builder.setTitle(getString(R.string.mgmt_account_are_you_sure)); builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text)); diff --git a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index f46d92f9..6aa40c41 100644 --- a/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -158,8 +158,8 @@ public class PublishProfilePictureActivity extends XmppActivity { if (this.avatarUri == null) { if (this.account.getAvatar() != null || this.defaultUri == null) { - this.avatar.setImageBitmap(this.account.getImage( - getApplicationContext(), 384)); + this.avatar.setImageBitmap(avatarService().get(account, + getPixel(194))); if (this.defaultUri != null) { this.avatar .setOnLongClickListener(this.backToDefaultListener); diff --git a/src/eu/siacs/conversations/ui/StartConversationActivity.java b/src/eu/siacs/conversations/ui/StartConversationActivity.java index a1a2d4c2..416e926a 100644 --- a/src/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/eu/siacs/conversations/ui/StartConversationActivity.java @@ -463,11 +463,11 @@ public class StartConversationActivity extends XmppActivity { public boolean onCreateOptionsMenu(Menu menu) { this.mOptionsMenu = menu; getMenuInflater().inflate(R.menu.start_conversation, menu); - MenuItem menuCreateContact = (MenuItem) menu + MenuItem menuCreateContact = menu .findItem(R.id.action_create_contact); - MenuItem menuCreateConference = (MenuItem) menu + MenuItem menuCreateConference = menu .findItem(R.id.action_join_conference); - mMenuSearchView = (MenuItem) menu.findItem(R.id.action_search); + mMenuSearchView = menu.findItem(R.id.action_search); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener); View mSearchView = mMenuSearchView.getActionView(); mSearchEditText = (EditText) mSearchView diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java index cd77557c..222f3295 100644 --- a/src/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -12,6 +12,7 @@ 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.AvatarService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; import eu.siacs.conversations.utils.ExceptionHelper; @@ -20,6 +21,8 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.AlertDialog.Builder; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -55,7 +58,6 @@ public abstract class XmppActivity extends Activity { public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; - protected boolean handledViewIntent = false; protected int mPrimaryTextColor; protected int mSecondaryTextColor; @@ -400,8 +402,7 @@ public abstract class XmppActivity extends Activity { private void quickEdit(final String previousValue, final OnValueEdited callback, boolean password) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - View view = (View) getLayoutInflater() - .inflate(R.layout.quickedit, null); + View view = getLayoutInflater().inflate(R.layout.quickedit, null); final EditText editor = (EditText) view.findViewById(R.id.editor); OnClickListener mClickListener = new OnClickListener() { @@ -448,7 +449,7 @@ public abstract class XmppActivity extends Activity { listener.onPresenceSelected(); } } else if (presences.size() == 1) { - String presence = (String) presences.asStringArray()[0]; + String presence = presences.asStringArray()[0]; conversation.setNextPresence(presence); listener.onPresenceSelected(); } else { @@ -526,6 +527,26 @@ public abstract class XmppActivity extends Activity { return this.mSecondaryBackgroundColor; } + public int getPixel(int dp) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + return ((int) (dp * metrics.density)); + } + + public boolean copyTextToClipboard(String text,int labelResId) { + ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + String label = getResources().getString(labelResId); + if (mClipBoardManager != null) { + ClipData mClipData = ClipData.newPlainText(label, text); + mClipBoardManager.setPrimaryClip(mClipData); + return true; + } + return false; + } + + public AvatarService avatarService() { + return xmppConnectionService.getAvatarService(); + } + class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { private final WeakReference<ImageView> imageViewReference; private Message message = null; diff --git a/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 5c25bf34..e13b3204 100644 --- a/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -28,13 +28,14 @@ public class AccountAdapter extends ArrayAdapter<Account> { if (view == null) { LayoutInflater inflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = (View) inflater.inflate(R.layout.account_row, parent, false); + view = inflater.inflate(R.layout.account_row, parent, false); } TextView jid = (TextView) view.findViewById(R.id.account_jid); jid.setText(account.getJid()); TextView statusView = (TextView) view.findViewById(R.id.account_status); ImageView imageView = (ImageView) view.findViewById(R.id.account_image); - imageView.setImageBitmap(account.getImage(activity, 48)); + imageView.setImageBitmap(activity.avatarService().get(account, + activity.getPixel(48))); switch (account.getStatus()) { case Account.STATUS_DISABLED: statusView.setText(getContext().getString( diff --git a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index f74856b0..b5c20dc5 100644 --- a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -5,6 +5,7 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.XmppActivity; @@ -34,14 +35,14 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { if (view == null) { LayoutInflater inflater = (LayoutInflater) activity .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = (View) inflater.inflate(R.layout.conversation_list_row, + view = inflater.inflate(R.layout.conversation_list_row, parent, false); } - Conversation conv = getItem(position); + Conversation conversation = getItem(position); if (this.activity instanceof ConversationActivity) { ConversationActivity activity = (ConversationActivity) this.activity; if (!activity.isConversationsOverviewHideable()) { - if (conv == activity.getSelectedConversation()) { + if (conversation == activity.getSelectedConversation()) { view.setBackgroundColor(activity .getSecondaryBackgroundColor()); } else { @@ -53,65 +54,85 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { } TextView convName = (TextView) view .findViewById(R.id.conversation_name); - if (conv.getMode() == Conversation.MODE_SINGLE + if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { - convName.setText(conv.getName()); + convName.setText(conversation.getName()); } else { - convName.setText(conv.getContactJid().split("/")[0]); + convName.setText(conversation.getContactJid().split("/")[0]); } - TextView convLastMsg = (TextView) view + TextView mLastMessage = (TextView) view .findViewById(R.id.conversation_lastmsg); + TextView mTimestamp = (TextView) view + .findViewById(R.id.conversation_lastupdate); ImageView imagePreview = (ImageView) view .findViewById(R.id.conversation_lastimage); - Message latestMessage = conv.getLatestMessage(); + Message message = conversation.getLatestMessage(); - if (latestMessage.getType() == Message.TYPE_TEXT - || latestMessage.getType() == Message.TYPE_PRIVATE) { - if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP) - && (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { - String body = Config.PARSE_EMOTICONS ? UIHelper - .transformAsciiEmoticons(latestMessage.getBody()) - : latestMessage.getBody(); - convLastMsg.setText(body); + if (!conversation.isRead()) { + convName.setTypeface(null, Typeface.BOLD); + } else { + convName.setTypeface(null, Typeface.NORMAL); + } + + if (message.getType() == Message.TYPE_IMAGE + || message.getDownloadable() != null) { + Downloadable d = message.getDownloadable(); + if (conversation.isRead()) { + mLastMessage.setTypeface(null, Typeface.ITALIC); } else { - convLastMsg.setText(R.string.encrypted_message_received); + mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC); } - convLastMsg.setVisibility(View.VISIBLE); - imagePreview.setVisibility(View.GONE); - } else if (latestMessage.getType() == Message.TYPE_IMAGE) { - if (latestMessage.getStatus() >= Message.STATUS_RECEIVED) { - convLastMsg.setVisibility(View.GONE); - imagePreview.setVisibility(View.VISIBLE); - activity.loadBitmap(latestMessage, imagePreview); - } else { - convLastMsg.setVisibility(View.VISIBLE); + if (d != null) { + mLastMessage.setVisibility(View.VISIBLE); imagePreview.setVisibility(View.GONE); - if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) { - convLastMsg.setText(R.string.image_offered_for_download); - } else if (latestMessage.getStatus() == Message.STATUS_RECEIVING) { - convLastMsg.setText(R.string.receiving_image); + if (d.getStatus() == Downloadable.STATUS_CHECKING) { + mLastMessage.setText(R.string.checking_image); + } else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) { + mLastMessage.setText(R.string.receiving_image); + } else if (d.getStatus() == Downloadable.STATUS_OFFER) { + mLastMessage.setText(R.string.image_offered_for_download); + } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { + mLastMessage.setText(R.string.image_offered_for_download); + } else if (d.getStatus() == Downloadable.STATUS_DELETED) { + mLastMessage.setText(R.string.image_file_deleted); } else { - convLastMsg.setText(""); + mLastMessage.setText(""); } + } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { + imagePreview.setVisibility(View.GONE); + mLastMessage.setVisibility(View.VISIBLE); + mLastMessage.setText(R.string.encrypted_message_received); + } else { + mLastMessage.setVisibility(View.GONE); + imagePreview.setVisibility(View.VISIBLE); + activity.loadBitmap(message, imagePreview); } - } - - if (!conv.isRead()) { - convName.setTypeface(null, Typeface.BOLD); - convLastMsg.setTypeface(null, Typeface.BOLD); } else { - convName.setTypeface(null, Typeface.NORMAL); - convLastMsg.setTypeface(null, Typeface.NORMAL); + if ((message.getEncryption() != Message.ENCRYPTION_PGP) + && (message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) { + String body = Config.PARSE_EMOTICONS ? UIHelper + .transformAsciiEmoticons(message.getBody()) : message + .getBody(); + mLastMessage.setText(body); + } else { + mLastMessage.setText(R.string.encrypted_message_received); + } + if (!conversation.isRead()) { + mLastMessage.setTypeface(null, Typeface.BOLD); + } else { + mLastMessage.setTypeface(null, Typeface.NORMAL); + } + mLastMessage.setVisibility(View.VISIBLE); + imagePreview.setVisibility(View.GONE); } - - ((TextView) view.findViewById(R.id.conversation_lastupdate)) - .setText(UIHelper.readableTimeDifference(getContext(), conv - .getLatestMessage().getTimeSent())); + mTimestamp.setText(UIHelper.readableTimeDifference(getContext(), + conversation.getLatestMessage().getTimeSent())); ImageView profilePicture = (ImageView) view .findViewById(R.id.conversation_image); - profilePicture.setImageBitmap(conv.getImage(activity, 56)); + profilePicture.setImageBitmap(activity.avatarService().get( + conversation, activity.getPixel(56))); return view; } diff --git a/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 0534bc25..143dfda1 100644 --- a/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -47,11 +47,11 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { @Override protected void publishResults(CharSequence constraint, FilterResults results) { - ArrayList<String> filteredList = (ArrayList<String>) results.values; + ArrayList filteredList = (ArrayList) results.values; if (results != null && results.count > 0) { clear(); - for (String c : filteredList) { - add(c); + for (Object c : filteredList) { + add((String) c); } notifyDataSetChanged(); } @@ -71,4 +71,4 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { public Filter getFilter() { return domainFilter; } -}
\ No newline at end of file +} diff --git a/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index df67e566..efc6b4d9 100644 --- a/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -4,6 +4,7 @@ import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.ui.XmppActivity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -14,8 +15,11 @@ import android.widget.TextView; public class ListItemAdapter extends ArrayAdapter<ListItem> { - public ListItemAdapter(Context context, List<ListItem> objects) { - super(context, 0, objects); + protected XmppActivity activity; + + public ListItemAdapter(XmppActivity activity, List<ListItem> objects) { + super(activity, 0, objects); + this.activity = activity; } @Override @@ -24,7 +28,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); ListItem item = getItem(position); if (view == null) { - view = (View) inflater.inflate(R.layout.contact, parent, false); + view = inflater.inflate(R.layout.contact, parent, false); } TextView name = (TextView) view.findViewById(R.id.contact_display_name); TextView jid = (TextView) view.findViewById(R.id.contact_jid); @@ -32,7 +36,8 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { jid.setText(item.getJid()); name.setText(item.getDisplayName()); - picture.setImageBitmap(item.getImage(48, getContext())); + picture.setImageBitmap(activity.avatarService().get(item, + activity.getPixel(48))); return view; } diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 2671cf50..a24f90d7 100644 --- a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.ui.adapter; -import java.util.HashMap; import java.util.List; import eu.siacs.conversations.Config; @@ -9,11 +8,10 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Message.ImageParams; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.utils.UIHelper; -import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.graphics.Typeface; import android.text.Spannable; import android.text.SpannableString; @@ -40,31 +38,26 @@ public class MessageAdapter extends ArrayAdapter<Message> { private ConversationActivity activity; - private Bitmap accountBitmap; - - private BitmapCache mBitmapCache = new BitmapCache(); private DisplayMetrics metrics; private OnContactPictureClicked mOnContactPictureClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; + private OnLongClickListener openContextMenu = new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + v.showContextMenu(); + return true; + } + }; + public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; metrics = getContext().getResources().getDisplayMetrics(); } - private Bitmap getSelfBitmap() { - if (this.accountBitmap == null) { - - if (getCount() > 0) { - this.accountBitmap = getItem(0).getConversation().getAccount() - .getImage(getContext(), 48); - } - } - return this.accountBitmap; - } - public void setOnContactPictureClicked(OnContactPictureClicked listener) { this.mOnContactPictureClickedListener = listener; } @@ -101,13 +94,14 @@ public class MessageAdapter extends ArrayAdapter<Message> { } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getMergedStatus() <= Message.STATUS_RECEIVED; - if (message.getType() == Message.TYPE_IMAGE) { - String[] fileParams = message.getBody().split(","); - try { - long size = Long.parseLong(fileParams[0]); - filesize = size / 1024 + " KB"; - } catch (NumberFormatException e) { - filesize = "0 KB"; + if (message.getType() == Message.TYPE_IMAGE + || message.getDownloadable() != null) { + ImageParams params = message.getImageParams(); + if (params.size != 0) { + filesize = params.size / 1024 + " KB"; + } + if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) { + error = true; } } switch (message.getMergedStatus()) { @@ -134,14 +128,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { info = getContext().getString(R.string.send_failed); error = true; break; - case Message.STATUS_SEND_REJECTED: - info = getContext().getString(R.string.send_rejected); - error = true; - break; - case Message.STATUS_RECEPTION_FAILED: - info = getContext().getString(R.string.reception_failed); - error = true; - break; default: if (multiReceived) { Contact contact = message.getContact(); @@ -268,6 +254,22 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setTextIsSelectable(true); } + private void displayDownloadableMessage(ViewHolder viewHolder, + final Message message, int resid) { + viewHolder.image.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(resid); + viewHolder.download_button.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + startDonwloadable(message); + } + }); + viewHolder.download_button.setOnLongClickListener(openContextMenu); + } + private void displayImageMessage(ViewHolder viewHolder, final Message message) { if (viewHolder.download_button != null) { @@ -275,23 +277,19 @@ public class MessageAdapter extends ArrayAdapter<Message> { } viewHolder.messageBody.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); - String[] fileParams = message.getBody().split(","); - if (fileParams.length == 3) { - double target = metrics.density * 288; - int w = Integer.parseInt(fileParams[1]); - int h = Integer.parseInt(fileParams[2]); - int scalledW; - int scalledH; - if (w <= h) { - scalledW = (int) (w / ((double) h / target)); - scalledH = (int) target; - } else { - scalledW = (int) target; - scalledH = (int) (h / ((double) w / target)); - } - viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH)); + ImageParams params = message.getImageParams(); + double target = metrics.density * 288; + int scalledW; + int scalledH; + if (params.width <= params.height) { + scalledW = (int) (params.width / ((double) params.height / target)); + scalledH = (int) target; + } else { + scalledW = (int) target; + scalledH = (int) (params.height / ((double) params.width / target)); } + viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( + scalledW, scalledH)); activity.loadBitmap(message, viewHolder.image); viewHolder.image.setOnClickListener(new OnClickListener() { @@ -303,23 +301,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { getContext().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, - activity.xmppConnectionService.getFileBackend() - .getJingleFileUri(message)); - shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.setType("image/webp"); - getContext().startActivity( - Intent.createChooser(shareIntent, - getContext().getText(R.string.share_with))); - return true; - } - }); + viewHolder.image.setOnLongClickListener(openContextMenu); } @Override @@ -331,17 +313,22 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder = new ViewHolder(); switch (type) { case NULL: - view = (View) activity.getLayoutInflater().inflate( + view = activity.getLayoutInflater().inflate( R.layout.message_null, parent, false); break; case SENT: - view = (View) activity.getLayoutInflater().inflate( + view = activity.getLayoutInflater().inflate( R.layout.message_sent, parent, false); viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.contact_picture.setImageBitmap(getSelfBitmap()); + viewHolder.contact_picture.setImageBitmap(activity + .avatarService().get( + item.getConversation().getAccount(), + activity.getPixel(48))); + viewHolder.download_button = (Button) view + .findViewById(R.id.download_button); viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view @@ -355,21 +342,18 @@ public class MessageAdapter extends ArrayAdapter<Message> { view.setTag(viewHolder); break; case RECEIVED: - view = (View) activity.getLayoutInflater().inflate( + view = activity.getLayoutInflater().inflate( R.layout.message_received, parent, false); viewHolder.message_box = (LinearLayout) view .findViewById(R.id.message_box); viewHolder.contact_picture = (ImageView) view .findViewById(R.id.message_photo); - viewHolder.download_button = (Button) view .findViewById(R.id.download_button); - if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { - - viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - item.getConversation().getContact(), getContext())); - + viewHolder.contact_picture.setImageBitmap(activity + .avatarService().get(item.getContact(), + activity.getPixel(48))); } viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); @@ -382,15 +366,17 @@ public class MessageAdapter extends ArrayAdapter<Message> { view.setTag(viewHolder); break; case STATUS: - view = (View) activity.getLayoutInflater().inflate( + view = activity.getLayoutInflater().inflate( R.layout.message_status, parent, false); 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().getContact(), getContext())); - viewHolder.contact_picture.setAlpha(128); + viewHolder.contact_picture.setImageBitmap(activity + .avatarService().get( + item.getConversation().getContact(), + activity.getPixel(32))); + viewHolder.contact_picture.setAlpha(0.5f); viewHolder.contact_picture .setOnClickListener(new OnClickListener() { @@ -465,38 +451,40 @@ public class MessageAdapter extends ArrayAdapter<Message> { if (item.getConversation().getMode() == Conversation.MODE_MULTI) { Contact contact = item.getContact(); if (contact != null) { - viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - contact, getContext())); + viewHolder.contact_picture.setImageBitmap(activity + .avatarService() + .get(contact, activity.getPixel(48))); } else { String name = item.getPresence(); if (name == null) { name = item.getCounterpart(); } - viewHolder.contact_picture.setImageBitmap(mBitmapCache.get( - name, getContext())); + viewHolder.contact_picture.setImageBitmap(activity + .avatarService().get(name, activity.getPixel(48))); } } } - if (item.getType() == Message.TYPE_IMAGE) { - if (item.getStatus() == Message.STATUS_RECEIVING) { + if (item.getType() == Message.TYPE_IMAGE + || item.getDownloadable() != null) { + Downloadable d = item.getDownloadable(); + if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) { displayInfoMessage(viewHolder, R.string.receiving_image); - } else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) { - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.GONE); - viewHolder.download_button.setVisibility(View.VISIBLE); - viewHolder.download_button - .setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Downloadable downloadable = item - .getDownloadable(); - if (downloadable != null) { - downloadable.start(); - } - } - }); + } else if (d != null + && d.getStatus() == Downloadable.STATUS_CHECKING) { + displayInfoMessage(viewHolder, R.string.checking_image); + } else if (d != null + && d.getStatus() == Downloadable.STATUS_DELETED) { + displayInfoMessage(viewHolder, R.string.image_file_deleted); + } else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) { + displayDownloadableMessage(viewHolder, item, + R.string.download_image); + } else if (d != null + && d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, item, + R.string.check_image_filesize); + } else if (d != null && d.getStatus() == Downloadable.STATUS_FAILED) { + displayInfoMessage(viewHolder, R.string.image_transmission_failed); } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED) || (item.getEncryption() == Message.ENCRYPTION_NONE) || (item.getEncryption() == Message.ENCRYPTION_OTR)) { @@ -534,6 +522,16 @@ public class MessageAdapter extends ArrayAdapter<Message> { return view; } + public void startDonwloadable(Message message) { + Downloadable downloadable = message.getDownloadable(); + if (downloadable != null) { + if (!downloadable.start()) { + Toast.makeText(activity, R.string.not_connected_try_again, + Toast.LENGTH_SHORT).show(); + } + } + } + private static class ViewHolder { protected LinearLayout message_box; @@ -547,30 +545,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { } - private class BitmapCache { - private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>(); - private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>(); - - public Bitmap get(Contact contact, Context context) { - if (!contactBitmaps.containsKey(contact.getJid())) { - contactBitmaps.put(contact.getJid(), - contact.getImage(48, context)); - } - return contactBitmaps.get(contact.getJid()); - } - - public Bitmap get(String name, Context context) { - if (unknownBitmaps.containsKey(name)) { - return unknownBitmaps.get(name); - } else { - Bitmap bm = UIHelper - .getContactPicture(name, 48, context, false); - unknownBitmaps.put(name, bm); - return bm; - } - } - } - public interface OnContactPictureClicked { public void onContactPictureClicked(Message message); } diff --git a/src/eu/siacs/conversations/utils/CryptoHelper.java b/src/eu/siacs/conversations/utils/CryptoHelper.java index a28b519e..47595c6e 100644 --- a/src/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/eu/siacs/conversations/utils/CryptoHelper.java @@ -5,7 +5,6 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.Arrays; import eu.siacs.conversations.entities.Account; import android.util.Base64; @@ -28,9 +27,11 @@ public class CryptoHelper { } public static byte[] hexToBytes(String hexString) { - byte[] array = new BigInteger(hexString, 16).toByteArray(); - if (array[0] == 0) { - array = Arrays.copyOfRange(array, 1, array.length); + int len = hexString.length(); + byte[] array = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character + .digit(hexString.charAt(i + 1), 16)); } return array; } diff --git a/src/eu/siacs/conversations/utils/DNSHelper.java b/src/eu/siacs/conversations/utils/DNSHelper.java index c51a75ac..f101e883 100644 --- a/src/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/eu/siacs/conversations/utils/DNSHelper.java @@ -33,7 +33,7 @@ public class DNSHelper { for (String dnsserver : dns) { InetAddress ip = InetAddress.getByName(dnsserver); Bundle b = queryDNS(host, ip); - if (b.containsKey("name")) { + if (b.containsKey("values")) { return b; } else if (b.containsKey("error") && "nosrv".equals(b.getString("error", null))) { @@ -45,7 +45,7 @@ public class DNSHelper { } public static Bundle queryDNS(String host, InetAddress dnsServer) { - Bundle namePort = new Bundle(); + Bundle bundle = new Bundle(); try { String qname = "_xmpp-client._tcp." + host; Log.d(Config.LOGTAG, @@ -133,42 +133,28 @@ public class DNSHelper { } if (result.size() == 0) { - namePort.putString("error", "nosrv"); - return namePort; + bundle.putString("error", "nosrv"); + return bundle; } - // we now have a list of servers to try :-) - - // classic name/port pair - String resultName = result.get(0).getName(); - namePort.putString("name", resultName); - namePort.putInt("port", result.get(0).getPort()); - - if (ips4.containsKey(resultName)) { - // we have an ip! - ArrayList<String> ip = ips4.get(resultName); - Collections.shuffle(ip, rnd); - namePort.putString("ipv4", ip.get(0)); - } - if (ips6.containsKey(resultName)) { - ArrayList<String> ip = ips6.get(resultName); - Collections.shuffle(ip, rnd); - namePort.putString("ipv6", ip.get(0)); - } - - // add all other records - int i = 0; + ArrayList<Bundle> values = new ArrayList<Bundle>(); for (SRV srv : result) { - namePort.putString("name" + i, srv.getName()); - namePort.putInt("port" + i, srv.getPort()); - i++; + Bundle namePort = new Bundle(); + namePort.putString("name", srv.getName()); + namePort.putInt("port", srv.getPort()); + if (ips4.containsKey(srv.getName())) { + ArrayList<String> ip = ips4.get(srv.getName()); + Collections.shuffle(ip, rnd); + namePort.putString("ipv4", ip.get(0)); + } + values.add(namePort); } - + bundle.putParcelableArrayList("values", values); } catch (SocketTimeoutException e) { - namePort.putString("error", "timeout"); + bundle.putString("error", "timeout"); } catch (Exception e) { - namePort.putString("error", "unhandled"); + bundle.putString("error", "unhandled"); } - return namePort; + return bundle; } final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); diff --git a/src/eu/siacs/conversations/utils/ExifHelper.java b/src/eu/siacs/conversations/utils/ExifHelper.java new file mode 100644 index 00000000..ceda7293 --- /dev/null +++ b/src/eu/siacs/conversations/utils/ExifHelper.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.siacs.conversations.utils; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; + +public class ExifHelper { + private static final String TAG = "CameraExif"; + + public static int getOrientation(InputStream is) { + if (is == null) { + return 0; + } + + byte[] buf = new byte[8]; + int length = 0; + + // ISO/IEC 10918-1:1993(E) + while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) { + int marker = buf[1] & 0xFF; + + // Check if the marker is a padding. + if (marker == 0xFF) { + continue; + } + + // Check if the marker is SOI or TEM. + if (marker == 0xD8 || marker == 0x01) { + continue; + } + // Check if the marker is EOI or SOS. + if (marker == 0xD9 || marker == 0xDA) { + return 0; + } + + // Get the length and check if it is reasonable. + if (!read(is, buf, 2)) { + return 0; + } + length = pack(buf, 0, 2, false); + if (length < 2) { + Log.e(TAG, "Invalid length"); + return 0; + } + length -= 2; + + // Break if the marker is EXIF in APP1. + if (marker == 0xE1 && length >= 6) { + if (!read(is, buf, 6)) return 0; + length -= 6; + if (pack(buf, 0, 4, false) == 0x45786966 && + pack(buf, 4, 2, false) == 0) { + break; + } + } + + // Skip other markers. + try { + is.skip(length); + } catch (IOException ex) { + return 0; + } + length = 0; + } + + // JEITA CP-3451 Exif Version 2.2 + if (length > 8) { + int offset = 0; + byte[] jpeg = new byte[length]; + if (!read(is, jpeg, length)) { + return 0; + } + + // Identify the byte order. + int tag = pack(jpeg, offset, 4, false); + if (tag != 0x49492A00 && tag != 0x4D4D002A) { + Log.e(TAG, "Invalid byte order"); + return 0; + } + boolean littleEndian = (tag == 0x49492A00); + + // Get the offset and check if it is reasonable. + int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; + if (count < 10 || count > length) { + Log.e(TAG, "Invalid offset"); + return 0; + } + offset += count; + length -= count; + + // Get the count and go through all the elements. + count = pack(jpeg, offset - 2, 2, littleEndian); + while (count-- > 0 && length >= 12) { + // Get the tag and check if it is orientation. + tag = pack(jpeg, offset, 2, littleEndian); + if (tag == 0x0112) { + // We do not really care about type and count, do we? + int orientation = pack(jpeg, offset + 8, 2, littleEndian); + switch (orientation) { + case 1: + return 0; + case 3: + return 180; + case 6: + return 90; + case 8: + return 270; + } + Log.i(TAG, "Unsupported orientation"); + return 0; + } + offset += 12; + length -= 12; + } + } + + Log.i(TAG, "Orientation not found"); + return 0; + } + + private static int pack(byte[] bytes, int offset, int length, + boolean littleEndian) { + int step = 1; + if (littleEndian) { + offset += length - 1; + step = -1; + } + + int value = 0; + while (length-- > 0) { + value = (value << 8) | (bytes[offset] & 0xFF); + offset += step; + } + return value; + } + + private static boolean read(InputStream is, byte[] buf, int length) { + try { + return is.read(buf, 0, length) == length; + } catch (IOException ex) { + return false; + } + } +} diff --git a/src/eu/siacs/conversations/utils/PhoneHelper.java b/src/eu/siacs/conversations/utils/PhoneHelper.java index 25cff099..5becc7e7 100644 --- a/src/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/eu/siacs/conversations/utils/PhoneHelper.java @@ -22,7 +22,7 @@ public class PhoneHelper { final String[] PROJECTION = new String[] { ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Data.PHOTO_THUMBNAIL_URI, + ContactsContract.Data.PHOTO_URI, ContactsContract.Data.LOOKUP_KEY, ContactsContract.CommonDataKinds.Im.DATA }; @@ -50,10 +50,8 @@ public class PhoneHelper { "displayname", cursor.getString(cursor .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); - contact.putString( - "photouri", - cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI))); + contact.putString("photouri", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.PHOTO_URI))); contact.putString("lookup", cursor.getString(cursor .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index 671e66d5..5141c83c 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -1,22 +1,18 @@ package eu.siacs.conversations.utils; -import java.io.FileNotFoundException; 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 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.MucOptions.User; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationManager; @@ -25,28 +21,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.net.Uri; -import android.provider.ContactsContract.Contacts; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.text.format.DateFormat; import android.text.format.DateUtils; -import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; -import android.widget.QuickContactBadge; import android.widget.TextView; public class UIHelper { - private static final int BG_COLOR = 0xFF181818; - private static final int FG_COLOR = 0xFFFAFAFA; - private static final int TRANSPARENT = 0x00000000; private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME @@ -123,167 +106,6 @@ public class UIHelper { } } - public static int getRealPx(int dp, Context context) { - final DisplayMetrics metrics = context.getResources() - .getDisplayMetrics(); - return ((int) (dp * metrics.density)); - } - - private static int getNameColor(String name) { - /* - * int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713, - * 0xFFe92727 }; - */ - int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, - 0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722, - 0xFF795548, 0xFF607d8b }; - return holoColors[(int) ((name.hashCode() & 0xffffffffl) % 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.setFlags(Paint.ANTI_ALIAS_FLAG); - 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) { - int tiles = (names.length > 4) ? 4 : (names.length < 1) ? 1 - : names.length; - Bitmap bitmap = Bitmap - .createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - - String[] letters = new String[tiles]; - int[] colors = new int[tiles]; - if (names.length < 1) { - letters[0] = "?"; - colors[0] = 0xFFe92727; - } else { - for (int i = 0; i < tiles; ++i) { - letters[i] = (names[i].length() > 0) ? names[i].substring(0, 1) - .toUpperCase(Locale.US) : " "; - colors[i] = getNameColor(names[i]); - } - - if (names.length > 4) { - letters[3] = "\u2026"; // Unicode ellipsis - colors[3] = 0xFF202020; - } - } - - bitmap.eraseColor(bgColor); - - switch (tiles) { - case 1: - drawTile(canvas, letters[0], colors[0], fgColor, 0, 0, size, size); - break; - - case 2: - 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: - 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: - 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 getMucContactPicture(Conversation conversation, - int size, int bgColor, int fgColor) { - List<User> members = conversation.getMucOptions().getUsers(); - if (members.size() == 0) { - return getUnknownContactPicture( - new String[] { conversation.getName() }, size, bgColor, - fgColor); - } - ArrayList<String> names = new ArrayList<String>(); - names.add(conversation.getMucOptions().getActualNick()); - for (User user : members) { - names.add(user.getName()); - if (names.size() > 4) { - break; - } - } - String[] mArrayNames = new String[names.size()]; - names.toArray(mArrayNames); - return getUnknownContactPicture(mArrayNames, size, bgColor, fgColor); - } - - public static Bitmap getContactPicture(Conversation conversation, - int dpSize, Context context, boolean notification) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - return getContactPicture(conversation.getContact(), dpSize, - context, notification); - } else { - int fgColor = UIHelper.FG_COLOR, bgColor = (notification) ? UIHelper.BG_COLOR - : UIHelper.TRANSPARENT; - - return getMucContactPicture(conversation, - getRealPx(dpSize, context), bgColor, fgColor); - } - } - - public static Bitmap getContactPicture(Contact contact, int dpSize, - Context context, boolean notification) { - String uri = contact.getProfilePhoto(); - if (uri == null) { - return getContactPicture(contact.getDisplayName(), dpSize, context, - notification); - } - try { - Bitmap bm = BitmapFactory.decodeStream(context.getContentResolver() - .openInputStream(Uri.parse(uri))); - return Bitmap.createScaledBitmap(bm, getRealPx(dpSize, context), - getRealPx(dpSize, context), false); - } catch (FileNotFoundException e) { - return getContactPicture(contact.getDisplayName(), dpSize, context, - notification); - } - } - - public static Bitmap getContactPicture(String name, int dpSize, - Context context, boolean notification) { - int fgColor = UIHelper.FG_COLOR, bgColor = (notification) ? UIHelper.BG_COLOR - : UIHelper.TRANSPARENT; - - return getUnknownContactPicture(new String[] { name }, - getRealPx(dpSize, context), bgColor, fgColor); - } - public static void showErrorNotification(Context context, List<Account> accounts) { NotificationManager mNotificationManager = (NotificationManager) context @@ -326,16 +148,6 @@ public class UIHelper { mNotificationManager.notify(1111, notification); } - public static void prepareContactBadge(final Activity activity, - QuickContactBadge badge, final Contact contact, Context context) { - if (contact.getSystemAccount() != null) { - String[] systemAccount = contact.getSystemAccount().split("#"); - long id = Long.parseLong(systemAccount[0]); - badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1])); - } - badge.setImageBitmap(contact.getImage(72, context)); - } - @SuppressLint("InflateParams") public static AlertDialog getVerifyFingerprintDialog( final ConversationActivity activity, @@ -370,25 +182,6 @@ public class UIHelper { return builder.create(); } - public static Bitmap getSelfContactPicture(Account account, int size, - boolean showPhoneSelfContactPicture, Context context) { - if (showPhoneSelfContactPicture) { - Uri selfiUri = PhoneHelper.getSefliUri(context); - if (selfiUri != null) { - try { - return BitmapFactory.decodeStream(context - .getContentResolver().openInputStream(selfiUri)); - } catch (FileNotFoundException e) { - return getContactPicture(account.getJid(), size, context, - false); - } - } - return getContactPicture(account.getJid(), size, context, false); - } else { - return getContactPicture(account.getJid(), size, context, false); - } - } - private final static class EmoticonPattern { Pattern pattern; String replacement; diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index 54409be4..39dcb362 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -4,14 +4,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; +import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; +import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; @@ -22,14 +25,19 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; +import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.xmlpull.v1.XmlPullParserException; import de.duenndns.ssl.MemorizingTrustManager; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; +import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import eu.siacs.conversations.Config; @@ -80,6 +88,7 @@ public class XmppConnection implements Runnable { private SparseArray<String> messageReceipts = new SparseArray<String>(); private boolean usingCompression = false; + private boolean usingEncryption = false; private int stanzasReceived = 0; private int stanzasSent = 0; @@ -104,6 +113,7 @@ public class XmppConnection implements Runnable { private OnBindListener bindListener = null; private OnMessageAcknowledged acknowledgedListener = null; private MemorizingTrustManager mMemorizingTrustManager; + private final Context applicationContext; public XmppConnection(Account account, XmppConnectionService service) { this.mRandom = service.getRNG(); @@ -112,6 +122,7 @@ public class XmppConnection implements Runnable { this.wakeLock = service.getPowerManager().newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, account.getJid()); tagWriter = new TagWriter(); + applicationContext = service.getApplicationContext(); } protected void changeStatus(int nextStatus) { @@ -135,6 +146,7 @@ public class XmppConnection implements Runnable { protected void connect() { Log.d(Config.LOGTAG, account.getJid() + ": connecting"); usingCompression = false; + usingEncryption = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); this.attempt++; @@ -145,29 +157,47 @@ public class XmppConnection implements Runnable { tagWriter = new TagWriter(); packetCallbacks.clear(); this.changeStatus(Account.STATUS_CONNECTING); - Bundle namePort = DNSHelper.getSRVRecord(account.getServer()); - if ("timeout".equals(namePort.getString("error"))) { + Bundle result = DNSHelper.getSRVRecord(account.getServer()); + ArrayList<Parcelable> values = result.getParcelableArrayList("values"); + if ("timeout".equals(result.getString("error"))) { Log.d(Config.LOGTAG, account.getJid() + ": dns timeout"); this.changeStatus(Account.STATUS_OFFLINE); return; - } - String srvRecordServer = namePort.getString("name"); - String srvIpServer = namePort.getString("ipv4"); - int srvRecordPort = namePort.getInt("port"); - if (srvRecordServer != null) { - if (srvIpServer != null) { - Log.d(Config.LOGTAG, account.getJid() - + ": using values from dns " + srvRecordServer - + "[" + srvIpServer + "]:" + srvRecordPort); - socket = new Socket(srvIpServer, srvRecordPort); - } else { - Log.d(Config.LOGTAG, account.getJid() - + ": using values from dns " + srvRecordServer - + ":" + srvRecordPort); - socket = new Socket(srvRecordServer, srvRecordPort); - } - } else if (namePort.containsKey("error") - && "nosrv".equals(namePort.getString("error", null))) { + } else if (values != null) { + int i = 0; + boolean socketError = true; + while (socketError && values.size() > i) { + Bundle namePort = (Bundle) values.get(i); + try { + String srvRecordServer = namePort.getString("name"); + int srvRecordPort = namePort.getInt("port"); + String srvIpServer = namePort.getString("ipv4"); + InetSocketAddress addr; + if (srvIpServer!=null) { + addr = new InetSocketAddress(srvIpServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + "[" + srvIpServer + "]:" + srvRecordPort); + } else { + addr = new InetSocketAddress(srvRecordServer, srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + + srvRecordServer + ":" + srvRecordPort); + } + socket = new Socket(); + socket.connect(addr, 20000); + socketError = false; + } catch (UnknownHostException e) { + i++; + } catch (IOException e) { + i++; + } + } + if (socketError) { + throw new IOException(); + } + } else if (result.containsKey("error") + && "nosrv".equals(result.getString("error", null))) { socket = new Socket(account.getServer(), 5222); } else { Log.d(Config.LOGTAG, account.getJid() @@ -504,6 +534,15 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } + private SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(applicationContext); + } + + private boolean enableLegacySSL() { + return getPreferences().getBoolean("enable_legacy_ssl", false); + } + private void switchOverToTls(Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); @@ -515,11 +554,27 @@ public class XmppConnection implements Runnable { SSLSocketFactory factory = sc.getSocketFactory(); HostnameVerifier verifier = this.mMemorizingTrustManager - .wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); + .wrapHostnameVerifier(new StrictHostnameVerifier()); SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); + // Support all protocols except legacy SSL. + // The min SDK version prevents us having to worry about SSLv2. In + // future, this may be + // true of SSLv3 as well. + final String[] supportProtocols; + if (enableLegacySSL()) { + supportProtocols = sslSocket.getSupportedProtocols(); + } else { + final List<String> supportedProtocols = new LinkedList<String>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = new String[supportedProtocols.size()]; + supportedProtocols.toArray(supportProtocols); + } + sslSocket.setEnabledProtocols(supportProtocols); + if (verifier != null && !verifier.verify(account.getServer(), sslSocket.getSession())) { @@ -533,6 +588,7 @@ public class XmppConnection implements Runnable { sendStartStream(); Log.d(Config.LOGTAG, account.getJid() + ": TLS connection established"); + usingEncryption = true; processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { @@ -562,20 +618,20 @@ public class XmppConnection implements Runnable { private void processStreamFeatures(Tag currentTag) throws XmlPullParserException, IOException { this.streamFeatures = tagReader.readElement(currentTag); - if (this.streamFeatures.hasChild("starttls") - && account.isOptionSet(Account.OPTION_USETLS)) { + if (this.streamFeatures.hasChild("starttls") && !usingEncryption) { sendStartTLS(); } else if (compressionAvailable()) { sendCompressionZlib(); } else if (this.streamFeatures.hasChild("register") - && (account.isOptionSet(Account.OPTION_REGISTER))) { + && account.isOptionSet(Account.OPTION_REGISTER) + && usingEncryption) { sendRegistryRequest(); } else if (!this.streamFeatures.hasChild("register") - && (account.isOptionSet(Account.OPTION_REGISTER))) { + && account.isOptionSet(Account.OPTION_REGISTER)) { changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") - && shouldAuthenticate) { + && shouldAuthenticate && usingEncryption) { List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); if (mechanisms.contains("PLAIN")) { @@ -591,6 +647,10 @@ public class XmppConnection implements Runnable { this.tagWriter.writeStanzaAsync(resume); } else if (this.streamFeatures.hasChild("bind") && shouldBind) { sendBindRequest(); + } else { + Log.d(Config.LOGTAG, account.getJid() + + ": incompatible server. disconnecting"); + disconnect(true); } } @@ -910,7 +970,7 @@ public class XmppConnection implements Runnable { } public void disconnect(boolean force) { - Log.d(Config.LOGTAG, account.getJid()+": disconnecting"); + Log.d(Config.LOGTAG, account.getJid() + ": disconnecting"); try { if (force) { socket.close(); diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 92fdbe0b..6b9ca9aa 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Downloadable; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; @@ -33,17 +34,18 @@ public class JingleConnection implements Downloadable { private JingleConnectionManager mJingleConnectionManager; private XmppConnectionService mXmppConnectionService; - public static final int STATUS_INITIATED = 0; - public static final int STATUS_ACCEPTED = 1; - public static final int STATUS_TERMINATED = 2; - public static final int STATUS_CANCELED = 3; - public static final int STATUS_FINISHED = 4; - public static final int STATUS_TRANSMITTING = 5; - public static final int STATUS_FAILED = 99; + protected static final int JINGLE_STATUS_INITIATED = 0; + protected static final int JINGLE_STATUS_ACCEPTED = 1; + protected static final int JINGLE_STATUS_TERMINATED = 2; + protected static final int JINGLE_STATUS_CANCELED = 3; + protected static final int JINGLE_STATUS_FINISHED = 4; + protected static final int JINGLE_STATUS_TRANSMITTING = 5; + protected static final int JINGLE_STATUS_FAILED = 99; private int ibbBlockSize = 4096; - private int status = -1; + private int mJingleStatus = -1; + private int mStatus = -1; private Message message; private String sessionId; private Account account; @@ -54,7 +56,7 @@ public class JingleConnection implements Downloadable { private String transportId; private Element fileOffer; - private JingleFile file = null; + private DownloadableFile file = null; private String contentName; private String contentCreator; @@ -71,11 +73,7 @@ public class JingleConnection implements Downloadable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE_ERROR) { - if (initiator.equals(account.getFullJid())) { - mXmppConnectionService.markMessage(message, - Message.STATUS_SEND_FAILED); - } - status = STATUS_FAILED; + cancel(); } } }; @@ -83,7 +81,7 @@ public class JingleConnection implements Downloadable { final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() { @Override - public void onFileTransmitted(JingleFile file) { + public void onFileTransmitted(DownloadableFile file) { if (responder.equals(account.getFullJid())) { sendSuccess(); if (acceptedAutomatically) { @@ -96,8 +94,8 @@ public class JingleConnection implements Downloadable { BitmapFactory.decodeFile(file.getAbsolutePath(), options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; - message.setBody(Long.toString(file.getSize()) + ',' - + imageWidth + ',' + imageHeight); + message.setBody(Long.toString(file.getSize()) + '|' + + imageWidth + '|' + imageHeight); mXmppConnectionService.databaseBackend.createMessage(message); mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVED); @@ -148,8 +146,8 @@ public class JingleConnection implements Downloadable { return this.sessionId; } - public String getAccountJid() { - return this.account.getFullJid(); + public Account getAccount() { + return this.account; } public String getCounterPart() { @@ -253,13 +251,14 @@ public class JingleConnection implements Downloadable { } public void init(Account account, JinglePacket packet) { - this.status = STATUS_INITIATED; + this.mJingleStatus = JINGLE_STATUS_INITIATED; Conversation conversation = this.mXmppConnectionService .findOrCreateConversation(account, packet.getFrom().split("/", 2)[0], false); this.message = new Message(conversation, "", Message.ENCRYPTION_NONE); + this.message.setStatus(Message.STATUS_RECEIVED); this.message.setType(Message.TYPE_IMAGE); - this.message.setStatus(Message.STATUS_RECEIVED_OFFER); + this.mStatus = Downloadable.STATUS_OFFER; this.message.setDownloadable(this); String[] fromParts = packet.getFrom().split("/", 2); this.message.setPresence(fromParts[1]); @@ -304,7 +303,8 @@ public class JingleConnection implements Downloadable { if (supportedFile) { long size = Long.parseLong(fileSize.getContent()); message.setBody(Long.toString(size)); - conversation.getMessages().add(message); + conversation.add(message); + mXmppConnectionService.updateConversationUi(); if (size <= this.mJingleConnectionManager .getAutoAcceptFileSize()) { Log.d(Config.LOGTAG, "auto accepting file from " @@ -323,7 +323,7 @@ public class JingleConnection implements Downloadable { .push(message); } this.file = this.mXmppConnectionService.getFileBackend() - .getJingleFile(message, false); + .getFile(message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); if (key == null) { @@ -350,12 +350,13 @@ public class JingleConnection implements Downloadable { } private void sendInitRequest() { + this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED); JinglePacket packet = this.bootstrapPacket("session-initiate"); Content content = new Content(this.contentCreator, this.contentName); if (message.getType() == Message.TYPE_IMAGE) { content.setTransportId(this.transportId); - this.file = this.mXmppConnectionService.getFileBackend() - .getJingleFile(message, false); + this.file = this.mXmppConnectionService.getFileBackend().getFile( + message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { Conversation conversation = this.message.getConversation(); this.mXmppConnectionService.renewSymmetricKey(conversation); @@ -369,7 +370,7 @@ public class JingleConnection implements Downloadable { content.socks5transport().setChildren(getCandidatesAsElements()); packet.setContent(content); this.sendJinglePacket(packet); - this.status = STATUS_INITIATED; + this.mJingleStatus = JINGLE_STATUS_INITIATED; } } @@ -382,8 +383,9 @@ public class JingleConnection implements Downloadable { } private void sendAccept() { - status = STATUS_ACCEPTED; - mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING); + mJingleStatus = JINGLE_STATUS_ACCEPTED; + this.mStatus = Downloadable.STATUS_DOWNLOADING; + mXmppConnectionService.updateConversationUi(); this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() { @@ -457,7 +459,7 @@ public class JingleConnection implements Downloadable { Content content = packet.getJingleContent(); mergeCandidates(JingleCandidate.parse(content.socks5transport() .getChildren())); - this.status = STATUS_ACCEPTED; + this.mJingleStatus = JINGLE_STATUS_ACCEPTED; mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.connectNextCandidate(); return true; @@ -492,7 +494,8 @@ public class JingleConnection implements Downloadable { } else if (content.socks5transport().hasChild("candidate-error")) { Log.d(Config.LOGTAG, "received candidate error"); this.receivedCandidate = true; - if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { + if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) + && (this.sentCandidate)) { this.connect(); } return true; @@ -504,7 +507,8 @@ public class JingleConnection implements Downloadable { JingleCandidate candidate = getCandidate(cid); candidate.flagAsUsedByCounterpart(); this.receivedCandidate = true; - if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) { + if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) + && (this.sentCandidate)) { this.connect(); } else { Log.d(Config.LOGTAG, @@ -532,7 +536,7 @@ public class JingleConnection implements Downloadable { this.sendFallbackToIbb(); } } else { - this.status = STATUS_TRANSMITTING; + this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; if (connection.needsActivation()) { if (connection.getCandidate().isOurs()) { Log.d(Config.LOGTAG, "candidate " @@ -619,13 +623,15 @@ public class JingleConnection implements Downloadable { packet.setReason(reason); this.sendJinglePacket(packet); this.disconnect(); - this.status = STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_RECEIVED); + this.mJingleStatus = JINGLE_STATUS_FINISHED; + this.message.setStatus(Message.STATUS_RECEIVED); + this.message.setDownloadable(null); + this.mXmppConnectionService.updateMessage(message); this.mJingleConnectionManager.finishConnection(this); } private void sendFallbackToIbb() { + Log.d(Config.LOGTAG, "sending fallback to ibb"); JinglePacket packet = this.bootstrapPacket("transport-replace"); Content content = new Content(this.contentCreator, this.contentName); this.transportId = this.mJingleConnectionManager.nextRandomId(); @@ -637,6 +643,7 @@ public class JingleConnection implements Downloadable { } private boolean receiveFallbackToIbb(JinglePacket packet) { + Log.d(Config.LOGTAG, "receiving fallack to ibb"); String receivedBlockSize = packet.getJingleContent().ibbTransport() .getAttribute("block-size"); if (receivedBlockSize != null) { @@ -691,7 +698,7 @@ public class JingleConnection implements Downloadable { } private void receiveSuccess() { - this.status = STATUS_FINISHED; + this.mJingleStatus = JINGLE_STATUS_FINISHED; this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND); this.disconnect(); @@ -699,20 +706,15 @@ public class JingleConnection implements Downloadable { } public void cancel() { - this.status = STATUS_CANCELED; + this.mJingleStatus = JINGLE_STATUS_CANCELED; this.disconnect(); if (this.message != null) { if (this.responder.equals(account.getFullJid())) { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_RECEPTION_FAILED); + this.mStatus = Downloadable.STATUS_FAILED; + this.mXmppConnectionService.updateConversationUi(); } else { - if (this.status == STATUS_INITIATED) { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_REJECTED); - } else { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED); - } + this.mXmppConnectionService.markMessage(this.message, + Message.STATUS_SEND_FAILED); } } this.mJingleConnectionManager.finishConnection(this); @@ -789,7 +791,7 @@ public class JingleConnection implements Downloadable { .setAttribute("cid", cid); packet.setContent(content); this.sentCandidate = true; - if ((receivedCandidate) && (status == STATUS_ACCEPTED)) { + if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { connect(); } this.sendJinglePacket(packet); @@ -802,7 +804,7 @@ public class JingleConnection implements Downloadable { content.socks5transport().addChild("candidate-error"); packet.setContent(content); this.sentCandidate = true; - if ((receivedCandidate) && (status == STATUS_ACCEPTED)) { + if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) { connect(); } this.sendJinglePacket(packet); @@ -816,8 +818,8 @@ public class JingleConnection implements Downloadable { return this.responder; } - public int getStatus() { - return this.status; + public int getJingleStatus() { + return this.mJingleStatus; } private boolean equalCandidateExists(JingleCandidate candidate) { @@ -867,17 +869,34 @@ public class JingleConnection implements Downloadable { return this.transport; } - public void start() { - if (status == STATUS_INITIATED) { - new Thread(new Runnable() { + public boolean start() { + if (account.getStatus() == Account.STATUS_ONLINE) { + if (mJingleStatus == JINGLE_STATUS_INITIATED) { + new Thread(new Runnable() { - @Override - public void run() { - sendAccept(); - } - }).start(); + @Override + public void run() { + sendAccept(); + } + }).start(); + } + return true; + } else { + return false; + } + } + + @Override + public int getStatus() { + return this.mStatus; + } + + @Override + public long getFileSize() { + if (this.file != null) { + return this.file.getExpectedSize(); } else { - Log.d(Config.LOGTAG, "status (" + status + ") was not ok"); + return 0; } } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 79090af6..d937146a 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -10,16 +10,14 @@ import android.util.Log; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class JingleConnectionManager { - - private XmppConnectionService xmppConnectionService; - +public class JingleConnectionManager extends AbstractConnectionManager { private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>(); private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>(); @@ -28,7 +26,7 @@ public class JingleConnectionManager { private SecureRandom random = new SecureRandom(); public JingleConnectionManager(XmppConnectionService service) { - this.xmppConnectionService = service; + super(service); } public void deliverPacket(Account account, JinglePacket packet) { @@ -38,7 +36,7 @@ public class JingleConnectionManager { connections.add(connection); } else { for (JingleConnection connection : connections) { - if (connection.getAccountJid().equals(account.getFullJid()) + if (connection.getAccount() == account && connection.getSessionId().equals( packet.getSessionId()) && connection.getCounterPart().equals(packet.getFrom())) { @@ -46,8 +44,13 @@ public class JingleConnectionManager { return; } } - account.getXmppConnection().sendIqPacket( - packet.generateRespone(IqPacket.TYPE_ERROR), null); + IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR); + Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("item-not-found", + "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("unknown-session", "urn:xmpp:jingle:errors:1"); + account.getXmppConnection().sendIqPacket(response, null); } } @@ -68,10 +71,6 @@ public class JingleConnectionManager { this.connections.remove(connection); } - public XmppConnectionService getXmppConnectionService() { - return this.xmppConnectionService; - } - public void getPrimaryCandidate(Account account, final OnPrimaryCandidateFound listener) { if (!this.primaryCandidates.containsKey(account.getJid())) { @@ -128,16 +127,6 @@ public class JingleConnectionManager { return new BigInteger(50, random).toString(32); } - public long getAutoAcceptFileSize() { - String config = this.xmppConnectionService.getPreferences().getString( - "auto_accept_file_size", "524288"); - try { - return Long.parseLong(config); - } catch (NumberFormatException e) { - return 524288; - } - } - public void deliverIbbPacket(Account account, IqPacket packet) { String sid = null; Element payload = null; @@ -152,7 +141,8 @@ public class JingleConnectionManager { } if (sid != null) { for (JingleConnection connection : connections) { - if (connection.hasTransportId(sid)) { + if (connection.getAccount() == account + && connection.hasTransportId(sid)) { JingleTransport transport = connection.getTransport(); if (transport instanceof JingleInbandTransport) { JingleInbandTransport inbandTransport = (JingleInbandTransport) transport; @@ -170,7 +160,7 @@ public class JingleConnectionManager { public void cancelInTransmission() { for (JingleConnection connection : this.connections) { - if (connection.getStatus() == JingleConnection.STATUS_TRANSMITTING) { + if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { connection.cancel(); } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java b/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java deleted file mode 100644 index 9253814b..00000000 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleFile.java +++ /dev/null @@ -1,68 +0,0 @@ -package eu.siacs.conversations.xmpp.jingle; - -import java.io.File; -import java.security.Key; - -import javax.crypto.spec.SecretKeySpec; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.utils.CryptoHelper; -import android.util.Log; - -public class JingleFile extends File { - - private static final long serialVersionUID = 2247012619505115863L; - - private long expectedSize = 0; - private String sha1sum; - private Key aeskey; - - public JingleFile(String path) { - super(path); - } - - public long getSize() { - return super.length(); - } - - public long getExpectedSize() { - if (this.aeskey != null) { - return (this.expectedSize / 16 + 1) * 16; - } else { - return this.expectedSize; - } - } - - public void setExpectedSize(long size) { - this.expectedSize = size; - } - - public String getSha1Sum() { - return this.sha1sum; - } - - public void setSha1Sum(String sum) { - this.sha1sum = sum; - } - - 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(Config.LOGTAG, "weird key"); - } - Log.d(Config.LOGTAG, - "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 c5498075..cc1e92f6 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.xmpp.jingle; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -10,6 +9,7 @@ import java.util.Arrays; import android.util.Base64; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -26,7 +26,7 @@ public class JingleInbandTransport extends JingleTransport { private boolean established = false; - private JingleFile file; + private DownloadableFile file; private InputStream fileInputStream = null; private OutputStream fileOutputStream; @@ -77,7 +77,7 @@ public class JingleInbandTransport extends JingleTransport { } @Override - public void receive(JingleFile file, + public void receive(DownloadableFile file, OnFileTransmissionStatusChanged callback) { this.onFileTransmissionStatusChanged = callback; this.file = file; @@ -86,7 +86,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = getOutputStream(file); + this.fileOutputStream = file.createOutputStream(); if (this.fileOutputStream == null) { callback.onFileTransferAborted(); return; @@ -100,20 +100,19 @@ public class JingleInbandTransport extends JingleTransport { } @Override - public void send(JingleFile file, OnFileTransmissionStatusChanged callback) { + public void send(DownloadableFile file, + OnFileTransmissionStatusChanged callback) { this.onFileTransmissionStatusChanged = callback; this.file = file; try { this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = this.getInputStream(file); + fileInputStream = this.file.createInputStream(); if (fileInputStream == null) { callback.onFileTransferAborted(); return; } this.sendNextBlock(); - } catch (FileNotFoundException e) { - callback.onFileTransferAborted(); } catch (NoSuchAlgorithmException e) { callback.onFileTransferAborted(); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 63f5a507..1da2f0cd 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -10,6 +10,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.utils.CryptoHelper; public class JingleSocks5Transport extends JingleTransport { @@ -29,11 +30,11 @@ public class JingleSocks5Transport extends JingleTransport { StringBuilder destBuilder = new StringBuilder(); destBuilder.append(jingleConnection.getSessionId()); if (candidate.isOurs()) { - destBuilder.append(jingleConnection.getAccountJid()); + destBuilder.append(jingleConnection.getAccount().getFullJid()); destBuilder.append(jingleConnection.getCounterPart()); } else { destBuilder.append(jingleConnection.getCounterPart()); - destBuilder.append(jingleConnection.getAccountJid()); + destBuilder.append(jingleConnection.getAccount().getFullJid()); } mDigest.reset(); this.destination = CryptoHelper.bytesToHex(mDigest @@ -86,7 +87,7 @@ public class JingleSocks5Transport extends JingleTransport { } - public void send(final JingleFile file, + public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { @@ -96,7 +97,7 @@ public class JingleSocks5Transport extends JingleTransport { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = getInputStream(file); + fileInputStream = file.createInputStream(); if (fileInputStream == null) { callback.onFileTransferAborted(); return; @@ -132,7 +133,7 @@ public class JingleSocks5Transport extends JingleTransport { } - public void receive(final JingleFile file, + public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { @@ -145,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream fileOutputStream = getOutputStream(file); + OutputStream fileOutputStream = file.createOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); return; diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index 07dc8ecc..1374e61c 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -1,88 +1,13 @@ package eu.siacs.conversations.xmpp.jingle; -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 eu.siacs.conversations.Config; - -import android.util.Log; +import eu.siacs.conversations.entities.DownloadableFile; public abstract class JingleTransport { public abstract void connect(final OnTransportConnected callback); - public abstract void receive(final JingleFile file, + public abstract void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback); - public abstract void send(final JingleFile file, + public abstract void send(final DownloadableFile file, final OnFileTransmissionStatusChanged 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(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(new FileInputStream(file), cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "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(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(file), - cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } - } - } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java index 19fd4d97..e45e7441 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java +++ b/src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java @@ -1,7 +1,9 @@ package eu.siacs.conversations.xmpp.jingle; +import eu.siacs.conversations.entities.DownloadableFile; + public interface OnFileTransmissionStatusChanged { - public void onFileTransmitted(JingleFile file); + public void onFileTransmitted(DownloadableFile file); public void onFileTransferAborted(); } diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index d19e6dfd..bcadbe77 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -1,7 +1,7 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; +import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.jingle.JingleFile; public class Content extends Element { @@ -25,7 +25,7 @@ public class Content extends Element { this.transportId = sid; } - public void setFileOffer(JingleFile actualFile, boolean otr) { + public void setFileOffer(DownloadableFile actualFile, boolean otr) { Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); Element offer = description.addChild("offer"); |