mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge develop into the PreviewParam rename
The 174 commits develop gained since this branch was cut touch preview data in three ways, and each is resolved so the flipped konsist rule still holds across the merged tree. Two screens the branch had renamed were deleted upstream by "Remove the account provider search screen and homeserver resolver": the changeaccountprovider and searchaccountprovider packages. Their deletion wins, so ChangeAccountProviderStatePreviewParam, SearchAccountProviderStatePreviewParam and the two views that used them go with them rather than coming back under the new name. Two preview classes develop added still carried the old suffix and are renamed here, along with the `@PreviewParameter` reference in each view: GlobalSearchStateProvider -> GlobalSearchStatePreviewParam and SendFailureDialogStateProvider -> SendFailureDialogStatePreviewParam. Everything else merged textually. No production class extending PreviewParameterProvider is left with a `Provider` name, and no reference to any of the 194 renamed names survives outside the CHANGES.md entry that records a past pull request title.
This commit is contained in:
+2
-1
@@ -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() ||
|
||||
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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.ComponentName
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.google.common.truth.Truth.assertWithMessage
|
||||
import io.element.android.features.share.api.ShareIntentData
|
||||
import io.element.android.features.share.api.UriToShare
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an intent without mime type is not handled`() {
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(Intent(Intent.ACTION_SEND))
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an intent with a listed mime type and no uri is handled`() {
|
||||
listOf(
|
||||
MimeTypes.Jpeg,
|
||||
MimeTypes.Mp4,
|
||||
MimeTypes.Mp3,
|
||||
MimeTypes.Pdf,
|
||||
"file/binary",
|
||||
"text/html",
|
||||
MimeTypes.Any,
|
||||
).forEach { type ->
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(
|
||||
Intent(Intent.ACTION_SEND).apply { this.type = type }
|
||||
)
|
||||
assertWithMessage("$type was not properly handled").that(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = emptyList(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a send multiple intent without uri is handled`() {
|
||||
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
|
||||
type = MimeTypes.Jpeg
|
||||
}
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent)
|
||||
assertThat(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = emptyList(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an uri is ignored when the intent is neither a send nor a send multiple`() {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
type = MimeTypes.Jpeg
|
||||
putExtra(Intent.EXTRA_STREAM, "content://sender/image.jpg".toUri())
|
||||
}
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent)
|
||||
assertThat(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = emptyList(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty text is not kept 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, "")
|
||||
}
|
||||
)
|
||||
assertThat(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = listOf(UriToShare(uri = uri, mimeType = "message/rfc822")),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a plain text intent without text is not handled`() {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = MimeTypes.PlainText
|
||||
}
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a plain text intent with an empty text is not handled`() {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = MimeTypes.PlainText
|
||||
putExtra(Intent.EXTRA_TEXT, "")
|
||||
}
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(intent)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the mime type of the uri overrides the one of the intent`() {
|
||||
Robolectric.setupContentProvider(ZipContentProvider::class.java, "sender")
|
||||
val uri = "content://sender/logs.zip".toUri()
|
||||
val result = createDefaultShareIntentHandler().handleIncomingShareIntent(
|
||||
aSendIntent(type = MimeTypes.Any, uri = uri)
|
||||
)
|
||||
assertThat(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = listOf(UriToShare(uri = uri, mimeType = "application/zip")),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the intent is made explicit for the application which will read the uri`() {
|
||||
val intent = aSendIntent(type = "message/rfc822", uri = "content://sender/logs.zip".toUri())
|
||||
givenAnApplicationHandling(intent)
|
||||
createDefaultShareIntentHandler().handleIncomingShareIntent(intent)
|
||||
assertThat(intent.action).isNull()
|
||||
assertThat(intent.component).isEqualTo(ComponentName(A_PACKAGE_NAME, AN_ACTIVITY_NAME))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the intent stays implicit when the uri permission cannot be granted`() {
|
||||
val uri = "content://sender/logs.zip".toUri()
|
||||
val intent = aSendIntent(type = "message/rfc822", uri = uri)
|
||||
givenAnApplicationHandling(intent)
|
||||
val result = DefaultShareIntentHandler(
|
||||
context = FailingGrantUriPermissionContext(RuntimeEnvironment.getApplication()),
|
||||
).handleIncomingShareIntent(intent)
|
||||
assertThat(intent.action).isEqualTo(Intent.ACTION_SEND)
|
||||
assertThat(intent.component).isNull()
|
||||
assertThat(result).isEqualTo(
|
||||
ShareIntentData.Uris(
|
||||
text = null,
|
||||
uris = listOf(UriToShare(uri = uri, mimeType = "message/rfc822")),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun aSendIntent(type: String, uri: Uri) = Intent(Intent.ACTION_SEND).apply {
|
||||
this.type = type
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
}
|
||||
|
||||
private fun givenAnApplicationHandling(intent: Intent) {
|
||||
val componentName = ComponentName(A_PACKAGE_NAME, AN_ACTIVITY_NAME)
|
||||
shadowOf(RuntimeEnvironment.getApplication().packageManager).apply {
|
||||
addActivityIfNotPresent(componentName)
|
||||
addIntentFilterForActivity(
|
||||
componentName,
|
||||
IntentFilter(intent.action).apply {
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addDataType(intent.type)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDefaultShareIntentHandler() = DefaultShareIntentHandler(
|
||||
context = RuntimeEnvironment.getApplication(),
|
||||
)
|
||||
|
||||
private class FailingGrantUriPermissionContext(context: Context) : ContextWrapper(context) {
|
||||
override fun grantUriPermission(toPackage: String, uri: Uri, modeFlags: Int) {
|
||||
error("Unable to grant Uri permission")
|
||||
}
|
||||
}
|
||||
|
||||
private class ZipContentProvider : ContentProvider() {
|
||||
override fun onCreate() = true
|
||||
|
||||
override fun getType(uri: Uri) = "application/zip"
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
projection: Array<out String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?,
|
||||
sortOrder: String?,
|
||||
): Cursor? = null
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val A_PACKAGE_NAME = "io.element.android.x"
|
||||
const val AN_ACTIVITY_NAME = "io.element.android.x.MainActivity"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user