From 0bd94de6891a259cfabce5c5ccbac588de950642 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Wed, 26 Apr 2023 21:31:11 -0700 Subject: Preview UI: specify metadata columns, change metada reading order Specify columns passed to ContentResolver#query. Read ContentResolver#getStreamTypes before ContentResolver#query because if the former returns, among others, an image mime types we can avoid reading the latter. Bug: 279674836 Test: unit tests, manual testing wiht Drive and a Photos prototype. Change-Id: Ide31f5c9aae21c9e0ce93bec9a8d829851532f4a --- .../contentpreview/ChooserContentPreviewUi.java | 21 +++++-- .../contentpreview/ChooserContentPreviewUiTest.kt | 72 ++++++++++++++++++---- 2 files changed, 77 insertions(+), 16 deletions(-) (limited to 'java') diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index 69d8c49f..8173d542 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -37,6 +37,7 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; @@ -52,6 +53,18 @@ import java.util.function.Consumer; * A content preview façade. */ public final class ChooserContentPreviewUi { + + /** + * A set of metadata columns we read for a content URI (see [readFileMetadata] method). + */ + @VisibleForTesting + static final String[] METADATA_COLUMNS = new String[] { + DocumentsContract.Document.COLUMN_FLAGS, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, + OpenableColumns.DISPLAY_NAME, + Downloads.Impl.COLUMN_TITLE + }; + /** * Delegate to build the default system action buttons to display in the preview layout, if/when * they're determined to be appropriate for the particular preview we display. @@ -209,9 +222,9 @@ public final class ChooserContentPreviewUi { if (typeClassifier.isImageType(mimeType)) { return builder.withPreviewUri(uri).build(); } - readFileMetadata(resolver, uri, builder); + readOtherFileTypes(resolver, uri, typeClassifier, builder); if (builder.getPreviewUri() == null) { - readOtherFileTypes(resolver, uri, typeClassifier, builder); + readFileMetadata(resolver, uri, builder); } return builder.build(); } @@ -329,7 +342,7 @@ public final class ChooserContentPreviewUi { } catch (SecurityException e) { logProviderPermissionWarning(uri, "mime type"); } catch (Throwable t) { - Log.e(ContentPreviewUi.TAG, "Failed to read content type, uri: " + uri, t); + Log.e(ContentPreviewUi.TAG, "Failed to read content type, uri: " + uri, t); } return null; } @@ -337,7 +350,7 @@ public final class ChooserContentPreviewUi { @Nullable private static Cursor query(ContentInterface resolver, Uri uri) { try { - return resolver.query(uri, null, null, null); + return resolver.query(uri, METADATA_COLUMNS, null, null); } catch (SecurityException e) { logProviderPermissionWarning(uri, "metadata"); } catch (Throwable t) { diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index f29fac84..63fa8766 100644 --- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -19,11 +19,15 @@ package com.android.intentresolver.contentpreview import android.content.ClipDescription import android.content.ContentInterface import android.content.Intent +import android.database.MatrixCursor import android.graphics.Bitmap +import android.media.MediaMetadata import android.net.Uri +import android.provider.DocumentsContract import com.android.intentresolver.any import com.android.intentresolver.anyOrNull import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory +import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.METADATA_COLUMNS import com.android.intentresolver.mock import com.android.intentresolver.whenever import com.android.intentresolver.widget.ActionRow @@ -59,7 +63,7 @@ class ChooserContentPreviewUiTest { private val transitionCallback = mock() @Test - fun test_ChooserContentPreview_non_send_intent_action_to_text_preview() { + fun test_nonSendIntentAction_useTextPreviewUi() { val targetIntent = Intent(Intent.ACTION_VIEW) val testSubject = ChooserContentPreviewUi( targetIntent, @@ -76,7 +80,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_text_mime_type_to_text_preview() { + fun test_textMimeType_useTextPreviewUi() { val targetIntent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, "Text Extra") @@ -96,7 +100,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_single_image_uri_to_image_preview() { + fun test_singleImageUri_useImagePreviewUi() { val uri = Uri.parse("content://$PROVIDER_NAME/test.png") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) @@ -117,7 +121,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_single_uri_without_preview_to_file_preview() { + fun test_singleNonImageUriWithoutPreview_useFilePreviewUi() { val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) @@ -138,7 +142,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_single_uri_crashing_getType_to_file_preview() { + fun test_singleUriWithFailingGetType_useFilePreviewUi() { val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) @@ -160,7 +164,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_single_uri_crashing_metadata_to_file_preview() { + fun test_singleNonImageUriWithFailingMetadata_useFilePreviewUi() { val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) @@ -185,7 +189,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_single_uri_with_preview_to_image_preview() { + fun test_SingleNonImageUriWithImageTypeInGetStreamTypes_useImagePreviewUi() { val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) @@ -208,7 +212,52 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_multiple_image_uri_to_image_preview() { + fun test_SingleNonImageUriWithThumbnailFlag_useImagePreviewUi() { + testMetadataToImagePreview( + columns = arrayOf(DocumentsContract.Document.COLUMN_FLAGS), + values = arrayOf( + DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL or + DocumentsContract.Document.FLAG_SUPPORTS_METADATA + ) + ) + } + + @Test + fun test_SingleNonImageUriWithMetadataIconUri_useImagePreviewUi() { + testMetadataToImagePreview( + columns = arrayOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI), + values = arrayOf("content://$PROVIDER_NAME/test.pdf?thumbnail"), + ) + } + + private fun testMetadataToImagePreview(columns: Array, values: Array) { + val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf") + val targetIntent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + } + whenever(contentResolver.getType(uri)).thenReturn("application/pdf") + whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)) + .thenReturn( + MatrixCursor(columns).apply { + addRow(values) + } + ) + val testSubject = ChooserContentPreviewUi( + targetIntent, + contentResolver, + imageClassifier, + imageLoader, + actionFactory, + transitionCallback, + headlineGenerator + ) + assertThat(testSubject.preferredContentPreview) + .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) + verify(transitionCallback, never()).onAllTransitionElementsReady() + } + + @Test + fun test_multipleImageUri_useImagePreviewUi() { val uri1 = Uri.parse("content://$PROVIDER_NAME/test.png") val uri2 = Uri.parse("content://$PROVIDER_NAME/test.jpg") val targetIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { @@ -237,7 +286,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_some_non_image_uri_to_image_preview() { + fun test_SomeImageUri_useImagePreviewUi() { val uri1 = Uri.parse("content://$PROVIDER_NAME/test.png") val uri2 = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { @@ -266,7 +315,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_some_non_image_uri_with_preview_to_image_preview() { + fun test_someNonImageUriWithPreview_useImagePreviewUi() { val uri1 = Uri.parse("content://$PROVIDER_NAME/test.mp4") val uri2 = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { @@ -297,7 +346,7 @@ class ChooserContentPreviewUiTest { } @Test - fun test_ChooserContentPreview_all_non_image_uris_without_preview_to_file_preview() { + fun test_allNonImageUrisWithoutPreview_useFilePreviewUi() { val uri1 = Uri.parse("content://$PROVIDER_NAME/test.html") val uri2 = Uri.parse("content://$PROVIDER_NAME/test.pdf") val targetIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { @@ -324,5 +373,4 @@ class ChooserContentPreviewUiTest { .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) verify(transitionCallback, times(1)).onAllTransitionElementsReady() } - } -- cgit v1.2.3-59-g8ed1b