Skip to content

Commit 257f41d

Browse files
authored
Merge pull request #6471 from vector-im/feature/adm/sharing-tests
Adding tests around the share intent handling
2 parents 909ce29 + 169ac9d commit 257f41d

File tree

10 files changed

+373
-38
lines changed

10 files changed

+373
-38
lines changed

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ object MimeTypes {
3333

3434
const val Ogg = "audio/ogg"
3535

36+
const val PlainText = "text/plain"
37+
3638
fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this
3739

3840
fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse()
3941
fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse()
4042
fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse()
43+
fun String?.isMimeTypeApplication() = this?.startsWith("application/").orFalse()
44+
fun String?.isMimeTypeFile() = this?.startsWith("file/").orFalse()
45+
fun String?.isMimeTypeText() = this?.startsWith("text/").orFalse()
46+
fun String?.isMimeTypeAny() = this?.startsWith("*/").orFalse()
4147
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.attachments
18+
19+
import android.content.Context
20+
import android.content.Intent
21+
import im.vector.lib.multipicker.MultiPicker
22+
import javax.inject.Inject
23+
24+
class MultiPickerIncomingFiles @Inject constructor(
25+
private val context: Context,
26+
) {
27+
28+
fun image(intent: Intent) = MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map { it.toContentAttachmentData() }
29+
30+
fun video(intent: Intent) = MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map { it.toContentAttachmentData() }
31+
32+
fun media(intent: Intent) = MultiPicker.get(MultiPicker.MEDIA).getIncomingFiles(context, intent).map { it.toContentAttachmentData() }
33+
34+
fun file(intent: Intent) = MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map { it.toContentAttachmentData() }
35+
36+
fun audio(intent: Intent) = MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map { it.toContentAttachmentData() }
37+
}

vector/src/main/java/im/vector/app/features/attachments/ShareIntentHandler.kt

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,53 +18,36 @@ package im.vector.app.features.attachments
1818

1919
import android.content.Context
2020
import android.content.Intent
21-
import im.vector.lib.multipicker.MultiPicker
2221
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
22+
import org.matrix.android.sdk.api.util.MimeTypes
23+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAny
24+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeApplication
25+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAudio
26+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeFile
27+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage
28+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeText
29+
import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeVideo
2330
import javax.inject.Inject
2431

25-
class ShareIntentHandler @Inject constructor() {
32+
class ShareIntentHandler @Inject constructor(
33+
private val multiPickerIncomingFiles: MultiPickerIncomingFiles,
34+
private val context: Context,
35+
) {
2636

2737
/**
2838
* This methods aims to handle incoming share intents.
2939
*
3040
* @return true if it can handle the intent data, false otherwise
3141
*/
32-
fun handleIncomingShareIntent(context: Context, intent: Intent, onFile: (List<ContentAttachmentData>) -> Unit, onPlainText: (String) -> Unit): Boolean {
42+
fun handleIncomingShareIntent(intent: Intent, onFile: (List<ContentAttachmentData>) -> Unit, onPlainText: (String) -> Unit): Boolean {
3343
val type = intent.resolveType(context) ?: return false
3444
return when {
35-
type == "text/plain" -> handlePlainText(intent, onPlainText)
36-
type.startsWith("image") -> {
37-
onFile(
38-
MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map {
39-
it.toContentAttachmentData()
40-
}
41-
)
42-
true
43-
}
44-
type.startsWith("video") -> {
45-
onFile(
46-
MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map {
47-
it.toContentAttachmentData()
48-
}
49-
)
50-
true
51-
}
52-
type.startsWith("audio") -> {
53-
onFile(
54-
MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map {
55-
it.toContentAttachmentData()
56-
}
57-
)
58-
true
59-
}
60-
61-
type.startsWith("application") || type.startsWith("file") || type.startsWith("text") || type.startsWith("*") -> {
62-
onFile(
63-
MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map {
64-
it.toContentAttachmentData()
65-
}
66-
)
67-
true
45+
type == MimeTypes.PlainText -> handlePlainText(intent, onPlainText)
46+
type.isMimeTypeImage() -> onFile(multiPickerIncomingFiles.image(intent)).let { true }
47+
type.isMimeTypeVideo() -> onFile(multiPickerIncomingFiles.video(intent)).let { true }
48+
type.isMimeTypeAudio() -> onFile(multiPickerIncomingFiles.audio(intent)).let { true }
49+
type.isMimeTypeApplication() || type.isMimeTypeFile() || type.isMimeTypeText() || type.isMimeTypeAny() -> {
50+
onFile(multiPickerIncomingFiles.file(intent)).let { true }
6851
}
6952
else -> false
7053
}

vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1622,7 +1622,7 @@ class TimelineFragment @Inject constructor(
16221622

16231623
private fun sendUri(uri: Uri): Boolean {
16241624
val shareIntent = Intent(Intent.ACTION_SEND, uri)
1625-
val isHandled = shareIntentHandler.handleIncomingShareIntent(requireContext(), shareIntent, ::onContentAttachmentsReady, onPlainText = {
1625+
val isHandled = shareIntentHandler.handleIncomingShareIntent(shareIntent, ::onContentAttachmentsReady, onPlainText = {
16261626
fatalError("Should not happen as we're generating a File based share Intent", vectorPreferences.failFast())
16271627
})
16281628
if (!isHandled) {

vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ class IncomingShareFragment @Inject constructor(
116116
}
117117

118118
private fun handleIncomingShareIntent(intent: Intent) = shareIntentHandler.handleIncomingShareIntent(
119-
requireContext(),
120119
intent,
121120
onFile = {
122121
val sharedData = SharedData.Attachments(it)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.attachments
18+
19+
import android.content.Intent
20+
import im.vector.app.test.fakes.FakeContext
21+
import im.vector.app.test.fakes.FakeFunction1
22+
import im.vector.app.test.fakes.FakeIntent
23+
import im.vector.app.test.fakes.FakeMultiPickerIncomingFiles
24+
import im.vector.app.test.fixtures.ContentAttachmentDataFixture.aContentAttachmentData
25+
import org.amshove.kluent.shouldBeEqualTo
26+
import org.junit.Test
27+
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
28+
29+
private val A_CONTEXT = FakeContext().instance
30+
private const val A_PLAIN_TEXT_EXTRA = "plain text for sharing"
31+
private val A_CONTENT_ATTACHMENT_LIST = listOf(aContentAttachmentData())
32+
33+
class ShareIntentHandlerTest {
34+
35+
private val fakeMultiPickerIncomingFiles = FakeMultiPickerIncomingFiles()
36+
private val onFile = FakeFunction1<List<ContentAttachmentData>>()
37+
private val onPlainText = FakeFunction1<String>()
38+
39+
private val shareIntentHandler = ShareIntentHandler(fakeMultiPickerIncomingFiles.instance, A_CONTEXT)
40+
41+
@Test
42+
fun `given an unhandled sharing intent type, when handling intent, then is not handled`() {
43+
val unknownShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "unknown/type") }
44+
45+
val handled = handleIncomingShareIntent(unknownShareIntent)
46+
47+
onFile.verifyNoInteractions()
48+
onPlainText.verifyNoInteractions()
49+
handled shouldBeEqualTo false
50+
}
51+
52+
@Test
53+
fun `given a plain text sharing intent, when handling intent, then is handled and parses plain text content`() {
54+
val plainTextShareIntent = FakeIntent().also {
55+
it.givenResolvesType(A_CONTEXT, "text/plain")
56+
it.givenCharSequenceExtra(key = Intent.EXTRA_TEXT, value = A_PLAIN_TEXT_EXTRA)
57+
}
58+
59+
val handled = handleIncomingShareIntent(plainTextShareIntent)
60+
61+
onFile.verifyNoInteractions()
62+
onPlainText.assertValue(A_PLAIN_TEXT_EXTRA)
63+
handled shouldBeEqualTo true
64+
}
65+
66+
@Test
67+
fun `given an empty plain text sharing intent, when handling intent, then is not handled`() {
68+
val plainTextShareIntent = FakeIntent().also {
69+
it.givenResolvesType(A_CONTEXT, "text/plain")
70+
it.givenCharSequenceExtra(key = Intent.EXTRA_TEXT, value = "")
71+
}
72+
73+
val handled = handleIncomingShareIntent(plainTextShareIntent)
74+
75+
onFile.verifyNoInteractions()
76+
onPlainText.verifyNoInteractions()
77+
handled shouldBeEqualTo false
78+
}
79+
80+
@Test
81+
fun `given an image sharing intent, when handling intent, then is handled and parses image files`() {
82+
val imageShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "image/png") }
83+
fakeMultiPickerIncomingFiles.givenImageReturns(imageShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
84+
85+
val handled = handleIncomingShareIntent(imageShareIntent)
86+
87+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
88+
onPlainText.verifyNoInteractions()
89+
handled shouldBeEqualTo true
90+
}
91+
92+
@Test
93+
fun `given an audio sharing intent, when handling intent, then is handled and parses audio files`() {
94+
val audioShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "audio/mp3") }
95+
fakeMultiPickerIncomingFiles.givenAudioReturns(audioShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
96+
97+
val handled = handleIncomingShareIntent(audioShareIntent)
98+
99+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
100+
onPlainText.verifyNoInteractions()
101+
handled shouldBeEqualTo true
102+
}
103+
104+
@Test
105+
fun `given an video sharing intent, when handling intent, then is handled and parses video files`() {
106+
val videoShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "video/mp4") }
107+
fakeMultiPickerIncomingFiles.givenVideoReturns(videoShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
108+
109+
val handled = handleIncomingShareIntent(videoShareIntent)
110+
111+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
112+
onPlainText.verifyNoInteractions()
113+
handled shouldBeEqualTo true
114+
}
115+
116+
@Test
117+
fun `given a file sharing intent, when handling intent, then is handled and parses files`() {
118+
val fileShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "file/*") }
119+
fakeMultiPickerIncomingFiles.givenFileReturns(fileShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
120+
121+
val handled = handleIncomingShareIntent(fileShareIntent)
122+
123+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
124+
onPlainText.verifyNoInteractions()
125+
handled shouldBeEqualTo true
126+
}
127+
128+
@Test
129+
fun `given a application sharing intent, when handling intent, then is handled and parses files`() {
130+
val fileShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "application/apk") }
131+
fakeMultiPickerIncomingFiles.givenFileReturns(fileShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
132+
133+
handleIncomingShareIntent(fileShareIntent)
134+
135+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
136+
onPlainText.verifyNoInteractions()
137+
}
138+
139+
@Test
140+
fun `given a text sharing intent, when handling intent, then is handled and parses text files`() {
141+
val fileShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "text/ics") }
142+
fakeMultiPickerIncomingFiles.givenFileReturns(fileShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
143+
144+
val handled = handleIncomingShareIntent(fileShareIntent)
145+
146+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
147+
onPlainText.verifyNoInteractions()
148+
handled shouldBeEqualTo true
149+
}
150+
151+
@Test
152+
fun `given a wildcard sharing intent, when handling intent, then is handled and parses files`() {
153+
val fileShareIntent = FakeIntent().also { it.givenResolvesType(A_CONTEXT, "*/*") }
154+
fakeMultiPickerIncomingFiles.givenFileReturns(fileShareIntent.instance, A_CONTENT_ATTACHMENT_LIST)
155+
156+
val handled = handleIncomingShareIntent(fileShareIntent)
157+
158+
onFile.assertValue(A_CONTENT_ATTACHMENT_LIST)
159+
onPlainText.verifyNoInteractions()
160+
handled shouldBeEqualTo true
161+
}
162+
163+
private fun handleIncomingShareIntent(intent: FakeIntent): Boolean {
164+
return shareIntentHandler.handleIncomingShareIntent(intent.instance, onFile.capture, onPlainText.capture)
165+
}
166+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.test.fakes
18+
19+
import org.amshove.kluent.shouldBeEqualTo
20+
21+
class FakeFunction1<T : Any> {
22+
23+
private lateinit var capturedValue: T
24+
25+
val capture: (T) -> Unit = {
26+
capturedValue = it
27+
}
28+
29+
fun verifyNoInteractions() {
30+
this::capturedValue.isInitialized shouldBeEqualTo false
31+
}
32+
33+
fun assertValue(value: T) {
34+
capturedValue shouldBeEqualTo value
35+
}
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.test.fakes
18+
19+
import android.content.Context
20+
import android.content.Intent
21+
import io.mockk.every
22+
import io.mockk.mockk
23+
24+
class FakeIntent {
25+
26+
val instance = mockk<Intent>()
27+
28+
fun givenResolvesType(context: Context, type: String?) {
29+
every { instance.resolveType(context) } returns type
30+
}
31+
32+
fun givenCharSequenceExtra(key: String, value: CharSequence) {
33+
every { instance.getCharSequenceExtra(key) } returns value
34+
}
35+
}

0 commit comments

Comments
 (0)