diff options
| author | 2024-02-28 01:35:14 +0000 | |
|---|---|---|
| committer | 2024-02-28 01:35:14 +0000 | |
| commit | 3d711f60025c7e36afb089b1529acb733b288dee (patch) | |
| tree | fe729b8960c0f73b8262c992e1be0a75c09cd617 /java/src | |
| parent | df1351d53307ec9c952cd6789434b629afa2c73c (diff) | |
| parent | cc90909746d6ba96711fcecf6b1cdc5393967e38 (diff) | |
Merge "Shareousel: read preview uri for non-image items." into main
Diffstat (limited to 'java/src')
3 files changed, 160 insertions, 67 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt index 3f306a80..96bb8258 100644 --- a/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt +++ b/java/src/com/android/intentresolver/contentpreview/PreviewDataProvider.kt @@ -18,7 +18,6 @@ package com.android.intentresolver.contentpreview import android.content.ContentInterface import android.content.Intent -import android.database.Cursor import android.media.MediaMetadata import android.net.Uri import android.provider.DocumentsContract @@ -277,8 +276,7 @@ constructor( val isImageType: Boolean get() = typeClassifier.isImageType(mimeType) val supportsImageType: Boolean by lazy { - contentResolver.getStreamTypesSafe(uri)?.firstOrNull(typeClassifier::isImageType) != - null + contentResolver.getStreamTypesSafe(uri).firstOrNull(typeClassifier::isImageType) != null } val supportsThumbnail: Boolean get() = query.supportsThumbnail @@ -290,7 +288,8 @@ constructor( private val query by lazy { readQueryResult() } private fun readQueryResult(): QueryResult = - contentResolver.querySafe(uri)?.use { cursor -> + // TODO: rewrite using methods from UiMetadataHelpers.kt + contentResolver.querySafe(uri, METADATA_COLUMNS)?.use { cursor -> if (!cursor.moveToFirst()) return@use null var flagColIdx = -1 @@ -371,51 +370,3 @@ private fun getFileName(uri: Uri): String { fileName.substring(index + 1) } } - -private fun ContentInterface.getTypeSafe(uri: Uri): String? = - runTracing("getType") { - try { - getType(uri) - } catch (e: SecurityException) { - logProviderPermissionWarning(uri, "mime type") - null - } catch (t: Throwable) { - Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t) - null - } - } - -private fun ContentInterface.getStreamTypesSafe(uri: Uri): Array<String>? = - runTracing("getStreamTypes") { - try { - getStreamTypes(uri, "*/*") - } catch (e: SecurityException) { - logProviderPermissionWarning(uri, "stream types") - null - } catch (t: Throwable) { - Log.e(ContentPreviewUi.TAG, "Failed to read stream types, uri: $uri", t) - null - } - } - -private fun ContentInterface.querySafe(uri: Uri): Cursor? = - runTracing("query") { - try { - query(uri, METADATA_COLUMNS, null, null) - } catch (e: SecurityException) { - logProviderPermissionWarning(uri, "metadata") - null - } catch (t: Throwable) { - Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t) - null - } - } - -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." - ) -} diff --git a/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt b/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt new file mode 100644 index 00000000..41638b1f --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/UriMetadataHelpers.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview + +import android.content.ContentInterface +import android.database.Cursor +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.OpenableColumns +import android.text.TextUtils +import android.util.Log +import com.android.intentresolver.measurements.runTracing + +internal fun ContentInterface.getTypeSafe(uri: Uri): String? = + runTracing("getType") { + try { + getType(uri) + } catch (e: SecurityException) { + logProviderPermissionWarning(uri, "mime type") + null + } catch (t: Throwable) { + Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t) + null + } + } + +internal fun ContentInterface.getStreamTypesSafe(uri: Uri): Array<String?> = + runTracing("getStreamTypes") { + try { + getStreamTypes(uri, "*/*") ?: emptyArray() + } catch (e: SecurityException) { + logProviderPermissionWarning(uri, "stream types") + emptyArray<String?>() + } catch (t: Throwable) { + Log.e(ContentPreviewUi.TAG, "Failed to read stream types, uri: $uri", t) + emptyArray<String?>() + } + } + +internal fun ContentInterface.querySafe(uri: Uri, columns: Array<String>): Cursor? = + runTracing("query") { + try { + query(uri, columns, null, null) + } catch (e: SecurityException) { + logProviderPermissionWarning(uri, "metadata") + null + } catch (t: Throwable) { + Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t) + null + } + } + +internal fun Cursor.readSupportsThumbnail(): Boolean = + runCatching { + val flagColIdx = columnNames.indexOf(DocumentsContract.Document.COLUMN_FLAGS) + flagColIdx >= 0 && ((getInt(flagColIdx) and FLAG_SUPPORTS_THUMBNAIL) != 0) + } + .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) } + } + .getOrNull() + +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("") + +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." + ) +} diff --git a/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt b/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt index 784cefa0..45515e25 100644 --- a/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt +++ b/java/src/com/android/intentresolver/contentpreview/UriMetadataReader.kt @@ -16,25 +16,51 @@ package com.android.intentresolver.contentpreview -import android.content.ContentResolver +import android.content.ContentInterface +import android.media.MediaMetadata import android.net.Uri +import android.provider.DocumentsContract -// TODO: share this logic with PreviewDataProvider class UriMetadataReader( - private val contentResolver: ContentResolver, - private val mimeTypeClassifier: MimeTypeClassifier, + private val contentResolver: ContentInterface, + private val typeClassifier: MimeTypeClassifier, ) : (Uri) -> FileInfo { - fun getMetadata(uri: Uri): FileInfo = - FileInfo.Builder(uri) - .apply { - runCatching { - withMimeType(contentResolver.getType(uri)) - if (mimeTypeClassifier.isImageType(mimeType)) { - withPreviewUri(uri) - } - } - } - .build() + fun getMetadata(uri: Uri): FileInfo { + val builder = FileInfo.Builder(uri) + val mimeType = contentResolver.getTypeSafe(uri) + builder.withMimeType(mimeType) + if ( + typeClassifier.isImageType(mimeType) || + contentResolver.supportsImageType(uri) || + contentResolver.supportsThumbnail(uri) + ) { + builder.withPreviewUri(uri) + return builder.build() + } + val previewUri = contentResolver.readPreviewUri(uri) + if (previewUri != null) { + builder.withPreviewUri(previewUri) + } + return builder.build() + } override fun invoke(uri: Uri): FileInfo = getMetadata(uri) + + private fun ContentInterface.supportsImageType(uri: Uri): Boolean = + getStreamTypesSafe(uri).firstOrNull { typeClassifier.isImageType(it) } != null + + private fun ContentInterface.supportsThumbnail(uri: Uri): Boolean = + querySafe(uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS))?.use { cursor -> + cursor.moveToFirst() && cursor.readSupportsThumbnail() + } + ?: false + + private fun ContentInterface.readPreviewUri(uri: Uri): Uri? = + querySafe(uri, arrayOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI))?.use { cursor -> + if (cursor.moveToFirst()) { + cursor.readPreviewUri() + } else { + null + } + } } |