1 /* <lambda>null2 * Copyright 2022 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 androidx.camera.integration.extensions.utils 18 19 import android.content.ContentResolver 20 import android.content.ContentValues 21 import android.graphics.ImageFormat 22 import android.media.Image 23 import android.net.Uri 24 import android.os.Build 25 import android.provider.MediaStore 26 import android.util.Log 27 import androidx.camera.core.impl.utils.Exif 28 import androidx.core.net.toUri 29 import java.io.File 30 import java.io.FileInputStream 31 import java.io.FileOutputStream 32 33 private const val TAG = "FileUtil" 34 35 /** File util functions */ 36 object FileUtil { 37 38 /** 39 * Saves an [Image] to the specified file path. The format of the input [Image] must be JPEG or 40 * YUV_420_888 format. 41 */ 42 @JvmStatic 43 fun saveImage( 44 image: Image, 45 fileNamePrefix: String, 46 fileNameSuffix: String, 47 relativePath: String, 48 contentResolver: ContentResolver, 49 rotationDegrees: Int 50 ): Uri? { 51 require((image.format == ImageFormat.JPEG) or (image.format == ImageFormat.YUV_420_888)) { 52 "Incorrect image format of the input image proxy: ${image.format}" 53 } 54 55 val fileName = 56 if (fileNameSuffix.isNotEmpty() && fileNameSuffix[0] == '.') { 57 fileNamePrefix + fileNameSuffix 58 } else { 59 "$fileNamePrefix.$fileNameSuffix" 60 } 61 62 // Saves the image to the temp file 63 val tempFileUri = 64 saveImageToTempFile(image, fileNamePrefix, fileNameSuffix, null, rotationDegrees) 65 ?: return null 66 67 val contentValues = 68 ContentValues().apply { 69 put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) 70 put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") 71 put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) 72 } 73 74 // Copies the temp file to the final output path 75 return copyTempFileToOutputLocation( 76 contentResolver, 77 tempFileUri, 78 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 79 contentValues 80 ) 81 } 82 83 /** Saves an [Image] to a temp file. */ 84 @JvmStatic 85 fun saveImageToTempFile( 86 image: Image, 87 prefix: String, 88 suffix: String, 89 cacheDir: File? = null, 90 rotationDegrees: Int = 0 91 ): Uri? { 92 val tempFile = File.createTempFile(prefix, suffix, cacheDir) 93 94 val byteArray = 95 when (image.format) { 96 ImageFormat.JPEG -> { 97 ImageUtil.jpegImageToJpegByteArray(image) 98 } 99 ImageFormat.YUV_420_888 -> { 100 ImageUtil.yuvImageToJpegByteArray(image, 100) 101 } 102 else -> { 103 Log.e(TAG, "Incorrect image format of the input image proxy: ${image.format}") 104 return null 105 } 106 } 107 108 val outputStream = FileOutputStream(tempFile) 109 outputStream.write(byteArray) 110 outputStream.close() 111 112 // Updates Exif rotation tag info 113 val exif = Exif.createFromFile(tempFile) 114 exif.rotate(rotationDegrees) 115 exif.save() 116 117 return tempFile.toUri() 118 } 119 120 /** 121 * Copies temp file to the destination location. 122 * 123 * @return null if the copy process is failed. 124 */ 125 @JvmStatic 126 fun copyTempFileToOutputLocation( 127 contentResolver: ContentResolver, 128 tempFileUri: Uri, 129 targetUrl: Uri, 130 contentValues: ContentValues, 131 ): Uri? { 132 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 133 Log.e( 134 TAG, 135 "The known devices which support Extensions should be at least" + " Android Q!" 136 ) 137 return null 138 } 139 140 contentValues.put(MediaStore.Images.Media.IS_PENDING, 1) 141 142 val outputUri = contentResolver.insert(targetUrl, contentValues) ?: return null 143 144 if (copyTempFileByteArrayToOutputLocation(contentResolver, tempFileUri, outputUri)) { 145 contentValues.put(MediaStore.Images.Media.IS_PENDING, 0) 146 contentResolver.update(outputUri, contentValues, null, null) 147 return outputUri 148 } else { 149 Log.e(TAG, "Failed to copy the temp file to the output path!") 150 } 151 152 return null 153 } 154 155 /** 156 * Copies temp file byte array to output [Uri]. 157 * 158 * @return false if the [Uri] is not writable. 159 */ 160 @JvmStatic 161 private fun copyTempFileByteArrayToOutputLocation( 162 contentResolver: ContentResolver, 163 tempFileUri: Uri, 164 uri: Uri 165 ): Boolean { 166 contentResolver.openOutputStream(uri).use { outputStream -> 167 if (tempFileUri.path == null || outputStream == null) { 168 return false 169 } 170 171 val tempFile = File(tempFileUri.path!!) 172 173 FileInputStream(tempFile).use { inputStream -> 174 val buf = ByteArray(1024) 175 var len: Int 176 while (inputStream.read(buf).also { len = it } > 0) { 177 outputStream.write(buf, 0, len) 178 } 179 } 180 } 181 return true 182 } 183 } 184