From 2c0da1cadf13bbb4a08d0151c2342e0e7aa52b2b Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:44:11 +0300 Subject: [PATCH] Share a file whose mime type is not one of the supported ones handleIncomingShareIntent only accepted an intent whose resolved mime type started with image/, video/, audio/, application/, file/, text/ or */. An ACTION_SEND carrying EXTRA_STREAM with any other top level type, such as the message/rfc822 that some apps use when attaching a file, fell through to null and the share was dropped before the room picker ever appeared, even though the manifest filter accepts */*. The uris are extracted before that check, so their presence is enough to know the intent carries something to send. Accept the intent when it has uris and keep the mime type list for the case where it does not. --- .../share/impl/DefaultShareIntentHandler.kt | 3 +- .../impl/DefaultShareIntentHandlerTest.kt | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt index cfa06c3a97..2ae31fb00c 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandler.kt @@ -42,7 +42,8 @@ class DefaultShareIntentHandler( val uris = getIncomingUris(intent, type) return when { uris.isEmpty() && type == MimeTypes.PlainText -> handlePlainText(intent) - type.isMimeTypeImage() || + uris.isNotEmpty() || + type.isMimeTypeImage() || type.isMimeTypeVideo() || type.isMimeTypeAudio() || type.isMimeTypeApplication() || diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt new file mode 100644 index 0000000000..8cde31bd0c --- /dev/null +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareIntentHandlerTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.share.impl + +import android.content.Intent +import android.net.Uri +import androidx.core.net.toUri +import com.google.common.truth.Truth.assertThat +import io.element.android.features.share.api.ShareIntentData +import io.element.android.features.share.api.UriToShare +import io.element.android.tests.testutils.robolectric.RobolectricTest +import org.junit.Test +import org.robolectric.RuntimeEnvironment + +class DefaultShareIntentHandlerTest : RobolectricTest() { + @Test + fun `an image is shared as an uri`() { + val uri = "content://sender/image.jpg".toUri() + val result = createDefaultShareIntentHandler().handleIncomingShareIntent( + aSendIntent(type = "image/jpeg", uri = uri) + ) + assertThat(result).isEqualTo( + ShareIntentData.Uris( + text = null, + uris = listOf(UriToShare(uri = uri, mimeType = "image/jpeg")), + ) + ) + } + + @Test + fun `an uri with an unlisted mime type is shared`() { + val uri = "content://sender/logs.zip".toUri() + val result = createDefaultShareIntentHandler().handleIncomingShareIntent( + aSendIntent(type = "message/rfc822", uri = uri) + ) + assertThat(result).isEqualTo( + ShareIntentData.Uris( + text = null, + uris = listOf(UriToShare(uri = uri, mimeType = "message/rfc822")), + ) + ) + } + + @Test + fun `an uri with an unlisted mime type keeps the text as a caption`() { + val uri = "content://sender/logs.zip".toUri() + val result = createDefaultShareIntentHandler().handleIncomingShareIntent( + aSendIntent(type = "message/rfc822", uri = uri).apply { + putExtra(Intent.EXTRA_TEXT, "a caption") + } + ) + assertThat(result).isEqualTo( + ShareIntentData.Uris( + text = "a caption", + uris = listOf(UriToShare(uri = uri, mimeType = "message/rfc822")), + ) + ) + } + + @Test + fun `several uris with an unlisted mime type are shared`() { + val firstUri = "content://sender/first.zip".toUri() + val secondUri = "content://sender/second.zip".toUri() + val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { + type = "message/rfc822" + putParcelableArrayListExtra(Intent.EXTRA_STREAM, arrayListOf(firstUri, secondUri)) + } + val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent) + assertThat(result).isEqualTo( + ShareIntentData.Uris( + text = null, + uris = listOf( + UriToShare(uri = firstUri, mimeType = "message/rfc822"), + UriToShare(uri = secondUri, mimeType = "message/rfc822"), + ), + ) + ) + } + + @Test + fun `a plain text is shared as a text`() { + val intent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, "a text") + } + val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent) + assertThat(result).isEqualTo(ShareIntentData.PlainText("a text")) + } + + @Test + fun `an intent with neither uri nor text is not handled`() { + val intent = Intent(Intent.ACTION_SEND).apply { + type = "message/rfc822" + } + val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent) + assertThat(result).isNull() + } + + private fun aSendIntent(type: String, uri: Uri) = Intent(Intent.ACTION_SEND).apply { + this.type = type + putExtra(Intent.EXTRA_STREAM, uri) + } + + private fun createDefaultShareIntentHandler() = DefaultShareIntentHandler( + context = RuntimeEnvironment.getApplication(), + ) +}