diff options
Diffstat (limited to 'src/main')
54 files changed, 917 insertions, 506 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4eeb4c367..7787a652a 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -95,7 +95,7 @@ android:configChanges="orientation|screenSize" android:label="@string/app_name" android:launchMode="singleTask" - android:theme="@style/SplashTheme"> + android:theme="@style/SplashTheme" android:windowSoftInputMode="stateHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -108,7 +108,7 @@ android:launchMode="singleTask" android:minWidth="300dp" android:minHeight="300dp" - android:windowSoftInputMode="stateHidden"></activity> + android:windowSoftInputMode="stateHidden" /> <activity android:name=".ui.ScanActivity" android:screenOrientation="portrait" @@ -180,6 +180,17 @@ android:name=".ui.ChangePasswordActivity" android:label="@string/change_password_on_server" /> <activity + android:name=".ui.ChooseAccountForProfilePictureActivity" + android:enabled="false" + android:label="@string/choose_account"> + <intent-filter android:label="@string/set_profile_picture"> + <action android:name="android.intent.action.ATTACH_DATA" /> + <category android:name="android.intent.category.DEFAULT" /> + + <data android:mimeType="image/*" /> + </intent-filter> + </activity> + <activity android:name=".ui.ManageAccountActivity" android:label="@string/title_activity_manage_accounts" android:launchMode="singleTask" /> @@ -219,6 +230,13 @@ <action android:name="android.intent.action.SEND_MULTIPLE" /> <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="text/plain" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <action android:name="android.intent.action.SEND_MULTIPLE" /> + + <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="*/*" /> </intent-filter> diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index ce65587b7..5ad1f873c 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -85,6 +85,7 @@ public final class Config { public static final int FILE_SIZE = 1048576; // 1 MiB public static final int AVATAR_SIZE = 480; + public static final int SYSTEM_UI_AVATAR_SIZE = 48; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG; public static final int AVATAR_CHAR_LIMIT = 9400; diff --git a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java index 1c1a995a9..cc5e3fe70 100644 --- a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java +++ b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java @@ -135,6 +135,7 @@ public class PgpDecryptionService { } private void executeApi(Message message) { + boolean skipNotificationPush = false; synchronized (message) { Intent params = userInteractionResult != null ? userInteractionResult : new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); @@ -176,7 +177,7 @@ public class PgpDecryptionService { mXmppConnectionService.updateMessage(message); break; } - } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { + } else if (message.isFileOrImage()) { try { final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false); final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true); @@ -208,9 +209,12 @@ public class PgpDecryptionService { URL url = message.getFileParams().url; mXmppConnectionService.getFileBackend().updateFileParams(message, url); message.setEncryption(Message.ENCRYPTION_DECRYPTED); - inputFile.delete(); - mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile); mXmppConnectionService.updateMessage(message); + if (!inputFile.delete()) { + Log.w(Config.LOGTAG, "unable to delete pgp encrypted source file " + inputFile.getAbsolutePath()); + } + skipNotificationPush = true; + mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile, () -> notifyIfPending(message)); break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: synchronized (PgpDecryptionService.this) { @@ -231,7 +235,9 @@ public class PgpDecryptionService { } } } - notifyIfPending(message); + if (!skipNotificationPush) { + notifyIfPending(message); + } } private synchronized void notifyIfPending(Message message) { diff --git a/src/main/java/de/pixart/messenger/entities/Contact.java b/src/main/java/de/pixart/messenger/entities/Contact.java index aa7319ad9..3ee636fe4 100644 --- a/src/main/java/de/pixart/messenger/entities/Contact.java +++ b/src/main/java/de/pixart/messenger/entities/Contact.java @@ -463,10 +463,14 @@ public class Contact implements ListItem, Blockable { } public boolean setAvatar(Avatar avatar) { + return setAvatar(avatar, false); + } + + public boolean setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) { if (this.avatar != null && this.avatar.equals(avatar)) { return false; } else { - if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { + if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { return false; } this.avatar = avatar; diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java index 502f2638d..523e02b77 100644 --- a/src/main/java/de/pixart/messenger/entities/Conversation.java +++ b/src/main/java/de/pixart/messenger/entities/Conversation.java @@ -193,26 +193,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } - public void findMessagesWithFiles(final OnMessageFound onMessageFound) { - final ArrayList<Message> results = new ArrayList<>(); - synchronized (this.messages) { - for (final Message m : this.messages) { - if (m.isFileOrImage() && m.getEncryption() != Message.ENCRYPTION_PGP) { - results.add(m); - } - } - } - for (Message result : results) { - onMessageFound.onMessageFound(result); - } - } - public void findFailedMessagesWithFiles(final OnMessageFound onMessageFound) { final ArrayList<Message> results = new ArrayList<>(); synchronized (this.messages) { for (final Message m : this.messages) { if (m.isFileOrImage() && m.getEncryption() != Message.ENCRYPTION_PGP) { - if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isDeleted() && m.needsUploading()) { + if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isFileDeleted() && m.needsUploading()) { results.add(m); } } @@ -227,7 +213,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl final ArrayList<Message> results = new ArrayList<>(); synchronized (this.messages) { for (final Message m : this.messages) { - if (m.isDeleted()) { + if (m.isFileDeleted()) { results.add(m); } } @@ -242,7 +228,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl for (final Message message : this.messages) { if (message.getUuid().equals(uuid) && message.getEncryption() != Message.ENCRYPTION_PGP - && (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.treatAsDownloadable())) { + && (message.isFileOrImage() || message.treatAsDownloadable())) { return message; } } @@ -250,6 +236,23 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return null; } + public boolean markAsDeleted(final List<String> uuids) { + boolean deleted = false; + final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService(); + synchronized (this.messages) { + for (Message message : this.messages) { + if (uuids.contains(message.getUuid())) { + message.setFileDeleted(true); + deleted = true; + if (message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) { + pgpDecryptionService.discard(message); + } + } + } + } + return deleted; + } + public void clearMessages() { synchronized (this.messages) { this.messages.clear(); @@ -569,19 +572,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } - public String getParticipants() { - if (getMode() == MODE_MULTI) { - String generatedName = getMucOptions().createNameFromParticipants(); - if (generatedName != null) { - return generatedName; - } else { - return null; - } - } else { - return null; - } - } - public String getAccountUuid() { return this.accountUuid; } @@ -1117,7 +1107,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl for (int i = this.messages.size() - 1; i >= 0; --i) { Message message = this.messages.get(i); if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) && message.getEncryption() != Message.ENCRYPTION_PGP) { - if (message.getStatus() == Message.STATUS_SEND_FAILED && !message.isDeleted() && message.needsUploading()){ + if (message.getStatus() == Message.STATUS_SEND_FAILED && !message.isFileDeleted() && message.needsUploading()){ ++count; } } diff --git a/src/main/java/de/pixart/messenger/entities/IndividualMessage.java b/src/main/java/de/pixart/messenger/entities/IndividualMessage.java index 2a7b4c8da..195ea82c1 100644 --- a/src/main/java/de/pixart/messenger/entities/IndividualMessage.java +++ b/src/main/java/de/pixart/messenger/entities/IndividualMessage.java @@ -41,8 +41,8 @@ public class IndividualMessage extends Message { super(conversation); } - private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable) { - super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable); + private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted) { + super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted); } public static Message createDateSeparator(Message message) { @@ -99,7 +99,8 @@ public class IndividualMessage extends Message { cursor.getInt(cursor.getColumnIndex(OOB)) > 0, cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), - cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0); + cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, + cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0); } @Override diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java index abeda3ad3..c5843d840 100644 --- a/src/main/java/de/pixart/messenger/entities/Message.java +++ b/src/main/java/de/pixart/messenger/entities/Message.java @@ -73,6 +73,7 @@ public class Message extends AbstractEntity { public static final String ERROR_MESSAGE = "errorMsg"; public static final String READ_BY_MARKERS = "readByMarkers"; public static final String MARKABLE = "markable"; + public static final String FILE_DELETED = "file_deleted"; public static final String ME_COMMAND = "/me"; public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled"; @@ -86,6 +87,7 @@ public class Message extends AbstractEntity { protected int encryption; protected int status; protected int type; + protected boolean file_deleted = false; protected boolean carbon = false; protected boolean oob = false; protected String edited = null; @@ -139,6 +141,7 @@ public class Message extends AbstractEntity { false, null, null, + false, false); } @@ -148,7 +151,7 @@ public class Message extends AbstractEntity { final String remoteMsgId, final String relativeFilePath, final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted, final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers, - final boolean markable) { + final boolean markable, final boolean file_deleted) { this.conversation = conversation; this.uuid = uuid; this.conversationUuid = conversationUUid; @@ -171,6 +174,7 @@ public class Message extends AbstractEntity { this.errorMessage = errorMessage; this.readByMarkers = readByMarkers == null ? new HashSet<ReadByMarker>() : readByMarkers; this.markable = markable; + this.file_deleted = file_deleted; } public static Message fromCursor(Cursor cursor, Conversation conversation) { @@ -219,7 +223,8 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(OOB)) > 0, cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)), ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))), - cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0); + cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0, + cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0); } public static Message createStatusMessage(Conversation conversation, String body) { @@ -269,6 +274,7 @@ public class Message extends AbstractEntity { values.put(ERROR_MESSAGE, errorMessage); values.put(READ_BY_MARKERS, ReadByMarker.toJson(readByMarkers).toString()); values.put(MARKABLE, markable ? 1 : 0); + values.put(FILE_DELETED, file_deleted ? 1 : 0); return values; } @@ -390,6 +396,14 @@ public class Message extends AbstractEntity { return this.deleted; } + public boolean isFileDeleted() { + return this.file_deleted; + } + + public void setFileDeleted(boolean file_deleted) { + this.file_deleted = file_deleted; + } + public void markRead() { this.read = true; } @@ -840,10 +854,6 @@ public class Message extends AbstractEntity { return type == TYPE_FILE || type == TYPE_IMAGE; } - public boolean isDeleted() { - return (type == TYPE_FILE || type == TYPE_IMAGE) && getTransferable() != null && getTransferable().getFileSize() == 0; - } - public boolean hasFileOnRemoteHost() { return isFileOrImage() && getFileParams().url != null; } diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java index 6954995d9..01daad1bf 100644 --- a/src/main/java/de/pixart/messenger/entities/MucOptions.java +++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java @@ -101,8 +101,9 @@ public class MucOptions { return tookProposedNickFromBookmark; } - void notifyOfBookmarkNick(String nick) { - if (nick != null && nick.trim().equals(getSelf().getFullJid().getResource())) { + void notifyOfBookmarkNick(final String nick) { + final String normalized = normalize(account.getJid(),nick); + if (normalized != null && normalized.equals(getSelf().getFullJid().getResource())) { this.tookProposedNickFromBookmark = true; } } @@ -388,15 +389,15 @@ public class MucOptions { private String getProposedNick() { final Bookmark bookmark = this.conversation.getBookmark(); - final String bookmarkedNick = bookmark == null ? null : bookmark.getNick(); - if (bookmarkedNick != null && !bookmarkedNick.trim().isEmpty()) { + final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick()); + if (bookmarkedNick != null) { this.tookProposedNickFromBookmark = true; - return bookmarkedNick.trim(); + return bookmarkedNick; } else if (!conversation.getJid().isBareJid()) { return conversation.getJid().getResource(); } else { - final String displayName = account.getDisplayName(); - if (TextUtils.isEmpty(displayName)) { + final String displayName = normalize(account.getJid(), account.getDisplayName()); + if (displayName == null) { return JidHelper.localPartOrFallback(account.getJid()); } else { return displayName; @@ -404,6 +405,18 @@ public class MucOptions { } } + private static String normalize(Jid account, String nick) { + if (account == null || TextUtils.isEmpty(nick)) { + return null; + } + try { + return account.withResource(nick).getResource(); + } catch (IllegalArgumentException e) { + return null; + } + + } + public String getActualNick() { if (this.self.getName() != null) { return this.self.getName(); @@ -531,7 +544,7 @@ public class MucOptions { public Jid createJoinJid(String nick) { try { - return Jid.of(this.conversation.getJid().asBareJid().toString() + "/" + nick); + return conversation.getJid().withResource(nick); } catch (final IllegalArgumentException e) { return null; } diff --git a/src/main/java/de/pixart/messenger/entities/Transferable.java b/src/main/java/de/pixart/messenger/entities/Transferable.java index c07ec750d..e1deb744d 100644 --- a/src/main/java/de/pixart/messenger/entities/Transferable.java +++ b/src/main/java/de/pixart/messenger/entities/Transferable.java @@ -18,50 +18,12 @@ public interface Transferable { "gpg", "otr" ); - List<String> WELL_KNOWN_EXTENSIONS = Arrays.asList( - //documents - "pdf", - "doc", - "docx", - "txt", - //audio - "m4a", - "m4b", - "mp3", - "mp2", - "wav", - "aac", - "aif", - "aiff", - "aifc", - "mid", - "midi", - "3gpp", - //video - "avi", - "mp4", - "mpeg", - "mpg", - "mpe", - "mov", - "3gp", - //applications - "apk", - //contact - "vcf", - //calendar - "ics", - //compressed - "zip", - "rar" - ); int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; int STATUS_FAILED = 0x202; int STATUS_OFFER = 0x203; int STATUS_DOWNLOADING = 0x204; - int STATUS_DELETED = 0x205; int STATUS_OFFER_CHECK_FILESIZE = 0x206; int STATUS_UPLOADING = 0x207; diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java index cf867d796..102a11626 100644 --- a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java @@ -137,16 +137,14 @@ public class HttpDownloadConnection implements Transferable { public void cancel() { this.canceled = true; mHttpConnectionManager.finishConnection(this); + message.setTransferable(null); if (message.isFileOrImage()) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - } else { - message.setTransferable(null); + message.setFileDeleted(true); } mHttpConnectionManager.updateConversationUi(true); } private void finish() { - mXmppConnectionService.getFileBackend().updateMediaScanner(file); message.setTransferable(null); mHttpConnectionManager.finishConnection(this); boolean notify = acceptedAutomatically && !message.isRead(); @@ -154,9 +152,12 @@ public class HttpDownloadConnection implements Transferable { notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify); } mHttpConnectionManager.updateConversationUi(true); - if (notify) { - mXmppConnectionService.getNotificationService().push(message); - } + final boolean notifyAfterScan = notify; + mXmppConnectionService.getFileBackend().updateMediaScanner(file, () -> { + if (notifyAfterScan) { + mXmppConnectionService.getNotificationService().push(message); + } + }); } private void changeStatus(int status) { @@ -379,9 +380,9 @@ public class HttpDownloadConnection implements Transferable { connection.setUseCaches(false); connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getUserAgent()); connection.setRequestProperty("Accept-Encoding", "identity"); - final boolean tryResume = file.exists() && file.getKey() == null && file.getSize() > 0; + final long expected = file.getExpectedSize(); + final boolean tryResume = file.exists() && file.getKey() == null && file.getSize() > 0 && file.getSize() < expected; long resumeSize = 0; - long expected = file.getExpectedSize(); if (tryResume) { resumeSize = file.getSize(); Log.d(Config.LOGTAG, "http download trying resume after" + resumeSize + " of " + expected); diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index d8461d940..a1b6f498a 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -278,6 +278,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (account.getJid().asBareJid().equals(from)) { if (account.setAvatar(avatar.getFilename())) { mXmppConnectionService.databaseBackend.updateAccount(account); + mXmppConnectionService.notifyAccountAvatarHasChanged(account); } mXmppConnectionService.getAvatarService().clear(account); mXmppConnectionService.updateConversationUi(); @@ -650,7 +651,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet); mXmppConnectionService.updateMessage(replacedMessage, uuid); - mXmppConnectionService.getNotificationService().updateNotification(false); if (mXmppConnectionService.confirmMessages() && replacedMessage.getStatus() == Message.STATUS_RECEIVED && (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE) @@ -664,6 +664,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false); } } + mXmppConnectionService.getNotificationService().updateNotification(); return; } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out"); diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 85d32d55f..d4033eef1 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -60,7 +60,7 @@ import rocks.xmpp.addr.Jid; public class DatabaseBackend extends SQLiteOpenHelper { public static final String DATABASE_NAME = "history"; - public static final int DATABASE_VERSION = 44; // = Conversations DATABASE_VERSION + 2 + public static final int DATABASE_VERSION = 45; // = Conversations DATABASE_VERSION + 1 private static DatabaseBackend instance = null; private static String CREATE_CONTATCS_STATEMENT = "create table " @@ -161,6 +161,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ");"; private static String CREATE_MESSAGE_TIME_INDEX = "create INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")"; private static String CREATE_MESSAGE_CONVERSATION_INDEX = "create INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")"; + private static String CREATE_MESSAGE_DELETED_INDEX = "create index message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")"; + private static String CREATE_MESSAGE_FILE_DELETED_INDEX = "create index message_file_deleted_index ON " + Message.TABLENAME + "(" + Message.FILE_DELETED + ")"; + private static String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "create INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")"; + private static String CREATE_MESSAGE_TYPE_INDEX = "create INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; private static String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING FTS4(uuid TEXT PRIMARY KEY, body TEXT)"; private static String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; @@ -233,6 +237,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.ERROR_MESSAGE + " TEXT," + Message.READ_BY_MARKERS + " TEXT," + Message.MARKABLE + " NUMBER DEFAULT 0," + + Message.FILE_DELETED + " NUMBER DEFAULT 0," + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -240,6 +245,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_TIME_INDEX); db.execSQL(CREATE_MESSAGE_CONVERSATION_INDEX); + db.execSQL(CREATE_MESSAGE_DELETED_INDEX); + db.execSQL(CREATE_MESSAGE_FILE_DELETED_INDEX); + db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX); + db.execSQL(CREATE_MESSAGE_TYPE_INDEX); db.execSQL(CREATE_CONTATCS_STATEMENT); db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); db.execSQL(CREATE_SESSIONS_STATEMENT); @@ -337,7 +346,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } /* Any migrations that alter the Account table need to happen BEFORE this migration, as it * depends on account de-serialization. - */ + */ if (oldVersion < 17 && newVersion >= 17 && newVersion < 31) { List<Account> accounts = getAccounts(db); for (Account account : accounts) { @@ -535,6 +544,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 44 && newVersion >= 44) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.DELETED + " NUMBER DEFAULT 0"); } + + if (oldVersion < 45 && newVersion >= 45) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.FILE_DELETED + " NUMBER DEFAULT 0"); + db.execSQL(CREATE_MESSAGE_DELETED_INDEX); + db.execSQL(CREATE_MESSAGE_FILE_DELETED_INDEX); + db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX); + db.execSQL(CREATE_MESSAGE_TYPE_INDEX); + } } private void canonicalizeJids(SQLiteDatabase db) { @@ -797,9 +814,64 @@ public class DatabaseBackend extends SQLiteOpenHelper { }; } + public List<String> markFileAsDeleted(final File file, final boolean internal) { + SQLiteDatabase db = this.getReadableDatabase(); + String selection; + String[] selectionArgs; + if (internal) { + final String name = file.getName(); + if (name.endsWith(".pgp")) { + selection = "(" + Message.RELATIVE_FILE_PATH + " IN(?,?) OR (" + Message.RELATIVE_FILE_PATH + "=? and encryption in(1,4))) and type in (1,2)"; + selectionArgs = new String[]{file.getAbsolutePath(), name, name.substring(0, name.length() - 4)}; + } else { + selection = Message.RELATIVE_FILE_PATH + " IN(?,?) and type in (1,2)"; + selectionArgs = new String[]{file.getAbsolutePath(), name}; + } + } else { + selection = Message.RELATIVE_FILE_PATH + "=? and type in (1,2)"; + selectionArgs = new String[]{file.getAbsolutePath()}; + } + final List<String> uuids = new ArrayList<>(); + Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID}, selection, selectionArgs, null, null, null); + while (cursor != null && cursor.moveToNext()) { + uuids.add(cursor.getString(0)); + } + if (cursor != null) { + cursor.close(); + } + markFileAsDeleted(uuids); + return uuids; + } + + public void markFileAsDeleted(List<String> uuids) { + SQLiteDatabase db = this.getReadableDatabase(); + final ContentValues contentValues = new ContentValues(); + final String where = Message.UUID + "=?"; + contentValues.put(Message.FILE_DELETED, 1); + db.beginTransaction(); + for (String uuid : uuids) { + db.update(Message.TABLENAME, contentValues, where, new String[]{uuid}); + } + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public List<FilePath> getAllNonDeletedFilePath() { + final SQLiteDatabase db = this.getReadableDatabase(); + final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH}, "type in (1,2) and file_deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null", null, null, null, null); + final List<FilePath> list = new ArrayList<>(); + while (cursor != null && cursor.moveToNext()) { + list.add(new FilePath(cursor.getString(0), cursor.getString(1))); + } + if (cursor != null) { + cursor.close(); + } + return list; + } + public List<FilePath> getRelativeFilePaths(String account, Jid jid, int limit) { SQLiteDatabase db = this.getReadableDatabase(); - final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; + final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and file_deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; final String[] args = {account, jid.toEscapedString(), jid.toEscapedString() + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + String.valueOf(limit) : ""), args); List<FilePath> filesPaths = new ArrayList<>(); @@ -874,6 +946,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + private List<Account> getAccounts(SQLiteDatabase db) { List<Account> list = new ArrayList<>(); Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, @@ -910,11 +983,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { return db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args) == 1; } - public void updateMessage(Message message, String uuid) { + public boolean updateMessage(Message message, String uuid) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {uuid}; - db.update(Message.TABLENAME, message.getContentValues(), Message.UUID - + "=?", args); + return db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args) == 1; } public void readRoster(Roster roster) { @@ -953,10 +1025,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void deleteMessageInConversation(Message message) { long start = SystemClock.elapsedRealtime(); final SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); ContentValues values = new ContentValues(); values.put(Message.DELETED, "1"); String[] args = {message.getUuid()}; int rows = db.update("messages", values, "uuid =?", args); + db.setTransactionSuccessful(); + db.endTransaction(); Log.d(Config.LOGTAG, "deleted " + rows + " message in " + (SystemClock.elapsedRealtime() - start) + "ms"); } @@ -1527,7 +1602,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { storeIdentityKey(account, account.getJid().asBareJid().toString(), true, CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false)); } - public void recreateAxolotlDb(SQLiteDatabase db) { + + private void recreateAxolotlDb(SQLiteDatabase db) { Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java index 705e5489b..fae15dbda 100644 --- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java @@ -15,6 +15,7 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.media.MediaMetadataRetriever; +import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Environment; @@ -101,15 +102,57 @@ public class FileBackend { } } + public static Uri getMediaUri(Context context, File file) { + final String filePath = file.getAbsolutePath(); + final Cursor cursor = context.getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Images.Media._ID}, + MediaStore.Images.Media.DATA + "=? ", + new String[]{filePath}, null); + if (cursor != null && cursor.moveToFirst()) { + final int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); + cursor.close(); + return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id)); + } else { + return null; + } + } + public void updateMediaScanner(File file) { + updateMediaScanner(file, null); + } + + public void updateMediaScanner(File file, final Runnable callback) { if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images")) || file.getAbsolutePath().startsWith(getConversationsDirectory("Videos"))) { - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + MediaScannerConnection.scanFile(mXmppConnectionService, new String[]{file.getAbsolutePath()}, null, new MediaScannerConnection.MediaScannerConnectionClient() { + @Override + public void onMediaScannerConnected() { + + } + + @Override + public void onScanCompleted(String path, Uri uri) { + if (callback != null && file.getAbsolutePath().equals(path)) { + callback.run(); + } else { + Log.d(Config.LOGTAG, "media scanner scanned wrong file"); + if (callback != null) { + callback.run(); + } + } + } + }); + return; + /*Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); - mXmppConnectionService.sendBroadcast(intent); + mXmppConnectionService.sendBroadcast(intent);*/ } else { createNoMedia(); } + if (callback != null) { + callback.run(); + } } public boolean deleteFile(File file) { @@ -130,6 +173,10 @@ public class FileBackend { return getFile(message, true); } + public DownloadableFile getFileForPath(String path) { + return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path))); + } + public DownloadableFile getFileForPath(String path, String mime) { final DownloadableFile file; if (path.startsWith("/")) { @@ -148,6 +195,11 @@ public class FileBackend { return file; } + public boolean isInternalFile(final File file) { + final File internalFile = getFileForPath(file.getName()); + return file.getAbsolutePath().equals(internalFile.getAbsolutePath()); + } + public DownloadableFile getFile(Message message, boolean decrypted) { final boolean encrypted = !decrypted && (message.getEncryption() == Message.ENCRYPTION_PGP @@ -212,13 +264,8 @@ public class FileBackend { List<Attachment> attachments = new ArrayList<>(); for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) { final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path)); - Log.d(Config.LOGTAG, "mime=" + mime); - File file = getFileForPath(relativeFilePath.path, mime); - if (file.exists()) { - attachments.add(Attachment.of(relativeFilePath.uuid, file, mime)); - } else { - Log.d(Config.LOGTAG, "file " + file.getAbsolutePath() + " doesn't exist"); - } + final File file = getFileForPath(relativeFilePath.path, mime); + attachments.add(Attachment.of(relativeFilePath.uuid, file, mime)); } return attachments; } @@ -975,11 +1022,11 @@ public class FileBackend { public void updateFileParams(Message message, URL url) { DownloadableFile file = getFile(message); final String mime = file.getMimeType(); - boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/")); - boolean video = mime != null && mime.startsWith("video/"); - boolean audio = mime != null && mime.startsWith("audio/"); - boolean vcard = mime != null && mime.contains("vcard"); - boolean apk = mime != null && mime.equals("application/vnd.android.package-archive"); + final boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/")); + final boolean video = mime != null && mime.startsWith("video/"); + final boolean audio = mime != null && mime.startsWith("audio/"); + final boolean vcard = mime != null && mime.contains("vcard"); + final boolean apk = mime != null && mime.equals("application/vnd.android.package-archive"); final StringBuilder body = new StringBuilder(); if (url != null) { body.append(url.toString()); @@ -1003,16 +1050,8 @@ public class FileBackend { body.append("|0|0|0|").append(getAPK(file, mXmppConnectionService.getApplicationContext())); } message.setBody(body.toString()); - } - - public int getMediaRuntime(Uri uri) { - try { - MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); - mediaMetadataRetriever.setDataSource(mXmppConnectionService, uri); - return Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); - } catch (RuntimeException e) { - return 0; - } + message.setFileDeleted(false); + message.setType(image ? Message.TYPE_IMAGE : Message.TYPE_FILE); } private int getMediaRuntime(File file) { diff --git a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java index e4c07366d..eb38da8b3 100644 --- a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java +++ b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java @@ -3,7 +3,6 @@ package de.pixart.messenger.services; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.support.v4.content.ContextCompat; import android.util.Log; import de.pixart.messenger.Config; @@ -16,15 +15,7 @@ public class AlarmReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (intent.getAction().contains("exportlogs")) { Log.d(Config.LOGTAG, "Received alarm broadcast to export logs"); - try { - if (Compatibility.runsAndTargetsTwentySix(context)) { - ContextCompat.startForegroundService(context, new Intent(context, ExportLogsService.class)); - } else { - context.startService(new Intent(context, ExportLogsService.class)); - } - } catch (RuntimeException e) { - Log.d(Config.LOGTAG, "AlarmReceiver was unable to start ExportLogsService"); - } + Compatibility.startService(context, new Intent(context, ExportLogsService.class)); } } } diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java index 23e2da4d6..93d24a63f 100644 --- a/src/main/java/de/pixart/messenger/services/AvatarService.java +++ b/src/main/java/de/pixart/messenger/services/AvatarService.java @@ -1,5 +1,6 @@ package de.pixart.messenger.services; +import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -42,6 +43,8 @@ import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded; import de.pixart.messenger.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; +import static de.pixart.messenger.Config.SYSTEM_UI_AVATAR_SIZE; + public class AvatarService implements OnAdvancedStreamFeaturesLoaded { private static final int FG_COLOR = 0xFFFAFAFA; @@ -75,6 +78,10 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return value == null ? "" : value.toString(); } + public static int getSystemUiAvatarSize(final Context context) { + return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density); + } + private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { if (contact.isSelf()) { return get(contact.getAccount(), size, cachedOnly); diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java index 4c0aec3ca..7bda592e5 100644 --- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java +++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java @@ -18,31 +18,44 @@ import java.util.List; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.ui.ConversationsActivity; +import de.pixart.messenger.utils.Compatibility; @TargetApi(Build.VERSION_CODES.M) public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { private final Object lock = new Object(); - + private final int MAX_TARGETS = 5; private XmppConnectionService mXmppConnectionService; - private final int MAX_TARGETS = 5; + private static boolean textOnly(IntentFilter filter) { + for (int i = 0; i < filter.countDataTypes(); ++i) { + if (!"text/plain".equals(filter.getDataType(i))) { + return false; + } + } + return true; + } @Override public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { - Intent intent = new Intent(this, XmppConnectionService.class); + final ArrayList<ChooserTarget> chooserTargets = new ArrayList<>(); + if (!EventReceiver.hasEnabledAccounts(this)) { + return chooserTargets; + } + final Intent intent = new Intent(this, XmppConnectionService.class); intent.setAction("contact_chooser"); + Compatibility.startService(this, intent); bindService(intent, this, Context.BIND_AUTO_CREATE); - ArrayList<ChooserTarget> chooserTargets = new ArrayList<>(); try { waitForService(); final ArrayList<Conversation> conversations = new ArrayList<>(); if (!mXmppConnectionService.areMessagesInitialized()) { return chooserTargets; } - mXmppConnectionService.populateWithOrderedConversations(conversations, false); + + mXmppConnectionService.populateWithOrderedConversations(conversations, textOnly(matchedFilter)); final ComponentName componentName = new ComponentName(this, ConversationsActivity.class); - final int pixel = (int) (48 * getResources().getDisplayMetrics().density); + final int pixel = AvatarService.getSystemUiAvatarSize(this); for (Conversation conversation : conversations) { if (conversation.sentMessagesCount() == 0) { continue; diff --git a/src/main/java/de/pixart/messenger/services/EventReceiver.java b/src/main/java/de/pixart/messenger/services/EventReceiver.java index 03bf68ed9..8b3a6b415 100644 --- a/src/main/java/de/pixart/messenger/services/EventReceiver.java +++ b/src/main/java/de/pixart/messenger/services/EventReceiver.java @@ -25,16 +25,7 @@ public class EventReceiver extends BroadcastReceiver { } final String action = originalIntent.getAction(); if (action.equals("ui") || hasEnabledAccounts(context)) { - try { - if (Compatibility.runsAndTargetsTwentySix(context)) { - intentForService.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true); - ContextCompat.startForegroundService(context, intentForService); - } else { - context.startService(intentForService); - } - } catch (RuntimeException e) { - Log.d(Config.LOGTAG, "EventReceiver was unable to start service"); - } + Compatibility.startService(context, intentForService); } else { Log.d(Config.LOGTAG, "EventReceiver ignored action " + intentForService.getAction()); } diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index 83441cce1..8ded39d50 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -383,7 +383,13 @@ public class NotificationService { mBuilder.setColor(ContextCompat.getColor(mXmppConnectionService, R.color.primary)); } - public void updateNotification(final boolean notify) { + public void updateNotification() { + synchronized (notifications) { + updateNotification(false); + } + } + + private void updateNotification(final boolean notify) { updateNotification(notify, null, false); } @@ -523,15 +529,15 @@ public class NotificationService { if (messages.size() >= 1) { final Conversation conversation = (Conversation) messages.get(0).getConversation(); final UnreadConversation.Builder mUnreadBuilder = new UnreadConversation.Builder(conversation.getName().toString()); - mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService() - .get(conversation, getPixel(64))); + mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService().get(conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService))); mBuilder.setContentTitle(conversation.getName()); if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) { int count = messages.size(); mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count)); } else { Message message; - if ((message = getImage(messages)) != null) { + //TODO starting with Android 9 we might want to put images in MessageStyle + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && (message = getImage(messages)) != null) { modifyForImage(mBuilder, mUnreadBuilder, message, messages); } else { modifyForTextOnly(mBuilder, mUnreadBuilder, messages); @@ -615,8 +621,7 @@ public class NotificationService { private void modifyForImage(final Builder builder, final UnreadConversation.Builder uBuilder, final Message message, final ArrayList<Message> messages) { try { - final Bitmap bitmap = mXmppConnectionService.getFileBackend() - .getThumbnail(message, getPixel(288), false); + final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(message, getPixel(288), false); final ArrayList<Message> tmp = new ArrayList<>(); for (final Message msg : messages) { if (msg.getType() == Message.TYPE_TEXT @@ -651,44 +656,39 @@ public class NotificationService { } else { builder.setName(UIHelper.getMessageDisplayName(message)); } - IconCompat icon = getIcon(message); - if (icon != null) { - builder.setIcon(icon); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + builder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(message, AvatarService.getSystemUiAvatarSize(mXmppConnectionService), false))); } return builder.build(); } - private IconCompat getIcon(Message message) { - final Contact contact; - if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { - contact = message.getContact(); - } else { - Jid jid = message.getTrueCounterpart(); - contact = jid == null ? null : message.getConversation().getAccount().getRoster().getContact(jid); - } - if (contact != null) { - if (contact.getProfilePhoto() != null && QuickConversationsService.isConversations()) { - return IconCompat.createWithContentUri(contact.getProfilePhoto()); - } - if (contact.getAvatarFilename() != null) { - return IconCompat.createWithContentUri(mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename())); - } - } - return null; - } - private void modifyForTextOnly(final Builder builder, final UnreadConversation.Builder uBuilder, final ArrayList<Message> messages) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - final Person me = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me)).build(); - NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me); final Conversation conversation = (Conversation) messages.get(0).getConversation(); - if (conversation.getMode() == Conversation.MODE_MULTI) { + final Person.Builder meBuilder = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + meBuilder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation.getAccount(), AvatarService.getSystemUiAvatarSize(mXmppConnectionService)))); + } + final Person me = meBuilder.build(); + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me); + final boolean multiple = conversation.getMode() == Conversation.MODE_MULTI; + if (multiple) { messagingStyle.setConversationTitle(conversation.getName()); } for (Message message : messages) { final Person sender = message.getStatus() == Message.STATUS_RECEIVED ? getPerson(message) : null; - messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isImageMessage(message)) { + final Uri dataUri = FileBackend.getMediaUri(mXmppConnectionService, mXmppConnectionService.getFileBackend().getFile(message)); + NotificationCompat.MessagingStyle.Message imageMessage = new NotificationCompat.MessagingStyle.Message(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender); + if (dataUri != null) { + imageMessage.setData(message.getMimeType(), dataUri); + } + messagingStyle.addMessage(imageMessage); + } else { + messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender); + } } + messagingStyle.setGroupConversation(multiple); builder.setStyle(messagingStyle); } else { if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) { @@ -733,16 +733,21 @@ public class NotificationService { if (message.getStatus() != Message.STATUS_RECEIVED) { return null; } - if (message.getType() != Message.TYPE_TEXT - && message.getTransferable() == null - && message.getEncryption() != Message.ENCRYPTION_PGP - && message.getFileParams().height > 0) { + if (isImageMessage(message)) { image = message; } } return image; } + private static boolean isImageMessage(Message message) { + return message.getType() != Message.TYPE_TEXT + && message.getTransferable() == null + && !message.isFileDeleted() + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getFileParams().height > 0; + } + private Message getFirstDownloadableMessage(final Iterable<Message> messages) { for (final Message message : messages) { if (message.getTransferable() != null || (message.getType() == Message.TYPE_TEXT && message.treatAsDownloadable())) { @@ -773,7 +778,7 @@ public class NotificationService { } private PendingIntent createShowLocationIntent(final Message message) { - Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message, mXmppConnectionService); + Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(mXmppConnectionService, message); for (Intent intent : intents) { if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) { return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/src/main/java/de/pixart/messenger/services/ShortcutService.java b/src/main/java/de/pixart/messenger/services/ShortcutService.java index fa4853665..94bbc15cd 100644 --- a/src/main/java/de/pixart/messenger/services/ShortcutService.java +++ b/src/main/java/de/pixart/messenger/services/ShortcutService.java @@ -123,6 +123,7 @@ public class ShortcutService { intent.setAction(Intent.ACTION_VIEW); intent.setData(Uri.parse("xmpp:" + contact.getJid().asBareJid().toString())); intent.putExtra("account", contact.getAccount().getJid().asBareJid().toString()); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 98e330ad0..c34afff07 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -8,6 +8,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -52,6 +53,7 @@ import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; +import java.io.File; import java.net.URL; import java.security.SecureRandom; import java.security.Security; @@ -116,6 +118,7 @@ import de.pixart.messenger.parser.MessageParser; import de.pixart.messenger.parser.PresenceParser; import de.pixart.messenger.persistance.DatabaseBackend; import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.ui.ChooseAccountForProfilePictureActivity; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.UiCallback; import de.pixart.messenger.ui.interfaces.OnAvatarPublication; @@ -192,7 +195,8 @@ public class XmppConnectionService extends Service { private final IBinder mBinder = new XmppConnectionBinder(); private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private final IqGenerator mIqGenerator = new IqGenerator(this); - private final List<String> mInProgressAvatarFetches = new ArrayList<>(); + private final Set<String> mInProgressAvatarFetches = new HashSet<>(); + private final Set<String> mOmittedPepAvatarFetches = new HashSet<>(); private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>(); private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> { if (packet.getType() != IqPacket.TYPE.RESULT) { @@ -238,7 +242,6 @@ public class XmppConnectionService extends Service { ) { @Override public void onEvent(int event, String path) { - Log.d(Config.LOGTAG, "event " + event + " path=" + path); markFileDeleted(path); } }; @@ -336,6 +339,7 @@ public class XmppConnectionService extends Service { syncDirtyContacts(account); } }; + private boolean destroyed = false; private int unreadCount = -1; private AtomicLong mLastExpiryRun = new AtomicLong(0); private SecureRandom mRandom; @@ -375,7 +379,6 @@ public class XmppConnectionService extends Service { if (!conversation.startOtrIfNeeded()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": couldn't start OTR with " + conversation.getContact().getJid() + " when needed"); } - checkDeletedFiles(conversation); sendUnsentMessages(conversation); resendFailedFileMessages(conversation); } @@ -582,7 +585,7 @@ public class XmppConnectionService extends Service { final String action = intent == null ? null : intent.getAction(); final boolean needsForegroundService = intent != null && intent.getBooleanExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false); if (needsForegroundService) { - Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event"); + Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")"); toggleForegroundService(true); } String pushedAccountHash = null; @@ -1117,6 +1120,7 @@ public class XmppConnectionService extends Service { @SuppressLint("TrulyRandom") @Override public void onCreate() { + this.destroyed = false; OmemoSetting.load(this); ExceptionHelper.init(getApplicationContext()); try { @@ -1152,8 +1156,10 @@ public class XmppConnectionService extends Service { editor.putBoolean(SettingsActivity.SHOW_FOREGROUND_SERVICE, true); Log.d(Config.LOGTAG, Build.MANUFACTURER + " is on blacklist. enabling foreground service"); } - editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts()).apply(); + final boolean hasEnabledAccounts = hasEnabledAccounts(); + editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); editor.apply(); + toggleSetProfilePictureActivity(hasEnabledAccounts); restoreFromDatabase(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { @@ -1161,7 +1167,8 @@ public class XmppConnectionService extends Service { } if (Compatibility.hasStoragePermission(this)) { Log.d(Config.LOGTAG, "starting file observer"); - new Thread(fileObserver::startWatching).start(); + mFileAddingExecutor.execute(this.fileObserver::startWatching); + mFileAddingExecutor.execute(this::checkForDeletedFiles); } if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @@ -1202,6 +1209,30 @@ public class XmppConnectionService extends Service { CancelAutomaticExport(false); } + private void checkForDeletedFiles() { + if (destroyed) { + Log.d(Config.LOGTAG, "Do not check for deleted files because service has been destroyed"); + return; + } + final List<String> deletedUuids = new ArrayList<>(); + final List<DatabaseBackend.FilePath> relativeFilePaths = databaseBackend.getAllNonDeletedFilePath(); + for (final DatabaseBackend.FilePath filePath : relativeFilePaths) { + if (destroyed) { + Log.d(Config.LOGTAG, "Stop checking for deleted files because service has been destroyed"); + return; + } + final File file = fileBackend.getFileForPath(filePath.path); + if (!file.exists()) { + deletedUuids.add(filePath.uuid.toString()); + } + } + Log.d(Config.LOGTAG, "found " + deletedUuids.size() + " deleted files on start up. total=" + relativeFilePaths.size()); + if (deletedUuids.size() > 0) { + databaseBackend.markFileAsDeleted(deletedUuids); + markUuidsAsDeletedFiles(deletedUuids); + } + } + public void startContactObserver() { getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, new ContentObserver(null) { @Override @@ -1230,6 +1261,7 @@ public class XmppConnectionService extends Service { } catch (IllegalArgumentException e) { //ignored } + destroyed = false; fileObserver.stopWatching(); super.onDestroy(); // cancel scheduled exporter @@ -1238,7 +1270,8 @@ public class XmppConnectionService extends Service { public void restartFileObserver() { Log.d(Config.LOGTAG, "restarting file observer"); - new Thread(fileObserver::restartWatching).start(); + mFileAddingExecutor.execute(this.fileObserver::restartWatching); + mFileAddingExecutor.execute(this::checkForDeletedFiles); } public void toggleScreenEventReceiver() { @@ -1776,7 +1809,6 @@ public class XmppConnectionService extends Service { private void restoreMessages(Conversation conversation) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); - checkDeletedFiles(conversation); conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING)); conversation.findUnreadMessages(message -> mNotificationService.pushFromBacklog(message)); } @@ -1817,45 +1849,42 @@ public class XmppConnectionService extends Service { return this.conversations; } - private void checkDeletedFiles(Conversation conversation) { - conversation.findMessagesWithFiles(message -> { - if (!getFileBackend().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); - } - } - }); + private void markFileDeleted(final String path) { + final File file = new File(path); + final boolean isInternalFile = fileBackend.isInternalFile(file); + final List<String> uuids = databaseBackend.markFileAsDeleted(file, isInternalFile); + Log.d(Config.LOGTAG, "deleted file " + path + " internal=" + isInternalFile + ", database hits=" + uuids.size()); + markUuidsAsDeletedFiles(uuids); } - private void markFileDeleted(final String path) { - Log.d(Config.LOGTAG, "deleted file " + path); + private void markUuidsAsDeletedFiles(List<String> uuids) { + boolean deleted = false; for (Conversation conversation : getConversations()) { - conversation.findMessagesWithFiles(message -> { - DownloadableFile file = fileBackend.getFile(message); - if (file.getAbsolutePath().equals(path)) { - if (!file.exists()) { - 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(); - } - } else { - Log.d(Config.LOGTAG, "found matching message for file " + path + " but file still exists"); - } - } - }); + deleted |= conversation.markAsDeleted(uuids); + } + if (deleted) { + updateConversationUi(); } } public void populateWithOrderedConversations(final List<Conversation> list) { - populateWithOrderedConversations(list, true); + populateWithOrderedConversations(list, true, true); } - public void populateWithOrderedConversations(final List<Conversation> list, boolean includeNoFileUpload) { + public void populateWithOrderedConversations(final List<Conversation> list, final boolean includeNoFileUpload) { + populateWithOrderedConversations(list, includeNoFileUpload, true); + } + + public void populateWithOrderedConversations(final List<Conversation> list, final boolean includeNoFileUpload, final boolean sort) { + final List<String> orderedUuids; + if (sort) { + orderedUuids = null; + } else { + orderedUuids = new ArrayList<>(); + for (Conversation conversation : list) { + orderedUuids.add(conversation.getUuid()); + } + } list.clear(); if (includeNoFileUpload) { list.addAll(getConversations()); @@ -1868,7 +1897,18 @@ public class XmppConnectionService extends Service { } } try { - Collections.sort(list); + if (orderedUuids != null) { + Collections.sort(list, (a, b) -> { + final int indexA = orderedUuids.indexOf(a.getUuid()); + final int indexB = orderedUuids.indexOf(b.getUuid()); + if (indexA == -1 || indexB == -1 || indexA == indexB) { + return a.compareTo(b); + } + return indexA - indexB; + }); + } else { + Collections.sort(list); + } } catch (IllegalArgumentException e) { //ignore } @@ -1886,7 +1926,6 @@ public class XmppConnectionService extends Service { List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp); if (messages.size() > 0) { conversation.addAll(0, messages); - checkDeletedFiles(conversation); callback.onMoreMessagesLoaded(messages.size(), conversation); } else if (conversation.hasMessagesLeftOnServer() && account.isOnlineAndConnected() @@ -2033,7 +2072,6 @@ public class XmppConnectionService extends Service { } } } - checkDeletedFiles(c); if (joinAfterCreate) { joinMuc(c); @@ -2097,7 +2135,6 @@ public class XmppConnectionService extends Service { updateConversationUi(); c.messagesLoaded.set(true); } - checkDeletedFiles(c); } }); updateConversationUi(); @@ -2153,7 +2190,19 @@ public class XmppConnectionService extends Service { } private void syncEnabledAccountSetting() { - getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts()).apply(); + final boolean hasEnabledAccounts = hasEnabledAccounts(); + getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply(); + toggleSetProfilePictureActivity(hasEnabledAccounts); + } + + private void toggleSetProfilePictureActivity(final boolean enabled) { + try { + final ComponentName name = new ComponentName(this, ChooseAccountForProfilePictureActivity.class); + final int targetState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + getPackageManager().setComponentEnabledSetting(name, targetState, PackageManager.DONT_KILL_APP); + } catch (IllegalStateException e) { + Log.d(Config.LOGTAG, "unable to toggle profile picture actvitiy"); + } } public void createAccountFromKey(final String alias, final OnAccountCreated callback) { @@ -3386,6 +3435,7 @@ public class XmppConnectionService extends Service { if (account.setAvatar(avatar.getFilename())) { getAvatarService().clear(account); databaseBackend.updateAccount(account); + notifyAccountAvatarHasChanged(account); } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB"); if (callback != null) { @@ -3465,7 +3515,7 @@ public class XmppConnectionService extends Service { public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { final String KEY = generateFetchKey(account, avatar); synchronized (this.mInProgressAvatarFetches) { - if (!this.mInProgressAvatarFetches.contains(KEY)) { + if (mInProgressAvatarFetches.add(KEY)) { switch (avatar.origin) { case PEP: this.mInProgressAvatarFetches.add(KEY); @@ -3476,6 +3526,10 @@ public class XmppConnectionService extends Service { fetchAvatarVcard(account, avatar, callback); break; } + } else if (avatar.origin == Avatar.Origin.PEP) { + mOmittedPepAvatarFetches.add(KEY); + } else { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": already fetching " + avatar.origin + " avatar for " + avatar.owner); } } } @@ -3533,54 +3587,54 @@ public class XmppConnectionService extends Service { private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) { IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (mInProgressAvatarFetches) { - mInProgressAvatarFetches.remove(generateFetchKey(account, avatar)); - } - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element vCard = packet.findChild("vCard", "vcard-temp"); - Element photo = vCard != null ? vCard.findChild("PHOTO") : null; - String image = photo != null ? photo.findChildContent("BINVAL") : null; - if (image != null) { - avatar.image = image; - if (getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": successfully fetched vCard avatar for " + avatar.owner); - if (avatar.owner.isBareJid()) { - if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); - account.setAvatar(avatar.getFilename()); - databaseBackend.updateAccount(account); - getAvatarService().clear(account); - updateAccountUi(); - } else { - Contact contact = account.getRoster().getContact(avatar.owner); - if (contact.setAvatar(avatar)) { - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } - } - updateConversationUi(); + this.sendIqPacket(account, packet, (account1, packet1) -> { + final boolean previouslyOmittedPepFetch; + synchronized (mInProgressAvatarFetches) { + final String KEY = generateFetchKey(account1, avatar); + mInProgressAvatarFetches.remove(KEY); + previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); + } + if (packet1.getType() == IqPacket.TYPE.RESULT) { + Element vCard = packet1.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account1.getJid().asBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); + if (avatar.owner.isBareJid()) { + if (account1.getJid().asBareJid().equals(avatar.owner) && account1.getAvatar() == null) { + Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": had no avatar. replacing with vcard"); + account1.setAvatar(avatar.getFilename()); + databaseBackend.updateAccount(account1); + getAvatarService().clear(account1); + updateAccountUi(); } else { - Conversation conversation = find(account, avatar.owner.asBareJid()); - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); - if (user != null) { - if (user.setAvatar(avatar)) { - getAvatarService().clear(user); - updateConversationUi(); - updateMucRosterUi(); - } - if (user.getRealJid() != null) { - Contact contact = account.getRoster().getContact(user.getRealJid()); - if (contact.setAvatar(avatar)) { - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } + Contact contact = account1.getRoster().getContact(avatar.owner); + if (contact.setAvatar(avatar, previouslyOmittedPepFetch)) { + syncRoster(account1); + getAvatarService().clear(contact); + updateRosterUi(); + } + } + updateConversationUi(); + } else { + Conversation conversation = find(account1, avatar.owner.asBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + if (user.getRealJid() != null) { + Contact contact = account1.getRoster().getContact(user.getRealJid()); + if (contact.setAvatar(avatar)) { + syncRoster(account1); + getAvatarService().clear(contact); + updateRosterUi(); } } } @@ -3625,6 +3679,23 @@ public class XmppConnectionService extends Service { }); } + public void notifyAccountAvatarHasChanged(final Account account) { + final XmppConnection connection = account.getXmppConnection(); + if (connection != null && connection.getFeatures().bookmarksConversion()) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": avatar changed. resending presence to online group chats"); + for (Conversation conversation : conversations) { + if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) { + final MucOptions mucOptions = conversation.getMucOptions(); + if (mucOptions.online()) { + PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); + packet.setTo(mucOptions.getSelf().getFullJid()); + connection.sendPresencePacket(packet); + } + } + } + } + } + public void deleteContactOnServer(Contact contact) { contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); contact.resetOption(Contact.Options.DIRTY_PUSH); diff --git a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java index 7163f77cc..b642909b0 100644 --- a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java +++ b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java @@ -25,6 +25,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem BlockContactDialog.show(BlocklistActivity.this, (Contact) getListItems().get(position)); return true; }); + this.binding.fab.show(); this.binding.fab.setOnClickListener((v) -> showEnterJidDialog()); } @@ -38,7 +39,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem } filterContacts(); Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG); - if (fragment != null && fragment instanceof OnBackendConnected) { + if (fragment instanceof OnBackendConnected) { ((OnBackendConnected) fragment).onBackendConnected(); } } diff --git a/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java new file mode 100644 index 000000000..2d109809e --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java @@ -0,0 +1,82 @@ +package de.pixart.messenger.ui; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import de.pixart.messenger.R; +import de.pixart.messenger.entities.Account; +import de.pixart.messenger.ui.adapter.AccountAdapter; + +public class ChooseAccountForProfilePictureActivity extends XmppActivity { + + protected final List<Account> accountList = new ArrayList<>(); + protected ListView accountListView; + protected AccountAdapter mAccountAdapter; + + @Override + protected void refreshUiReal() { + loadEnabledAccounts(); + mAccountAdapter.notifyDataSetChanged(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_manage_accounts); + setSupportActionBar(findViewById(R.id.toolbar)); + configureActionBar(getSupportActionBar(), false); + accountListView = findViewById(R.id.account_list); + this.mAccountAdapter = new AccountAdapter(this, accountList, false); + accountListView.setAdapter(this.mAccountAdapter); + accountListView.setOnItemClickListener((arg0, view, position, arg3) -> { + final Account account = accountList.get(position); + goToProfilePictureActivity(account); + }); + } + + @Override + protected void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } + } + + @Override + void onBackendConnected() { + loadEnabledAccounts(); + if (accountList.size() == 1) { + goToProfilePictureActivity(accountList.get(0)); + return; + } + mAccountAdapter.notifyDataSetChanged(); + } + + private void loadEnabledAccounts() { + accountList.clear(); + for (Account account : xmppConnectionService.getAccounts()) { + if (account.isEnabled()) { + accountList.add(account); + } + } + } + + private void goToProfilePictureActivity(Account account) { + final Intent startIntent = getIntent(); + final Uri uri = startIntent == null ? null : startIntent.getData(); + if (uri != null) { + Intent intent = new Intent(this, PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); + intent.setData(uri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(intent); + } + finish(); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 7c9a5830b..ec9957b7c 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -289,8 +289,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - new EmojiService(this).init(useBundledEmoji); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details); this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings); this.binding.invite.setOnClickListener(inviteListener); diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 4df6391da..d7205506d 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -107,7 +107,10 @@ import de.pixart.messenger.ui.util.ScrollState; import de.pixart.messenger.ui.util.SendButtonAction; import de.pixart.messenger.ui.util.SendButtonTool; import de.pixart.messenger.ui.util.ShareUtil; +import de.pixart.messenger.ui.util.ViewUtil; import de.pixart.messenger.ui.widget.EditMessage; +import de.pixart.messenger.utils.Compatibility; +import de.pixart.messenger.utils.GeoHelper; import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.MessageUtils; import de.pixart.messenger.utils.NickValidityChecker; @@ -1277,12 +1280,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } - final boolean deleted = t != null && t instanceof TransferablePlaceholder; + final boolean deleted = m.isFileDeleted(); final boolean encrypted = m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED || m.getEncryption() == Message.ENCRYPTION_PGP; final boolean receiving = m.getStatus() == Message.STATUS_RECEIVED && (t instanceof JingleConnection || t instanceof HttpDownloadConnection); activity.getMenuInflater().inflate(R.menu.message_context, menu); menu.setHeaderTitle(R.string.message_options); + MenuItem openWith = menu.findItem(R.id.open_with); MenuItem copyMessage = menu.findItem(R.id.copy_message); MenuItem copyLink = menu.findItem(R.id.copy_link); MenuItem quoteMessage = menu.findItem(R.id.quote_message); @@ -1309,11 +1313,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke copyLink.setVisible(true); } } - if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED && !deleted) { retryDecryption.setVisible(true); } if (!showError && relevantForCorrection.getType() == Message.TYPE_TEXT + && !m.isGeoUri() && relevantForCorrection.isLastCorrectableMessage() && m.getConversation() instanceof Conversation && (((Conversation) m.getConversation()).getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) { @@ -1355,6 +1360,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (showError) { showErrorMessage.setVisible(true); } + final String mime = m.isFileOrImage() ? m.getMimeType() : null; + if ((m.isGeoUri() && GeoHelper.openInOsmAnd(getActivity(), m)) || (mime != null && mime.startsWith("audio/"))) { + openWith.setVisible(true); + } } } @@ -1400,6 +1409,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case R.id.show_error_message: showErrorMessage(selectedMessage); return true; + case R.id.open_with: + openWith(selectedMessage); + return true; default: return super.onContextItemSelected(item); } @@ -1670,7 +1682,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke Log.d(Config.LOGTAG, "type: " + transferable.getClass().getName()); Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); } - } else if (message.treatAsDownloadable()) { + } else if (message.treatAsDownloadable() || message.hasFileOnRemoteHost()) { createNewConnection(message); } } @@ -1843,6 +1855,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return null; } + private void openWith(final Message message) { + if (message.isGeoUri()) { + GeoHelper.view(getActivity(), message); + } else { + final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + ViewUtil.view(activity, file); + } + } + private void showErrorMessage(final Message message) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.error_message); @@ -1876,7 +1897,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke builder.setMessage(R.string.delete_file_dialog_msg); builder.setPositiveButton(R.string.confirm, (dialog, which) -> { if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + message.setFileDeleted(true); + activity.xmppConnectionService.updateMessage(message, false); activity.onConversationsListItemUpdated(); refresh(); } @@ -1890,8 +1912,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } final Conversation conversation = (Conversation) message.getConversation(); - DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - if (file.exists()) { + final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + if ((file.exists() && file.canRead()) || message.hasFileOnRemoteHost()) { final XmppConnection xmppConnection = conversation.getAccount().getXmppConnection(); if (!message.hasFileOnRemoteHost() && xmppConnection != null @@ -1907,9 +1929,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke }); return; } + } else if (!Compatibility.hasStoragePermission(getActivity())) { + Toast.makeText(activity, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + return; } else { Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + message.setFileDeleted(true); + activity.xmppConnectionService.updateMessage(message, false); activity.onConversationsListItemUpdated(); refresh(); return; diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java index 400519ab3..807fdf94d 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java @@ -418,7 +418,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio super.onCreate(savedInstanceState); ConversationMenuConfigurator.reloadFeatures(this); OmemoSetting.load(this); - new EmojiService(this).init(useBundledEmoji()); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations); setSupportActionBar((Toolbar) binding.toolbar); configureActionBar(getSupportActionBar()); @@ -766,65 +765,49 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } } else { - if (conversation.getParticipants() != null) { - ChatState state = ChatState.COMPOSING; - List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); - if (userWithChatStates.size() == 0) { - state = ChatState.PAUSED; - userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); - } - List<MucOptions.User> users = conversation.getMucOptions().getUsers(true); - if (state == ChatState.COMPOSING) { - if (userWithChatStates.size() > 0) { - if (userWithChatStates.size() == 1) { - MucOptions.User user = userWithChatStates.get(0); - absubtitle.setText(EmojiWrapper.transform(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user)))); - } else { - StringBuilder builder = new StringBuilder(); - for (MucOptions.User user : userWithChatStates) { - if (builder.length() != 0) { - builder.append(", "); - } - builder.append(UIHelper.getDisplayName(user)); + ChatState state = ChatState.COMPOSING; + List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); + if (userWithChatStates.size() == 0) { + state = ChatState.PAUSED; + userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); + } + List<MucOptions.User> users = conversation.getMucOptions().getUsers(true); + if (state == ChatState.COMPOSING) { + if (userWithChatStates.size() > 0) { + if (userWithChatStates.size() == 1) { + MucOptions.User user = userWithChatStates.get(0); + absubtitle.setText(EmojiWrapper.transform(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user)))); + } else { + StringBuilder builder = new StringBuilder(); + for (MucOptions.User user : userWithChatStates) { + if (builder.length() != 0) { + builder.append(", "); } - absubtitle.setText(EmojiWrapper.transform(getString(R.string.contacts_are_typing, builder.toString()))); + builder.append(UIHelper.getDisplayName(user)); } - } - } else { - if (users.size() == 0) { - absubtitle.setText(getString(R.string.one_participant)); - } else { - int size = users.size() + 1; - absubtitle.setText(getString(R.string.more_participants, size)); + absubtitle.setText(EmojiWrapper.transform(getString(R.string.contacts_are_typing, builder.toString()))); } } - absubtitle.setSelected(true); - absubtitle.setOnClickListener(view15 -> { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - switchToContactDetails(conversation.getContact()); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); - overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - } - }); } else { - absubtitle.setText(R.string.no_participants); - abtitle.setSelected(true); - absubtitle.setOnClickListener(view16 -> { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - switchToContactDetails(conversation.getContact()); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); - overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - } - }); + if (users.size() == 0) { + absubtitle.setText(getString(R.string.one_participant)); + } else { + int size = users.size() + 1; + absubtitle.setText(getString(R.string.more_participants, size)); + } } + absubtitle.setSelected(true); + absubtitle.setOnClickListener(view15 -> { + if (conversation.getMode() == Conversation.MODE_SINGLE) { + switchToContactDetails(conversation.getContact()); + } else if (conversation.getMode() == Conversation.MODE_MULTI) { + Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + startActivity(intent); + overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + } + }); } return; } @@ -837,10 +820,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } - public boolean useBundledEmoji() { - return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - } - public void verifyOtrSessionDialog(final Conversation conversation, View view) { if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); diff --git a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java index 900f4a42d..059a6b94c 100644 --- a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java +++ b/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java @@ -60,6 +60,10 @@ public class CreateConferenceDialog extends DialogFragment { builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); builder.setNegativeButton(R.string.cancel, null); DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); + binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { + mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()); + return true; + }); return builder.create(); } diff --git a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java index d0c7980fd..02aa46277 100644 --- a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java +++ b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java @@ -114,42 +114,51 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected AlertDialog dialog = builder.create(); View.OnClickListener dialogOnClick = v -> { - final Jid accountJid; - if (!binding.account.isEnabled() && account == null) { - return; - } - try { - if (Config.DOMAIN_LOCK != null) { - accountJid = Jid.of((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null); - } else { - accountJid = Jid.of((String) binding.account.getSelectedItem()); - } - } catch (final IllegalArgumentException e) { - return; - } - final Jid contactJid; - try { - contactJid = Jid.of(binding.jid.getText().toString()); - } catch (final IllegalArgumentException e) { - binding.jid.setError(getActivity().getString(R.string.invalid_jid)); - return; - } - - if (mListener != null) { - try { - if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) { - dialog.dismiss(); - } - } catch (JidError error) { - binding.jid.setError(error.toString()); - } - } + handleEnter(binding, account, dialog); }; + binding.jid.setOnEditorActionListener((v, actionId, event) -> { + handleEnter(binding, account, dialog); + return true; + }); + dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick); return dialog; } + private void handleEnter(EnterJidDialogBinding binding, String account, Dialog dialog) { + final Jid accountJid; + if (!binding.account.isEnabled() && account == null) { + return; + } + try { + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.of((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + accountJid = Jid.of((String) binding.account.getSelectedItem()); + } + } catch (final IllegalArgumentException e) { + return; + } + final Jid contactJid; + try { + contactJid = Jid.of(binding.jid.getText().toString()); + } catch (final IllegalArgumentException e) { + binding.jid.setError(getActivity().getString(R.string.invalid_jid)); + return; + } + + if (mListener != null) { + try { + if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) { + dialog.dismiss(); + } + } catch (JidError error) { + binding.jid.setError(error.toString()); + } + } + } + public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) { this.mListener = listener; } diff --git a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java index a8702ebb6..4d05d6d2c 100644 --- a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java +++ b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java @@ -77,6 +77,10 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon AlertDialog dialog = builder.create(); dialog.show(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked())); + binding.jid.setOnEditorActionListener((v, actionId, event) -> { + mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked()); + return true; + }); return dialog; } diff --git a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java index bca4d3dbc..607e2626d 100644 --- a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java @@ -35,7 +35,7 @@ import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; -public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated { +public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState { private final String STATE_SELECTED_ACCOUNT = "selected_account"; @@ -92,14 +92,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda accountListView = findViewById(R.id.account_list); this.mAccountAdapter = new AccountAdapter(this, accountList); accountListView.setAdapter(this.mAccountAdapter); - accountListView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView<?> arg0, View view, - int position, long arg3) { - switchToAccount(accountList.get(position)); - } - }); + accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position))); registerForContextMenu(accountListView); } @@ -227,6 +220,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } + @Override public void onClickTglAccountState(Account account, boolean enable) { if (enable) { enableAccount(account); diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java index 469ce8021..e38591848 100644 --- a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java +++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java @@ -15,6 +15,8 @@ import android.widget.Toast; import com.theartofdev.edmodo.cropper.CropImage; +import java.util.concurrent.atomic.AtomicBoolean; + import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; @@ -34,6 +36,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC private Account account; private boolean support = false; private boolean publishing = false; + private AtomicBoolean handledExternalUri = new AtomicBoolean(false); private OnLongClickListener backToDefaultListener = new OnLongClickListener() { @Override @@ -103,6 +106,19 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC }); this.avatar.setOnClickListener(v -> chooseAvatar()); this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext()); + if (savedInstanceState != null) { + this.avatarUri = savedInstanceState.getParcelable("uri"); + this.handledExternalUri.set(savedInstanceState.getBoolean("handle_external_uri", false)); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + if (this.avatarUri != null) { + outState.putParcelable("uri", this.avatarUri); + } + outState.putBoolean("handle_external_uri", handledExternalUri.get()); + super.onSaveInstanceState(outState); } @@ -160,10 +176,20 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC final Intent intent = getIntent(); this.mInitialAccountSetup = intent != null && intent.getBooleanExtra("setup", false); + final Uri uri = intent != null ? intent.getData() : null; + + if (uri != null && handledExternalUri.compareAndSet(false, true)) { + CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAspectRatio(1, 1) + .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) + .start(this); + return; + } + if (this.mInitialAccountSetup) { this.cancelButton.setText(R.string.skip); } - configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup); + configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get()); } protected void loadImageIntoPreview(Uri uri) { diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java index dc41011fb..9c45d01b8 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java @@ -549,15 +549,7 @@ public class SettingsActivity extends XmppActivity implements } private void startExport() { - try { - if (Compatibility.runsAndTargetsTwentySix(this)) { - ContextCompat.startForegroundService(this, new Intent(this, ExportLogsService.class)); - } else { - this.startService(new Intent(this, ExportLogsService.class)); - } - } catch (RuntimeException e) { - Log.d(Config.LOGTAG, "SettingsActivity was unable to start ExportLogsService"); - } + Compatibility.startService(this, new Intent(this, ExportLogsService.class)); } private void displayToast(final String msg) { diff --git a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java index 2d535a44c..114532ba1 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java @@ -27,11 +27,6 @@ public class ShareViaAccountActivity extends XmppActivity { accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); } - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setHomeButtonEnabled(this.accountList.size() > 0); - actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); - } mAccountAdapter.notifyDataSetChanged(); } diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java index 0d1a3286d..6306a004c 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java @@ -73,8 +73,6 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - new EmojiService(this).init(useBundledEmoji); setContentView(R.layout.activity_share_with); setSupportActionBar(findViewById(R.id.toolbar)); if (getSupportActionBar() != null) { @@ -130,7 +128,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0, false); } } @@ -182,7 +180,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer } public void refreshUiReal() { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0); + //TODO inject desired order to not resort on refresh + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0, false); mAdapter.notifyDataSetChanged(); } diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java index d8c565345..3e8d07dd2 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java @@ -65,8 +65,6 @@ public class ShowLocationActivity extends XmppActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - new EmojiService(this).init(useBundledEmoji); setContentView(R.layout.activity_show_locaction); setTitle(getString(R.string.show_location)); setSupportActionBar(findViewById(R.id.toolbar)); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index 825494d90..9900c7b9b 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -261,7 +261,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - new EmojiService(this).init(useBundledEmoji()); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_start_conversation); Toolbar toolbar = (Toolbar) binding.toolbar; setSupportActionBar(toolbar); @@ -939,10 +938,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne finish(); } - public boolean useBundledEmoji() { - return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - } - @Override public void onCreateDialogPositiveClick(Spinner spinner, String name) { if (!xmppConnectionServiceBound) { diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index 65383c2aa..ed2a56a31 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -76,6 +76,7 @@ import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.Presences; import de.pixart.messenger.services.AvatarService; import de.pixart.messenger.services.BarcodeProvider; +import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.UpdateService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.XmppConnectionBinder; @@ -91,6 +92,8 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist; import pl.droidsonroids.gif.GifDrawable; import rocks.xmpp.addr.Jid; +import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; + public abstract class XmppActivity extends ActionBarActivity { protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; @@ -237,7 +240,11 @@ public abstract class XmppActivity extends ActionBarActivity { public void connectToBackend() { Intent intent = new Intent(this, XmppConnectionService.class); intent.setAction("ui"); - startService(intent); + try { + startService(intent); + } catch (IllegalStateException e) { + Log.w(Config.LOGTAG, "unable to start service from " + getClass().getSimpleName()); + } bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @@ -391,6 +398,7 @@ public abstract class XmppActivity extends ActionBarActivity { setVolumeControlStream(AudioManager.STREAM_NOTIFICATION); metrics = getResources().getDisplayMetrics(); ExceptionHelper.init(getApplicationContext()); + new EmojiService(this).init(getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji))); this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); mColorRed = ContextCompat.getColor(this, R.color.red800); if (isDarkTheme()) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java index e37ea6121..391fee0b9 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java @@ -85,13 +85,17 @@ public class AccountAdapter extends ArrayAdapter<Account> { tglAccountState.setVisibility(View.GONE); } tglAccountState.setOnCheckedChangeListener((compoundButton, b) -> { - if (b == isDisabled && activity instanceof ManageAccountActivity) { - ((ManageAccountActivity) activity).onClickTglAccountState(account, b); + if (b == isDisabled && activity instanceof OnTglAccountState) { + ((OnTglAccountState) activity).onClickTglAccountState(account, b); } }); return view; } + public interface OnTglAccountState { + void onClickTglAccountState(Account account, boolean state); + } + public static boolean cancelPotentialWork(Account account, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index d61510611..a60631523 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -139,7 +139,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte viewHolder.lastMessage.setTypeface(null, Typeface.NORMAL); viewHolder.sender.setTypeface(null, Typeface.ITALIC); } else { - final boolean fileAvailable = message.getTransferable() == null || message.getTransferable().getStatus() != Transferable.STATUS_DELETED; + final boolean fileAvailable = !message.isFileDeleted(); final boolean showPreviewText; if (fileAvailable && (message.isFileOrImage() || message.treatAsDownloadable() || message.isGeoUri())) { final int imageResource; @@ -276,32 +276,30 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte viewHolder.sender.setVisibility(View.GONE); } } else { - if (conversation.getParticipants() != null) { - ChatState state = ChatState.COMPOSING; - List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); - if (userWithChatStates.size() == 0) { - state = ChatState.PAUSED; - userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); - } - if (state == ChatState.COMPOSING) { - if (userWithChatStates.size() > 0) { - if (userWithChatStates.size() == 1) { - MucOptions.User user = userWithChatStates.get(0); - viewHolder.lastMessage.setText(activity.getString(R.string.contact_is_typing, UIHelper.getDisplayName(user))); - viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC); - viewHolder.sender.setVisibility(View.GONE); - } else { - StringBuilder builder = new StringBuilder(); - for (MucOptions.User user : userWithChatStates) { - if (builder.length() != 0) { - builder.append(", "); - } - builder.append(UIHelper.getDisplayName(user)); + ChatState state = ChatState.COMPOSING; + List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); + if (userWithChatStates.size() == 0) { + state = ChatState.PAUSED; + userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5); + } + if (state == ChatState.COMPOSING) { + if (userWithChatStates.size() > 0) { + if (userWithChatStates.size() == 1) { + MucOptions.User user = userWithChatStates.get(0); + viewHolder.lastMessage.setText(activity.getString(R.string.contact_is_typing, UIHelper.getDisplayName(user))); + viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC); + viewHolder.sender.setVisibility(View.GONE); + } else { + StringBuilder builder = new StringBuilder(); + for (MucOptions.User user : userWithChatStates) { + if (builder.length() != 0) { + builder.append(", "); } - viewHolder.lastMessage.setText(activity.getString(R.string.contacts_are_typing, builder.toString())); - viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC); - viewHolder.sender.setVisibility(View.GONE); + builder.append(UIHelper.getDisplayName(user)); } + viewHolder.lastMessage.setText(activity.getString(R.string.contacts_are_typing, builder.toString())); + viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC); + viewHolder.sender.setVisibility(View.GONE); } } } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 1c53f1b5d..9d7624e79 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -246,7 +246,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie final Transferable transferable = message.getTransferable(); boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getMergedStatus() <= Message.STATUS_RECEIVED; - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || transferable != null) { + if (message.isFileOrImage() || transferable != null) { FileParams params = message.getFileParams(); if (params.size > (1 * 1024 * 1024)) { filesize = Math.round(params.size * 1f / (1024 * 1024)) + " MiB"; @@ -962,10 +962,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie }); final Transferable transferable = message.getTransferable(); - if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) { - if (transferable.getStatus() == Transferable.STATUS_OFFER) { + if (message.isFileDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) { + if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message))); - } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { + } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground); @@ -1119,20 +1119,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE); return; } - DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); - if (!file.exists()) { - Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - return; - } - String mime = file.getMimeType(); - if (mime == null) { - mime = "*/*"; - } - ViewUtil.view(activity, file, mime); + final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); + ViewUtil.view(activity, file); } public void showLocation(Message message) { - for (Intent intent : GeoHelper.createGeoIntentsFromMessage(message, this.getContext())) { + for (Intent intent : GeoHelper.createGeoIntentsFromMessage(this.getContext(), message)) { if (intent.resolveActivity(getContext().getPackageManager()) != null) { getContext().startActivity(intent); activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); diff --git a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java index 07e25e31d..db729f0e4 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java +++ b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java @@ -14,6 +14,7 @@ import java.util.List; import de.pixart.messenger.Config; import de.pixart.messenger.R; +import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.ui.MediaViewerActivity; @@ -25,6 +26,18 @@ public class ViewUtil { view(context, file, mime); } + public static void view(Context context, DownloadableFile file) { + if (!file.exists()) { + Toast.makeText(context, R.string.file_deleted, Toast.LENGTH_SHORT).show(); + return; + } + String mime = file.getMimeType(); + if (mime == null) { + mime = "*/*"; + } + view(context, file, mime); + } + public static void view(Context context, File file, String mime) { Uri uri; try { diff --git a/src/main/java/de/pixart/messenger/utils/Compatibility.java b/src/main/java/de/pixart/messenger/utils/Compatibility.java index 38f9db378..c8922e18b 100644 --- a/src/main/java/de/pixart/messenger/utils/Compatibility.java +++ b/src/main/java/de/pixart/messenger/utils/Compatibility.java @@ -1,6 +1,7 @@ package de.pixart.messenger.utils; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -11,15 +12,19 @@ import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.support.annotation.BoolRes; import android.support.v4.content.ContextCompat; +import android.util.Log; import java.util.Arrays; import java.util.Collections; import java.util.List; +import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.SettingsFragment; +import static de.pixart.messenger.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE; + public class Compatibility { private static final List<String> UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList( SettingsActivity.SHOW_FOREGROUND_SERVICE, @@ -102,4 +107,17 @@ public class Compatibility { } } } + + public static void startService(Context context, Intent intent) { + try { + if (Compatibility.runsAndTargetsTwentySix(context)) { + intent.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true); + ContextCompat.startForegroundService(context, intent); + } else { + context.startService(intent); + } + } catch (RuntimeException e) { + Log.d(Config.LOGTAG, context.getClass().getSimpleName() + " was unable to start service"); + } + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java index 73d5589e4..5e2948b5b 100644 --- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java +++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java @@ -8,6 +8,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; import de.pixart.messenger.Config; @@ -21,16 +22,26 @@ public abstract class ConversationsFileObserver { private final String path; private final List<SingleFileObserver> mObservers = new ArrayList<>(); + private final AtomicBoolean shouldStop = new AtomicBoolean(true); protected ConversationsFileObserver(String path) { this.path = path; } - public synchronized void startWatching() { + public void startWatching() { + shouldStop.set(false); + startWatchingInternal(); + } + + private synchronized void startWatchingInternal() { Stack<String> stack = new Stack<>(); stack.push(path); while (!stack.empty()) { + if (shouldStop.get()) { + Log.d(Config.LOGTAG, "file observer received command to stop"); + return; + } String parent = stack.pop(); mObservers.add(new SingleFileObserver(parent, FileObserver.DELETE | FileObserver.MOVED_FROM)); final File path = new File(parent); @@ -44,6 +55,10 @@ public abstract class ConversationsFileObserver { continue; } for (File file : files) { + if (shouldStop.get()) { + Log.d(Config.LOGTAG, "file observer received command to stop"); + return; + } if (file.isDirectory() && file.getName().charAt(0) != '.') { final String currentPath = file.getAbsolutePath(); if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(currentPath)) { @@ -74,7 +89,12 @@ public abstract class ConversationsFileObserver { return false; } - public synchronized void stopWatching() { + public void stopWatching() { + shouldStop.set(true); + stopWatchingInternal(); + } + + private synchronized void stopWatchingInternal() { for (FileObserver observer : mObservers) { observer.stopWatching(); } diff --git a/src/main/java/de/pixart/messenger/utils/GeoHelper.java b/src/main/java/de/pixart/messenger/utils/GeoHelper.java index 209fd75db..f6af7c018 100644 --- a/src/main/java/de/pixart/messenger/utils/GeoHelper.java +++ b/src/main/java/de/pixart/messenger/utils/GeoHelper.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import org.osmdroid.util.GeoPoint; + import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; @@ -11,6 +13,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import de.pixart.messenger.Config; +import de.pixart.messenger.R; import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Conversational; @@ -43,41 +46,43 @@ public class GeoHelper { return "https://xmpp.pix-art.de/staticmap/staticmap.php?center=" + latitude + "," + longitude + "&size=500x500&markers=" + latitude + "," + longitude + "&zoom= " + Config.DEFAULT_ZOOM; } - public static ArrayList<Intent> createGeoIntentsFromMessage(Message message, Context context) { - final ArrayList<Intent> intents = new ArrayList<>(); - Matcher matcher = GEO_URI.matcher(message.getBody()); + private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException { + Matcher matcher = GEO_URI.matcher(body); + ; if (!matcher.matches()) { - return intents; + throw new IllegalArgumentException("Invalid geo uri"); } double latitude; double longitude; try { latitude = Double.parseDouble(matcher.group(1)); if (latitude > 90.0 || latitude < -90.0) { - return intents; + throw new IllegalArgumentException("Invalid geo uri"); } longitude = Double.parseDouble(matcher.group(2)); if (longitude > 180.0 || longitude < -180.0) { - return intents; + throw new IllegalArgumentException("Invalid geo uri"); } - } catch (NumberFormatException nfe) { + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid geo uri", e); + } + return new GeoPoint(latitude, longitude); + } + + public static ArrayList<Intent> createGeoIntentsFromMessage(Context context, Message message) { + final ArrayList<Intent> intents = new ArrayList<>(); + final GeoPoint geoPoint; + try { + geoPoint = parseGeoPoint(message.getBody()); + } catch (IllegalArgumentException e) { return intents; } final Conversational conversation = message.getConversation(); - String label; - if (conversation instanceof Conversation && conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) { - try { - label = "(" + URLEncoder.encode(((Conversation) conversation).getName().toString(), "UTF-8") + ")"; - } catch (UnsupportedEncodingException e) { - label = ""; - } - } else { - label = ""; - } + final String label = getLabel(context, message); Intent locationPluginIntent = new Intent(context, ShowLocationActivity.class); - locationPluginIntent.putExtra("latitude", latitude); - locationPluginIntent.putExtra("longitude", longitude); + locationPluginIntent.putExtra("latitude", geoPoint.getLatitude()); + locationPluginIntent.putExtra("longitude", geoPoint.getLongitude()); if (message.getStatus() != Message.STATUS_RECEIVED) { locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString()); locationPluginIntent.putExtra("name", conversation.getAccount().getJid().getLocal()); @@ -93,8 +98,38 @@ public class GeoHelper { intents.add(locationPluginIntent); Intent geoIntent = new Intent(Intent.ACTION_VIEW); - geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label)); + geoIntent.setData(Uri.parse("geo:" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "?q=" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + label)); intents.add(geoIntent); return intents; } + + public static void view(Context context, Message message) { + final GeoPoint geoPoint = parseGeoPoint(message.getBody()); + final String label = getLabel(context, message); + context.startActivity(geoIntent(geoPoint, label)); + } + + private static Intent geoIntent(GeoPoint geoPoint, String label) { + Intent geoIntent = new Intent(Intent.ACTION_VIEW); + geoIntent.setData(Uri.parse("geo:" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "?q=" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "(" + label + ")")); + return geoIntent; + } + + public static boolean openInOsmAnd(Context context, Message message) { + final GeoPoint geoPoint = parseGeoPoint(message.getBody()); + final String label = getLabel(context, message); + return geoIntent(geoPoint, label).resolveActivity(context.getPackageManager()) != null; + } + + private static String getLabel(Context context, Message message) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + try { + return URLEncoder.encode(UIHelper.getMessageDisplayName(message), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } else { + return context.getString(R.string.me); + } + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java index 83d63cdaa..3a3e32f73 100644 --- a/src/main/java/de/pixart/messenger/utils/Namespace.java +++ b/src/main/java/de/pixart/messenger/utils/Namespace.java @@ -24,4 +24,5 @@ public final class Namespace { public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0"; public static final String BOOKMARKS = "storage:bookmarks"; public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0"; + public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0"; } diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index 03af31e23..ea1878557 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -263,8 +263,6 @@ public class UIHelper { case Transferable.STATUS_OFFER_CHECK_FILESIZE: return new Pair<>(context.getString(R.string.x_file_offered_for_download, getFileDescriptionString(context, message)), true); - case Transferable.STATUS_DELETED: - return new Pair<>(context.getString(R.string.file_deleted), true); case Transferable.STATUS_FAILED: return new Pair<>(context.getString(R.string.file_transmission_failed), true); case Transferable.STATUS_UPLOADING: @@ -278,6 +276,8 @@ public class UIHelper { default: return new Pair<>("", false); } + } else if (message.isFileOrImage() && message.isFileDeleted()) { + return new Pair<>(context.getString(R.string.file_deleted), true); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { return new Pair<>(context.getString(R.string.pgp_message), true); } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { @@ -286,7 +286,7 @@ public class UIHelper { return new Pair<>(context.getString(R.string.not_encrypted_for_this_device), true); } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { return new Pair<>(context.getString(R.string.omemo_decryption_failed), true); - } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { + } else if (message.isFileOrImage()) { return new Pair<>(getFileDescriptionString(context, message), true); } else { final String body = MessageUtils.filterLtrRtl(message.getBody()); @@ -307,6 +307,9 @@ public class UIHelper { SpannableStringBuilder builder = new SpannableStringBuilder(); for (CharSequence l : CharSequenceUtils.split(styledBody, '\n')) { if (l.length() > 0) { + if (l.toString().equals("```")) { + continue; + } char first = l.charAt(0); if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l,0)) && first != '\u00bb') { CharSequence line = CharSequenceUtils.trim(l); diff --git a/src/main/java/de/pixart/messenger/xml/Element.java b/src/main/java/de/pixart/messenger/xml/Element.java index d7cace655..d1d3c50e9 100644 --- a/src/main/java/de/pixart/messenger/xml/Element.java +++ b/src/main/java/de/pixart/messenger/xml/Element.java @@ -74,7 +74,7 @@ public class Element { } private String findInternationalizedChildContent(String name, @NonNull String language) { - HashMap<String, String> contents = new HashMap<>(); + final HashMap<String, String> contents = new HashMap<>(); for (Element child : this.children) { if (name.equals(child.getName())) { String lang = child.getAttribute("xml:lang"); @@ -88,11 +88,12 @@ public class Element { } } } - String value = contents.get(null); + final String value = contents.get(null); if (value != null) { return value; } - return contents.size() > 0 ? contents.values().iterator().next() : null; + final String[] values = contents.values().toArray(new String[0]); + return values.length == 0 ? null : values[0]; } public Element findChild(String name, String xmlns) { diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index f364a0155..705a3178d 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -372,9 +372,8 @@ public class XmppConnection implements Runnable { if (!tlsFactoryVerifier.verifier.verify(account.getServer(), verifiedHostname, ((SSLSocket) localSocket).getSession())) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); - if (!iterator.hasNext()) { - throw new StateChangingException(Account.State.TLS_ERROR); - } + FileBackend.close(localSocket); + throw new StateChangingException(Account.State.TLS_ERROR); } } localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); @@ -385,13 +384,13 @@ public class XmppConnection implements Runnable { } break; // successfully connected to server that speaks xmpp } else { - localSocket.close(); - if (!iterator.hasNext()) { - throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); - } + FileBackend.close(localSocket); + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); } } catch (final StateChangingException e) { - throw e; + if (!iterator.hasNext()) { + throw e; + } } catch (InterruptedException e) { Log.d(Config.LOGTAG,account.getJid().asBareJid()+": thread was interrupted before beginning stream"); return; @@ -1780,6 +1779,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION) && pepPublishOptions(); } + public boolean avatarConversion() { + return hasDiscoFeature(account.getJid().asBareJid(), Namespace.AVATAR_CONVERSION) && pepPublishOptions(); + } + public boolean blocking() { return hasDiscoFeature(Jid.of(account.getServer()), Namespace.BLOCKING); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java index 2f3ec09ea..d4c6c7eba 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java @@ -117,8 +117,10 @@ public class JingleConnection implements Transferable { if (message.getEncryption() == Message.ENCRYPTION_PGP) { account.getPgpDecryptionService().decrypt(message, true); } else { - JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); + mXmppConnectionService.getFileBackend().updateMediaScanner(file, () -> JingleConnection.this.mXmppConnectionService.getNotificationService().push(message)); } + Log.d(Config.LOGTAG, "successfully transmitted file:" + file.getAbsolutePath() + " (" + CryptoHelper.bytesToHex(file.getSha1Sum()) + ")"); + return; } } else { if (ftVersion == Content.Version.FT_5) { //older Conversations will break when receiving a session-info @@ -483,7 +485,7 @@ public class JingleConnection implements Transferable { private void sendInitRequest() { JinglePacket packet = this.bootstrapPacket("session-initiate"); Content content = new Content(this.contentCreator, this.contentName); - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { + if (message.isFileOrImage()) { content.setTransportId(this.transportId); this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); if (message.getEncryption() == Message.ENCRYPTION_OTR) { diff --git a/src/main/res/layout/create_conference_dialog.xml b/src/main/res/layout/create_conference_dialog.xml index 3496b17f5..85a4cbf89 100644 --- a/src/main/res/layout/create_conference_dialog.xml +++ b/src/main/res/layout/create_conference_dialog.xml @@ -38,7 +38,8 @@ style="@style/Widget.Conversations.EditText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/create_dialog_group_chat_name" /> + android:hint="@string/create_dialog_group_chat_name" + android:imeOptions="actionNext" /> </android.support.design.widget.TextInputLayout> </LinearLayout> </layout>
\ No newline at end of file diff --git a/src/main/res/layout/dialog_join_conference.xml b/src/main/res/layout/dialog_join_conference.xml index 15017d5c1..27acefc4a 100644 --- a/src/main/res/layout/dialog_join_conference.xml +++ b/src/main/res/layout/dialog_join_conference.xml @@ -40,6 +40,7 @@ style="@style/Widget.Conversations.EditText" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:imeOptions="actionDone" android:inputType="textEmailAddress" /> </android.support.design.widget.TextInputLayout> diff --git a/src/main/res/layout/enter_jid_dialog.xml b/src/main/res/layout/enter_jid_dialog.xml index 4c0214f96..11f673916 100644 --- a/src/main/res/layout/enter_jid_dialog.xml +++ b/src/main/res/layout/enter_jid_dialog.xml @@ -40,6 +40,7 @@ style="@style/Widget.Conversations.EditText" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:imeOptions="actionDone" android:inputType="textEmailAddress" android:textColor="?attr/text_Color_Main" /> </android.support.design.widget.TextInputLayout> diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index 4112dfb8c..d92f85af4 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -2,9 +2,13 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item + android:id="@+id/open_with" + android:title="@string/open_with" + android:visible="false" /> + <item android:id="@+id/share_with" android:title="@string/share_with" - android:visible="false"/> + android:visible="false" /> <item android:id="@+id/copy_message" android:title="@string/copy_to_clipboard" diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index 5de3e4920..4645b2a4e 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -7,7 +7,7 @@ <string name="type_web" translatable="false">Web browser</string> <string name="type_console" translatable="false">Console</string> <string name="pref_about_message" translatable="false"><b>Pix-Art Messenger</b> - \n\nCopyright © 2014-2018 Christian Schneppe + \n\nCopyright © 2014-2019 Christian Schneppe \n\nhttp://jabber.pix-art.de \n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ GNU General Public License for more details. \n\nYou should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses - \n\nDownload the full original source code at https://github.com/siacs/Conversations (Copyright © 2014-2018 Daniel Gultsch) + \n\nDownload the full original source code at https://github.com/siacs/Conversations (Copyright © 2014-2019 Daniel Gultsch) \n\nDownload the modified source code at https://github.com/kriztan/Pix-Art-Messenger \n\nPix-Art Messenger recommends blabber.im as provider. More information can be found at https://blabber.im \n\nYou can support me by making a donation in form of a gift in sense of § 516 ff BGB i.e. a gratuitous donation. diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index e80438379..8867502a4 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -859,4 +859,7 @@ <string name="autojoin_groupchat">Automatically join this group chat</string> <string name="pref_play_gif_inside">Play GIF files in chat</string> <string name="pref_play_gif_inside_summary">Setting this to true plays GIF files directly inside the chat view.</string> + <string name="open_with">Open with…</string> + <string name="choose_account">Choose account</string> + <string name="set_profile_picture">Pix-Art Messenger profile picture</string> </resources> |