From 8e9c0bedf22da3ae95cb301b5366ec6da23295cb Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 16 Jun 2026 16:42:59 +0200 Subject: [PATCH] Fix sharing media inside the app (#7016) When a `MediaSource` is disposed, the local temp file associated to it will be deleted from disk. When we try sharing a media from the media viewer screen to another room, this will dismiss the screen, which in turn will dispose of the media source and remove the file, which then fails to be shared. If we copy it first and then make sure we delete it after trying to send it, it works fine. --- .../features/share/impl/DefaultOnSharedData.kt | 11 ++++++++++- .../impl/local/AndroidLocalMediaActions.kt | 15 +++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultOnSharedData.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultOnSharedData.kt index 3a71f02dc3..b3ae822790 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultOnSharedData.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultOnSharedData.kt @@ -7,6 +7,7 @@ package io.element.android.features.share.impl +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.net.Uri @@ -15,13 +16,14 @@ import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.features.share.api.OnSharedData import io.element.android.features.share.api.ShareIntentData +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber -import kotlin.collections.forEach @ContributesBinding(AppScope::class) class DefaultOnSharedData( @ApplicationContext private val context: Context, + private val buildMeta: BuildMeta, ) : OnSharedData { override fun invoke(data: ShareIntentData) { when (data) { @@ -29,6 +31,13 @@ class DefaultOnSharedData( // No-op, there is nothing to do for plain text intents. } is ShareIntentData.Uris -> { + val fileProvider = "${buildMeta.applicationId}.fileprovider" + for (sharedUri in data.uris) { + // Remove the local copy of the shared file, as it is not needed anymore + if (sharedUri.uri.scheme == ContentResolver.SCHEME_CONTENT && sharedUri.uri.host == fileProvider) { + context.contentResolver.delete(sharedUri.uri, null) + } + } revokeUriPermissions(data.uris.map { it.uri }) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index c7a01ea494..071e0dba64 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -100,7 +100,11 @@ class AndroidLocalMediaActions( override suspend fun share(localMedia: LocalMedia): Result = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) runCatchingExceptions { - val shareableUri = localMedia.toShareableUri() + // Make a copy of the shared file in the cache directory, otherwise the original file will be gone once this screen is dismissed + // and will prevent sharing the media to another room inside the app. + val copiedFile = localMedia.uri.toFile() + .copyTo(File(context.cacheDir, "temp/media/" + (localMedia.uri.lastPathSegment ?: "shared_file")), true) + val shareableUri = copiedFile.toShareableUri() val shareMediaIntent = Intent(Intent.ACTION_SEND) .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .putExtra(Intent.EXTRA_STREAM, shareableUri) @@ -157,10 +161,13 @@ class AndroidLocalMediaActions( } } - private fun LocalMedia.toShareableUri(): Uri { - val mediaAsFile = this.toFile() + private fun File.toShareableUri(): Uri { val authority = "${buildMeta.applicationId}.fileprovider" - return FileProvider.getUriForFile(context, authority, mediaAsFile).normalizeScheme() + return FileProvider.getUriForFile(context, authority, this).normalizeScheme() + } + + private fun LocalMedia.toShareableUri(): Uri { + return this.toFile().toShareableUri() } @RequiresApi(Build.VERSION_CODES.Q)