diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt index 290c9c8d..edcf9556 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt @@ -126,10 +126,10 @@ class MainActivity : val componentClassName = this.javaClass.name + val externalFileUris = if (savedInstanceState == null) getExternalFileUris(intent) else emptyList() val locale = dataStore.getLocale() ?: getLocale("en") val webEidUri = intent.data?.takeIf { WebEidUriUtil.isWebEidUri(it) } val browserPackage = if (webEidUri != null) resolveBrowserPackage(intent) else null - val externalFileUris = getExternalFileUris(intent) localeUtil.updateLocale(applicationContext, locale) diff --git a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt index b7c75d17..5b923d64 100644 --- a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt +++ b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt @@ -507,18 +507,22 @@ object FileUtil { } fun getExternalFileUris(intent: Intent): List { - val externalFileUris = mutableListOf() - intent.data?.takeIf { it.scheme in ALLOWED_FILE_SCHEMES }?.let { externalFileUris.add(it) } - intent.clipData?.let { clipData -> - for (i in 0 until clipData.itemCount) { - clipData - .getItemAt( - i, - )?.uri - ?.takeIf { it.scheme in ALLOWED_FILE_SCHEMES } - ?.let { externalFileUris.add(it) } + val clipData = intent.clipData + val data = intent.data + val uris = + when { + // The same file can be in both clipData and data, so read clipData only to avoid duplicates. + clipData != null && clipData.itemCount > 0 -> + (0 until clipData.itemCount).mapNotNull { clipData.getItemAt(it)?.uri } + // If there is no clipData, take the file from data, or from a share (EXTRA_STREAM). + data != null -> listOf(data) + intent.action == Intent.ACTION_SEND -> + listOfNotNull(intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) + intent.action == Intent.ACTION_SEND_MULTIPLE -> + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java).orEmpty() + else -> emptyList() } - } - return externalFileUris + // Only file-backed schemes are openable. Drop web (https) and deep-link (e.g. web-eid-mobile) URIs. + return uris.filter { it.scheme in ALLOWED_FILE_SCHEMES } } } diff --git a/utils-lib/src/test/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtilTest.kt b/utils-lib/src/test/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtilTest.kt index a7f3ca89..57af028b 100644 --- a/utils-lib/src/test/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtilTest.kt +++ b/utils-lib/src/test/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtilTest.kt @@ -175,30 +175,60 @@ class FileUtilTest { } @Test - fun fileUtil_getExternalFileUris_clipDataFiltersOutNonAllowedSchemes() = + fun fileUtil_getExternalFileUris_returnsSingleUriWhenFileInBothDataAndClipData() = runBlocking { val mockIntent = mock(Intent::class.java) val mockClipData = mock(ClipData::class.java) - val mockClipDataItem1 = mock(ClipData.Item::class.java) - val mockClipDataItem2 = mock(ClipData.Item::class.java) - val mockUri1 = mock(Uri::class.java) - val mockUri2 = mock(Uri::class.java) + val mockClipDataItem = mock(ClipData.Item::class.java) + val mockUri = mock(Uri::class.java) - `when`(mockUri1.scheme).thenReturn("content") - `when`(mockUri2.scheme).thenReturn("https") - `when`(mockClipDataItem1.uri).thenReturn(mockUri1) - `when`(mockClipDataItem2.uri).thenReturn(mockUri2) + `when`(mockUri.scheme).thenReturn("content") + `when`(mockIntent.data).thenReturn(mockUri) + `when`(mockClipDataItem.uri).thenReturn(mockUri) + `when`(mockClipData.getItemAt(0)).thenReturn(mockClipDataItem) + `when`(mockClipData.itemCount).thenReturn(1) + `when`(mockIntent.clipData).thenReturn(mockClipData) - `when`(mockClipData.getItemAt(0)).thenReturn(mockClipDataItem1) - `when`(mockClipData.getItemAt(1)).thenReturn(mockClipDataItem2) - `when`(mockClipData.itemCount).thenReturn(2) + val externalFileUris = FileUtil.getExternalFileUris(mockIntent) - `when`(mockIntent.clipData).thenReturn(mockClipData) + assertEquals(1, externalFileUris.size) + assertEquals(mockUri, externalFileUris.first()) + } + + @Test + fun fileUtil_getExternalFileUris_fallsBackToExtraStreamForActionSend() = + runBlocking { + val mockIntent = mock(Intent::class.java) + val mockUri = mock(Uri::class.java) + + `when`(mockUri.scheme).thenReturn("content") + `when`(mockIntent.action).thenReturn(Intent.ACTION_SEND) + `when`(mockIntent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)).thenReturn(mockUri) val externalFileUris = FileUtil.getExternalFileUris(mockIntent) assertEquals(1, externalFileUris.size) + assertEquals(mockUri, externalFileUris.first()) + } + + @Test + fun fileUtil_getExternalFileUris_fallsBackToExtraStreamForActionSendMultiple() = + runBlocking { + val mockIntent = mock(Intent::class.java) + val mockUri1 = mock(Uri::class.java) + val mockUri2 = mock(Uri::class.java) + + `when`(mockUri1.scheme).thenReturn("content") + `when`(mockUri2.scheme).thenReturn("content") + `when`(mockIntent.action).thenReturn(Intent.ACTION_SEND_MULTIPLE) + `when`(mockIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)) + .thenReturn(arrayListOf(mockUri1, mockUri2)) + + val externalFileUris = FileUtil.getExternalFileUris(mockIntent) + + assertEquals(2, externalFileUris.size) assertEquals(mockUri1, externalFileUris.first()) + assertEquals(mockUri2, externalFileUris.last()) } @Test