1 /* <lambda>null2 * 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.media.MediaMetadata 21 import android.net.Uri 22 import android.provider.DocumentsContract 23 import android.provider.MediaStore.MediaColumns 24 import android.util.Size 25 import dagger.Binds 26 import dagger.Module 27 import dagger.Provides 28 import dagger.hilt.InstallIn 29 import dagger.hilt.components.SingletonComponent 30 import javax.inject.Inject 31 32 fun interface UriMetadataReader { 33 fun getMetadata(uri: Uri): FileInfo 34 fun readPreviewSize(uri: Uri): Size? = null 35 } 36 37 class UriMetadataReaderImpl 38 @Inject 39 constructor( 40 private val contentResolver: ContentInterface, 41 private val typeClassifier: MimeTypeClassifier, 42 ) : UriMetadataReader { getMetadatanull43 override fun getMetadata(uri: Uri): FileInfo { 44 val builder = FileInfo.Builder(uri) 45 val mimeType = contentResolver.getTypeSafe(uri) 46 builder.withMimeType(mimeType) 47 if ( 48 typeClassifier.isImageType(mimeType) || 49 contentResolver.supportsImageType(uri) || 50 contentResolver.supportsThumbnail(uri) 51 ) { 52 builder.withPreviewUri(uri) 53 return builder.build() 54 } 55 val previewUri = contentResolver.readPreviewUri(uri) 56 if (previewUri != null) { 57 builder.withPreviewUri(previewUri) 58 } 59 return builder.build() 60 } 61 readPreviewSizenull62 override fun readPreviewSize(uri: Uri): Size? = contentResolver.readPreviewSize(uri) 63 64 private fun ContentInterface.supportsImageType(uri: Uri): Boolean = 65 getStreamTypesSafe(uri).firstOrNull { typeClassifier.isImageType(it) } != null 66 supportsThumbnailnull67 private fun ContentInterface.supportsThumbnail(uri: Uri): Boolean = 68 querySafe(uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS))?.use { cursor -> 69 cursor.moveToFirst() && cursor.readSupportsThumbnail() 70 } 71 ?: false 72 ContentInterfacenull73 private fun ContentInterface.readPreviewUri(uri: Uri): Uri? = 74 querySafe(uri, arrayOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI))?.use { cursor -> 75 if (cursor.moveToFirst()) { 76 cursor.readPreviewUri() 77 } else { 78 null 79 } 80 } 81 readPreviewSizenull82 private fun ContentInterface.readPreviewSize(uri: Uri): Size? = 83 querySafe(uri, arrayOf(MediaColumns.WIDTH, MediaColumns.HEIGHT))?.use { cursor -> 84 if (cursor.moveToFirst()) { 85 cursor.readSize() 86 } else { 87 null 88 } 89 } 90 } 91 92 @Module 93 @InstallIn(SingletonComponent::class) 94 interface UriMetadataReaderModule { 95 bindnull96 @Binds fun bind(impl: UriMetadataReaderImpl): UriMetadataReader 97 98 companion object { 99 @Provides fun classifier(): MimeTypeClassifier = DefaultMimeTypeClassifier 100 } 101 } 102