1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 com.android.intentresolver.contentpreview 18 19 import android.content.ContentInterface 20 import android.content.Intent 21 import android.database.MatrixCursor 22 import android.media.MediaMetadata 23 import android.net.Uri 24 import android.platform.test.flag.junit.CheckFlagsRule 25 import android.platform.test.flag.junit.DeviceFlagsValueProvider 26 import android.provider.DocumentsContract 27 import com.google.common.truth.Truth.assertThat 28 import kotlin.coroutines.EmptyCoroutineContext 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.flow.toList 32 import kotlinx.coroutines.test.TestScope 33 import kotlinx.coroutines.test.UnconfinedTestDispatcher 34 import kotlinx.coroutines.test.runTest 35 import org.junit.Rule 36 import org.junit.Test 37 import org.mockito.kotlin.any 38 import org.mockito.kotlin.mock 39 import org.mockito.kotlin.never 40 import org.mockito.kotlin.times 41 import org.mockito.kotlin.verify 42 import org.mockito.kotlin.whenever 43 44 @OptIn(ExperimentalCoroutinesApi::class) 45 class PreviewDataProviderTest { 46 private val contentResolver = mock<ContentInterface>() 47 private val mimeTypeClassifier = DefaultMimeTypeClassifier 48 private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) 49 @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 50 createDataProvidernull51 private fun createDataProvider( 52 targetIntent: Intent, 53 scope: CoroutineScope = testScope, 54 additionalContentUri: Uri? = null, 55 resolver: ContentInterface = contentResolver, 56 typeClassifier: MimeTypeClassifier = mimeTypeClassifier, 57 isPayloadTogglingEnabled: Boolean = false 58 ) = 59 PreviewDataProvider( 60 scope, 61 targetIntent, 62 additionalContentUri, 63 resolver, 64 isPayloadTogglingEnabled, 65 typeClassifier, 66 ) 67 68 @Test 69 fun test_nonSendIntentAction_resolvesToTextPreviewUiSynchronously() { 70 val targetIntent = Intent(Intent.ACTION_VIEW) 71 val testSubject = createDataProvider(targetIntent) 72 73 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) 74 verify(contentResolver, never()).getType(any()) 75 } 76 77 @Test test_sendSingleTextFileWithoutPreview_resolvesToFilePreviewUinull78 fun test_sendSingleTextFileWithoutPreview_resolvesToFilePreviewUi() { 79 val uri = Uri.parse("content://org.pkg.app/notes.txt") 80 val targetIntent = 81 Intent(Intent.ACTION_SEND).apply { 82 putExtra(Intent.EXTRA_STREAM, uri) 83 type = "text/plain" 84 } 85 whenever(contentResolver.getType(uri)).thenReturn("text/plain") 86 val testSubject = createDataProvider(targetIntent) 87 88 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 89 assertThat(testSubject.uriCount).isEqualTo(1) 90 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 91 verify(contentResolver, times(1)).getType(any()) 92 } 93 94 @Test test_sendIntentWithoutUris_resolvesToTextPreviewUiSynchronouslynull95 fun test_sendIntentWithoutUris_resolvesToTextPreviewUiSynchronously() { 96 val targetIntent = Intent(Intent.ACTION_SEND).apply { type = "image/png" } 97 val testSubject = createDataProvider(targetIntent) 98 99 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) 100 verify(contentResolver, never()).getType(any()) 101 } 102 103 @Test test_sendSingleImage_resolvesToImagePreviewUinull104 fun test_sendSingleImage_resolvesToImagePreviewUi() { 105 val uri = Uri.parse("content://org.pkg.app/image.png") 106 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 107 whenever(contentResolver.getType(uri)).thenReturn("image/png") 108 val testSubject = createDataProvider(targetIntent) 109 110 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 111 assertThat(testSubject.uriCount).isEqualTo(1) 112 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 113 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) 114 verify(contentResolver, times(1)).getType(any()) 115 } 116 117 @Test test_sendSingleNonImage_resolvesToFilePreviewUinull118 fun test_sendSingleNonImage_resolvesToFilePreviewUi() { 119 val uri = Uri.parse("content://org.pkg.app/paper.pdf") 120 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 121 whenever(contentResolver.getType(uri)).thenReturn("application/pdf") 122 val testSubject = createDataProvider(targetIntent) 123 124 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 125 assertThat(testSubject.uriCount).isEqualTo(1) 126 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 127 assertThat(testSubject.firstFileInfo?.previewUri).isNull() 128 verify(contentResolver, times(1)).getType(any()) 129 } 130 131 @Test test_sendSingleImageWithFailingGetType_resolvesToFilePreviewUinull132 fun test_sendSingleImageWithFailingGetType_resolvesToFilePreviewUi() { 133 val uri = Uri.parse("content://org.pkg.app/image.png") 134 val targetIntent = 135 Intent(Intent.ACTION_SEND).apply { 136 type = "image/png" 137 putExtra(Intent.EXTRA_STREAM, uri) 138 } 139 whenever(contentResolver.getType(uri)).thenThrow(SecurityException("test failure")) 140 val testSubject = createDataProvider(targetIntent) 141 142 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 143 assertThat(testSubject.uriCount).isEqualTo(1) 144 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 145 assertThat(testSubject.firstFileInfo?.previewUri).isNull() 146 verify(contentResolver, times(1)).getType(any()) 147 } 148 149 @Test test_sendSingleImageWithFailingMetadata_resolvesToFilePreviewUinull150 fun test_sendSingleImageWithFailingMetadata_resolvesToFilePreviewUi() { 151 val uri = Uri.parse("content://org.pkg.app/image.png") 152 val targetIntent = 153 Intent(Intent.ACTION_SEND).apply { 154 type = "image/png" 155 putExtra(Intent.EXTRA_STREAM, uri) 156 } 157 whenever(contentResolver.getStreamTypes(uri, "*/*")) 158 .thenThrow(SecurityException("test failure")) 159 whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)) 160 .thenThrow(SecurityException("test failure")) 161 val testSubject = createDataProvider(targetIntent) 162 163 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 164 assertThat(testSubject.uriCount).isEqualTo(1) 165 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 166 assertThat(testSubject.firstFileInfo?.previewUri).isNull() 167 verify(contentResolver, times(1)).getType(any()) 168 } 169 170 @Test test_SingleNonImageUriWithImageTypeInGetStreamTypes_useImagePreviewUinull171 fun test_SingleNonImageUriWithImageTypeInGetStreamTypes_useImagePreviewUi() { 172 val uri = Uri.parse("content://org.pkg.app/paper.pdf") 173 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 174 whenever(contentResolver.getStreamTypes(uri, "*/*")) 175 .thenReturn(arrayOf("application/pdf", "image/png")) 176 val testSubject = createDataProvider(targetIntent) 177 178 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 179 assertThat(testSubject.uriCount).isEqualTo(1) 180 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 181 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) 182 verify(contentResolver, times(1)).getType(any()) 183 } 184 185 @Test test_SingleNonImageUriWithThumbnailFlag_useImagePreviewUinull186 fun test_SingleNonImageUriWithThumbnailFlag_useImagePreviewUi() { 187 testMetadataToImagePreview( 188 columns = arrayOf(DocumentsContract.Document.COLUMN_FLAGS), 189 values = 190 arrayOf( 191 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL or 192 DocumentsContract.Document.FLAG_SUPPORTS_METADATA 193 ) 194 ) 195 } 196 197 @Test test_SingleNonImageUriWithMetadataIconUri_useImagePreviewUinull198 fun test_SingleNonImageUriWithMetadataIconUri_useImagePreviewUi() { 199 testMetadataToImagePreview( 200 columns = arrayOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI), 201 values = arrayOf("content://org.pkg.app/test.pdf?thumbnail"), 202 ) 203 } 204 testMetadataToImagePreviewnull205 private fun testMetadataToImagePreview(columns: Array<String>, values: Array<Any>) { 206 val uri = Uri.parse("content://org.pkg.app/test.pdf") 207 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 208 whenever(contentResolver.getType(uri)).thenReturn("application/pdf") 209 val cursor = MatrixCursor(columns).apply { addRow(values) } 210 whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) 211 212 val testSubject = createDataProvider(targetIntent) 213 214 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 215 assertThat(testSubject.uriCount).isEqualTo(1) 216 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 217 assertThat(testSubject.firstFileInfo?.previewUri).isNotNull() 218 verify(contentResolver, times(1)).getType(any()) 219 assertThat(cursor.isClosed).isTrue() 220 } 221 222 @Test test_emptyQueryResult_cursorGetsClosednull223 fun test_emptyQueryResult_cursorGetsClosed() { 224 val uri = Uri.parse("content://org.pkg.app/test.pdf") 225 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 226 whenever(contentResolver.getType(uri)).thenReturn("application/pdf") 227 val cursor = MatrixCursor(emptyArray()) 228 whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) 229 230 val testSubject = createDataProvider(targetIntent) 231 232 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 233 verify(contentResolver, times(1)).query(uri, METADATA_COLUMNS, null, null) 234 assertThat(cursor.isClosed).isTrue() 235 } 236 237 @Test test_multipleImageUri_useImagePreviewUinull238 fun test_multipleImageUri_useImagePreviewUi() { 239 val uri1 = Uri.parse("content://org.pkg.app/test.png") 240 val uri2 = Uri.parse("content://org.pkg.app/test.jpg") 241 val targetIntent = 242 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 243 putExtra( 244 Intent.EXTRA_STREAM, 245 ArrayList<Uri>().apply { 246 add(uri1) 247 add(uri2) 248 } 249 ) 250 } 251 whenever(contentResolver.getType(uri1)).thenReturn("image/png") 252 whenever(contentResolver.getType(uri2)).thenReturn("image/jpeg") 253 val testSubject = createDataProvider(targetIntent) 254 255 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 256 assertThat(testSubject.uriCount).isEqualTo(2) 257 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) 258 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) 259 // preview type can be determined by the first URI type 260 verify(contentResolver, times(1)).getType(any()) 261 } 262 263 @Test test_SomeImageUri_useImagePreviewUinull264 fun test_SomeImageUri_useImagePreviewUi() { 265 val uri1 = Uri.parse("content://org.pkg.app/test.png") 266 val uri2 = Uri.parse("content://org.pkg.app/test.pdf") 267 whenever(contentResolver.getType(uri1)).thenReturn("image/png") 268 whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") 269 val targetIntent = 270 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 271 putExtra( 272 Intent.EXTRA_STREAM, 273 ArrayList<Uri>().apply { 274 add(uri1) 275 add(uri2) 276 } 277 ) 278 } 279 val testSubject = createDataProvider(targetIntent) 280 281 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 282 assertThat(testSubject.uriCount).isEqualTo(2) 283 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) 284 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) 285 // preview type can be determined by the first URI type 286 verify(contentResolver, times(1)).getType(any()) 287 } 288 289 @Test test_someNonImageUriWithPreview_useImagePreviewUinull290 fun test_someNonImageUriWithPreview_useImagePreviewUi() { 291 val uri1 = Uri.parse("content://org.pkg.app/test.mp4") 292 val uri2 = Uri.parse("content://org.pkg.app/test.pdf") 293 val targetIntent = 294 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 295 putExtra( 296 Intent.EXTRA_STREAM, 297 ArrayList<Uri>().apply { 298 add(uri1) 299 add(uri2) 300 } 301 ) 302 } 303 whenever(contentResolver.getType(uri1)).thenReturn("video/mpeg4") 304 whenever(contentResolver.getStreamTypes(uri1, "*/*")).thenReturn(arrayOf("image/png")) 305 whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") 306 val testSubject = createDataProvider(targetIntent) 307 308 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 309 assertThat(testSubject.uriCount).isEqualTo(2) 310 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) 311 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) 312 verify(contentResolver, times(2)).getType(any()) 313 } 314 315 @Test test_allNonImageUrisWithoutPreview_useFilePreviewUinull316 fun test_allNonImageUrisWithoutPreview_useFilePreviewUi() { 317 val uri1 = Uri.parse("content://org.pkg.app/test.html") 318 val uri2 = Uri.parse("content://org.pkg.app/test.pdf") 319 val targetIntent = 320 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 321 putExtra( 322 Intent.EXTRA_STREAM, 323 ArrayList<Uri>().apply { 324 add(uri1) 325 add(uri2) 326 } 327 ) 328 } 329 whenever(contentResolver.getType(uri1)).thenReturn("text/html") 330 whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") 331 val testSubject = createDataProvider(targetIntent) 332 333 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) 334 assertThat(testSubject.uriCount).isEqualTo(2) 335 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) 336 assertThat(testSubject.firstFileInfo?.previewUri).isNull() 337 verify(contentResolver, times(2)).getType(any()) 338 } 339 340 @Test test_imagePreviewFileInfoFlow_dataLoadedOncenull341 fun test_imagePreviewFileInfoFlow_dataLoadedOnce() = 342 testScope.runTest { 343 val uri1 = Uri.parse("content://org.pkg.app/test.html") 344 val uri2 = Uri.parse("content://org.pkg.app/test.pdf") 345 val targetIntent = 346 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 347 putExtra( 348 Intent.EXTRA_STREAM, 349 ArrayList<Uri>().apply { 350 add(uri1) 351 add(uri2) 352 } 353 ) 354 } 355 whenever(contentResolver.getType(uri1)).thenReturn("text/html") 356 whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") 357 whenever(contentResolver.getStreamTypes(uri1, "*/*")) 358 .thenReturn(arrayOf("text/html", "image/jpeg")) 359 whenever(contentResolver.getStreamTypes(uri2, "*/*")) 360 .thenReturn(arrayOf("application/pdf", "image/png")) 361 val testSubject = createDataProvider(targetIntent) 362 363 val fileInfoListOne = testSubject.imagePreviewFileInfoFlow.toList() 364 val fileInfoListTwo = testSubject.imagePreviewFileInfoFlow.toList() 365 366 assertThat(fileInfoListOne).hasSize(2) 367 assertThat(fileInfoListOne).containsAtLeastElementsIn(fileInfoListTwo).inOrder() 368 369 verify(contentResolver, times(1)).getType(uri1) 370 verify(contentResolver, times(1)).getStreamTypes(uri1, "*/*") 371 verify(contentResolver, times(1)).getType(uri2) 372 verify(contentResolver, times(1)).getStreamTypes(uri2, "*/*") 373 } 374 375 @Test sendItemsWithAdditionalContentUri_showPayloadTogglingUinull376 fun sendItemsWithAdditionalContentUri_showPayloadTogglingUi() { 377 val uri = Uri.parse("content://org.pkg.app/image.png") 378 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 379 whenever(contentResolver.getType(uri)).thenReturn("image/png") 380 val testSubject = 381 createDataProvider( 382 targetIntent, 383 additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"), 384 isPayloadTogglingEnabled = true, 385 ) 386 387 assertThat(testSubject.previewType) 388 .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION) 389 assertThat(testSubject.uriCount).isEqualTo(1) 390 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 391 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) 392 verify(contentResolver, times(1)).getType(any()) 393 } 394 395 @Test sendItemsWithAdditionalContentUri_showImagePreviewUinull396 fun sendItemsWithAdditionalContentUri_showImagePreviewUi() { 397 val uri = Uri.parse("content://org.pkg.app/image.png") 398 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 399 whenever(contentResolver.getType(uri)).thenReturn("image/png") 400 val testSubject = 401 createDataProvider( 402 targetIntent, 403 additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"), 404 ) 405 406 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 407 assertThat(testSubject.uriCount).isEqualTo(1) 408 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 409 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) 410 verify(contentResolver, times(1)).getType(any()) 411 } 412 413 @Test sendItemsWithAdditionalContentUriWithSameAuthority_showImagePreviewUinull414 fun sendItemsWithAdditionalContentUriWithSameAuthority_showImagePreviewUi() { 415 val uri = Uri.parse("content://org.pkg.app/image.png") 416 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } 417 whenever(contentResolver.getType(uri)).thenReturn("image/png") 418 val testSubject = 419 createDataProvider( 420 targetIntent, 421 additionalContentUri = Uri.parse("content://org.pkg.app/extracontent"), 422 isPayloadTogglingEnabled = true, 423 ) 424 425 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) 426 assertThat(testSubject.uriCount).isEqualTo(1) 427 assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) 428 assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) 429 verify(contentResolver, times(1)).getType(any()) 430 } 431 432 @Test test_nonSendIntentActionWithAdditionalContentUri_resolvesToTextPreviewUiSynchronouslynull433 fun test_nonSendIntentActionWithAdditionalContentUri_resolvesToTextPreviewUiSynchronously() { 434 val targetIntent = Intent(Intent.ACTION_VIEW) 435 val testSubject = 436 createDataProvider( 437 targetIntent, 438 additionalContentUri = Uri.parse("content://org.pkg.app/extracontent") 439 ) 440 441 assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) 442 verify(contentResolver, never()).getType(any()) 443 } 444 } 445