diff options
53 files changed, 609 insertions, 286 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6354220b5..eea7db538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ * resume download of OMEMO encrypted files * channels now use '#' as symbol in avatar * support for ?register and ?register;preauth XMPP uri parameters +* update connection settings * use ExoPlayer for video playback (PAM) * show artist - title for audio files (PAM) -* show PDF preview in chat (PAM) -* UI improvements (PAM) -* bug fixes +* show PDF previews (PAM) +* minor UI improvements (PAM) +* use 12 byte IV for OMEMO +* a lot of bug fixes #### Version 2.3.4 * fixes for Jingle IBB file transfer diff --git a/build.gradle b/build.gradle index 5cfe82de8..989199663 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.1' } } @@ -40,12 +40,12 @@ configurations { dependencies { implementation project(':libs:android-transcoder') - playstoreImplementation('com.google.firebase:firebase-messaging:20.0.1') { + playstoreImplementation('com.google.firebase:firebase-messaging:20.1.3') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } - playstoreImplementation 'com.android.installreferrer:installreferrer:1.1.1' + playstoreImplementation 'com.android.installreferrer:installreferrer:1.1.2' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') { exclude group: 'com.android.support', module: 'appcompat-v7' @@ -69,10 +69,11 @@ dependencies { implementation 'androidx.emoji:emoji:1.0.0' gitImplementation 'androidx.emoji:emoji-appcompat:1.0.0' gitImplementation 'androidx.emoji:emoji-bundled:1.0.0' - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.0.0' // higher versions cause strange fab design + implementation 'androidx.cardview:cardview:1.0.0' // for compatibility implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' - implementation 'com.google.android.exoplayer:exoplayer-core:2.11.1' - implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.1' + implementation 'com.google.android.exoplayer:exoplayer-core:2.11.3' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.3' implementation 'pub.devrel:easypermissions:3.0.0' // version >= 3.0.0 needs android X libraries implementation 'com.wefika:flowlayout:0.4.1' implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5' @@ -84,7 +85,7 @@ dependencies { implementation 'org.osmdroid:osmdroid-android:6.1.5' implementation 'com.leinardi.android:speed-dial:3.1.1' // version >= 3.0.0 needs android X libraries implementation 'com.squareup.picasso:picasso:2.71828' - implementation 'com.squareup.okhttp3:okhttp:3.12.8' // versions > 3.12.x don't support API level < 21 anymore + implementation 'com.squareup.okhttp3:okhttp:3.12.10' // versions > 3.12.x don't support API level < 21 anymore implementation 'com.squareup.retrofit2:retrofit:2.6.4' //retrofit needs to stick with 2.6.x (https://github.com/square/retrofit/blob/master/CHANGELOG.md) implementation 'com.squareup.retrofit2:converter-gson:2.6.4' implementation 'com.google.guava:guava:28.2-android' @@ -104,9 +105,9 @@ android { minSdkVersion 16 targetSdkVersion 29 - versionCode 292 + versionCode 297 versionName "2.3.5" - versionNameSuffix " beta (2020-02-11)" //" beta (XXXX-XX-XX)" + //versionNameSuffix " beta (2020-04-01)" //" beta (XXXX-XX-XX)" //resConfigs "en" archivesBaseName += "-$versionName" diff --git a/docs/encryption.md b/docs/encryption.md index edd7e6c5e..ee17352ee 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -4,7 +4,7 @@ Pix-Art Messenger will remove the implementation of OTR encryption by 30th June Unfortunately, it does not make sense to continue support on a rather out-dated technology, even as we see some users keep using it. For the moment, OTR (as well as OpenPGP) can be activated via the expert settings for advanced users. Pix-Art Messenger always tries to be usable for even [non-technical users](https://github.com/kriztan/Pix-Art-Messenger/issues/227), OTR however is not counted as appropriate for this. -Please consider to use OMEMO in the future, many other clients has implemented this, have a look at [omemo.top](https://omemo.top/). However, if you really need to continue using it, please refer to e.g. [Miranda](https://www.miranda-ng.org/de/), [Pidgin](https://pidgin.im/), [Profanity](https://profanity-im.github.io/). You are also able to fork Pix-Art Messenger and continue the implementation on your own. +Please consider to use OMEMO in the future, many other clients has implemented this, have a look at [omemo.top](https://omemo.top/). However, if you really need to continue using it, please refer to e.g. [Miranda](https://www.miranda-ng.org/de/), [Pidgin plugin](https://github.com/gkdr/lurch/), [Profanity](https://profanity-im.github.io/) or [Coy.im](https://coy.im/). You are also able to fork Pix-Art Messenger and continue the implementation on your own. Please consider the differences between encryption protocols and also advantages of OMEMO: diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9cbdf2b53..32b31286a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/libs/xmpp-addr/build.gradle b/libs/xmpp-addr/build.gradle index 2d30752c4..0675efe7d 100644 --- a/libs/xmpp-addr/build.gradle +++ b/libs/xmpp-addr/build.gradle @@ -7,7 +7,7 @@ repositories { } dependencies { - implementation 'rocks.xmpp:precis:1.0.0' + implementation 'rocks.xmpp:precis:1.1.0' } sourceCompatibility = "8" diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index bf7ade63c..daf25028a 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -110,7 +110,7 @@ public final class Config { public static final boolean REMOVE_BROKEN_DEVICES = false; public static final boolean OMEMO_PADDING = false; public static final boolean PUT_AUTH_TAG_INTO_KEY = true; - public static final boolean TWELVE_BYTE_IV = false; + public static final boolean TWELVE_BYTE_IV = true; public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096; public static final int MAX_STORAGE_MESSAGE_CHARS = 2 * 1024 * 1024; //2MB diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java index 3bc0c2901..85a5ddd87 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java @@ -652,7 +652,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final boolean wipe, final boolean firstAttempt) { final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; - IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, getOwnDeviceId(), publishOptions); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java index 6fbf8c3dc..553de40e5 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java @@ -121,7 +121,7 @@ public class XmppAxolotlMessage { private static byte[] generateIv() { final SecureRandom random = new SecureRandom(); - byte[] iv = new byte[Config.TWELVE_BYTE_IV ? 12 : 16]; + final byte[] iv = new byte[12]; random.nextBytes(iv); return iv; } diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java index c270f5b43..154f98af9 100644 --- a/src/main/java/de/pixart/messenger/entities/Account.java +++ b/src/main/java/de/pixart/messenger/entities/Account.java @@ -541,13 +541,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } } - public boolean hasBookmarkFor(final Jid jid) { - synchronized (this.bookmarks) { - return this.bookmarks.containsKey(jid.asBareJid()); - } - } - - Bookmark getBookmark(final Jid jid) { + public Bookmark getBookmark(final Jid jid) { synchronized (this.bookmarks) { return this.bookmarks.get(jid.asBareJid()); } diff --git a/src/main/java/de/pixart/messenger/entities/DownloadableFile.java b/src/main/java/de/pixart/messenger/entities/DownloadableFile.java index 936b8dd61..1209d16eb 100644 --- a/src/main/java/de/pixart/messenger/entities/DownloadableFile.java +++ b/src/main/java/de/pixart/messenger/entities/DownloadableFile.java @@ -1,7 +1,10 @@ package de.pixart.messenger.entities; +import android.util.Log; + import java.io.File; +import de.pixart.messenger.Config; import de.pixart.messenger.utils.MimeUtils; public class DownloadableFile extends File { @@ -67,6 +70,7 @@ public class DownloadableFile extends File { this.iv = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf}; System.arraycopy(keyIvCombo, 0, aeskey, 0, 32); } + Log.d(Config.LOGTAG, "using " + this.iv.length + "-byte IV for file transmission"); } public void setKey(byte[] key) { diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java index b2297cd08..21ab33975 100644 --- a/src/main/java/de/pixart/messenger/entities/Message.java +++ b/src/main/java/de/pixart/messenger/entities/Message.java @@ -637,6 +637,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable !this.isGeoUri() && !message.isWebUri() && !this.isWebUri() && + !message.isOOb() && + !this.isOOb() && !message.treatAsDownloadable() && !this.treatAsDownloadable() && !message.getBody().startsWith(ME_COMMAND) && diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java index 4eedfcf3f..2293cb7ea 100644 --- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java @@ -106,11 +106,12 @@ public class HttpUploadConnection implements Transferable { } else { this.mime = this.file.getMimeType(); } + final long originalFileSize = file.getSize(); this.delayed = delay; if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { - this.key = new byte[48]; + this.key = new byte[44]; mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKeyAndIv(this.key); } @@ -129,7 +130,7 @@ public class HttpUploadConnection implements Transferable { md5 = null; } - this.file.setExpectedSize(file.getSize() + (file.getKey() != null ? 16 : 0)); + this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0)); message.resetFileParams(); this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() { @Override diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index 5fd701f89..ff0c3066a 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -551,6 +551,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (conversation.getMucOptions().isSelf(counterpart)) { status = Message.STATUS_SEND_RECEIVED; isCarbon = true; //not really carbon but received from another resource + //TODO this would be the place to change the body after something like mod_pastebin if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) { return; } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 36d9c945b..9ea4c5cd9 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -53,6 +53,7 @@ import de.pixart.messenger.services.ShortcutService; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.CursorUtils; import de.pixart.messenger.utils.FtsUtils; +import de.pixart.messenger.utils.Resolver; import de.pixart.messenger.xmpp.InvalidJid; import de.pixart.messenger.xmpp.mam.MamReference; import rocks.xmpp.addr.Jid; @@ -60,7 +61,7 @@ import rocks.xmpp.addr.Jid; public class DatabaseBackend extends SQLiteOpenHelper { public static final String DATABASE_NAME = "history"; - public static final int DATABASE_VERSION = 50; // = Conversations DATABASE_VERSION + 4 + public static final int DATABASE_VERSION = 51; // = Conversations DATABASE_VERSION + 5 private static DatabaseBackend instance = null; private static String CREATE_CONTATCS_STATEMENT = "create table " @@ -148,6 +149,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT IGNORE" + ");"; + private static String RESOLVER_RESULTS_TABLENAME = "resolver_results"; + + private static String CREATE_RESOLVER_RESULTS_TABLE = "create table " + RESOLVER_RESULTS_TABLENAME + "(" + + Resolver.Result.DOMAIN + " TEXT," + + Resolver.Result.HOSTNAME + " TEXT," + + Resolver.Result.IP + " BLOB," + + Resolver.Result.PRIORITY + " NUMBER," + + Resolver.Result.DIRECT_TLS + " NUMBER," + + Resolver.Result.AUTHENTICATED + " NUMBER," + + Resolver.Result.PORT + " NUMBER," + + Resolver.Result.TIME_REQUESTED + " NUMBER," + + "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE" + + ");"; + 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 + ")"; @@ -246,6 +261,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); @@ -557,7 +573,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { Log.d(Config.LOGTAG, "deleted old edit information in " + diff + "ms"); } - db.execSQL("DROP TABLE IF EXISTS resolver_results"); + if (oldVersion < 51 && newVersion >= 51) { + // values in resolver_result are cache and not worth to store + db.execSQL("DROP TABLE IF EXISTS " + RESOLVER_RESULTS_TABLENAME); + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); + } } private boolean isColumnExisting(SQLiteDatabase db, String TableName, String ColumnName) { @@ -694,6 +714,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { return result; } + public void saveResolverResult(String domain, Resolver.Result result) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = result.toContentValues(); + contentValues.put(Resolver.Result.DOMAIN, domain); + db.insert(RESOLVER_RESULTS_TABLENAME, null, contentValues); + } + + public synchronized Resolver.Result findResolverResult(String domain) { + SQLiteDatabase db = this.getReadableDatabase(); + String where = Resolver.Result.DOMAIN + "=?"; + String[] whereArgs = {domain}; + final Cursor cursor = db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); + Resolver.Result result = null; + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + result = Resolver.Result.fromCursor(cursor); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to find cached resolver result in database " + e.getMessage()); + return null; + } finally { + cursor.close(); + } + } + return result; + } + public void insertPresenceTemplate(PresenceTemplate template) { SQLiteDatabase db = this.getWritableDatabase(); String whereToDelete = PresenceTemplate.MESSAGE + "=?"; diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java index cba382b61..7787c8942 100644 --- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java +++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java @@ -5,6 +5,14 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.io.CipherOutputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; @@ -15,12 +23,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.concurrent.atomic.AtomicLong; -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import de.pixart.messenger.Config; import de.pixart.messenger.R; @@ -28,9 +31,6 @@ import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.utils.Compatibility; public class AbstractConnectionManager { - private static final String KEYTYPE = "AES"; - private static final String CIPHERMODE = "AES/GCM/NoPadding"; - private static final String PROVIDER = "BC"; private static final int UI_REFRESH_THRESHOLD = Config.REFRESH_UI_INTERVAL; private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0); protected XmppConnectionService mXmppConnectionService; @@ -41,10 +41,8 @@ public class AbstractConnectionManager { public static InputStream upgrade(DownloadableFile file, InputStream is) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, NoSuchProviderException { if (file.getKey() != null && file.getIv() != null) { - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); return new CipherInputStream(is, cipher); } else { return is; @@ -63,10 +61,8 @@ public class AbstractConnectionManager { return null; } try { - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); return new CipherOutputStream(os, cipher); } catch (Exception e) { Log.d(Config.LOGTAG, "unable to create cipher output stream", e); diff --git a/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java b/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java index f321332d7..3534b65bf 100644 --- a/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java +++ b/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java @@ -83,6 +83,7 @@ import javax.net.ssl.X509TrustManager; import de.pixart.messenger.R; import de.pixart.messenger.crypto.DomainHostnameVerifier; import de.pixart.messenger.entities.MTMDecision; +import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.ui.MemorizingActivity; /** @@ -98,32 +99,26 @@ import de.pixart.messenger.ui.MemorizingActivity; public class MemorizingTrustManager { + final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; + public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; + public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; + public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; + final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice"; + final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); - - final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; - public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; - public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; - final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice"; - private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); - public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; private final static int NOTIFICATION_ID = 100509; - - final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; - static String KEYSTORE_DIR = "KeyStore"; static String KEYSTORE_FILE = "KeyStore.bks"; - + private static int decisionId = 0; + private static SparseArray<MTMDecision> openDecisions = new SparseArray<MTMDecision>(); Context master; AppCompatActivity foregroundAct; NotificationManager notificationManager; - private static int decisionId = 0; - private static SparseArray<MTMDecision> openDecisions = new SparseArray<MTMDecision>(); - Handler masterHandler; private File keyStoreFile; private KeyStore appKeyStore; @@ -131,13 +126,14 @@ public class MemorizingTrustManager { private X509TrustManager appTrustManager; private String poshCacheDir; - /** Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager. - * + /** + * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager. + * <p> * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - * + * - Application + * - Activity + * - Service + * <p> * The context is used for file management, to display the dialog / * notification and for obtaining translated strings. * @@ -150,13 +146,14 @@ public class MemorizingTrustManager { this.defaultTrustManager = defaultTrustManager; } - /** Creates an instance of the MemorizingTrustManager class using the system X509TrustManager. - * + /** + * Creates an instance of the MemorizingTrustManager class using the system X509TrustManager. + * <p> * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - * + * - Application + * - Activity + * - Service + * <p> * The context is used for file management, to display the dialog / * notification and for obtaining translated strings. * @@ -168,6 +165,78 @@ public class MemorizingTrustManager { this.defaultTrustManager = getTrustManager(null); } + /** + * Changes the path for the KeyStore file. + * <p> + * The actual filename relative to the app's directory will be + * <code>app_<i>dirname</i>/<i>filename</i></code>. + * + * @param dirname directory to store the KeyStore. + * @param filename file name for the KeyStore. + */ + public static void setKeyStoreFile(String dirname, String filename) { + KEYSTORE_DIR = dirname; + KEYSTORE_FILE = filename; + } + + private static boolean isIp(final String server) { + return server != null && ( + PATTERN_IPV4.matcher(server).matches() + || PATTERN_IPV6.matcher(server).matches() + || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() + || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); + } + + private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException { + MessageDigest md; + try { + md = MessageDigest.getInstance(digest); + } catch (NoSuchAlgorithmException e) { + return null; + } + md.update(certificate.getEncoded()); + return Base64.encodeToString(md.digest(), Base64.NO_WRAP); + } + + private static String hexString(byte[] data) { + StringBuffer si = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + si.append(String.format("%02x", data[i])); + if (i < data.length - 1) + si.append(":"); + } + return si.toString(); + } + + private static String certHash(final X509Certificate cert, String digest) { + try { + MessageDigest md = MessageDigest.getInstance(digest); + md.update(cert.getEncoded()); + return hexString(md.digest()); + } catch (java.security.cert.CertificateEncodingException e) { + return e.getMessage(); + } catch (java.security.NoSuchAlgorithmException e) { + return e.getMessage(); + } + } + + public static void interactResult(int decisionId, int choice) { + MTMDecision d; + synchronized (openDecisions) { + d = openDecisions.get(decisionId); + openDecisions.remove(decisionId); + } + if (d == null) { + LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!"); + return; + } + synchronized (d) { + d.state = choice; + d.notify(); + } + } + void init(Context m) { master = m; masterHandler = new Handler(m.getMainLooper()); @@ -191,14 +260,13 @@ public class MemorizingTrustManager { appKeyStore = loadAppKeyStore(); } - /** * Binds an Activity to the MTM for displaying the query dialog. - * + * <p> * This is useful if your connection is run from a service that is * triggered by user interaction -- in such cases the activity is * visible and the user tends to ignore the service notification. - * + * <p> * You should never have a hidden activity bound to MTM! Use this * function in onResume() and @see unbindDisplayActivity in onPause(). * @@ -210,7 +278,7 @@ public class MemorizingTrustManager { /** * Removes an Activity from the MTM display stack. - * + * <p> * Always call this function when the Activity added with * {@link #bindDisplayActivity(AppCompatActivity)} is hidden. * @@ -223,20 +291,6 @@ public class MemorizingTrustManager { } /** - * Changes the path for the KeyStore file. - * - * The actual filename relative to the app's directory will be - * <code>app_<i>dirname</i>/<i>filename</i></code>. - * - * @param dirname directory to store the KeyStore. - * @param filename file name for the KeyStore. - */ - public static void setKeyStoreFile(String dirname, String filename) { - KEYSTORE_DIR = dirname; - KEYSTORE_FILE = filename; - } - - /** * Get a list of all certificate aliases stored in MTM. * * @return an {@link Enumeration} of all certificates @@ -254,7 +308,6 @@ public class MemorizingTrustManager { * Get a certificate for a given alias. * * @param alias the certificate's alias as returned by {@link #getCertificates()}. - * * @return the certificate associated with the alias or <tt>null</tt> if none found. */ public Certificate getCertificate(String alias) { @@ -275,8 +328,8 @@ public class MemorizingTrustManager { * (b) new connections are created using TLS renegotiation, without a new cert * check. * </p> - * @param alias the certificate's alias as returned by {@link #getCertificates()}. * + * @param alias the certificate's alias as returned by {@link #getCertificates()}. * @throws KeyStoreException if the certificate could not be deleted. */ public void deleteCertificate(String alias) throws KeyStoreException { @@ -291,17 +344,16 @@ public class MemorizingTrustManager { * the given instance of {@link MemorizingTrustManager}, and leverages an * existing {@link HostnameVerifier}. The returned verifier performs the * following steps, returning as soon as one of them succeeds: - * </p> - * <ol> - * <li>Success, if the wrapped defaultVerifier accepts the certificate.</li> - * <li>Success, if the server certificate is stored in the keystore under the given hostname.</li> - * <li>Ask the user and return accordingly.</li> - * <li>Failure on exception.</li> - * </ol> + * /p> + * <ol> + * <li>Success, if the wrapped defaultVerifier accepts the certificate.</li> + * <li>Success, if the server certificate is stored in the keystore under the given hostname.</li> + * <li>Ask the user and return accordingly.</li> + * <li>Failure on exception.</li> + * </ol> * * @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check * @return a new hostname verifier using the MTM's key store - * * @throws IllegalArgumentException if the defaultVerifier parameter is null */ public DomainHostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier, final boolean interactive) { @@ -337,13 +389,17 @@ public class MemorizingTrustManager { LOGGER.log(Level.SEVERE, "getAppKeyStore()", e); return null; } + FileInputStream fileInputStream = null; try { ks.load(null, null); - ks.load(new java.io.FileInputStream(keyStoreFile), "MTM".toCharArray()); + fileInputStream = new FileInputStream(keyStoreFile); + ks.load(fileInputStream, "MTM".toCharArray()); } catch (java.io.FileNotFoundException e) { LOGGER.log(Level.INFO, "getAppKeyStore(" + keyStoreFile + ") - file does not exist"); } catch (Exception e) { LOGGER.log(Level.SEVERE, "getAppKeyStore(" + keyStoreFile + ")", e); + } finally { + FileBackend.close(fileInputStream); } return ks; } @@ -574,26 +630,6 @@ public class MemorizingTrustManager { } } - private static boolean isIp(final String server) { - return server != null && ( - PATTERN_IPV4.matcher(server).matches() - || PATTERN_IPV6.matcher(server).matches() - || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() - || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() - || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); - } - - private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException { - MessageDigest md; - try { - md = MessageDigest.getInstance(digest); - } catch (NoSuchAlgorithmException e) { - return null; - } - md.update(certificate.getEncoded()); - return Base64.encodeToString(md.digest(), Base64.NO_WRAP); - } - private X509Certificate[] getAcceptedIssuers() { LOGGER.log(Level.FINE, "getAcceptedIssuers()"); return defaultTrustManager.getAcceptedIssuers(); @@ -609,28 +645,6 @@ public class MemorizingTrustManager { return myId; } - private static String hexString(byte[] data) { - StringBuffer si = new StringBuffer(); - for (int i = 0; i < data.length; i++) { - si.append(String.format("%02x", data[i])); - if (i < data.length - 1) - si.append(":"); - } - return si.toString(); - } - - private static String certHash(final X509Certificate cert, String digest) { - try { - MessageDigest md = MessageDigest.getInstance(digest); - md.update(cert.getEncoded()); - return hexString(md.digest()); - } catch (java.security.cert.CertificateEncodingException e) { - return e.getMessage(); - } catch (java.security.NoSuchAlgorithmException e) { - return e.getMessage(); - } - } - private void certDetails(StringBuffer si, X509Certificate c) { SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd"); si.append("\n"); @@ -705,6 +719,7 @@ public class MemorizingTrustManager { certDetails(si, cert); return si.toString(); } + /** * Returns the top-most entry of the activity stack. * @@ -715,7 +730,7 @@ public class MemorizingTrustManager { } int interact(final String message, final int titleId) { - /* prepare the MTMDecision blocker object */ + /* prepare the MTMDecision blocker object */ MTMDecision choice = new MTMDecision(); final int myId = createDecisionId(choice); @@ -773,20 +788,20 @@ public class MemorizingTrustManager { } } - public static void interactResult(int decisionId, int choice) { - MTMDecision d; - synchronized (openDecisions) { - d = openDecisions.get(decisionId); - openDecisions.remove(decisionId); - } - if (d == null) { - LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!"); - return; - } - synchronized (d) { - d.state = choice; - d.notify(); - } + public X509TrustManager getNonInteractive(String domain) { + return new NonInteractiveMemorizingTrustManager(domain); + } + + public X509TrustManager getInteractive(String domain) { + return new InteractiveMemorizingTrustManager(domain); + } + + public X509TrustManager getNonInteractive() { + return new NonInteractiveMemorizingTrustManager(null); + } + + public X509TrustManager getInteractive() { + return new InteractiveMemorizingTrustManager(null); } class MemorizingHostnameVerifier implements DomainHostnameVerifier { @@ -840,23 +855,6 @@ public class MemorizingTrustManager { } } - - public X509TrustManager getNonInteractive(String domain) { - return new NonInteractiveMemorizingTrustManager(domain); - } - - public X509TrustManager getInteractive(String domain) { - return new InteractiveMemorizingTrustManager(domain); - } - - public X509TrustManager getNonInteractive() { - return new NonInteractiveMemorizingTrustManager(null); - } - - public X509TrustManager getInteractive() { - return new InteractiveMemorizingTrustManager(null); - } - private class NonInteractiveMemorizingTrustManager implements X509TrustManager { private final String domain; diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index 826b5172d..fe97344c0 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -59,10 +59,13 @@ import de.pixart.messenger.ui.EditAccountActivity; import de.pixart.messenger.ui.TimePreference; import de.pixart.messenger.utils.AccountUtils; import de.pixart.messenger.utils.Compatibility; +import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.GeoHelper; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.XmppConnection; +import static de.pixart.messenger.ui.util.MyLinkify.replaceYoutube; + public class NotificationService { public static final Object CATCHUP_LOCK = new Object(); @@ -705,14 +708,14 @@ public class NotificationService { SpannableString styledString; for (Message message : messages) { final SpannableString name = UIHelper.getColoredUsername(mXmppConnectionService, message); - styledString = new SpannableString(name + ": " + message.getBody()); + styledString = new SpannableString(name + ": " + EmojiWrapper.transform(replaceYoutube(mXmppConnectionService, message.getBody()))); style.addLine(styledString); } builder.setStyle(style); int count = messages.size(); if (count == 1) { final SpannableString name = UIHelper.getColoredUsername(mXmppConnectionService, messages.get(0)); - styledString = new SpannableString(name + ": " + messages.get(0).getBody()); + styledString = new SpannableString(name + ": " + EmojiWrapper.transform(replaceYoutube(mXmppConnectionService, messages.get(0).getBody()))); builder.setContentText(styledString); builder.setTicker(styledString); } else { diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index c72a06aae..a51dbe49f 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -3473,16 +3473,20 @@ public class XmppConnectionService extends Service { conversation.setAttribute("accept_non_anonymous", true); updateConversation(conversation); } - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + if (options.containsKey("muc#roomconfig_moderatedroom")) { + final boolean moderated = "1".equals(options.getString("muc#roomconfig_moderatedroom")); + options.putString("members_by_default", moderated ? "0" : "1"); + } + final IqPacket request = new IqPacket(IqPacket.TYPE.GET); request.setTo(conversation.getJid().asBareJid()); request.query("http://jabber.org/protocol/muc#owner"); sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { - Data data = Data.parse(packet.query().findChild("x", Namespace.DATA)); + final Data data = Data.parse(packet.query().findChild("x", Namespace.DATA)); data.submit(options); - IqPacket set = new IqPacket(IqPacket.TYPE.SET); + final IqPacket set = new IqPacket(IqPacket.TYPE.SET); set.setTo(conversation.getJid().asBareJid()); set.query("http://jabber.org/protocol/muc#owner").addChild(data); sendIqPacket(account, set, new OnIqPacketReceived() { diff --git a/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java index 6c2a5c562..ba572e837 100644 --- a/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChannelDiscoveryActivity.java @@ -36,6 +36,7 @@ import de.pixart.messenger.ui.adapter.ChannelSearchResultAdapter; import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.SoftKeyboardUtils; import de.pixart.messenger.utils.AccountUtils; +import me.drakeet.support.toast.ToastCompat; import rocks.xmpp.addr.Jid; public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.OnActionExpandListener, TextView.OnEditorActionListener, ChannelDiscoveryService.OnChannelSearchResultsFound, ChannelSearchResultAdapter.OnChannelSearchResultSelected { @@ -219,10 +220,12 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O @Override public void onChannelSearchResult(final Room result) { - List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService); + final List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService); if (accounts.size() == 1) { joinChannelSearchResult(accounts.get(0), result); - } else if (accounts.size() > 0) { + } else if (accounts.size() == 0) { + ToastCompat.makeText(this, R.string.please_enable_an_account, ToastCompat.LENGTH_LONG).show(); + } else { final AtomicReference<String> account = new AtomicReference<>(accounts.get(0)); final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.choose_account); diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 9ab0f50fc..8354d6517 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -139,6 +139,7 @@ import static de.pixart.messenger.ui.SettingsActivity.WARN_UNENCRYPTED_CHAT; import static de.pixart.messenger.ui.XmppActivity.EXTRA_ACCOUNT; import static de.pixart.messenger.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION; import static de.pixart.messenger.ui.util.SoftKeyboardUtils.hideSoftKeyboard; +import static de.pixart.messenger.utils.Compatibility.runsTwentyOne; import static de.pixart.messenger.utils.PermissionUtils.allGranted; import static de.pixart.messenger.utils.PermissionUtils.getFirstDenied; import static de.pixart.messenger.utils.PermissionUtils.readGranted; @@ -632,6 +633,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke popup.setOnMenuItemClickListener(attachmentItem -> { switch (attachmentItem.getItemId()) { case R.id.attach_choose_picture: + case R.id.attach_choose_video: case R.id.attach_take_picture: case R.id.attach_record_video: case R.id.attach_choose_file: @@ -996,6 +998,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case ATTACHMENT_CHOICE_CHOOSE_FILE: case ATTACHMENT_CHOICE_RECORD_VIDEO: case ATTACHMENT_CHOICE_RECORD_VOICE: + case ATTACHMENT_CHOICE_CHOOSE_VIDEO: final Attachment.Type type = requestCode == ATTACHMENT_CHOICE_RECORD_VOICE ? Attachment.Type.RECORDING : Attachment.Type.FILE; final List<Attachment> fileUris = Attachment.extractAttachments(getActivity(), data, type); mediaPreviewAdapter.addMediaPreviews(fileUris); @@ -1151,7 +1154,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke menuNeedHelp.setVisible(true); menuSearchUpdates.setVisible(false); ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu, activity.xmppConnectionService.getAttachmentChoicePreference(), hasAttachments); - ConversationMenuConfigurator.configureEncryptionMenu(conversation, menu); + ConversationMenuConfigurator.configureEncryptionMenu(conversation, menu, activity); } else { menuNeedHelp.setVisible(false); menuSearchUpdates.setVisible(true); @@ -1434,6 +1437,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke handleEncryptionSelection(item); break; case R.id.attach_choose_picture: + case R.id.attach_choose_video: case R.id.attach_take_picture: case R.id.attach_record_video: case R.id.attach_choose_file: @@ -1494,6 +1498,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case R.id.attach_choose_picture: attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); break; + case R.id.attach_choose_video: + attachFile(ATTACHMENT_CHOICE_CHOOSE_VIDEO); + break; case R.id.attach_take_picture: attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); break; @@ -1678,7 +1685,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } private void updateChatBG() { - if (activity.unicoloredBG()) { + if (activity.unicoloredBG() || !runsTwentyOne()) { binding.conversationsFragment.setBackgroundResource(0); binding.conversationsFragment.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary)); } else { @@ -2141,12 +2148,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke public void onStart() { super.onStart(); if (this.reInitRequiredOnStart && this.conversation != null) { + if (activity != null) { + updateChatBG(); + } final Bundle extras = pendingExtras.pop(); reInit(this.conversation, extras != null); if (extras != null) { processExtras(extras); } } else if (conversation == null && activity != null && activity.xmppConnectionService != null) { + updateChatBG(); final String uuid = pendingConversationsUuid.pop(); Log.d(Config.LOGTAG, "ConversationFragment.onStart() - activity was bound but no conversation loaded. uuid=" + uuid); if (uuid != null) { diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index 3afcf83bb..a6ab04be2 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -364,7 +364,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat return; } - if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) { + final List<Account> accounts = xmppConnectionService == null ? null : xmppConnectionService.getAccounts(); + if (accounts != null && accounts.size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) { Intent intent = SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister); StartConversationActivity.addInviteUri(intent, getIntent()); startActivity(intent); diff --git a/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java index 2ed416c17..b75af44e9 100644 --- a/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java +++ b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java @@ -3,6 +3,7 @@ package de.pixart.messenger.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -13,6 +14,7 @@ import androidx.databinding.DataBindingUtil; import java.util.ArrayList; import java.util.List; +import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.databinding.ActivityMediaBrowserBinding; import de.pixart.messenger.entities.Account; @@ -38,6 +40,14 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded private String account; private String jid; + @Override + protected void onStart() { + super.onStart(); + filter(OnlyImagesVideos); + invalidateOptionsMenu(); + refreshUiReal(); + } + public static void launch(Context context, Contact contact) { launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString()); } @@ -79,7 +89,7 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded } @Override - protected void refreshUiReal() { + public void refreshUiReal() { mMediaAdapter.notifyDataSetChanged(); } @@ -145,19 +155,18 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded @Override public void onMediaLoaded(List<Attachment> attachments) { + allAttachments.clear(); allAttachments.addAll(attachments); runOnUiThread(() -> { - if (OnlyImagesVideos) { - filter(OnlyImagesVideos); - } else { - loadAttachments(allAttachments); - } + filter(OnlyImagesVideos); }); } private void loadAttachments(List<Attachment> attachments) { if (attachments.size() > 0) { - mMediaAdapter.setAttachments(attachments); + if (mMediaAdapter.getItemCount() != attachments.size()) { + mMediaAdapter.setAttachments(attachments); + } this.binding.noMedia.setVisibility(View.GONE); this.binding.progressbar.setVisibility(View.GONE); } else { @@ -172,20 +181,28 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded } } + @Override + public void onResume() { + super.onResume(); + filter(OnlyImagesVideos); + } + protected void filterAttachments(boolean needle) { if (allAttachments.size() > 0) { - final ArrayList<Attachment> attachments = new ArrayList<>(allAttachments); - filteredAttachments.clear(); if (needle) { + final ArrayList<Attachment> attachments = new ArrayList<>(allAttachments); + filteredAttachments.clear(); for (Attachment attachment : attachments) { if (attachment.getMime() != null && (attachment.getMime().startsWith("image/") || attachment.getMime().startsWith("video/"))) { filteredAttachments.add(attachment); } } + loadAttachments(filteredAttachments); } else { - filteredAttachments.addAll(allAttachments); + loadAttachments(allAttachments); } - loadAttachments(filteredAttachments); + } else { + loadAttachments(allAttachments); } } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java index 9f309ac5f..fb63a7a86 100644 --- a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java +++ b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java @@ -76,8 +76,8 @@ public abstract class OmemoActivity extends XmppActivity { } @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, requestCode, intent); + public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); if (requestCode == ScanActivity.REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) { String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); XmppUri uri = new XmppUri(result == null ? "" : result); diff --git a/src/main/java/de/pixart/messenger/ui/PublishGroupChatProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishGroupChatProfilePictureActivity.java index 9c70640c3..fc0d34beb 100644 --- a/src/main/java/de/pixart/messenger/ui/PublishGroupChatProfilePictureActivity.java +++ b/src/main/java/de/pixart/messenger/ui/PublishGroupChatProfilePictureActivity.java @@ -51,6 +51,8 @@ import de.pixart.messenger.ui.interfaces.OnAvatarPublication; import de.pixart.messenger.ui.util.PendingItem; import me.drakeet.support.toast.ToastCompat; +import static de.pixart.messenger.ui.PublishProfilePictureActivity.REQUEST_CHOOSE_PICTURE; + public class PublishGroupChatProfilePictureActivity extends XmppActivity implements OnAvatarPublication { private final PendingItem<String> pendingConversationUuid = new PendingItem<>(); @@ -96,7 +98,7 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme configureActionBar(getSupportActionBar()); this.binding.cancelButton.setOnClickListener((v) -> this.finish()); this.binding.secondaryHint.setVisibility(View.GONE); - this.binding.accountImage.setOnClickListener((v) -> this.chooseAvatar()); + this.binding.accountImage.setOnClickListener((v) -> PublishProfilePictureActivity.chooseAvatar(this)); Intent intent = getIntent(); String uuid = intent == null ? null : intent.getStringExtra("uuid"); if (uuid != null) { @@ -116,7 +118,7 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { - CropImage.ActivityResult result = CropImage.getActivityResult(data); + final CropImage.ActivityResult result = CropImage.getActivityResult(data); if (resultCode == RESULT_OK) { this.uri = result.getUri(); if (xmppConnectionServiceBound) { @@ -128,17 +130,13 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme ToastCompat.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(); } } + } else if (requestCode == REQUEST_CHOOSE_PICTURE) { + if (resultCode == RESULT_OK) { + PublishProfilePictureActivity.cropUri(this, data.getData()); + } } } - private void chooseAvatar() { - CropImage.activity() - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(1, 1) - .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) - .start(this); - } - @Override public void onAvatarPublicationSucceeded() { runOnUiThread(() -> { diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java index f505bd3a3..0c634d8f3 100644 --- a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java +++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java @@ -1,8 +1,10 @@ package de.pixart.messenger.ui; +import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -28,6 +30,8 @@ import me.drakeet.support.toast.ToastCompat; public class PublishProfilePictureActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication { + public static final int REQUEST_CHOOSE_PICTURE = 0x1337; + private ImageView avatar; private TextView hintOrWarning; private TextView secondaryHint; @@ -108,7 +112,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC } finish(); }); - this.avatar.setOnClickListener(v -> chooseAvatar()); + this.avatar.setOnClickListener(v -> chooseAvatar(this)); this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext()); if (savedInstanceState != null) { this.avatarUri = savedInstanceState.getParcelable("uri"); @@ -141,15 +145,28 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC ToastCompat.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(); } } + } else if (requestCode == REQUEST_CHOOSE_PICTURE) { + if (resultCode == RESULT_OK) { + cropUri(this, data.getData()); + } } } - private void chooseAvatar() { - CropImage.activity() - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(1, 1) - .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) - .start(this); + public static void chooseAvatar(final Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + activity.startActivityForResult( + Intent.createChooser(intent, activity.getString(R.string.attach_choose_picture)), + REQUEST_CHOOSE_PICTURE + ); + } else { + CropImage.activity() + .setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAspectRatio(1, 1) + .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) + .start(activity); + } } @Override @@ -183,10 +200,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC 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); + cropUri(this, uri); return; } @@ -196,6 +210,13 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get()); } + public static void cropUri(final Activity activity, final Uri uri) { + CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAspectRatio(1, 1) + .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) + .start(activity); + } + protected void loadImageIntoPreview(Uri uri) { Bitmap bm = null; diff --git a/src/main/java/de/pixart/messenger/ui/SearchActivity.java b/src/main/java/de/pixart/messenger/ui/SearchActivity.java index 8fd76df2e..dce7b370c 100644 --- a/src/main/java/de/pixart/messenger/ui/SearchActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SearchActivity.java @@ -33,6 +33,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.util.Log; import android.view.ContextMenu; import android.view.Menu; import android.view.MenuItem; @@ -47,6 +48,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.databinding.ActivitySearchBinding; import de.pixart.messenger.entities.Contact; @@ -64,6 +66,7 @@ import de.pixart.messenger.ui.util.ShareUtil; import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.FtsUtils; import de.pixart.messenger.utils.MessageUtils; +import de.pixart.messenger.utils.UIHelper; import static de.pixart.messenger.ui.util.SoftKeyboardUtils.hideSoftKeyboard; import static de.pixart.messenger.ui.util.SoftKeyboardUtils.showKeyboard; @@ -115,9 +118,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc searchField.addTextChangedListener(this); searchField.setHint(R.string.search_messages); searchField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); - if (term == null) { - showKeyboard(searchField); - } + showKeyboard(searchField); return super.onCreateOptionsMenu(menu); } @@ -150,7 +151,8 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc @Override public boolean onContextItemSelected(MenuItem item) { final Message message = selectedMessageReference.get(); - final String user = selectedMessageReference.get().getConversation().getContact().getDisplayName(); + final boolean multi = message.getConversation().getMode() == Conversational.MODE_MULTI; + final String user = multi ? UIHelper.getDisplayedMucCounterpart(message.getCounterpart()) : null; if (message != null) { switch (item.getItemId()) { case R.id.open_conversation: @@ -183,6 +185,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc } private void quote(Message message, String user) { + Log.d(Config.LOGTAG, "Quote User: " + user); switchToConversationAndQuote(wrap(message.getConversation()), MessageUtils.prepareQuote(message), user); } diff --git a/src/main/java/de/pixart/messenger/ui/SetSettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SetSettingsActivity.java index 5c4ce2622..27119f5fd 100644 --- a/src/main/java/de/pixart/messenger/ui/SetSettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SetSettingsActivity.java @@ -36,7 +36,7 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS static final int CHATSTATES = 4; static final int CONFIRMMESSAGES = 5; static final int LASTSEEN = 6; - static final int INVIDEOUS = 7; + static final int INVIDIOUS = 7; @Override protected void refreshUiReal() { @@ -66,7 +66,7 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS this.binding.actionInfoChatStates.setOnClickListener(string -> showInfo(CHATSTATES)); this.binding.actionInfoConfirmMessages.setOnClickListener(string -> showInfo(CONFIRMMESSAGES)); this.binding.actionInfoLastSeen.setOnClickListener(string -> showInfo(LASTSEEN)); - this.binding.actionInfoInvideous.setOnClickListener(string -> showInfo(INVIDEOUS)); + this.binding.actionInfoInvidious.setOnClickListener(string -> showInfo(INVIDIOUS)); } private void getDefaults() { @@ -76,7 +76,7 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS this.binding.chatStates.setChecked(getResources().getBoolean(R.bool.chat_states)); this.binding.confirmMessages.setChecked(getResources().getBoolean(R.bool.confirm_messages)); this.binding.lastSeen.setChecked(getResources().getBoolean(R.bool.last_activity)); - this.binding.invideous.setChecked(getResources().getBoolean(R.bool.use_invidious)); + this.binding.invidious.setChecked(getResources().getBoolean(R.bool.use_invidious)); } private void next(View view) { @@ -122,7 +122,7 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS title = getString(R.string.pref_broadcast_last_activity); message = getString(R.string.pref_broadcast_last_activity_summary); break; - case INVIDEOUS: + case INVIDIOUS: title = getString(R.string.pref_use_invidious); message = getString(R.string.pref_use_invidious_summary); break; @@ -171,7 +171,7 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS } else { preferences.edit().putBoolean(BROADCAST_LAST_ACTIVITY, false).apply(); } - if (this.binding.invideous.isChecked()) { + if (this.binding.invidious.isChecked()) { preferences.edit().putBoolean(USE_INVIDIOUS, true).apply(); } else { preferences.edit().putBoolean(USE_INVIDIOUS, false).apply(); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index 8f4e468c7..b0e880cba 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -1003,10 +1003,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } if (isBookmarkChecked) { - if (account.hasBookmarkFor(conferenceJid)) { - layout.setError(getString(R.string.bookmark_already_exists)); + Bookmark bookmark = account.getBookmark(conferenceJid); + if (bookmark != null) { + dialog.dismiss(); + openConversationsForBookmark(bookmark); } else { - final Bookmark bookmark = new Bookmark(account, conferenceJid.asBareJid()); + bookmark = new Bookmark(account, conferenceJid.asBareJid()); bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin)); final String nick = conferenceJid.getResource(); if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) { @@ -1100,6 +1102,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { activity.conference_context_id = acmi.position; + final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position); + final Conversation conversation = bookmark.getConversation(); + final MenuItem share = menu.findItem(R.id.context_share_uri); + share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous()); } else if (mResContextMenu == R.menu.contact_context) { activity.contact_context_id = acmi.position; final Contact contact = (Contact) activity.contacts.get(acmi.position); @@ -1110,7 +1116,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne showContactDetailsItem.setVisible(false); } deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER)); - XmppConnection xmpp = contact.getAccount().getXmppConnection(); + final XmppConnection xmpp = contact.getAccount().getXmppConnection(); if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) { if (contact.isBlocked()) { blockUnblockItem.setTitle(R.string.unblock_contact); diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index 4f9cb587a..624119bb8 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -97,6 +97,7 @@ import me.drakeet.support.toast.ToastCompat; import pl.droidsonroids.gif.GifDrawable; import rocks.xmpp.addr.Jid; +import static de.pixart.messenger.ui.SettingsActivity.ENABLE_OTR_ENCRYPTION; import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; import static de.pixart.messenger.ui.SettingsActivity.USE_INTERNAL_UPDATER; @@ -111,6 +112,7 @@ public abstract class XmppActivity extends ActionBarActivity { public static final String EXTRA_ACCOUNT = "account"; public XmppConnectionService xmppConnectionService; + public MediaBrowserActivity mediaBrowserActivity; public boolean xmppConnectionServiceBound = false; protected int mColorWarningButton; @@ -438,6 +440,10 @@ public abstract class XmppActivity extends ActionBarActivity { return getBooleanPreference("unicolored_chatbg", R.bool.use_unicolored_chatbg); } + public boolean enableOTR() { + return getBooleanPreference(ENABLE_OTR_ENCRYPTION, R.bool.enable_otr); + } + public void setBubbleColor(final View v, final int backgroundColor, final int borderColor) { GradientDrawable shape = (GradientDrawable) v.getBackground(); shape.setColor(backgroundColor); @@ -528,10 +534,10 @@ public abstract class XmppActivity extends ActionBarActivity { intent.putExtra(Intent.EXTRA_TEXT, text); if (asQuote) { intent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true); - intent.putExtra(ConversationsActivity.EXTRA_ACCOUNT, nick); + intent.putExtra(ConversationsActivity.EXTRA_USER, nick); } } - if (nick != null) { + if (nick != null && !asQuote) { intent.putExtra(ConversationsActivity.EXTRA_NICK, nick); intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm); } @@ -1092,8 +1098,8 @@ public abstract class XmppActivity extends ActionBarActivity { protected String doInBackground(XmppConnection... params) { String uri = null; if (this.connection != null) { - XmppConnection.Features features = connection.getFeatures(); - if (features.adhocinvite) { + XmppConnection.Features features = this.connection.getFeatures(); + if (features != null && features.adhocinvite) { int i = 0; uri = this.connection.getAdHocInviteUrl(Jid.ofDomain(this.account.getJid().getDomain())); try { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java index 778dbbb13..3dfa9b560 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java @@ -1,34 +1,49 @@ package de.pixart.messenger.ui.adapter; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.AsyncTask; +import android.util.Log; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.AttrRes; import androidx.annotation.DimenRes; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.RecyclerView; +import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.RejectedExecutionException; +import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.databinding.MediaBinding; +import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.ExportBackupService; import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.util.Attachment; import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.ui.util.ViewUtil; +import de.pixart.messenger.utils.MimeUtils; +import me.drakeet.support.toast.ToastCompat; public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> { @@ -140,9 +155,105 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol loadPreview(attachment, holder.binding.media); } else { cancelPotentialWork(attachment, holder.binding.media); - renderPreview(activity, attachment, holder.binding.media); + renderPreview(this.activity, attachment, holder.binding.media); } - holder.binding.getRoot().setOnClickListener(v -> ViewUtil.view(activity, attachment)); + holder.binding.getRoot().setOnClickListener(v -> ViewUtil.view(this.activity, attachment)); + holder.binding.getRoot().setOnLongClickListener(v -> { + setSelection(v); + final PopupMenu popupMenu = new PopupMenu(this.activity, v); + popupMenu.inflate(R.menu.media_viewer); + popupMenu.getMenu().findItem(R.id.action_delete).setVisible(isDeletableFile(new File(attachment.getUri().getPath()))); + popupMenu.setOnMenuItemClickListener(item -> { + switch (item.getItemId()) { + case R.id.action_share: + share(attachment); + return true; + case R.id.action_open: + open(attachment); + return true; + case R.id.action_delete: + deleteFile(attachment); + return true; + } + return false; + }); + popupMenu.setOnDismissListener(menu -> resetSelection(v)); + popupMenu.show(); + return true; + }); + } + + private void setSelection(final View v) { + v.setBackgroundColor(StyledAttributes.getColor(this.activity, R.attr.colorAccent)); + } + + private void resetSelection(final View v) { + v.setBackgroundColor(0); + } + + private void share(final Attachment attachment) { + final Intent share = new Intent(Intent.ACTION_SEND); + final File file = new File(attachment.getUri().getPath()); + share.setType(attachment.getMime()); + share.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(this.activity, file)); + try { + this.activity.startActivity(Intent.createChooser(share, this.activity.getText(R.string.share_with))); + } catch (ActivityNotFoundException e) { + //This should happen only on faulty androids because normally chooser is always available + ToastCompat.makeText(this.activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + } + } + + private void deleteFile(final Attachment attachment) { + final File file = new File(attachment.getUri().getPath()); + final int hash = attachment.hashCode(); + final AlertDialog.Builder builder = new AlertDialog.Builder(this.activity); + builder.setNegativeButton(R.string.cancel, null); + builder.setTitle(R.string.delete_file_dialog); + builder.setMessage(R.string.delete_file_dialog_msg); + builder.setPositiveButton(R.string.confirm, (dialog, which) -> { + if (activity.xmppConnectionService.getFileBackend().deleteFile(file)) { + for (int i = 0; i < attachments.size(); i++) { + if (hash == attachments.get(i).hashCode()) { + attachments.remove(i); + notifyDataSetChanged(); + this.activity.refreshUi(); + return; + } + } + } + }); + builder.create().show(); + } + + private void open(final Attachment attachment) { + final File file = new File(attachment.getUri().getPath()); + final Uri uri; + try { + uri = FileBackend.getUriForFile(this.activity, file); + } catch (SecurityException e) { + Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e); + ToastCompat.makeText(this.activity, this.activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); + return; + } + String mime = MimeUtils.guessMimeTypeFromUri(this.activity, uri); + Intent openIntent = new Intent(Intent.ACTION_VIEW); + openIntent.setDataAndType(uri, mime); + openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PackageManager manager = this.activity.getPackageManager(); + List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0); + if (info.size() == 0) { + openIntent.setDataAndType(uri, "*/*"); + } + try { + this.activity.startActivity(openIntent); + } catch (ActivityNotFoundException e) { + ToastCompat.makeText(this.activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + } + } + + private boolean isDeletableFile(File file) { + return (file == null || !file.toString().startsWith("/") || file.toString().contains(FileBackend.getConversationsDirectory("null"))); } public void setAttachments(List<Attachment> attachments) { 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 0593869ca..f85670ec0 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -493,7 +493,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.progressBar.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); - } private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) { @@ -681,6 +680,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie private void displayOpenableMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground) { toggleWhisperInfo(viewHolder, message, false, darkBackground); + viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); + viewHolder.progressBar.setVisibility(View.GONE); final String mimeType = message.getMimeType(); if (mimeType != null && message.getMimeType().contains("vcard")) { try { @@ -1299,9 +1304,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie } private void markFileExisting(Message message) { - Log.d(Config.LOGTAG, "Found and restored orphaned file"); + Log.d(Config.LOGTAG, "Found and restored orphaned file " + message.getRelativeFilePath()); message.setFileDeleted(false); activity.xmppConnectionService.updateMessage(message, false); + activity.xmppConnectionService.updateConversation((Conversation) message.getConversation()); } private boolean checkFileExistence(Message message, View view, ViewHolder viewHolder) { diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java index 9797da56f..8a9434622 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java +++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java @@ -29,6 +29,7 @@ package de.pixart.messenger.ui.util; +import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.view.Menu; @@ -42,8 +43,7 @@ import de.pixart.messenger.crypto.OmemoSetting; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Conversational; import de.pixart.messenger.entities.Message; - -import static de.pixart.messenger.ui.SettingsActivity.ENABLE_OTR_ENCRYPTION; +import de.pixart.messenger.ui.XmppActivity; public class ConversationMenuConfigurator { @@ -93,7 +93,7 @@ public class ConversationMenuConfigurator { menu.findItem(R.id.attach_location).setVisible(locationAvailable); } - public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu) { + public static void configureEncryptionMenu(@NonNull Conversation conversation, Menu menu, final XmppActivity activity) { final MenuItem menuSecure = menu.findItem(R.id.action_security); final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); if (!participating) { @@ -130,7 +130,7 @@ public class ConversationMenuConfigurator { menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } - otr.setVisible(Config.supportOtr() && conversation.getBooleanAttribute(ENABLE_OTR_ENCRYPTION, false)); + otr.setVisible(Config.supportOtr() && activity.enableOTR()); if (conversation.getMode() == Conversation.MODE_MULTI) { otr.setVisible(false); } diff --git a/src/main/java/de/pixart/messenger/utils/MimeUtils.java b/src/main/java/de/pixart/messenger/utils/MimeUtils.java index 8603a8a89..31159e27a 100644 --- a/src/main/java/de/pixart/messenger/utils/MimeUtils.java +++ b/src/main/java/de/pixart/messenger/utils/MimeUtils.java @@ -278,6 +278,8 @@ public final class MimeUtils { add("image/jpeg", "jpg"); add("image/jpeg", "jpeg"); add("image/jpeg", "jpe"); + add("image/jpeg", "jfif"); + add("image/jpeg", "jif"); add("image/pcx", "pcx"); add("image/png", "png"); add("image/svg+xml", "svg"); diff --git a/src/main/java/de/pixart/messenger/utils/Resolver.java b/src/main/java/de/pixart/messenger/utils/Resolver.java index ed5e62fd3..a7c286cc7 100644 --- a/src/main/java/de/pixart/messenger/utils/Resolver.java +++ b/src/main/java/de/pixart/messenger/utils/Resolver.java @@ -1,6 +1,7 @@ package de.pixart.messenger.utils; import android.content.ContentValues; +import android.database.Cursor; import android.util.Log; import androidx.annotation.NonNull; @@ -168,6 +169,7 @@ public class Resolver { final Result result = new Result(); result.ip = InetAddress.getByName(domain); result.port = port; + result.authenticated = true; return result; } catch (UnknownHostException e) { e.printStackTrace(); @@ -202,18 +204,20 @@ public class Resolver { })); fallbackThreads.add(new Thread(() -> { try { - for (CNAME cname : resolveWithFallback(record.name, CNAME.class, result.isAuthenticData()).getAnswersOrEmptySet()) { - final List<Result> ipv6s = resolveIp(record, cname.name, AAAA.class, result.isAuthenticData(), directTls); + ResolverResult<CNAME> cnames = resolveWithFallback(record.name, CNAME.class, result.isAuthenticData()); + for (CNAME cname : cnames.getAnswersOrEmptySet()) { + final List<Result> ipv6s = resolveIp(record, cname.name, AAAA.class, cnames.isAuthenticData(), directTls); synchronized (fallbackResults) { fallbackResults.addAll(ipv6s); } - final List<Result> ipv4s = resolveIp(record, cname.name, A.class, result.isAuthenticData(), directTls); + final List<Result> ipv4s = resolveIp(record, cname.name, A.class, cnames.isAuthenticData(), directTls); synchronized (results) { fallbackResults.addAll(ipv4s); } } + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "cname in srv (agains RFC2782) - run slow fallback"); } catch (Throwable throwable) { - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving srv cname-fallback records", throwable); + Log.i(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving srv cname-fallback records", throwable); } })); } @@ -358,12 +362,14 @@ public class Resolver { } public static class Result implements Comparable<Result>, Callable<Result> { + public static final String DOMAIN = "domain"; public static final String IP = "ip"; public static final String HOSTNAME = "hostname"; public static final String PORT = "port"; public static final String PRIORITY = "priority"; public static final String DIRECT_TLS = "directTls"; public static final String AUTHENTICATED = "authenticated"; + public static final String TIME_REQUESTED = "time_requested"; private InetAddress ip; private DNSName hostname; @@ -371,12 +377,14 @@ public class Resolver { private boolean directTls = false; private boolean authenticated = false; private int priority; + private long timeRequested; private Socket socket; private String logID = ""; static Result fromRecord(final SRV srv, final boolean directTls) { Result result = new Result(); + result.timeRequested = System.currentTimeMillis(); result.port = srv.port; result.hostname = srv.name; result.directTls = directTls; @@ -386,6 +394,7 @@ public class Resolver { static Result createDefault(final DNSName hostname, final InetAddress ip, final int port) { Result result = new Result(); + result.timeRequested = System.currentTimeMillis(); result.port = port; result.hostname = hostname; result.ip = ip; @@ -430,6 +439,10 @@ public class Resolver { return authenticated; } + public boolean isOutdated() { + return (System.currentTimeMillis() - timeRequested) > 300_000; + } + public Socket getSocket() { return socket; } @@ -506,6 +519,23 @@ public class Resolver { throw new Exception("Resolver.Result was not possible to connect - should be catched by executor"); } + public static Result fromCursor(Cursor cursor) { + final Result result = new Result(); + try { + result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP))); + } catch (UnknownHostException e) { + result.ip = null; + } + final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); + result.hostname = hostname == null ? null : DNSName.from(hostname); + result.port = cursor.getInt(cursor.getColumnIndex(PORT)); + result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0; + result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; + result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); + result.timeRequested = cursor.getLong(cursor.getColumnIndex(TIME_REQUESTED)); + return result; + } + public ContentValues toContentValues() { final ContentValues contentValues = new ContentValues(); contentValues.put(IP, ip == null ? null : ip.getAddress()); @@ -514,6 +544,7 @@ public class Resolver { contentValues.put(PRIORITY, priority); contentValues.put(DIRECT_TLS, directTls ? 1 : 0); contentValues.put(AUTHENTICATED, authenticated ? 1 : 0); + contentValues.put(TIME_REQUESTED, timeRequested); return contentValues; } } diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index e2b3c2f2c..7a40ba6d6 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -35,6 +35,7 @@ import de.pixart.messenger.entities.Presence; import de.pixart.messenger.entities.Transferable; import de.pixart.messenger.services.ExportBackupService; import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.util.MyLinkify; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.entities.Message.DELETED_MESSAGE_BODY; @@ -316,7 +317,7 @@ public class UIHelper { return new Pair<>(context.getString(R.string.x_file_offered_for_download, getFileDescriptionString(context, message)), true); } else { - SpannableStringBuilder styledBody = new SpannableStringBuilder(body); + SpannableStringBuilder styledBody = new SpannableStringBuilder(MyLinkify.replaceYoutube(context, body)); if (textColor != 0) { StylingHelper.format(styledBody, 0, styledBody.length() - 1, textColor); } @@ -603,8 +604,6 @@ public class UIHelper { return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336, 0, account); case DND: return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336, 0, account); - case OFFLINE: - return new ListItem.Tag(context.getString(R.string.presence_offline), 0xff808080, 1, account); default: return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24, 0, account); } diff --git a/src/main/java/de/pixart/messenger/xml/XmlElementReader.java b/src/main/java/de/pixart/messenger/xml/XmlElementReader.java new file mode 100644 index 000000000..108822509 --- /dev/null +++ b/src/main/java/de/pixart/messenger/xml/XmlElementReader.java @@ -0,0 +1,19 @@ +package de.pixart.messenger.xml; + +import com.google.common.io.ByteSource; + +import java.io.IOException; +import java.io.InputStream; + +public class XmlElementReader { + + public static Element read(byte[] bytes) throws IOException { + return read(ByteSource.wrap(bytes).openStream()); + } + + public static Element read(InputStream inputStream) throws IOException { + final XmlReader xmlReader = new XmlReader(); + xmlReader.setInputStream(inputStream); + return xmlReader.readElement(xmlReader.readTag()); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/xml/XmlReader.java b/src/main/java/de/pixart/messenger/xml/XmlReader.java index 6447f12ef..ef2a33158 100644 --- a/src/main/java/de/pixart/messenger/xml/XmlReader.java +++ b/src/main/java/de/pixart/messenger/xml/XmlReader.java @@ -87,8 +87,7 @@ public class XmlReader implements Closeable { return null; } - public Element readElement(Tag currentTag) throws XmlPullParserException, - IOException { + public Element readElement(Tag currentTag) throws IOException { Element element = new Element(currentTag.getName()); element.setAttributes(currentTag.getAttributes()); Tag nextTag = this.readTag(); diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index be9fd5a3a..a41924e08 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -317,11 +317,17 @@ public class XmppConnection implements Runnable { } } else { final String domain = account.getJid().getDomain(); - final Resolver.Result result; + final Resolver.Result storedBackupResult = mXmppConnectionService.databaseBackend.findResolverResult(domain); + Resolver.Result result = null; final boolean hardcoded = extended && !account.getHostname().isEmpty(); if (hardcoded) { result = Resolver.fromHardCoded(account.getHostname(), account.getPort()); - } else { + } else if (storedBackupResult != null && !storedBackupResult.isOutdated()) { + storedBackupResult.connect(); + result = storedBackupResult; + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": loaded backup resolver result from db: " + storedBackupResult); + } + if (result == null || result.getSocket() == null) { result = Resolver.resolve(domain); } if (result == null) { @@ -348,6 +354,9 @@ public class XmppConnection implements Runnable { localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); if (startXmpp(localSocket)) { localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this + if (!hardcoded && !result.equals(storedBackupResult)) { + mXmppConnectionService.databaseBackend.saveResolverResult(domain, result); + } // successfully connected to server that speaks xmpp } else { FileBackend.close(localSocket); @@ -1994,4 +2003,4 @@ public class XmppConnection implements Runnable { return Config.USE_BOOKMARKS2 /* || hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS2_COMPAT)*/; } } -}
\ No newline at end of file +} diff --git a/src/main/res/drawable/list_item_background_light.xml b/src/main/res/drawable/list_item_background_light.xml index f064403da..917f85dab 100644 --- a/src/main/res/drawable/list_item_background_light.xml +++ b/src/main/res/drawable/list_item_background_light.xml @@ -28,5 +28,5 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@color/grey300" android:state_activated="true" /> + <item android:drawable="@color/grey500" android:state_activated="true" /> </selector>
\ No newline at end of file diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml index cb02d53e2..09aac94dc 100644 --- a/src/main/res/layout/activity_contact_details.xml +++ b/src/main/res/layout/activity_contact_details.xml @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" @@ -238,9 +238,9 @@ android:visibility="visible" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/media_wrapper" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -289,9 +289,9 @@ android:textColor="?attr/colorAccent" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/keys_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -350,7 +350,7 @@ android:textColor="?attr/colorAccent" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> </LinearLayout> </ScrollView> </LinearLayout> diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index a1646f553..efc361bf0 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -25,7 +25,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/editor" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -307,9 +307,9 @@ android:layout_marginTop="8dp" android:text="@string/register_account" /> </RelativeLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/os_optimization" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -369,9 +369,9 @@ android:textColor="@color/accent" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/stats" android:layout_width="fill_parent" android:layout_height="fill_parent" @@ -819,9 +819,9 @@ </LinearLayout> </RelativeLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/other_device_keys_card" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -862,7 +862,7 @@ android:layout_gravity="center_horizontal" android:text="@string/clear_other_devices" /> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> </LinearLayout> </ScrollView> diff --git a/src/main/res/layout/activity_media_viewer.xml b/src/main/res/layout/activity_media_viewer.xml index 2b64fb81a..c82ed1603 100644 --- a/src/main/res/layout/activity_media_viewer.xml +++ b/src/main/res/layout/activity_media_viewer.xml @@ -26,6 +26,10 @@ android:id="@+id/messageVideoView" android:layout_width="match_parent" android:layout_height="match_parent" + app:played_color="?attr/colorAccent" + app:scrubber_color="?attr/colorAccent" + app:show_shuffle_button="false" + app:show_buffering="never" android:visibility="gone" /> <com.leinardi.android.speeddial.SpeedDialOverlayLayout diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index 894649cc2..e53d22823 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" @@ -312,9 +312,9 @@ android:visibility="gone" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" @@ -408,9 +408,9 @@ </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/users_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -484,9 +484,9 @@ tools:text="View n Participants" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> - <com.google.android.material.card.MaterialCardView + <androidx.cardview.widget.CardView android:id="@+id/media_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -537,7 +537,7 @@ android:textColor="?attr/colorAccent" /> </LinearLayout> </LinearLayout> - </com.google.android.material.card.MaterialCardView> + </androidx.cardview.widget.CardView> </LinearLayout> </ScrollView> </LinearLayout> diff --git a/src/main/res/layout/activity_set_settings.xml b/src/main/res/layout/activity_set_settings.xml index 99d596e0a..80d85efa0 100644 --- a/src/main/res/layout/activity_set_settings.xml +++ b/src/main/res/layout/activity_set_settings.xml @@ -234,7 +234,7 @@ android:textAppearance="@style/TextAppearance.Conversations.Body1" /> <ImageButton - android:id="@+id/action_info_invideous" + android:id="@+id/action_info_invidious" android:layout_width="wrap_content" android:layout_height="wrap_content" android:alpha="?attr/icon_alpha" @@ -244,7 +244,7 @@ android:src="?attr/icon_help" /> <androidx.appcompat.widget.AppCompatCheckBox - android:id="@+id/invideous" + android:id="@+id/invidious" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" /> diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml index 178cd04c8..30a9d3906 100644 --- a/src/main/res/layout/activity_trust_keys.xml +++ b/src/main/res/layout/activity_trust_keys.xml @@ -92,7 +92,7 @@ android:paddingEnd="16dp" android:paddingRight="16dp" android:text="@string/disable_encryption" - android:textColor="@color/accent" /> + android:textColor="?attr/colorAccent" /> </LinearLayout> </LinearLayout> diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index d7f177514..9748a6b22 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -6,7 +6,6 @@ android:id="@+id/conversations_fragment" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@drawable/chatbg" android:clickable="false"> <ListView diff --git a/src/main/res/menu/choose_attachment.xml b/src/main/res/menu/choose_attachment.xml index a9a57404d..96e540f59 100644 --- a/src/main/res/menu/choose_attachment.xml +++ b/src/main/res/menu/choose_attachment.xml @@ -12,6 +12,11 @@ android:title="@string/attach_choose_picture" /> <item + android:id="@+id/attach_choose_video" + android:icon="?attr/ic_attach_video" + android:title="@string/attach_choose_video" /> + + <item android:id="@+id/attach_take_picture" android:icon="?attr/ic_attach_camera" android:title="@string/action_take_photo" /> diff --git a/src/main/res/menu/fragment_conversation.xml b/src/main/res/menu/fragment_conversation.xml index 8858d941d..ac7d8a1d2 100644 --- a/src/main/res/menu/fragment_conversation.xml +++ b/src/main/res/menu/fragment_conversation.xml @@ -43,6 +43,11 @@ android:title="@string/attach_choose_picture" /> <item + android:id="@+id/attach_choose_video" + android:icon="?attr/ic_attach_video" + android:title="@string/attach_choose_video" /> + + <item android:id="@+id/attach_take_picture" android:icon="?attr/ic_attach_camera" android:title="@string/action_take_photo" /> diff --git a/src/main/res/menu/media_viewer.xml b/src/main/res/menu/media_viewer.xml index c14181d24..e30f56cd1 100644 --- a/src/main/res/menu/media_viewer.xml +++ b/src/main/res/menu/media_viewer.xml @@ -14,6 +14,5 @@ android:id="@+id/action_delete" android:icon="@drawable/ic_delete_white_24dp" android:orderInCategory="20" - android:title="@string/action_delete" - android:visible="false" /> + android:title="@string/action_delete" /> </menu>
\ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 45ba43d61..15a0ff7ac 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -215,7 +215,6 @@ <string name="channel_bare_jid_example">channel@conference.example.com</string> <string name="save_as_bookmark">Save as bookmark</string> <string name="delete_bookmark">Delete bookmark</string> - <string name="bookmark_already_exists">This bookmark already exists</string> <string name="topic">Topic</string> <string name="joining_conference">Joining group chat…</string> <string name="leave">Leave</string> @@ -1005,4 +1004,6 @@ <string name="show_videos_images_only">Only images/videos</string> <string name="show_avatar">Show avatar</string> <string name="action_delete">Delete</string> + <string name="attach_choose_video">Choose video</string> + <string name="please_enable_an_account">Please enable an account</string> </resources> diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index 185699519..0b5fdc3ac 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -154,4 +154,12 @@ <style name="Conversations.Dialog" parent="ThemeOverlay.MaterialComponents.Dialog.Alert" > <item name="android:buttonStyle">@style/Widget.Conversations.Button.Borderless</item> </style> + + <style name="ExoMediaButton.Previous"> + <item name="android:visibility">gone</item> + </style> + + <style name="ExoMediaButton.Next"> + <item name="android:visibility">gone</item> + </style> </resources>
\ No newline at end of file diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index d20d1c304..dfb07ab52 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -336,6 +336,9 @@ <item name="color_bubble_warning">@color/lightred</item> <item name="chat_bg">@drawable/bg_light_orange</item> + + <item name="windowActionModeOverlay">true</item> + <item name="android:actionModeBackground">@color/accent_orange</item> </style> <style name="ConversationsTheme.Orange.Dark" parent="ConversationsTheme.Dark"> @@ -351,6 +354,9 @@ <item name="color_bubble_warning">@color/darkred</item> <item name="chat_bg">@drawable/bg_dark_orange</item> + + <item name="windowActionModeOverlay">true</item> + <item name="android:actionModeBackground">@color/accent_orange</item> </style> <style name="ConversationsTheme.Dialog" parent="Theme.MaterialComponents.Light.Dialog"> diff --git a/src/main/res/xml/file_paths.xml b/src/main/res/xml/file_paths.xml index 847faf68f..0cc514a45 100644 --- a/src/main/res/xml/file_paths.xml +++ b/src/main/res/xml/file_paths.xml @@ -11,7 +11,7 @@ path="Images/" /> <files-path name="videos" - path="Videos" /> + path="Videos/" /> <files-path name="files" path="Files/" /> |