diff options
5 files changed, 177 insertions, 32 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/ContentPreviewType.java b/java/src/com/android/intentresolver/contentpreview/ContentPreviewType.java index ad1c6c01..79bb9d3c 100644 --- a/java/src/com/android/intentresolver/contentpreview/ContentPreviewType.java +++ b/java/src/com/android/intentresolver/contentpreview/ContentPreviewType.java @@ -25,11 +25,13 @@ import java.lang.annotation.Retention; @Retention(SOURCE) @IntDef({ContentPreviewType.CONTENT_PREVIEW_FILE, ContentPreviewType.CONTENT_PREVIEW_IMAGE, - ContentPreviewType.CONTENT_PREVIEW_TEXT}) + ContentPreviewType.CONTENT_PREVIEW_TEXT, + ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION}) public @interface ContentPreviewType { // Starting at 1 since 0 is considered "undefined" for some of the database transformations // of tron logs. int CONTENT_PREVIEW_IMAGE = 1; int CONTENT_PREVIEW_FILE = 2; int CONTENT_PREVIEW_TEXT = 3; + int CONTENT_PREVIEW_PAYLOAD_SELECTION = 4; } diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt index 659f7dc9..8073cfec 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt @@ -31,6 +31,7 @@ import androidx.annotation.OpenForTesting import androidx.annotation.VisibleForTesting import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_FILE import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE +import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION import com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_TEXT import com.android.intentresolver.measurements.runTracing import com.android.intentresolver.util.ownedByCurrentUser @@ -74,7 +75,11 @@ open class PreviewDataProvider constructor( private val scope: CoroutineScope, private val targetIntent: Intent, + private val additionalContentUri: Uri?, private val contentResolver: ContentInterface, + // TODO: replace with the ChooserServiceFlags ref when PreviewViewModel dependencies are sorted + // out + private val isPayloadTogglingEnabled: Boolean, private val typeClassifier: MimeTypeClassifier = DefaultMimeTypeClassifier, ) { @@ -125,6 +130,9 @@ constructor( * IMAGE, FILE, TEXT. */ if (!targetIntent.isSend || records.isEmpty()) { CONTENT_PREVIEW_TEXT + } else if (isPayloadTogglingEnabled && shouldShowPayloadSelection()) { + // TODO: replace with the proper flags injection + CONTENT_PREVIEW_PAYLOAD_SELECTION } else { try { runBlocking(scope.coroutineContext) { @@ -143,6 +151,23 @@ constructor( } } + private fun shouldShowPayloadSelection(): Boolean { + val extraContentUri = additionalContentUri ?: return false + return runCatching { + val authority = extraContentUri.authority + // TODO: verify that authority is case-sensitive + records.firstOrNull { authority == it.uri.authority } == null + } + .onFailure { + Log.w( + ContentPreviewUi.TAG, + "Failed to check URI authorities; no payload toggling", + it + ) + } + .getOrDefault(false) + } + /** * The first shared URI's metadata. This call wait's for the data to be loaded and falls back to * a crude value if the data is not loaded within a time limit. diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt b/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt index 7369fa0f..d694c6ff 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewViewModel.kt @@ -49,7 +49,13 @@ class PreviewViewModel( override val previewDataProvider by lazy { val targetIntent = requireNotNull(this.targetIntent) { "Not initialized" } - PreviewDataProvider(viewModelScope + dispatcher, targetIntent, contentResolver) + PreviewDataProvider( + viewModelScope + dispatcher, + targetIntent, + additionalContentUri, + contentResolver, + isPayloadTogglingEnabled, + ) } override val imageLoader by lazy { diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index 560f4be4..ad53eef4 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -18,6 +18,9 @@ package com.android.intentresolver.contentpreview import android.content.Intent import android.net.Uri +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import com.android.intentresolver.ContentTypeHint import com.android.intentresolver.TestPreviewImageLoader import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory @@ -31,6 +34,7 @@ import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -51,6 +55,7 @@ class ChooserContentPreviewUiTest { override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} } private val transitionCallback = mock<ImagePreviewView.TransitionElementStatusCallback>() + @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @Test fun test_textPreviewType_useTextPreviewUi() { @@ -146,4 +151,34 @@ class ChooserContentPreviewUiTest { verify(previewData, times(1)).imagePreviewFileInfoFlow verify(transitionCallback, never()).onAllTransitionElementsReady() } + + @Test + @RequiresFlagsDisabled(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING) + fun test_imagePayloadSelectionType_useImagePreviewUi() { + // Event if we returned wrong type due to a bug, we should not use payload selection UI + val uri = Uri.parse("content://org.pkg.app/img.png") + whenever(previewData.previewType) + .thenReturn(ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION) + whenever(previewData.uriCount).thenReturn(2) + whenever(previewData.firstFileInfo) + .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) + whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) + val testSubject = + ChooserContentPreviewUi( + testScope, + previewData, + Intent(Intent.ACTION_SEND), + imageLoader, + actionFactory, + transitionCallback, + headlineGenerator, + ContentTypeHint.NONE, + testMetadataText, + ) + assertThat(testSubject.preferredContentPreview) + .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) + assertThat(testSubject.mContentPreviewUi).isInstanceOf(UnifiedContentPreviewUi::class.java) + verify(previewData, times(1)).imagePreviewFileInfoFlow + verify(transitionCallback, never()).onAllTransitionElementsReady() + } } diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt index 4a8c1392..babfaaf5 100644 --- a/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt +++ b/tests/unit/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt @@ -21,16 +21,20 @@ import android.content.Intent import android.database.MatrixCursor import android.media.MediaMetadata import android.net.Uri +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.DocumentsContract import com.android.intentresolver.mock import com.android.intentresolver.whenever import com.google.common.truth.Truth.assertThat import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.any import org.mockito.Mockito.never @@ -42,12 +46,29 @@ class PreviewDataProviderTest { private val contentResolver = mock<ContentInterface>() private val mimeTypeClassifier = DefaultMimeTypeClassifier private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) + @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + private fun createDataProvider( + targetIntent: Intent, + scope: CoroutineScope = testScope, + additionalContentUri: Uri? = null, + resolver: ContentInterface = contentResolver, + typeClassifier: MimeTypeClassifier = mimeTypeClassifier, + isPayloadTogglingEnabled: Boolean = false + ) = + PreviewDataProvider( + scope, + targetIntent, + additionalContentUri, + resolver, + isPayloadTogglingEnabled, + typeClassifier, + ) @Test fun test_nonSendIntentAction_resolvesToTextPreviewUiSynchronously() { val targetIntent = Intent(Intent.ACTION_VIEW) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) verify(contentResolver, never()).getType(any()) @@ -62,8 +83,7 @@ class PreviewDataProviderTest { type = "text/plain" } whenever(contentResolver.getType(uri)).thenReturn("text/plain") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -74,8 +94,7 @@ class PreviewDataProviderTest { @Test fun test_sendIntentWithoutUris_resolvesToTextPreviewUiSynchronously() { val targetIntent = Intent(Intent.ACTION_SEND).apply { type = "image/png" } - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) verify(contentResolver, never()).getType(any()) @@ -86,8 +105,7 @@ class PreviewDataProviderTest { val uri = Uri.parse("content://org.pkg.app/image.png") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } whenever(contentResolver.getType(uri)).thenReturn("image/png") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -101,8 +119,7 @@ class PreviewDataProviderTest { val uri = Uri.parse("content://org.pkg.app/paper.pdf") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } whenever(contentResolver.getType(uri)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -120,8 +137,7 @@ class PreviewDataProviderTest { putExtra(Intent.EXTRA_STREAM, uri) } whenever(contentResolver.getType(uri)).thenThrow(SecurityException("test failure")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -142,8 +158,7 @@ class PreviewDataProviderTest { .thenThrow(SecurityException("test failure")) whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)) .thenThrow(SecurityException("test failure")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -158,8 +173,7 @@ class PreviewDataProviderTest { val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } whenever(contentResolver.getStreamTypes(uri, "*/*")) .thenReturn(arrayOf("application/pdf", "image/png")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -195,8 +209,7 @@ class PreviewDataProviderTest { val cursor = MatrixCursor(columns).apply { addRow(values) } whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(1) @@ -214,8 +227,7 @@ class PreviewDataProviderTest { val cursor = MatrixCursor(emptyArray()) whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) verify(contentResolver, times(1)).query(uri, METADATA_COLUMNS, null, null) @@ -238,8 +250,7 @@ class PreviewDataProviderTest { } whenever(contentResolver.getType(uri1)).thenReturn("image/png") whenever(contentResolver.getType(uri2)).thenReturn("image/jpeg") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(2) @@ -265,8 +276,7 @@ class PreviewDataProviderTest { } ) } - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(2) @@ -293,8 +303,7 @@ class PreviewDataProviderTest { whenever(contentResolver.getType(uri1)).thenReturn("video/mpeg4") whenever(contentResolver.getStreamTypes(uri1, "*/*")).thenReturn(arrayOf("image/png")) whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) assertThat(testSubject.uriCount).isEqualTo(2) @@ -319,8 +328,7 @@ class PreviewDataProviderTest { } whenever(contentResolver.getType(uri1)).thenReturn("text/html") whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) assertThat(testSubject.uriCount).isEqualTo(2) @@ -350,8 +358,7 @@ class PreviewDataProviderTest { .thenReturn(arrayOf("text/html", "image/jpeg")) whenever(contentResolver.getStreamTypes(uri2, "*/*")) .thenReturn(arrayOf("application/pdf", "image/png")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) + val testSubject = createDataProvider(targetIntent) val fileInfoListOne = testSubject.imagePreviewFileInfoFlow.toList() val fileInfoListTwo = testSubject.imagePreviewFileInfoFlow.toList() @@ -364,4 +371,74 @@ class PreviewDataProviderTest { verify(contentResolver, times(1)).getType(uri2) verify(contentResolver, times(1)).getStreamTypes(uri2, "*/*") } + + @Test + fun sendItemsWithAdditionalContentUri_showPayloadTogglingUi() { + val uri = Uri.parse("content://org.pkg.app/image.png") + val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } + whenever(contentResolver.getType(uri)).thenReturn("image/png") + val testSubject = + createDataProvider( + targetIntent, + additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"), + isPayloadTogglingEnabled = true, + ) + + assertThat(testSubject.previewType) + .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION) + assertThat(testSubject.uriCount).isEqualTo(1) + assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) + assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) + verify(contentResolver, times(1)).getType(any()) + } + + @Test + fun sendItemsWithAdditionalContentUri_showImagePreviewUi() { + val uri = Uri.parse("content://org.pkg.app/image.png") + val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } + whenever(contentResolver.getType(uri)).thenReturn("image/png") + val testSubject = + createDataProvider( + targetIntent, + additionalContentUri = Uri.parse("content://org.pkg.app.extracontent"), + ) + + assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) + assertThat(testSubject.uriCount).isEqualTo(1) + assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) + assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) + verify(contentResolver, times(1)).getType(any()) + } + + @Test + fun sendItemsWithAdditionalContentUriWithSameAuthority_showImagePreviewUi() { + val uri = Uri.parse("content://org.pkg.app/image.png") + val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } + whenever(contentResolver.getType(uri)).thenReturn("image/png") + val testSubject = + createDataProvider( + targetIntent, + additionalContentUri = Uri.parse("content://org.pkg.app/extracontent"), + isPayloadTogglingEnabled = true, + ) + + assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) + assertThat(testSubject.uriCount).isEqualTo(1) + assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) + assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) + verify(contentResolver, times(1)).getType(any()) + } + + @Test + fun test_nonSendIntentActionWithAdditionalContentUri_resolvesToTextPreviewUiSynchronously() { + val targetIntent = Intent(Intent.ACTION_VIEW) + val testSubject = + createDataProvider( + targetIntent, + additionalContentUri = Uri.parse("content://org.pkg.app/extracontent") + ) + + assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) + verify(contentResolver, never()).getType(any()) + } } |