From 96d6b7503729184af9698ad81b705da485ea3814 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Jan 2020 20:41:29 +0100 Subject: [PATCH 1/8] Fix broken link --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 01cdae74dd..547837228b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,4 +7,4 @@ - [ ] Pull request is based on the develop branch - [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md) - [ ] Pull request includes screenshots or videos if containing UI changes -- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off) +- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45834afa21..489ec94b55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing code to Matrix -Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst +Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md Android support can be found in this [![Riot Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riot-android:matrix.org.svg?label=%23riot-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riot-android:matrix.org) room. From 159c96681fdd5bb31b2b806cc4c03edc1098db5f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Jan 2020 21:06:29 +0100 Subject: [PATCH 2/8] Improve attachment encryption and decryption code --- .../crypto/AttachmentEncryptionTest.kt | 2 - .../android/internal/crypto/CryptoModule.kt | 4 +- .../crypto/attachments/EncryptionResult.kt | 27 ++++ .../attachments/MXEncryptedAttachments.kt | 127 ++++++++---------- .../SessionRealmConfigurationFactory.kt | 13 +- ...serCacheDirectory.kt => FileQualifiers.kt} | 12 +- .../internal/session/DefaultFileService.kt | 24 ++-- .../android/internal/session/SessionModule.kt | 10 +- .../internal/session/signout/SignOutTask.kt | 2 +- 9 files changed, 117 insertions(+), 104 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/{UserCacheDirectory.kt => FileQualifiers.kt} (73%) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt index 84b3f24191..7aedab7e2b 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt @@ -55,8 +55,6 @@ class AttachmentEncryptionTest { assertNotNull(decryptedStream) - inputStream.close() - val buffer = ByteArray(100) val len = decryptedStream!!.read(buffer) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 9bf3d227c0..b728750acc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -31,7 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.di.CryptoDatabase -import im.vector.matrix.android.internal.di.UserCacheDirectory +import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.cache.ClearCacheTask @@ -53,7 +53,7 @@ internal abstract class CryptoModule { @Provides @CryptoDatabase @SessionScope - fun providesRealmConfiguration(@UserCacheDirectory directory: File, + fun providesRealmConfiguration(@SessionFilesDirectory directory: File, @UserMd5 userMd5: String, realmKeysUtils: RealmKeysUtils): RealmConfiguration { return RealmConfiguration.Builder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt new file mode 100644 index 0000000000..d9e9a62b9e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.attachments + +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo + +/** + * Define the result of an encryption file + */ +data class EncryptionResult( + var encryptedFileInfo: EncryptedFileInfo, + var encryptedByteArray: ByteArray +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt index 503bf8e4ff..7c993d9cc0 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -35,17 +35,9 @@ object MXEncryptedAttachments { private const val SECRET_KEY_SPEC_ALGORITHM = "AES" private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - /** - * Define the result of an encryption file - */ - data class EncryptionResult( - var encryptedFileInfo: EncryptedFileInfo, - var encryptedByteArray: ByteArray - ) - /*** * Encrypt an attachment stream. - * @param attachmentStream the attachment stream + * @param attachmentStream the attachment stream. Will be closed after this method call. * @param mimetype the mime type * @return the encryption file info */ @@ -67,9 +59,7 @@ object MXEncryptedAttachments { val key = ByteArray(32) secureRandom.nextBytes(key) - val outStream = ByteArrayOutputStream() - - outStream.use { + ByteArrayOutputStream().use { outputStream -> val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM) val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM) val ivParameterSpec = IvParameterSpec(initVectorBytes) @@ -81,20 +71,22 @@ object MXEncryptedAttachments { var read: Int var encodedBytes: ByteArray - read = attachmentStream.read(data) - while (read != -1) { - encodedBytes = encryptCipher.update(data, 0, read) - messageDigest.update(encodedBytes, 0, encodedBytes.size) - outStream.write(encodedBytes) - read = attachmentStream.read(data) + attachmentStream.use { inputStream -> + read = inputStream.read(data) + while (read != -1) { + encodedBytes = encryptCipher.update(data, 0, read) + messageDigest.update(encodedBytes, 0, encodedBytes.size) + outputStream.write(encodedBytes) + read = inputStream.read(data) + } } // encrypt the latest chunk encodedBytes = encryptCipher.doFinal() messageDigest.update(encodedBytes, 0, encodedBytes.size) - outStream.write(encodedBytes) + outputStream.write(encodedBytes) - val result = EncryptionResult( + return EncryptionResult( encryptedFileInfo = EncryptedFileInfo( url = null, mimetype = mimetype, @@ -109,18 +101,16 @@ object MXEncryptedAttachments { hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))), v = "v2" ), - encryptedByteArray = outStream.toByteArray() + encryptedByteArray = outputStream.toByteArray() ) - - Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms") - return result + .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") } } } /** * Decrypt an attachment * - * @param attachmentStream the attachment stream + * @param attachmentStream the attachment stream. Will be closed after this method call. * @param encryptedFileInfo the encryption file info * @return the decrypted attachment stream */ @@ -138,7 +128,7 @@ object MXEncryptedAttachments { /** * Decrypt an attachment * - * @param attachmentStream the attachment stream + * @param attachmentStream the attachment stream. Will be closed after this method call. * @param elementToDecrypt the elementToDecrypt info * @return the decrypted attachment stream */ @@ -151,59 +141,50 @@ object MXEncryptedAttachments { val t0 = System.currentTimeMillis() - val outStream = ByteArrayOutputStream() + ByteArrayOutputStream().use { outputStream -> + try { + val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT) + val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT) - try { - val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT) - val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT) + val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM) + val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM) + val ivParameterSpec = IvParameterSpec(initVectorBytes) + decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) - val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM) - val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM) - val ivParameterSpec = IvParameterSpec(initVectorBytes) - decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM) - val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM) + var read: Int + val data = ByteArray(CRYPTO_BUFFER_SIZE) + var decodedBytes: ByteArray - var read: Int - val data = ByteArray(CRYPTO_BUFFER_SIZE) - var decodedBytes: ByteArray + attachmentStream.use { inputStream -> + read = inputStream.read(data) + while (read != -1) { + messageDigest.update(data, 0, read) + decodedBytes = decryptCipher.update(data, 0, read) + outputStream.write(decodedBytes) + read = inputStream.read(data) + } + } - read = attachmentStream.read(data) - while (read != -1) { - messageDigest.update(data, 0, read) - decodedBytes = decryptCipher.update(data, 0, read) - outStream.write(decodedBytes) - read = attachmentStream.read(data) + // decrypt the last chunk + decodedBytes = decryptCipher.doFinal() + outputStream.write(decodedBytes) + + val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT)) + + if (elementToDecrypt.sha256 != currentDigestValue) { + Timber.e("## decryptAttachment() : Digest value mismatch") + return null + } + + return ByteArrayInputStream(outputStream.toByteArray()) + .also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") } + } catch (oom: OutOfMemoryError) { + Timber.e(oom, "## decryptAttachment() : failed ${oom.message}") + } catch (e: Exception) { + Timber.e(e, "## decryptAttachment() : failed ${e.message}") } - - // decrypt the last chunk - decodedBytes = decryptCipher.doFinal() - outStream.write(decodedBytes) - - val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT)) - - if (elementToDecrypt.sha256 != currentDigestValue) { - Timber.e("## decryptAttachment() : Digest value mismatch") - outStream.close() - return null - } - - val decryptedStream = ByteArrayInputStream(outStream.toByteArray()) - outStream.close() - - Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms") - - return decryptedStream - } catch (oom: OutOfMemoryError) { - Timber.e(oom, "## decryptAttachment() : failed ${oom.message}") - } catch (e: Exception) { - Timber.e(e, "## decryptAttachment() : failed ${e.message}") - } - - try { - outStream.close() - } catch (closeException: Exception) { - Timber.e(closeException, "## decryptAttachment() : fail to close the file") } return null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt index ddc7f5e8e6..0b5af8397d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt @@ -18,8 +18,8 @@ package im.vector.matrix.android.internal.database import android.content.Context import im.vector.matrix.android.internal.database.model.SessionRealmModule +import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.SessionId -import im.vector.matrix.android.internal.di.UserCacheDirectory import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.session.SessionModule import io.realm.Realm @@ -36,11 +36,12 @@ private const val REALM_NAME = "disk_store.realm" * It will handle corrupted realm by clearing the db file. It allows to just clear cache without losing your crypto keys. * It's clearly not perfect but there is no way to catch the native crash. */ -internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils, - @UserCacheDirectory val directory: File, - @SessionId val sessionId: String, - @UserMd5 val userMd5: String, - context: Context) { +internal class SessionRealmConfigurationFactory @Inject constructor( + private val realmKeysUtils: RealmKeysUtils, + @SessionFilesDirectory val directory: File, + @SessionId val sessionId: String, + @UserMd5 val userMd5: String, + context: Context) { private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/UserCacheDirectory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt similarity index 73% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/UserCacheDirectory.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt index 93df68ed07..dc36b02809 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/UserCacheDirectory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/FileQualifiers.kt @@ -14,16 +14,14 @@ * limitations under the License. */ -/* - * Unfortunatly "ktlint-disable filename" this does not work so this file is renamed to UserCacheDirectory.kt - * If a new qualifier is added, please rename this file ti FileQualifiers.kt... - */ -/* ktlint-disable filename */ - package im.vector.matrix.android.internal.di import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.RUNTIME) -annotation class UserCacheDirectory +annotation class SessionFilesDirectory + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class SessionCacheDirectory diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt index 0160e88b8c..cf7e1b1d83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session -import android.content.Context import android.os.Environment import arrow.core.Try import im.vector.matrix.android.api.MatrixCallback @@ -25,7 +24,8 @@ import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments -import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.SessionCacheDirectory +import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.md5 @@ -41,12 +41,13 @@ import java.io.File import java.io.IOException import javax.inject.Inject -internal class DefaultFileService @Inject constructor(private val context: Context, - @SessionId private val sessionId: String, - private val contentUrlResolver: ContentUrlResolver, - private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { - - val okHttpClient = OkHttpClient() +internal class DefaultFileService @Inject constructor( + @SessionCacheDirectory + private val cacheDirectory: File, + private val contentUrlResolver: ContentUrlResolver, + @Unauthenticated + private val okHttpClient: OkHttpClient, + private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService { /** * Download file in the cache folder, and eventually decrypt it @@ -103,10 +104,9 @@ internal class DefaultFileService @Inject constructor(private val context: Conte return when (downloadMode) { FileService.DownloadMode.FOR_INTERNAL_USE -> { // Create dir tree (MF stands for Matrix File): - // /MF/// - val tmpFolderRoot = File(context.cacheDir, "MF") - val tmpFolderUser = File(tmpFolderRoot, sessionId) - File(tmpFolderUser, id.md5()) + // //MF// + val tmpFolderSession = File(cacheDirectory, "MF") + File(tmpFolderSession, id.md5()) } FileService.DownloadMode.TO_EXPORT -> { Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 2c72d93ebb..7739405aef 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -93,7 +93,7 @@ internal abstract class SessionModule { @JvmStatic @Provides - @UserCacheDirectory + @SessionFilesDirectory fun providesFilesDir(@UserMd5 userMd5: String, @SessionId sessionId: String, context: Context): File { @@ -106,6 +106,14 @@ internal abstract class SessionModule { return File(context.filesDir, sessionId) } + @JvmStatic + @Provides + @SessionCacheDirectory + fun providesCacheDir(@SessionId sessionId: String, + context: Context): File { + return File(context.cacheDir, sessionId) + } + @JvmStatic @Provides @SessionDatabase diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index 9c31ce567b..3e87486359 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -52,7 +52,7 @@ internal class DefaultSignOutTask @Inject constructor( private val sessionParamsStore: SessionParamsStore, @SessionDatabase private val clearSessionDataTask: ClearCacheTask, @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, - @UserCacheDirectory private val userFile: File, + @SessionFilesDirectory private val userFile: File, private val realmKeysUtils: RealmKeysUtils, @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, From ae26bf33691b65e320f2fa4e512e377ffef1c45b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Jan 2020 21:07:52 +0100 Subject: [PATCH 3/8] Signout also clear cache --- .../matrix/android/internal/session/signout/SignOutTask.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index 3e87486359..e6913f8b54 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -52,7 +52,8 @@ internal class DefaultSignOutTask @Inject constructor( private val sessionParamsStore: SessionParamsStore, @SessionDatabase private val clearSessionDataTask: ClearCacheTask, @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, - @SessionFilesDirectory private val userFile: File, + @SessionFilesDirectory private val sessionFiles: File, + @SessionCacheDirectory private val sessionCache: File, private val realmKeysUtils: RealmKeysUtils, @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, @@ -98,7 +99,8 @@ internal class DefaultSignOutTask @Inject constructor( clearCryptoDataTask.execute(Unit) Timber.d("SignOut: clear file system") - userFile.deleteRecursively() + sessionFiles.deleteRecursively() + sessionCache.deleteRecursively() Timber.d("SignOut: clear the database keys") realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) From ca157c7567c839d3f35594ca64b501acfb2e11c7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Jan 2020 21:39:41 +0100 Subject: [PATCH 4/8] Better logs --- .../internal/crypto/attachments/MXEncryptedAttachments.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt index 7c993d9cc0..3c2aae3cd5 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -181,9 +181,9 @@ object MXEncryptedAttachments { return ByteArrayInputStream(outputStream.toByteArray()) .also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") } } catch (oom: OutOfMemoryError) { - Timber.e(oom, "## decryptAttachment() : failed ${oom.message}") + Timber.e(oom, "## decryptAttachment() failed: OOM") } catch (e: Exception) { - Timber.e(e, "## decryptAttachment() : failed ${e.message}") + Timber.e(e, "## decryptAttachment() failed") } } From d72f1ac5769e953e5371c9d1431d9268d232de0a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Jan 2020 11:36:53 +0100 Subject: [PATCH 5/8] Avoid exposing internal classes --- .../src/main/java/im/vector/matrix/android/api/Matrix.kt | 7 +++++++ .../internal/crypto/attachments/EncryptionResult.kt | 2 +- .../internal/crypto/attachments/MXEncryptedAttachments.kt | 2 +- .../im/vector/riotx/core/glide/VectorGlideModelLoader.kt | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 34e284fd94..4c97c20a57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -24,10 +24,13 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.internal.SessionManager +import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt +import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments import im.vector.matrix.android.internal.di.DaggerMatrixComponent import im.vector.matrix.android.internal.network.UserAgentHolder import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager +import java.io.InputStream import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -96,5 +99,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo fun getSdkVersion(): String { return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")" } + + fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? { + return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt index d9e9a62b9e..3b89b3f559 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/EncryptionResult.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo /** * Define the result of an encryption file */ -data class EncryptionResult( +internal data class EncryptionResult( var encryptedFileInfo: EncryptedFileInfo, var encryptedByteArray: ByteArray ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt index 3c2aae3cd5..e83895709e 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -29,7 +29,7 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -object MXEncryptedAttachments { +internal object MXEncryptedAttachments { private const val CRYPTO_BUFFER_SIZE = 32 * 1024 private const val CIPHER_ALGORITHM = "AES/CTR/NoPadding" private const val SECRET_KEY_SPEC_ALGORITHM = "AES" diff --git a/vector/src/main/java/im/vector/riotx/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/riotx/core/glide/VectorGlideModelLoader.kt index 8ab0bd3a09..1a90b0c34f 100644 --- a/vector/src/main/java/im/vector/riotx/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/riotx/core/glide/VectorGlideModelLoader.kt @@ -24,7 +24,7 @@ import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.signature.ObjectKey -import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments +import im.vector.matrix.android.api.Matrix import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.features.media.ImageContentRenderer import okhttp3.OkHttpClient @@ -116,7 +116,7 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde return } stream = if (data.elementToDecrypt != null && data.elementToDecrypt.k.isNotBlank()) { - MXEncryptedAttachments.decryptAttachment(inputStream, data.elementToDecrypt) + Matrix.decryptStream(inputStream, data.elementToDecrypt) } else { inputStream } From b0aa9fbd8fbd367f0f121930f4a3a9a5aed88d3a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Jan 2020 12:20:39 +0100 Subject: [PATCH 6/8] Inform user when the download of a file starts --- .../home/room/detail/RoomDetailFragment.kt | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0cae97b562..c5b84c0b24 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -251,7 +251,7 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) } roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair -> - val message = requireContext().getString(pair.first, *pair.second.toTypedArray()) + val message = getString(pair.first, *pair.second.toTypedArray()) showSnackWithMessage(message, Snackbar.LENGTH_LONG) } sharedActionViewModel @@ -280,11 +280,12 @@ class RoomDetailFragment @Inject constructor( } roomDetailViewModel.downloadedFileEvent.observeEvent(this) { downloadFileState -> + val activity = requireActivity() if (downloadFileState.throwable != null) { - requireActivity().toast(errorFormatter.toHumanReadable(downloadFileState.throwable)) + activity.toast(errorFormatter.toHumanReadable(downloadFileState.throwable)) } else if (downloadFileState.file != null) { - requireActivity().toast(getString(R.string.downloaded_file, downloadFileState.file.path)) - addEntryToDownloadManager(requireContext(), downloadFileState.file, downloadFileState.mimeType) + activity.toast(getString(R.string.downloaded_file, downloadFileState.file.path)) + addEntryToDownloadManager(activity, downloadFileState.file, downloadFileState.mimeType) } } @@ -369,9 +370,9 @@ class RoomDetailFragment @Inject constructor( AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.error_file_too_big, - error.filename, - TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), - TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) + error.filename, + TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), + TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) )) .setPositiveButton(R.string.ok, null) .show() @@ -454,11 +455,11 @@ class RoomDetailFragment @Inject constructor( updateComposerText(defaultContent) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) - composerLayout.sendButton.setContentDescription(getString(descriptionRes)) + composerLayout.sendButton.contentDescription = getString(descriptionRes) avatarRenderer.render( MatrixItem.UserItem(event.root.senderId - ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), + ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), composerLayout.composerRelatedMessageAvatar ) @@ -477,7 +478,7 @@ class RoomDetailFragment @Inject constructor( // Ignore update to avoid saving a draft composerLayout.composerEditText.setText(text) composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length - ?: 0) + ?: 0) } } @@ -928,6 +929,7 @@ class RoomDetailFragment @Inject constructor( val action = RoomDetailAction.DownloadFile(eventId, messageFileContent) // We need WRITE_EXTERNAL permission if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_DOWNLOAD_FILE)) { + showSnackWithMessage(getString(R.string.downloading_file, messageFileContent.getFileName()), Snackbar.LENGTH_LONG) roomDetailViewModel.handle(action) } else { roomDetailViewModel.pendingAction = action @@ -940,6 +942,12 @@ class RoomDetailFragment @Inject constructor( PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> { val action = roomDetailViewModel.pendingAction if (action != null) { + (action as? RoomDetailAction.DownloadFile) + ?.messageFileContent + ?.getFileName() + ?.let { + showSnackWithMessage(getString(R.string.downloading_file, it), Snackbar.LENGTH_LONG) + } roomDetailViewModel.pendingAction = null roomDetailViewModel.handle(action) } @@ -1052,8 +1060,7 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.Copy -> { // I need info about the current selected message :/ copyToClipboard(requireContext(), action.content, false) - val msg = requireContext().getString(R.string.copied_to_clipboard) - showSnackWithMessage(msg, Snackbar.LENGTH_SHORT) + showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } is EventSharedAction.Delete -> { roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason))) @@ -1127,7 +1134,7 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.CopyPermalink -> { val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) - showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) + showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } is EventSharedAction.Resend -> { roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) @@ -1171,7 +1178,7 @@ class RoomDetailFragment @Inject constructor( val startToCompose = composerLayout.composerEditText.text.isNullOrBlank() if (startToCompose - && userId == session.myUserId) { + && userId == session.myUserId) { // Empty composer, current user: start an emote composerLayout.composerEditText.setText(Command.EMOTE.command + " ") composerLayout.composerEditText.setSelection(Command.EMOTE.length) From f1a2fb51f5316e48f460780a806047511967425e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Jan 2020 12:51:33 +0100 Subject: [PATCH 7/8] Properly configure Snackbar styles --- .../home/room/detail/RoomDetailFragment.kt | 98 +++++++++---------- vector/src/main/res/values/colors_riot.xml | 2 +- vector/src/main/res/values/style_snackbar.xml | 14 +++ vector/src/main/res/values/theme_dark.xml | 8 ++ vector/src/main/res/values/theme_light.xml | 8 ++ 5 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 vector/src/main/res/values/style_snackbar.xml diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index c5b84c0b24..b6450dcaeb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -292,9 +292,9 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode -> when (mode) { is SendMode.REGULAR -> renderRegularMode(mode.text) - is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) - is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) + is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) + is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) } } @@ -321,9 +321,9 @@ class RoomDetailFragment @Inject constructor( super.onActivityCreated(savedInstanceState) if (savedInstanceState == null) { when (val sharedData = roomDetailArgs.sharedData) { - is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false)) + is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false)) is SharedData.Attachments -> roomDetailViewModel.handle(RoomDetailAction.SendMedia(sharedData.attachmentData)) - null -> Timber.v("No share data to process") + null -> Timber.v("No share data to process") } } } @@ -548,7 +548,7 @@ class RoomDetailFragment @Inject constructor( is MessageTextItem -> { return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED } - else -> false + else -> false } } } @@ -563,9 +563,9 @@ class RoomDetailFragment @Inject constructor( withState(roomDetailViewModel) { val showJumpToUnreadBanner = when (it.unreadState) { UnreadState.Unknown, - UnreadState.HasNoUnread -> false + UnreadState.HasNoUnread -> false is UnreadState.ReadMarkerNotLoaded -> true - is UnreadState.HasUnread -> { + is UnreadState.HasUnread -> { if (it.canShowJumpToReadMarker) { val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() @@ -724,7 +724,7 @@ class RoomDetailFragment @Inject constructor( navigator.openRoom(vectorBaseActivity, async()) vectorBaseActivity.finish() } - is Fail -> { + is Fail -> { vectorBaseActivity.hideWaitingView() vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error)) } @@ -733,23 +733,23 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { when (sendMessageResult) { - is SendMessageResult.MessageSent -> { + is SendMessageResult.MessageSent -> { updateComposerText("") } - is SendMessageResult.SlashCommandHandled -> { + is SendMessageResult.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } updateComposerText("") } - is SendMessageResult.SlashCommandError -> { + is SendMessageResult.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is SendMessageResult.SlashCommandUnknown -> { + is SendMessageResult.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is SendMessageResult.SlashCommandResultOk -> { + is SendMessageResult.SlashCommandResultOk -> { updateComposerText("") } - is SendMessageResult.SlashCommandResultError -> { + is SendMessageResult.SlashCommandResultError -> { displayCommandError(sendMessageResult.throwable.localizedMessage) } is SendMessageResult.SlashCommandNotImplemented -> { @@ -787,7 +787,7 @@ class RoomDetailFragment @Inject constructor( private fun displayRoomDetailActionResult(result: Async) { when (result) { - is Fail -> { + is Fail -> { AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(result.error)) @@ -798,7 +798,7 @@ class RoomDetailFragment @Inject constructor( when (val data = result.invoke()) { is RoomDetailAction.ReportContent -> { when { - data.spam -> { + data.spam -> { AlertDialog.Builder(requireActivity()) .setTitle(R.string.content_reported_as_spam_title) .setMessage(R.string.content_reported_as_spam_content) @@ -820,7 +820,7 @@ class RoomDetailFragment @Inject constructor( .show() .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } - else -> { + else -> { AlertDialog.Builder(requireActivity()) .setTitle(R.string.content_reported_title) .setMessage(R.string.content_reported_content) @@ -929,7 +929,7 @@ class RoomDetailFragment @Inject constructor( val action = RoomDetailAction.DownloadFile(eventId, messageFileContent) // We need WRITE_EXTERNAL permission if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_DOWNLOAD_FILE)) { - showSnackWithMessage(getString(R.string.downloading_file, messageFileContent.getFileName()), Snackbar.LENGTH_LONG) + showSnackWithMessage(getString(R.string.downloading_file, messageFileContent.getFileName())) roomDetailViewModel.handle(action) } else { roomDetailViewModel.pendingAction = action @@ -939,20 +939,18 @@ class RoomDetailFragment @Inject constructor( override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (allGranted(grantResults)) { when (requestCode) { - PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> { + PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> { val action = roomDetailViewModel.pendingAction if (action != null) { (action as? RoomDetailAction.DownloadFile) ?.messageFileContent ?.getFileName() - ?.let { - showSnackWithMessage(getString(R.string.downloading_file, it), Snackbar.LENGTH_LONG) - } + ?.let { showSnackWithMessage(getString(R.string.downloading_file, it)) } roomDetailViewModel.pendingAction = null roomDetailViewModel.handle(action) } } - PERMISSION_REQUEST_CODE_INCOMING_URI -> { + PERMISSION_REQUEST_CODE_INCOMING_URI -> { val pendingUri = roomDetailViewModel.pendingUri if (pendingUri != null) { roomDetailViewModel.pendingUri = null @@ -1050,22 +1048,22 @@ class RoomDetailFragment @Inject constructor( private fun handleActions(action: EventSharedAction) { when (action) { - is EventSharedAction.AddReaction -> { + is EventSharedAction.AddReaction -> { startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE) } - is EventSharedAction.ViewReactions -> { + is EventSharedAction.ViewReactions -> { ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } - is EventSharedAction.Copy -> { + is EventSharedAction.Copy -> { // I need info about the current selected message :/ copyToClipboard(requireContext(), action.content, false) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } - is EventSharedAction.Delete -> { + is EventSharedAction.Delete -> { roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason))) } - is EventSharedAction.Share -> { + is EventSharedAction.Share -> { // TODO current data communication is too limited // Need to now the media type // TODO bad, just POC @@ -1093,10 +1091,10 @@ class RoomDetailFragment @Inject constructor( } ) } - is EventSharedAction.ViewEditHistory -> { + is EventSharedAction.ViewEditHistory -> { onEditedDecorationClicked(action.messageInformationData) } - is EventSharedAction.ViewSource -> { + is EventSharedAction.ViewSource -> { val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null) view.findViewById(R.id.event_content_text_view)?.let { it.text = action.content @@ -1107,7 +1105,7 @@ class RoomDetailFragment @Inject constructor( .setPositiveButton(R.string.ok, null) .show() } - is EventSharedAction.ViewDecryptedSource -> { + is EventSharedAction.ViewDecryptedSource -> { val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null) view.findViewById(R.id.event_content_text_view)?.let { it.text = action.content @@ -1118,31 +1116,31 @@ class RoomDetailFragment @Inject constructor( .setPositiveButton(R.string.ok, null) .show() } - is EventSharedAction.QuickReact -> { + is EventSharedAction.QuickReact -> { // eventId,ClickedOn,Add roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } - is EventSharedAction.Edit -> { + is EventSharedAction.Edit -> { roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.Quote -> { + is EventSharedAction.Quote -> { roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.Reply -> { + is EventSharedAction.Reply -> { roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString())) } - is EventSharedAction.CopyPermalink -> { + is EventSharedAction.CopyPermalink -> { val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } - is EventSharedAction.Resend -> { + is EventSharedAction.Resend -> { roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) } - is EventSharedAction.Remove -> { + is EventSharedAction.Remove -> { roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId)) } - is EventSharedAction.ReportContentSpam -> { + is EventSharedAction.ReportContentSpam -> { roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is spam", spam = true)) } @@ -1150,19 +1148,19 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) } - is EventSharedAction.ReportContentCustom -> { + is EventSharedAction.ReportContentCustom -> { promptReasonToReportContent(action) } - is EventSharedAction.IgnoreUser -> { + is EventSharedAction.IgnoreUser -> { roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(action.senderId)) } - is EventSharedAction.OnUrlClicked -> { + is EventSharedAction.OnUrlClicked -> { onUrlClicked(action.url) } - is EventSharedAction.OnUrlLongClicked -> { + is EventSharedAction.OnUrlLongClicked -> { onUrlLongClicked(action.url) } - else -> { + else -> { Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show() } } @@ -1224,9 +1222,7 @@ class RoomDetailFragment @Inject constructor( } private fun showSnackWithMessage(message: String, duration: Int = Snackbar.LENGTH_SHORT) { - val snack = Snackbar.make(view!!, message, duration) - snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) - snack.show() + Snackbar.make(view!!, message, duration).show() } // VectorInviteView.Callback @@ -1269,10 +1265,10 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile() + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile() AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery() - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio() + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio() AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact() AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") } diff --git a/vector/src/main/res/values/colors_riot.xml b/vector/src/main/res/values/colors_riot.xml index cc08a3a85d..4e04bd5552 100644 --- a/vector/src/main/res/values/colors_riot.xml +++ b/vector/src/main/res/values/colors_riot.xml @@ -129,7 +129,7 @@ #368BD6 #368BD6 - + #368BD6 #ff812d diff --git a/vector/src/main/res/values/style_snackbar.xml b/vector/src/main/res/values/style_snackbar.xml new file mode 100644 index 0000000000..7e0dbc0e0b --- /dev/null +++ b/vector/src/main/res/values/style_snackbar.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index a05081eec7..528a4ed2f1 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -206,6 +206,14 @@ @style/PreferenceThemeOverlay.v14.Material @style/Vector.BottomSheet.Dark + + + + @style/VectorSnackBarStyle + + @style/VectorSnackBarButton + + @style/VectorSnackBarText