diff options
author | 2023-12-01 09:36:53 -0800 | |
---|---|---|
committer | 2024-10-01 15:37:37 -0700 | |
commit | f6400db571602fbbb3c5fa88276c3e5ed40792da (patch) | |
tree | d081d49d3d6f10a5d5a61922c05311ba0df29d9f /java | |
parent | 7e6f54913c7a6cfc5b50d16fe3b0d38a7c1bfe20 (diff) |
Retrieve URI title metadata through separate query calls
As a file name accessed independently from preview-related metadata and
also asynchronously, make it be retrieved through a separate provider
calls. This way an error during preview metadata call won't affect the
file name reading.
Bug: 365748223
Test: atest IntentResolver-tests-unit
Test: manual testing with a test app that shares a MediaProvider video
Flag: com.android.intentresolver.individual_metadata_title_read
Change-Id: I2e87913149e679fee22e4371d2b42f485c8b04e4
Diffstat (limited to 'java')
3 files changed, 54 insertions, 53 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt index 9b2dbebf..07cbaa04 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt @@ -24,15 +24,16 @@ import android.provider.DocumentsContract import android.provider.DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL import android.provider.Downloads import android.provider.OpenableColumns +import android.service.chooser.Flags.chooserPayloadToggling import android.text.TextUtils import android.util.Log import androidx.annotation.OpenForTesting import androidx.annotation.VisibleForTesting +import com.android.intentresolver.Flags.individualMetadataTitleRead 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.inject.ChooserServiceFlags import com.android.intentresolver.measurements.runTracing import com.android.intentresolver.util.ownedByCurrentUser import java.util.concurrent.atomic.AtomicInteger @@ -55,14 +56,19 @@ import kotlinx.coroutines.withTimeoutOrNull * A set of metadata columns we read for a content URI (see * [PreviewDataProvider.UriRecord.readQueryResult] method). */ -@VisibleForTesting -val METADATA_COLUMNS = +private val METADATA_COLUMNS = arrayOf( DocumentsContract.Document.COLUMN_FLAGS, MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, OpenableColumns.DISPLAY_NAME, - Downloads.Impl.COLUMN_TITLE + Downloads.Impl.COLUMN_TITLE, ) + +/** Preview-related metadata columns. */ +@VisibleForTesting +val ICON_METADATA_COLUMNS = + arrayOf(DocumentsContract.Document.COLUMN_FLAGS, MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI) + private const val TIMEOUT_MS = 1_000L /** @@ -77,7 +83,6 @@ constructor( private val targetIntent: Intent, private val additionalContentUri: Uri?, private val contentResolver: ContentInterface, - private val featureFlags: ChooserServiceFlags, private val typeClassifier: MimeTypeClassifier = DefaultMimeTypeClassifier, ) { @@ -128,7 +133,7 @@ constructor( * IMAGE, FILE, TEXT. */ if (!targetIntent.isSend || records.isEmpty()) { CONTENT_PREVIEW_TEXT - } else if (featureFlags.chooserPayloadToggling() && shouldShowPayloadSelection()) { + } else if (chooserPayloadToggling() && shouldShowPayloadSelection()) { // TODO: replace with the proper flags injection CONTENT_PREVIEW_PAYLOAD_SELECTION } else { @@ -141,7 +146,7 @@ constructor( Log.w( ContentPreviewUi.TAG, "An attempt to read preview type from a cancelled scope", - e + e, ) CONTENT_PREVIEW_FILE } @@ -159,7 +164,7 @@ constructor( Log.w( ContentPreviewUi.TAG, "Failed to check URI authorities; no payload toggling", - it + it, ) } .getOrDefault(false) @@ -183,7 +188,7 @@ constructor( Log.w( ContentPreviewUi.TAG, "An attempt to read first file info from a cancelled scope", - e + e, ) } builder.build() @@ -212,14 +217,20 @@ constructor( if (records.isEmpty()) { throw IndexOutOfBoundsException("There are no shared URIs") } - callerScope.launch { - val result = scope.async { getFirstFileName() }.await() - callback.accept(result) - } + callerScope.launch { callback.accept(getFirstFileName()) } } + /** + * Returns a title for the first shared URI which is read from URI metadata or, if the metadata + * is not provided, derived from the URI. + */ @Throws(IndexOutOfBoundsException::class) - private fun getFirstFileName(): String { + suspend fun getFirstFileName(): String { + return scope.async { getFirstFileNameInternal() }.await() + } + + @Throws(IndexOutOfBoundsException::class) + private fun getFirstFileNameInternal(): String { if (records.isEmpty()) throw IndexOutOfBoundsException("There are no shared URIs") val record = records[0] @@ -282,16 +293,23 @@ constructor( get() = query.supportsThumbnail val title: String - get() = query.title + get() = if (individualMetadataTitleRead()) titleFromQuery else query.title val iconUri: Uri? get() = query.iconUri - private val query by lazy { readQueryResult() } + private val query by lazy { + readQueryResult( + if (individualMetadataTitleRead()) ICON_METADATA_COLUMNS else METADATA_COLUMNS + ) + } + + private val titleFromQuery by lazy { + readDisplayNameFromQuery().takeIf { !TextUtils.isEmpty(it) } ?: readTitleFromQuery() + } - private fun readQueryResult(): QueryResult = - // TODO: rewrite using methods from UiMetadataHelpers.kt - contentResolver.querySafe(uri, METADATA_COLUMNS)?.use { cursor -> + private fun readQueryResult(columns: Array<String>): QueryResult = + contentResolver.querySafe(uri, columns)?.use { cursor -> if (!cursor.moveToFirst()) return@use null var flagColIdx = -1 @@ -329,12 +347,23 @@ constructor( QueryResult(supportsThumbnail, title, iconUri) } ?: QueryResult() + + private fun readTitleFromQuery(): String = readStringColumn(Downloads.Impl.COLUMN_TITLE) + + private fun readDisplayNameFromQuery(): String = + readStringColumn(OpenableColumns.DISPLAY_NAME) + + private fun readStringColumn(column: String): String = + contentResolver.querySafe(uri, arrayOf(column))?.use { cursor -> + if (!cursor.moveToFirst()) return@use null + cursor.readString(column) + } ?: "" } private class QueryResult( val supportsThumbnail: Boolean = false, val title: String = "", - val iconUri: Uri? = null + val iconUri: Uri? = null, ) } diff --git a/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt b/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt index c532b9a5..80d0e058 100644 --- a/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt +++ b/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt @@ -22,11 +22,8 @@ import android.media.MediaMetadata import android.net.Uri import android.provider.DocumentsContract import android.provider.DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL -import android.provider.Downloads import android.provider.MediaStore.MediaColumns.HEIGHT import android.provider.MediaStore.MediaColumns.WIDTH -import android.provider.OpenableColumns -import android.text.TextUtils import android.util.Log import android.util.Size import com.android.intentresolver.measurements.runTracing @@ -78,12 +75,7 @@ internal fun Cursor.readSupportsThumbnail(): Boolean = .getOrDefault(false) internal fun Cursor.readPreviewUri(): Uri? = - runCatching { - columnNames - .indexOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI) - .takeIf { it >= 0 } - ?.let { getString(it)?.let(Uri::parse) } - } + runCatching { readString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI)?.let(Uri::parse) } .getOrNull() fun Cursor.readSize(): Size? { @@ -105,34 +97,15 @@ fun Cursor.readSize(): Size? { } } -internal fun Cursor.readTitle(): String = - runCatching { - var nameColIndex = -1 - var titleColIndex = -1 - // TODO: double-check why Cursor#getColumnInded didn't work - columnNames.forEachIndexed { i, columnName -> - when (columnName) { - OpenableColumns.DISPLAY_NAME -> nameColIndex = i - Downloads.Impl.COLUMN_TITLE -> titleColIndex = i - } - } - - var title = "" - if (nameColIndex >= 0) { - title = getString(nameColIndex) ?: "" - } - if (TextUtils.isEmpty(title) && titleColIndex >= 0) { - title = getString(titleColIndex) ?: "" - } - title - } - .getOrDefault("") +internal fun Cursor.readString(columnName: String): String? = + runCatching { columnNames.indexOf(columnName).takeIf { it >= 0 }?.let { getString(it) } } + .getOrNull() private fun logProviderPermissionWarning(uri: Uri, dataName: String) { // The ContentResolver already logs the exception. Log something more informative. Log.w( ContentPreviewUi.TAG, "Could not read $uri $dataName. If a preview is desired, call Intent#setClipData() to" + - " ensure that the sharesheet is given permission." + " ensure that the sharesheet is given permission.", ) } diff --git a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt index e6f12750..fe7e9109 100644 --- a/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt +++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt @@ -95,7 +95,6 @@ constructor( chooserRequest.targetIntent, chooserRequest.additionalContentUri, contentResolver, - flags, ) } |