1 /*
2 * Copyright 2024 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.database.Cursor
21 import android.media.MediaMetadata
22 import android.net.Uri
23 import android.provider.DocumentsContract
24 import android.provider.DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL
25 import android.provider.MediaStore.MediaColumns.HEIGHT
26 import android.provider.MediaStore.MediaColumns.WIDTH
27 import android.util.Log
28 import android.util.Size
29 import com.android.intentresolver.measurements.runTracing
30
getTypeSafenull31 internal fun ContentInterface.getTypeSafe(uri: Uri): String? =
32 runTracing("getType") {
33 try {
34 getType(uri)
35 } catch (e: SecurityException) {
36 logProviderPermissionWarning(uri, "mime type")
37 null
38 } catch (t: Throwable) {
39 Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t)
40 null
41 }
42 }
43
getStreamTypesSafenull44 internal fun ContentInterface.getStreamTypesSafe(uri: Uri): Array<String?> =
45 runTracing("getStreamTypes") {
46 try {
47 getStreamTypes(uri, "*/*") ?: emptyArray()
48 } catch (e: SecurityException) {
49 logProviderPermissionWarning(uri, "stream types")
50 emptyArray<String?>()
51 } catch (t: Throwable) {
52 Log.e(ContentPreviewUi.TAG, "Failed to read stream types, uri: $uri", t)
53 emptyArray<String?>()
54 }
55 }
56
querySafenull57 internal fun ContentInterface.querySafe(uri: Uri, columns: Array<String>): Cursor? =
58 runTracing("query") {
59 try {
60 query(uri, columns, null, null)
61 } catch (e: SecurityException) {
62 logProviderPermissionWarning(uri, "metadata")
63 null
64 } catch (t: Throwable) {
65 Log.e(ContentPreviewUi.TAG, "Failed to read metadata, uri: $uri", t)
66 null
67 }
68 }
69
readSupportsThumbnailnull70 internal fun Cursor.readSupportsThumbnail(): Boolean =
71 runCatching {
72 val flagColIdx = columnNames.indexOf(DocumentsContract.Document.COLUMN_FLAGS)
73 flagColIdx >= 0 && ((getInt(flagColIdx) and FLAG_SUPPORTS_THUMBNAIL) != 0)
74 }
75 .getOrDefault(false)
76
readPreviewUrinull77 internal fun Cursor.readPreviewUri(): Uri? =
78 runCatching { readString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI)?.let(Uri::parse) }
79 .getOrNull()
80
Cursornull81 fun Cursor.readSize(): Size? {
82 val widthIdx = columnNames.indexOf(WIDTH)
83 val heightIdx = columnNames.indexOf(HEIGHT)
84 return if (widthIdx < 0 || heightIdx < 0 || isNull(widthIdx) || isNull(heightIdx)) {
85 null
86 } else {
87 runCatching {
88 val width = getInt(widthIdx)
89 val height = getInt(heightIdx)
90 if (width >= 0 && height > 0) {
91 Size(width, height)
92 } else {
93 null
94 }
95 }
96 .getOrNull()
97 }
98 }
99
readStringnull100 internal fun Cursor.readString(columnName: String): String? =
101 runCatching { columnNames.indexOf(columnName).takeIf { it >= 0 }?.let { getString(it) } }
102 .getOrNull()
103
logProviderPermissionWarningnull104 private fun logProviderPermissionWarning(uri: Uri, dataName: String) {
105 // The ContentResolver already logs the exception. Log something more informative.
106 Log.w(
107 ContentPreviewUi.TAG,
108 "Could not read $uri $dataName. If a preview is desired, call Intent#setClipData() to" +
109 " ensure that the sharesheet is given permission.",
110 )
111 }
112