@@ -24,7 +24,6 @@ import kotlinx.coroutines.sync.Mutex
2424import kotlinx.coroutines.sync.withLock
2525import java.io.ByteArrayOutputStream
2626import java.io.File
27- import java.io.FileOutputStream
2827import java.text.SimpleDateFormat
2928import java.util.*
3029import kotlin.time.ExperimentalTime
@@ -34,7 +33,7 @@ private data class KeyParams(
3433 val outputSize : BinarySize = 32 .bytes,
3534)
3635
37- class SecureImageManager (
36+ class SecureImageRepository (
3837 private val appContext : Context ,
3938 private val preferencesManager : AppPreferencesManager ,
4039 private val authorizationManager : AuthorizationManager ,
@@ -148,6 +147,95 @@ class SecureImageManager(
148147 return secretDerivation.deriveSecret(keyInput.toByteArray()).toByteArray()
149148 }
150149
150+ /* *
151+ * Compresses a bitmap to JPEG format with the specified quality
152+ */
153+ private fun compressBitmapToJpeg (bitmap : Bitmap , quality : Int ): ByteArray {
154+ return ByteArrayOutputStream ().use { outputStream ->
155+ bitmap.compress(CompressFormat .JPEG , quality, outputStream)
156+ outputStream.toByteArray()
157+ }
158+ }
159+
160+ /* *
161+ * Encrypts and saves image data to a file, then renames it to the target file
162+ */
163+ private suspend fun encryptAndSaveImage (imageBytes : ByteArray , tempFile : File , targetFile : File ) {
164+ tempFile.writeBytes(imageBytes)
165+
166+ val pin = authorizationManager.securityPin ? : throw IllegalStateException (" No Security PIN" )
167+ encryptToFile(
168+ plainPin = pin.plainPin,
169+ hashedPin = pin.hashedPin,
170+ plain = tempFile.readBytes(),
171+ targetFile = tempFile,
172+ )
173+
174+ tempFile.renameTo(targetFile)
175+ }
176+
177+ /* *
178+ * Processes an image with metadata and prepares it for saving
179+ */
180+ private fun processImageWithMetadata (
181+ bitmap : Bitmap ,
182+ sourceJpgBytes : ByteArray ,
183+ quality : Int
184+ ): ByteArray {
185+ val newJpgBytes = compressBitmapToJpeg(bitmap, quality)
186+ var updatedBytes = newJpgBytes
187+
188+ val metadata = Kim .readMetadata(sourceJpgBytes)
189+ if (metadata != null ) {
190+ // Apply all existing metadata to the new image
191+ metadata.convertToPhotoMetadata().let { photoMetadata ->
192+ if (photoMetadata.takenDate != null ) {
193+ updatedBytes = Kim .update(bytes = updatedBytes, MetadataUpdate .TakenDate (photoMetadata.takenDate!! ))
194+ }
195+
196+ if (photoMetadata.orientation != null ) {
197+ updatedBytes =
198+ Kim .update(bytes = updatedBytes, MetadataUpdate .Orientation (photoMetadata.orientation!! ))
199+ }
200+
201+ if (photoMetadata.gpsCoordinates != null ) {
202+ updatedBytes =
203+ Kim .update(bytes = updatedBytes, MetadataUpdate .GpsCoordinates (photoMetadata.gpsCoordinates!! ))
204+ }
205+ }
206+ }
207+
208+ return updatedBytes
209+ }
210+
211+ /* *
212+ * Applies specific metadata to an image for the saveImage function
213+ */
214+ private fun applySaveImageMetadata (
215+ imageBytes : ByteArray ,
216+ latLng : GpsCoordinates ? ,
217+ applyRotation : Boolean ,
218+ rotationDegrees : Int
219+ ): ByteArray {
220+ val dateUpdate: MetadataUpdate = MetadataUpdate .TakenDate (System .currentTimeMillis())
221+ var updatedBytes = Kim .update(bytes = imageBytes, dateUpdate)
222+
223+ if (applyRotation) {
224+ updatedBytes = Kim .update(bytes = updatedBytes, MetadataUpdate .Orientation (TiffOrientation .STANDARD ))
225+ } else {
226+ val tiffOrientation = calculateTiffOrientation(rotationDegrees)
227+ val orientationUpdate: MetadataUpdate = MetadataUpdate .Orientation (tiffOrientation)
228+ updatedBytes = Kim .update(bytes = updatedBytes, orientationUpdate)
229+ }
230+
231+ if (latLng != null ) {
232+ val gpsUpdate: MetadataUpdate = MetadataUpdate .GpsCoordinates (latLng)
233+ updatedBytes = Kim .update(bytes = updatedBytes, gpsUpdate)
234+ }
235+
236+ return updatedBytes
237+ }
238+
151239 @OptIn(ExperimentalTime ::class )
152240 suspend fun saveImage (
153241 image : CapturedImage ,
@@ -172,53 +260,9 @@ class SecureImageManager(
172260 rawSensorBitmap = rawSensorBitmap.rotate(image.rotationDegrees)
173261 }
174262
175- FileOutputStream (tempFile).use { outputStream ->
176- rawSensorBitmap.compress(CompressFormat .JPEG , quality, outputStream)
177- }
178-
179- val dateUpdate: MetadataUpdate = MetadataUpdate .TakenDate (System .currentTimeMillis())
180- var updatedBytes = Kim .update(bytes = tempFile.readBytes(), dateUpdate)
181-
182- if (applyRotation) {
183- updatedBytes = Kim .update(bytes = updatedBytes, MetadataUpdate .Orientation (TiffOrientation .STANDARD ))
184- } else {
185- val tiffOrientation = calculateTiffOrientation(image.rotationDegrees)
186- val orientationUpdate: MetadataUpdate = MetadataUpdate .Orientation (tiffOrientation)
187- updatedBytes = Kim .update(bytes = updatedBytes, orientationUpdate)
188- }
189-
190- if (latLng != null ) {
191- val gpsUpdate: MetadataUpdate = MetadataUpdate .GpsCoordinates (latLng)
192- updatedBytes = Kim .update(bytes = updatedBytes, gpsUpdate)
193- }
194-
195- tempFile.writeBytes(updatedBytes)
196-
197- // val thumbnailBitmap = ThumbnailUtils.createImageThumbnail(photoFile, Size(640, 480), null)
198- // val thumbnailBytes = thumbnailBitmap.let { bitmap ->
199- // ByteArrayOutputStream().use { outputStream ->
200- // bitmap.compress(CompressFormat.JPEG, quality, outputStream)
201- // outputStream.toByteArray()
202- // }
203- // }
204- //
205- // photoFile.writeBytes(
206- // Kim.updateThumbnail(
207- // bytes = photoFile.readBytes(),
208- // thumbnailBytes = thumbnailBytes
209- // )
210- // )
211-
212- val pin = authorizationManager.securityPin ? : throw IllegalStateException (" No Security PIN" )
213-
214- encryptToFile(
215- plainPin = pin.plainPin,
216- hashedPin = pin.hashedPin,
217- plain = tempFile.readBytes(),
218- targetFile = tempFile,
219- )
220-
221- tempFile.renameTo(photoFile)
263+ val jpgBytes = compressBitmapToJpeg(rawSensorBitmap, quality)
264+ val updatedBytes = applySaveImageMetadata(jpgBytes, latLng, applyRotation, image.rotationDegrees)
265+ encryptAndSaveImage(updatedBytes, tempFile, photoFile)
222266
223267 return photoFile
224268 }
@@ -229,49 +273,12 @@ class SecureImageManager(
229273 quality : Int = 90
230274 ): PhotoDef {
231275 val jpgBytes = decryptJpg(photoDef)
232-
233- val metadata = Kim .readMetadata(jpgBytes)
234-
235- val newJpgBytes = ByteArrayOutputStream ().use { outputStream ->
236- bitmap.compress(CompressFormat .JPEG , quality, outputStream)
237- outputStream.toByteArray()
238- }
276+ val updatedBytes = processImageWithMetadata(bitmap, jpgBytes, quality)
239277
240278 val dir = getGalleryDirectory()
241279 val tempFile = File (dir, " ${photoDef.photoName} .tmp" )
242280
243- var updatedBytes = newJpgBytes
244-
245- if (metadata != null ) {
246- // Apply all existing metadata to the new image
247- metadata.convertToPhotoMetadata().let { photoMetadata ->
248- if (photoMetadata.takenDate != null ) {
249- updatedBytes = Kim .update(bytes = updatedBytes, MetadataUpdate .TakenDate (photoMetadata.takenDate!! ))
250- }
251-
252- if (photoMetadata.orientation != null ) {
253- updatedBytes =
254- Kim .update(bytes = updatedBytes, MetadataUpdate .Orientation (photoMetadata.orientation!! ))
255- }
256-
257- if (photoMetadata.gpsCoordinates != null ) {
258- updatedBytes =
259- Kim .update(bytes = updatedBytes, MetadataUpdate .GpsCoordinates (photoMetadata.gpsCoordinates!! ))
260- }
261- }
262- }
263-
264- tempFile.writeBytes(updatedBytes)
265-
266- val pin = authorizationManager.securityPin ? : throw IllegalStateException (" No Security PIN" )
267- encryptToFile(
268- plainPin = pin.plainPin,
269- hashedPin = pin.hashedPin,
270- plain = tempFile.readBytes(),
271- targetFile = tempFile,
272- )
273-
274- tempFile.renameTo(photoDef.photoFile)
281+ encryptAndSaveImage(updatedBytes, tempFile, photoDef.photoFile)
275282
276283 thumbnailCache.evictThumbnail(photoDef)
277284 getThumbnail(photoDef).delete()
@@ -285,54 +292,14 @@ class SecureImageManager(
285292 quality : Int = 90
286293 ): PhotoDef {
287294 val jpgBytes = decryptJpg(photoDef)
288-
289- val metadata = Kim .readMetadata(jpgBytes)
290-
291- val newJpgBytes = ByteArrayOutputStream ().use { outputStream ->
292- bitmap.compress(CompressFormat .JPEG , quality, outputStream)
293- outputStream.toByteArray()
294- }
295+ val updatedBytes = processImageWithMetadata(bitmap, jpgBytes, quality)
295296
296297 val dir = getGalleryDirectory()
297-
298- // Create a new unique filename based on the current time
299- val dateFormat = SimpleDateFormat (" yyyyMMdd_HHmmss_SS" , Locale .US )
300- val newImageName = " photo_" + dateFormat.format(Date ()) + " .jpg"
298+ val newImageName = photoDef.photoName.substringBefore(" .jpg" ) + " _cp.jpg"
301299 val newPhotoFile = File (dir, newImageName)
302300 val tempFile = File (dir, " $newImageName .tmp" )
303301
304- var updatedBytes = newJpgBytes
305-
306- if (metadata != null ) {
307- // Apply all existing metadata to the new image
308- metadata.convertToPhotoMetadata().let { photoMetadata ->
309- if (photoMetadata.takenDate != null ) {
310- updatedBytes = Kim .update(bytes = updatedBytes, MetadataUpdate .TakenDate (photoMetadata.takenDate!! ))
311- }
312-
313- if (photoMetadata.orientation != null ) {
314- updatedBytes =
315- Kim .update(bytes = updatedBytes, MetadataUpdate .Orientation (photoMetadata.orientation!! ))
316- }
317-
318- if (photoMetadata.gpsCoordinates != null ) {
319- updatedBytes =
320- Kim .update(bytes = updatedBytes, MetadataUpdate .GpsCoordinates (photoMetadata.gpsCoordinates!! ))
321- }
322- }
323- }
324-
325- tempFile.writeBytes(updatedBytes)
326-
327- val pin = authorizationManager.securityPin ? : throw IllegalStateException (" No Security PIN" )
328- encryptToFile(
329- plainPin = pin.plainPin,
330- hashedPin = pin.hashedPin,
331- plain = tempFile.readBytes(),
332- targetFile = tempFile,
333- )
334-
335- tempFile.renameTo(newPhotoFile)
302+ encryptAndSaveImage(updatedBytes, tempFile, newPhotoFile)
336303
337304 // Create a new PhotoDef for the new file
338305 val newPhotoDef = PhotoDef (
0 commit comments