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. 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/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/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..3b89b3f559 --- /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 + */ +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 503bf8e4ff..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,23 +29,15 @@ 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" 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") + } catch (e: Exception) { + Timber.e(e, "## decryptAttachment() failed") } - - // 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..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, - @UserCacheDirectory 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)) 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 } 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..f4ec90f8a0 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())) roomDetailViewModel.handle(action) } else { roomDetailViewModel.pendingAction = action @@ -940,6 +942,10 @@ 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)) } roomDetailViewModel.pendingAction = null roomDetailViewModel.handle(action) } @@ -1052,8 +1058,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 +1132,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 +1176,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) @@ -1217,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 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