diff options
30 files changed, 681 insertions, 179 deletions
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 61de9bb3..e1adff10 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-2.12-all.zip diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java index d2eb77b7..a9f0c6ad 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java @@ -9,7 +9,6 @@ import java.io.File; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.utils.ImageUtil; -import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor; /** @@ -32,7 +31,7 @@ public class ConversationsPlusApplication extends Application { ConversationsPlusApplication.instance = this; ConversationsPlusPreferences.init(PreferenceManager.getDefaultSharedPreferences(getAppContext())); ImageUtil.initBitmapCache(); - FileBackend.createNoMedia(); + FileBackend.init(); } /** diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java index 3f6f8ce7..bba52954 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java @@ -33,12 +33,18 @@ import de.thedevstack.conversationsplus.ui.UiCallback; public class PgpEngine { private OpenPgpApi api; private XmppConnectionService mXmppConnectionService; + private static PgpEngine INSTANCE; public PgpEngine(OpenPgpApi api, XmppConnectionService service) { this.api = api; this.mXmppConnectionService = service; + INSTANCE = this; } + public static PgpEngine getInstance() { + return INSTANCE; + } + public void decrypt(final Message message, final UiCallback<Message> callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); @@ -62,7 +68,7 @@ public class PgpEngine { final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER - && ConversationsPlusPreferences.autoDownloadFileLink() + && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink()) && ConversationsPlusPreferences.autoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java index f8856f90..4bc27a7d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java @@ -41,6 +41,7 @@ import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.parser.IqParser; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; @@ -946,7 +947,7 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo public void run() { XmppAxolotlMessage axolotlMessage = encrypt(message); if (axolotlMessage == null) { - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); //mXmppConnectionService.updateConversationUi(); } else { Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java index 9e9366d7..bfd00b5d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java @@ -140,6 +140,7 @@ public class Conversation extends AbstractEntity implements Blockable { } public Message findMessageWithFileAndUuid(final String uuid) { + // TODO Implement this method to find a message by a real filename - not uuid synchronized (this.messages) { for (final Message message : this.messages) { if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java index 6a6a5e50..b0a5bb3b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java @@ -83,6 +83,8 @@ public class Message extends AbstractEntity { private String axolotlFingerprint = null; private Decision mTreatAsDownloadAble = Decision.NOT_DECIDED; + private boolean httpUploaded; + private Message() { } @@ -535,6 +537,10 @@ public class Message extends AbstractEntity { mTreatAsDownloadAble = Decision.NEVER; } + public void setTreatAsDownloadable(Decision downloadable) { + this.mTreatAsDownloadAble = downloadable; + } + public Decision treatAsDownloadable() { // only test this ones, body will not change if (mTreatAsDownloadAble != Decision.NOT_DECIDED) { @@ -554,9 +560,6 @@ public class Message extends AbstractEntity { if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { mTreatAsDownloadAble = Decision.NEVER; return mTreatAsDownloadAble; - } else if (oob) { - mTreatAsDownloadAble = Decision.MUST; - return mTreatAsDownloadAble; } String extension = extractRelevantExtension(url); if (extension == null) { @@ -754,6 +757,14 @@ public class Message extends AbstractEntity { return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption; } + public boolean isHttpUploaded() { + return httpUploaded; + } + + public void setHttpUploaded(boolean httpUploaded) { + this.httpUploaded = httpUploaded; + } + private static int getCleanedEncryption(int encryption) { if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { return ENCRYPTION_PGP; diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java index 2e49ebfe..5a1d6f3f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java @@ -16,6 +16,7 @@ import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; +import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; @@ -112,6 +113,9 @@ public class MessageGenerator extends AbstractGenerator { if (message.hasFileOnRemoteHost()) { Message.FileParams fileParams = message.getFileParams(); content = fileParams.url.toString(); + if (message.isHttpUploaded()) { + packet.addChild(new HttpUploadHint()); + } packet.addChild("x","jabber:x:oob").addChild("url").setContent(content); if (fileParams.width > 0 && fileParams.height > 0) { addXhtmlImImage(packet,fileParams); diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java index 6c546fee..231a6ca7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java @@ -91,12 +91,14 @@ public class HttpUploadConnection implements Transferable { private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); StreamUtil.close(mFileInputStream); } public void init(Message message, boolean delay) { this.message = message; + this.message.setHttpUploaded(true); + this.message.setNoDownloadable(); this.account = message.getConversation().getAccount(); this.file = FileBackend.getFile(message, false); this.mime = this.file.getMimeType(); @@ -143,7 +145,7 @@ public class HttpUploadConnection implements Transferable { } }); message.setTransferable(this); - mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); + MessageUtil.markMessage(message, Message.STATUS_UNSEND); } private class FileUploader implements Runnable { diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java index 6fb18266..a8e5929b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java @@ -3,6 +3,8 @@ package de.thedevstack.conversationsplus.parser; import android.util.Log; import android.util.Pair; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint; import de.tzur.conversations.Settings; import net.java.otr4j.session.Session; @@ -342,12 +344,12 @@ public class MessageParser extends AbstractParser implements if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { status = Message.STATUS_SEND_RECEIVED; isCarbon = true; //not really carbon but received from another resource - if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) { + if (MessageUtil.markMessage(conversation, remoteMsgId, status)) { return; } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { Message message = conversation.findSentMessageWithBody(packet.getBody()); if (message != null) { - mXmppConnectionService.markMessage(message, status); + MessageUtil.markMessage(message, status); return; } } @@ -390,6 +392,10 @@ public class MessageParser extends AbstractParser implements if (serverMsgId == null) { serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer()); } + message.setHttpUploaded(packet.hasChild(HttpUploadHint.ELEMENT_NAME, HttpUploadHint.NAMESPACE)); + if (message.isHttpUploaded()) { + message.setTreatAsDownloadable(Message.Decision.MUST); + } message.setCounterpart(counterpart); message.setRemoteMsgId(remoteMsgId); @@ -465,7 +471,7 @@ public class MessageParser extends AbstractParser implements if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && ConversationsPlusPreferences.autoAcceptFileSize() > 0 - && ConversationsPlusPreferences.autoDownloadFileLink()) { + && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { this.mXmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message); } else { if (query == null) { @@ -530,7 +536,7 @@ public class MessageParser extends AbstractParser implements while (message != null && message.getStatus() == Message.STATUS_SEND_RECEIVED && message.getTimeSent() < displayedMessage.getTimeSent()) { - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED); + MessageUtil.markMessage(message, Message.STATUS_SEND_DISPLAYED); message = message.prev(); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java new file mode 100644 index 00000000..7e3fdab0 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java @@ -0,0 +1,203 @@ +package de.thedevstack.conversationsplus.persistance; + +import android.database.Cursor; + +/** + * Created by steckbrief on 15.04.2016. + */ +public abstract class CursorHelper { + + static double getDouble(Cursor cursor, String columnName) { + if (null == cursor) { + return Double.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Double.MIN_VALUE; + } + return getDouble(cursor, columnIndex); + } + + static String getString(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getString(cursor, columnIndex); + } + + static float getFloat(Cursor cursor, String columnName) { + if (null == cursor) { + return Float.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Float.MIN_VALUE; + } + return getFloat(cursor, columnIndex); + } + + static int getInt(Cursor cursor, String columnName) { + if (null == cursor) { + return Integer.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Integer.MIN_VALUE; + } + return getInt(cursor, columnIndex); + } + + static long getLong(Cursor cursor, String columnName) { + if (null == cursor) { + return Long.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Long.MIN_VALUE; + } + return getLong(cursor, columnIndex); + } + + static int getShort(Cursor cursor, String columnName) { + if (null == cursor) { + return Short.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Short.MIN_VALUE; + } + return getShort(cursor, columnIndex); + } + + static byte[] getBlob(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getBlob(cursor, columnIndex); + } + + /** + * Returns the zero-based index for the given column name, or -1 if the column doesn't exist. + * If you expect the column to exist use {@link Cursor#getColumnIndexOrThrow(String)} instead, which + * will make the error more clear. + * + * @param columnName the name of the target column. + * @return the zero-based column index for the given column name, or -1 if + * the column name does not exist. + * @see Cursor#getColumnIndexOrThrow(String) + */ + static int getColumnIndex(Cursor cursor, String columnName) { + return cursor.getColumnIndex(columnName); + } + + /** + * Returns the value of the requested column as a byte array. + * + * <p>The result and whether this method throws an exception when the + * column value is null or the column type is not a blob type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a byte array. + */ + static byte[] getBlob(Cursor cursor, int columnIndex) { + return cursor.getBlob(columnIndex); + } + + /** + * Returns the value of the requested column as a String. + * + * <p>The result and whether this method throws an exception when the + * column value is null or the column type is not a string type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a String. + */ + static String getString(Cursor cursor, int columnIndex) { + return cursor.getString(columnIndex); + } + + /** + * Returns the value of the requested column as a short. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Short.MIN_VALUE</code>, + * <code>Short.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a short. + */ + static short getShort(Cursor cursor, int columnIndex) { + return cursor.getShort(columnIndex); + } + + /** + * Returns the value of the requested column as an int. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Integer.MIN_VALUE</code>, + * <code>Integer.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as an int. + */ + static int getInt(Cursor cursor, int columnIndex) { + return cursor.getInt(columnIndex); + } + + /** + * Returns the value of the requested column as a long. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [<code>Long.MIN_VALUE</code>, + * <code>Long.MAX_VALUE</code>] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a long. + */ + static long getLong(Cursor cursor, int columnIndex) { + return cursor.getLong(columnIndex); + } + + /** + * Returns the value of the requested column as a float. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a <code>float</code> value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a float. + */ + static float getFloat(Cursor cursor, int columnIndex) { + return cursor.getFloat(columnIndex); + } + + /** + * Returns the value of the requested column as a double. + * + * <p>The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a <code>double</code> value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a double. + */ + static double getDouble(Cursor cursor, int columnIndex) { + return cursor.getDouble(columnIndex); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java index 4157d6f8..5a5746d5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java @@ -53,6 +53,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; private static final int DATABASE_VERSION = 25; + private static final int C_TO_CPLUS_VERSION_OFFSET = 1000; + private static final int CPLUS_DATABASE_VERSION = 1; + private static final int CPLUS_DATABASE_VERSION_MULTIPLIER = 100; + private static final int PHYSICAL_DATABASE_VERSION = DATABASE_VERSION + C_TO_CPLUS_VERSION_OFFSET + (CPLUS_DATABASE_VERSION * CPLUS_DATABASE_VERSION_MULTIPLIER); private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -129,8 +133,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT IGNORE" + ");"; + private static int calculateCDatabaseVersion(int physicalDatabaseVersion) { + return physicalDatabaseVersion % CPLUS_DATABASE_VERSION_MULTIPLIER; + } + + private static int calculateCPLusDatabaseVersion(int physicalDatabaseVersion) { + int cPlusDatabaseVersion = (physicalDatabaseVersion - C_TO_CPLUS_VERSION_OFFSET) / CPLUS_DATABASE_VERSION_MULTIPLIER; + return cPlusDatabaseVersion < 0 ? 0 : cPlusDatabaseVersion; + } + private DatabaseBackend(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); + super(context, DATABASE_NAME, null, PHYSICAL_DATABASE_VERSION); } @Override @@ -176,10 +189,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); + + // Create Conversations+ related tables + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); } + protected void onUpgradeCPlusDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + Logging.d("db.upgrade.cplus", "Updating Conversations+ database from version '" + oldVersion + "' to '" + newVersion + "'"); + if (oldVersion < newVersion) { + if (oldVersion == 0 && newVersion == 1) { + Logging.d("db.upgrade.cplus", "Creating additional parameters table for messages."); + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); + db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") " + + " SELECT " + Message.UUID + " FROM " + Message.TABLENAME); + } + } + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgradeConversationsDatabase(db, calculateCDatabaseVersion(oldVersion), calculateCDatabaseVersion(newVersion)); + onUpgradeCPlusDatabase(db, calculateCPLusDatabaseVersion(oldVersion), calculateCPLusDatabaseVersion(newVersion)); + } + + protected void onUpgradeConversationsDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + Logging.d("db.upgrade.conversations", "Updating Conversations database from version '" + oldVersion + "' to '" + newVersion + "'"); + if (oldVersion == newVersion) { + return; + } if (oldVersion < 2 && newVersion >= 2) { db.execSQL("update " + Account.TABLENAME + " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); @@ -396,8 +433,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void createMessage(Message message) { + Logging.d("db.msg.insert", "Inserting new message with uuid '" + message.getUuid() + "', isRead: " + message.isRead()); + SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); db.insert(Message.TABLENAME, null, message.getContentValues()); + db.insert(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, null, MessageDatabaseAccess.getAdditionalParametersContentValues(message)); + db.setTransactionSuccessful(); + db.endTransaction(); } public void createAccount(Account account) { @@ -471,6 +514,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.moveToLast(); do { Message message = Message.fromCursor(cursor); + MessageDatabaseAccess.populateMessageParameters(db, message); message.setConversation(conversation); list.add(message); } while (cursor.moveToPrevious()); @@ -502,6 +546,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { @Override public Message next() { Message message = Message.fromCursor(cursor); + MessageDatabaseAccess.populateMessageParameters(db, message); cursor.moveToNext(); return message; } @@ -598,17 +643,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void updateMessage(Message message) { + Logging.d("db.msg.update", "Updating message with uuid '" + message.getUuid() + "', isRead: " + message.isRead()); SQLiteDatabase db = this.getWritableDatabase(); String[] args = {message.getUuid()}; + db.beginTransaction(); db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + db.setTransactionSuccessful(); + db.endTransaction(); } public void updateMessage(Message message, String uuid) { + Logging.d("db.msg.update", "Updating message with uuid '" + uuid + "', isRead: " + message.isRead()); + SQLiteDatabase db = this.getWritableDatabase(); String[] args = {uuid}; + db.beginTransaction(); db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + db.setTransactionSuccessful(); + db.endTransaction(); } public void readRoster(Roster roster) { @@ -642,6 +698,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void deleteMessagesInConversation(Conversation conversation) { + Logging.d("db.msg.delete", "Deleting messages in conversation with uuid '" + conversation.getUuid()); + SQLiteDatabase db = this.getWritableDatabase(); String[] args = {conversation.getUuid()}; db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java index 5645caf6..9f7e473e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java @@ -21,6 +21,7 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.exceptions.FileCopyException; +import de.thedevstack.conversationsplus.persistance.observers.FileDeletionObserver; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -30,8 +31,34 @@ import de.thedevstack.conversationsplus.services.XmppConnectionService; public class FileBackend { private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + private static FileBackend INSTANCE; - public static void createNoMedia() { + private FileDeletionObserver privateFilesDirectoryObserver; + private FileDeletionObserver privateFilesImageDirectoryObserver; + private FileDeletionObserver conversationsFilesDirectoryObserver; + private FileDeletionObserver conversationsImagesDirectoryObserver; + + public static void init() { + if (null == INSTANCE) { + INSTANCE = new FileBackend(); + } + INSTANCE.initFileObservers(); + INSTANCE.createNoMedia(); + } + + private void initFileObservers() { + this.privateFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateFileDirectoryPath()); + this.privateFilesImageDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); + this.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); + this.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getPrivateImageDirectoryPath()); + + this.privateFilesDirectoryObserver.startWatching(); + this.privateFilesImageDirectoryObserver.startWatching(); + this.conversationsFilesDirectoryObserver.startWatching(); + this.conversationsImagesDirectoryObserver.startWatching(); + } + + private void createNoMedia() { final File nomedia = new File(getConversationsFileDirectory()+".nomedia"); if (!nomedia.exists()) { try { @@ -42,6 +69,19 @@ public class FileBackend { } } + public static void onFileTransferFolderChanged() { + INSTANCE.conversationsFilesDirectoryObserver.stopWatching(); + INSTANCE.conversationsFilesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsFileDirectory()); + INSTANCE.conversationsFilesDirectoryObserver.startWatching(); + INSTANCE.createNoMedia(); + } + + public static void onImageTransferFolderChanged() { + INSTANCE.conversationsImagesDirectoryObserver.stopWatching(); + INSTANCE.conversationsImagesDirectoryObserver = new FileDeletionObserver(FileBackend.getConversationsImageDirectory()); + INSTANCE.conversationsImagesDirectoryObserver.startWatching(); + } + public static void updateMediaScanner(File file, XmppConnectionService xmppConnectionService) { if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java new file mode 100644 index 00000000..7776174d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java @@ -0,0 +1,59 @@ +package de.thedevstack.conversationsplus.persistance; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +/** + * Created by steckbrief on 15.04.2016. + */ +public class MessageDatabaseAccess { + static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters"; + static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload"; + static final String COLUMN_NAME_MSG_PARAMS_MSGUUID = "message_uuid"; + static final String COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION = "treatasdownloadable_decision"; + + static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V0 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', " + + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; + + static ContentValues getAdditionalParametersContentValues(Message message) { + ContentValues additionalParameters = new ContentValues(); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID, message.getUuid()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD, message.isHttpUploaded() ? 1 : 0); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION, message.treatAsDownloadable().name()); + + return additionalParameters; + } + + static void populateMessageParametersFromCursor(Cursor cursor, Message message) { + boolean isHttpUploaded = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD) == 1; + message.setHttpUploaded(isHttpUploaded); + String downloadable = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION); + Message.Decision treatAsDownloadable = Message.Decision.NOT_DECIDED; + try { + treatAsDownloadable = Message.Decision.valueOf(downloadable); + } catch (IllegalArgumentException e) { + // Should only happen if the database is corrupted, but to be on the save side catch it here + Logging.e("db.msg", "Unknown Decision for treatAsDownloadable found: '" + downloadable + "'"); + } + message.setTreatAsDownloadable(treatAsDownloadable); + } + + static void populateMessageParameters(SQLiteDatabase db, Message message) { + Cursor paramsCursor = db.query(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, + null, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", + new String[] {message.getUuid()}, + null, null, null, null); + paramsCursor.moveToNext(); + MessageDatabaseAccess.populateMessageParametersFromCursor(paramsCursor, message); + paramsCursor.close(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java new file mode 100644 index 00000000..3534ccd2 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java @@ -0,0 +1,45 @@ +package de.thedevstack.conversationsplus.persistance.observers; + +import android.os.FileObserver; + +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; + +/** + * Observer to mark messages containing files which are deleted. + */ +public class FileDeletionObserver extends FileObserver { + public FileDeletionObserver(String path) { + super(path, FileObserver.DELETE); + } + + @Override + public void onEvent(int event, String path) { + if (null != path) { + markFileDeleted(path.split("\\.")[0]); + } + } + + private void markFileDeleted(String uuid) { + if (null != XmppConnectionServiceAccessor.xmppConnectionService) { + for (Conversation conversation : XmppConnectionServiceAccessor.xmppConnectionService.getConversations()) { + Message message = conversation.findMessageWithFileAndUuid(uuid); + if (message != null) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); + } else { + UiUpdateHelper.updateConversationUi(); + } + return; + } + } + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java index c1426080..9c0e55db 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java @@ -7,7 +7,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.database.ContentObserver; import android.graphics.Bitmap; import android.media.AudioManager; @@ -17,13 +16,11 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.FileObserver; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.security.KeyChain; import android.util.DisplayMetrics; @@ -66,6 +63,7 @@ import de.thedevstack.conversationsplus.utils.FileUtils; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.Config; @@ -204,16 +202,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private PushManagementService mPushManagementService = new PushManagementService(this); private OnConversationUpdate mOnConversationUpdate = null; - private final FileObserver fileObserver = new FileObserver( - FileBackend.getConversationsImageDirectory()) { - - @Override - public void onEvent(int event, String path) { - if (event == FileObserver.DELETE) { - markFileDeleted(path.split("\\.")[0]); - } - } - }; private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { @Override @@ -229,7 +217,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getAccount() == account) { Message message = conversation.findUnsentMessageWithUuid(uuid); if (message != null) { - markMessage(message, Message.STATUS_SEND); + MessageUtil.markMessage(message, Message.STATUS_SEND); } } } @@ -372,64 +360,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void attachLocationToConversation(final Conversation conversation, - final Uri uri, - final UiCallback<Message> callback) { - int encryption = conversation.getNextEncryption(); - if (encryption == Message.ENCRYPTION_PGP) { - encryption = Message.ENCRYPTION_DECRYPTED; - } - Message message = new Message(conversation, uri.toString(), encryption); - if (conversation.getNextCounterpart() != null) { - message.setCounterpart(conversation.getNextCounterpart()); - } - if (encryption == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message, callback); - } else { - callback.success(message); - } - } - - public void attachFileToConversation(final Conversation conversation, - final Uri uri, - final UiCallback<Message> callback) { - final Message message; - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); - } else { - message = new Message(conversation, "", conversation.getNextEncryption()); - } - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_FILE); - String path = FileUtils.getPath(uri); - if (path != null) { - message.setRelativeFilePath(path); - MessageUtil.updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message, callback); - } else { - callback.success(message); - } - } else { - ConversationsPlusApplication.executeFileAdding(new Runnable() { - @Override - public void run() { - try { - FileBackend.copyFileToPrivateStorage(message, uri); - MessageUtil.updateFileParams(message); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - getPgpEngine().encrypt(message, callback); - } else { - callback.success(message); - } - } catch (FileCopyException e) { - callback.error(e.getResId(), message); - } - } - }); - } - } - public Conversation find(Bookmark bookmark) { return find(bookmark.getAccount(), bookmark.getJid()); } @@ -665,7 +595,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); - this.fileObserver.startWatching(); if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @@ -690,6 +619,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa toggleForegroundService(); updateUnreadCountBadge(); UiUpdateHelper.initXmppConnectionService(this); + XmppConnectionServiceAccessor.initXmppConnectionService(this); toggleScreenEventReceiver(); } @@ -840,7 +770,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { - markMessage(message, Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); } }); } @@ -952,9 +882,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (resend) { if (packet != null && addToConversation) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { - markMessage(message, Message.STATUS_UNSEND); + MessageUtil.markMessage(message, Message.STATUS_UNSEND); } else { - markMessage(message, Message.STATUS_SEND); + MessageUtil.markMessage(message, Message.STATUS_SEND); } } } else { @@ -1170,31 +1100,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); final int s = message.getStatus(); if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { - markMessage(message, Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); } } } }); } - private void markFileDeleted(String uuid) { - for (Conversation conversation : getConversations()) { - Message message = conversation.findMessageWithFileAndUuid(uuid); - if (message != null) { - if (!FileBackend.isFileAvailable(message)) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - final int s = message.getStatus(); - if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { - markMessage(message, Message.STATUS_SEND_FAILED); - } else { - updateConversationUi(); - } - } - return; - } - } - } - public void populateWithOrderedConversations(final List<Conversation> list) { populateWithOrderedConversations(list, true); } @@ -2203,11 +2115,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } - public void updateMessage(Message message, String uuid) { - databaseBackend.updateMessage(message, uuid); - updateConversationUi(); - } - protected void syncDirtyContacts(Account account) { for (Contact contact : account.getRoster().getContacts()) { if (contact.getOption(Contact.Options.DIRTY_PUSH)) { @@ -2379,7 +2286,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { - markMessage(message, Message.STATUS_WAITING); + MessageUtil.markMessage(message, Message.STATUS_WAITING); } }); } @@ -2394,7 +2301,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); if (message != null) { - markMessage(message, status); + MessageUtil.markMessage(message, status); } return message; } @@ -2402,31 +2309,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return null; } - public boolean markMessage(Conversation conversation, String uuid, int status) { - if (uuid == null) { - return false; - } else { - Message message = conversation.findSentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, status); - return true; - } else { - return false; - } - } - } - - public void markMessage(Message message, int status) { - if (status == Message.STATUS_SEND_FAILED - && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message - .getStatus() == Message.STATUS_SEND_DISPLAYED)) { - return; - } - message.setStatus(status); - databaseBackend.updateMessage(message); - updateConversationUi(); - } - public int unreadCount() { int count = 0; for (Conversation conversation : getConversations()) { @@ -2724,7 +2606,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void resendFailedMessages(final Message message) { if (message.getStatus() == Message.STATUS_SEND_FAILED) { message.setTime(System.currentTimeMillis()); - markMessage(message, Message.STATUS_WAITING); + MessageUtil.markMessage(message, Message.STATUS_WAITING); this.resendMessage(message, false); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java index e46095e7..7138f8e3 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java @@ -52,6 +52,7 @@ import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; +import de.thedevstack.conversationsplus.utils.ConversationUtil; import de.timroes.android.listview.EnhancedListView; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -1483,7 +1484,7 @@ public class ConversationActivity extends XmppActivity if (conversation == null) { return; } - xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() { + ConversationUtil.attachLocationToConversation(conversation,uri, new UiCallback<Message>() { @Override public void success(Message message) { @@ -1508,7 +1509,7 @@ public class ConversationActivity extends XmppActivity } final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); prepareFileToast.show(); - xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() { + ConversationUtil.attachFileToConversation(conversation, uri, new UiCallback<Message>() { @Override public void success(Message message) { hidePrepareFileToast(prepareFileToast); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java index 4ae88a5e..f065a25b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java @@ -69,6 +69,7 @@ import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictu import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictureLongClicked; import de.thedevstack.conversationsplus.ui.listeners.ConversationSwipeRefreshListener; import de.thedevstack.conversationsplus.utils.GeoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -693,7 +694,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (transferable != null) { transferable.cancel(); } else { - activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java index 35816cff..84eb7016 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java @@ -9,13 +9,10 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.util.Log; import android.widget.Toast; import java.security.KeyStoreException; @@ -31,7 +28,6 @@ import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.services.ExportLogsService; import de.thedevstack.conversationsplus.xmpp.XmppConnection; import github.ankushsachdeva.emojicon.EmojiconHandler; @@ -187,7 +183,9 @@ public class SettingsActivity extends XmppActivity implements } else if ("parse_emoticons".equals(name)) { EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS); } else if ("file_transfer_folder".equals(name)) { - FileBackend.createNoMedia(); + FileBackend.onFileTransferFolderChanged(); + } else if ("img_transfer_folder".equals(name)) { + FileBackend.onImageTransferFolderChanged(); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java index fe3ed73f..28e8beb8 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java @@ -31,6 +31,7 @@ import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.adapter.ConversationAdapter; +import de.thedevstack.conversationsplus.utils.ConversationUtil; import de.thedevstack.conversationsplus.utils.FileUtils; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -300,9 +301,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer @Override public void onPresenceSelected() { replaceToast(getString(R.string.preparing_file)); - ShareWithActivity.this.xmppConnectionService - .attachFileToConversation(conversation, share.uris.get(0), - attachFileCallback); + ConversationUtil.attachFileToConversation(conversation, share.uris.get(0), attachFileCallback); switchToConversation(conversation, null, true); finish(); } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java index dfa3147d..e1c30a56 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java @@ -13,6 +13,7 @@ import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.utils.UIHelper; +import de.thedevstack.conversationsplus.utils.ui.TextViewUtil; /** * Fills the contents to the message details dialog. @@ -59,11 +60,11 @@ public class MessageDetailsDialog extends AbstractAlertDialog { view.findViewById(R.id.dlgMsgDetFileTable).setVisibility(View.VISIBLE); if (null != message.getFileParams()) { Message.FileParams params = message.getFileParams(); - TextView tvFilesize = (TextView) view.findViewById(R.id.dlgMsgDetFileSize); - tvFilesize.setText(UIHelper.getHumanReadableFileSize(params.size)); + TextViewUtil.setText(view, R.id.dlgMsgDetFileSize, UIHelper.getHumanReadableFileSize(params.size)); } - TextView mimetype = (TextView) view.findViewById(R.id.dlgMsgDetFileMimeType); - mimetype.setText(message.getMimeType()); + TextViewUtil.setText(view, R.id.dlgMsgDetFileMimeType, message.getMimeType()); + + TextViewUtil.setText(view, R.id.dlgMsgDetFileHttpUploaded, message.isHttpUploaded() ? R.string.cplus_yes : R.string.cplus_no); } } @@ -107,7 +108,6 @@ public class MessageDetailsDialog extends AbstractAlertDialog { * @param message the message to display in dialog */ protected void displayMessageTypeInfo(View view, Message message) { - TextView msgTypeTextView = (TextView) view.findViewById(R.id.dlgMsgDetMsgType); int msgTypeResId; switch (message.getType()) { case Message.TYPE_PRIVATE: @@ -126,7 +126,7 @@ public class MessageDetailsDialog extends AbstractAlertDialog { default: msgTypeResId = R.string.dlg_msg_details_msg_type_text; } - msgTypeTextView.setText(msgTypeResId); + TextViewUtil.setText(view, R.id.dlgMsgDetMsgType, msgTypeResId); } /** @@ -146,10 +146,8 @@ public class MessageDetailsDialog extends AbstractAlertDialog { if (conversation.getMode() == Conversation.MODE_MULTI) { // Change label of sending and receiving party to MUC terminology - TextView senderLabel = (TextView) view.findViewById(R.id.dlgMsgDetLblSender); - senderLabel.setText(R.string.dlg_msg_details_sender_nick); - TextView receipientLabel = (TextView) view.findViewById(R.id.dlgMsgDetLblReceipient); - receipientLabel.setText(R.string.dlg_msg_details_receipient_nick); + TextViewUtil.setText(view, R.id.dlgMsgDetLblSender, R.string.dlg_msg_details_sender_nick); + TextViewUtil.setText(view, R.id.dlgMsgDetLblReceipient, R.string.dlg_msg_details_receipient_nick); // Get own nick for MUC me = conversation.getMucOptions().getActualNick(); @@ -173,7 +171,6 @@ public class MessageDetailsDialog extends AbstractAlertDialog { * @param message the message to display in dialog */ protected void displayMessageSentTime(View view, Message message) { - TextView timeSent = (TextView) view.findViewById(R.id.dlgMsgDetTimeSent); - timeSent.setText(DateFormat.format("dd.MM.yyyy kk:mm:ss", new Date(message.getTimeSent()))); + TextViewUtil.setText(view, R.id.dlgMsgDetTimeSent, DateFormat.format("dd.MM.yyyy kk:mm:ss", new Date(message.getTimeSent()))); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java index 231f738c..b7340ce0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java @@ -13,6 +13,7 @@ import java.io.InputStream; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.crypto.PgpEngine; import de.thedevstack.conversationsplus.enums.UserDecision; import de.thedevstack.conversationsplus.exceptions.UiException; import de.thedevstack.conversationsplus.utils.FileUtils; @@ -29,7 +30,7 @@ import de.thedevstack.conversationsplus.ui.XmppActivity; import de.thedevstack.conversationsplus.utils.StreamUtil; /** - * Created by tzur on 31.10.2015. + * Listener to let the user decide whether to resize a picture before sending or not. */ public class ResizePictureUserDecisionListener implements UserDecisionListener { protected Uri uri; @@ -156,7 +157,7 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { String filePath = FileUtils.getPath(uri); MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - xmppConnectionService.getPgpEngine().encrypt(message, callback); + PgpEngine.getInstance().encrypt(message, callback); } else { callback.success(message); } @@ -195,7 +196,7 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { int imageHeight = resizedAndRotatedImage.getHeight(); MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - xmppConnectionService.getPgpEngine().encrypt(message, callback); + PgpEngine.getInstance().encrypt(message, callback); } else { callback.success(message); } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java new file mode 100644 index 00000000..77c2c728 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java @@ -0,0 +1,75 @@ +package de.thedevstack.conversationsplus.utils; + +import android.net.Uri; + +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.crypto.PgpEngine; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.exceptions.FileCopyException; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.ui.UiCallback; + +/** + * Utility class to work with conversations. + */ +public class ConversationUtil { + + public static void attachLocationToConversation(final Conversation conversation, + final Uri uri, + final UiCallback<Message> callback) { + int encryption = conversation.getNextEncryption(); + if (encryption == Message.ENCRYPTION_PGP) { + encryption = Message.ENCRYPTION_DECRYPTED; + } + Message message = new Message(conversation, uri.toString(), encryption); + if (conversation.getNextCounterpart() != null) { + message.setCounterpart(conversation.getNextCounterpart()); + } + if (encryption == Message.ENCRYPTION_DECRYPTED) { + PgpEngine.getInstance().encrypt(message, callback); + } else { + callback.success(message); + } + } + + public static void attachFileToConversation(final Conversation conversation, + final Uri uri, + final UiCallback<Message> callback) { + final Message message; + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); + } else { + message = new Message(conversation, "", conversation.getNextEncryption()); + } + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_FILE); + String path = FileUtils.getPath(uri); + if (path != null) { + message.setRelativeFilePath(path); + MessageUtil.updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + PgpEngine.getInstance().encrypt(message, callback); + } else { + callback.success(message); + } + } else { + ConversationsPlusApplication.executeFileAdding(new Runnable() { + @Override + public void run() { + try { + FileBackend.copyFileToPrivateStorage(message, uri); + MessageUtil.updateFileParams(message); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + PgpEngine.getInstance().encrypt(message, callback); + } else { + callback.success(message); + } + } catch (FileCopyException e) { + callback.error(e.getResId(), message); + } + } + }); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java index 6a668938..67433b31 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -6,8 +6,11 @@ import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.persistance.FileBackend; /** @@ -15,6 +18,32 @@ import de.thedevstack.conversationsplus.persistance.FileBackend; */ public final class MessageUtil { + + public static boolean markMessage(Conversation conversation, String uuid, int status) { + if (uuid == null) { + return false; + } else { + Message message = conversation.findSentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, status); + return true; + } else { + return false; + } + } + } + + public static void markMessage(Message message, int status) { + if (status == Message.STATUS_SEND_FAILED + && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message + .getStatus() == Message.STATUS_SEND_DISPLAYED)) { + return; + } + message.setStatus(status); + DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateMessage(message); + UiUpdateHelper.updateConversationUi(); + } + public static boolean wasHighlightedOrPrivate(final Message message) { final String nick = message.getConversation().getMucOptions().getActualNick(); final Pattern highlight = generateNickHighlightPattern(nick); diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/XmppConnectionServiceAccessor.java b/src/main/java/de/thedevstack/conversationsplus/utils/XmppConnectionServiceAccessor.java new file mode 100644 index 00000000..1f1d7cf4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/XmppConnectionServiceAccessor.java @@ -0,0 +1,31 @@ +package de.thedevstack.conversationsplus.utils; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.services.XmppConnectionService; + +/** + * Accessor utility to access XmppConnectionService without having to pass the XmppConnectionService every time. + */ +public final class XmppConnectionServiceAccessor { + public static XmppConnectionService xmppConnectionService; + + /** + * Initializes the XmppConnectionService. + * This method needs to be called once in XmppConnectionService#onCreate. + * @param xmppConnectionService + */ + public static void initXmppConnectionService(XmppConnectionService xmppConnectionService) { + if (null == XmppConnectionServiceAccessor.xmppConnectionService) { + XmppConnectionServiceAccessor.xmppConnectionService = xmppConnectionService; + } else { + Logging.e("XmppConnectionServiceAccessor", "XMPP Connection Service already instantiated."); + } + } + + /** + * Avoid instantiation + */ + private XmppConnectionServiceAccessor() { + // avoid instantiation + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java index bb08014b..a775dad6 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ui/TextViewUtil.java @@ -1,12 +1,28 @@ package de.thedevstack.conversationsplus.utils.ui; import android.support.annotation.StringRes; +import android.view.View; import android.widget.TextView; /** * Created by steckbrief on 29.03.2016. */ public final class TextViewUtil { + + public static void setText(View parentView, int textViewId, CharSequence text) { + TextView tv = (TextView) parentView.findViewById(textViewId); + if (null != tv) { + tv.setText(text); + } + } + + public static void setText(View parentView, int textViewId, int textResId) { + TextView tv = (TextView) parentView.findViewById(textViewId); + if (null != tv) { + tv.setText(textResId); + } + } + public static void enable(TextView tv) { setColorEnabledAndTextResId(tv, null, true, null); } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java new file mode 100644 index 00000000..3e9d7f2b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java @@ -0,0 +1,15 @@ +package de.thedevstack.conversationsplus.xmpp.httpuploadim; + +import de.thedevstack.conversationsplus.xml.Element; + +/** + * Created by steckbrief on 17.04.2016. + */ +public class HttpUploadHint extends Element { + public static final String NAMESPACE = "urn:xmpp:hints"; + public static final String ELEMENT_NAME = "httpupload"; + + public HttpUploadHint() { + super(ELEMENT_NAME, NAMESPACE); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java index b4c05ce4..422bdff0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java @@ -103,7 +103,7 @@ public class JingleConnection implements Transferable { sendSuccess(); MessageUtil.updateFileParams(message); mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED); + MessageUtil.markMessage(message,Message.STATUS_RECEIVED); if (acceptedAutomatically) { message.markUnread(); JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); @@ -457,7 +457,7 @@ public class JingleConnection implements Transferable { if (packet.getType() == IqPacket.TYPE.RESULT) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer"); mJingleStatus = JINGLE_STATUS_INITIATED; - mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); + MessageUtil.markMessage(message, Message.STATUS_OFFERED); } else { fail(); } @@ -548,7 +548,7 @@ public class JingleConnection implements Transferable { mergeCandidates(JingleCandidate.parse(content.socks5transport() .getChildren())); this.mJingleStatus = JINGLE_STATUS_ACCEPTED; - mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); + MessageUtil.markMessage(message, Message.STATUS_UNSEND); this.connectNextCandidate(); return true; } @@ -780,7 +780,7 @@ public class JingleConnection implements Transferable { private void receiveSuccess() { this.mJingleStatus = JINGLE_STATUS_FINISHED; - this.mXmppConnectionService.markMessage(this.message,Message.STATUS_SEND_RECEIVED); + MessageUtil.markMessage(this.message,Message.STATUS_SEND_RECEIVED); this.disconnectSocks5Connections(); if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); @@ -803,8 +803,7 @@ public class JingleConnection implements Transferable { } this.mXmppConnectionService.updateConversationUi(); } else { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(this.message, Message.STATUS_SEND_FAILED); this.message.setTransferable(null); } } @@ -825,8 +824,7 @@ public class JingleConnection implements Transferable { } this.mXmppConnectionService.updateConversationUi(); } else { - this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED); + MessageUtil.markMessage(this.message, Message.STATUS_SEND_FAILED); this.message.setTransferable(null); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java index 3284a936..2deb79c6 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java @@ -15,6 +15,7 @@ import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; @@ -65,7 +66,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { old.cancel(); } JingleConnection connection = new JingleConnection(this); - mXmppConnectionService.markMessage(message,Message.STATUS_WAITING); + MessageUtil.markMessage(message,Message.STATUS_WAITING); connection.init(message); this.connections.add(connection); return connection; diff --git a/src/main/res/layout/dialog_message_details.xml b/src/main/res/layout/dialog_message_details.xml index 84159f44..7c5d92cb 100644 --- a/src/main/res/layout/dialog_message_details.xml +++ b/src/main/res/layout/dialog_message_details.xml @@ -128,6 +128,28 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@string/dlg_msg_details_httpuploaded" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="" + android:textAlignment="textEnd" + android:gravity="end" + android:id="@+id/dlgMsgDetFileHttpUploaded" /> + </TableRow> + + <TableRow + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + android:layout_marginTop="0.3dp" + android:layout_marginLeft="0.3dp" + android:layout_marginRight="0.3dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:text="@string/dlg_msg_details_file_mime" /> <TextView diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index b6aa8899..3da33010 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -670,6 +670,7 @@ <string name="select_image_and_crop">Select image and crop</string> <string name="this_account_is_disabled">You have disabled this account</string> <string name="cplus_copy_item">Copy item</string> + <string name="dlg_msg_details_httpuploaded">Shared using HTTP upload</string> <string name="security_error_invalid_file_access">Security error: Invalid file access</string> <string name="no_application_to_share_uri">No application found to share URI</string> <string name="share_uri_with">Share URI with…</string> |