1 /* 2 * Copyright (C) 2023 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.google.android.wallpaper.weathereffects.data.repository 18 19 import android.content.Context 20 import android.graphics.Bitmap 21 import android.graphics.BitmapFactory 22 import android.util.Log 23 import kotlinx.coroutines.CoroutineDispatcher 24 import kotlinx.coroutines.Dispatchers 25 import kotlinx.coroutines.withContext 26 27 object WallpaperFileUtils { 28 /** 29 * Exports the [bitmap] to an image file in local storage. 30 * This method may take several seconds to complete, so it should be called from 31 * a background [dispatcher]. 32 * 33 * @param context the [Context] of the caller 34 * @param bitmap the source to be exported 35 * @param dispatcher the dispatcher to run within. 36 * @return `true` when exported successfully 37 */ exportnull38 suspend fun export( 39 context: Context, 40 fileName: String, 41 bitmap: Bitmap, 42 dispatcher: CoroutineDispatcher = Dispatchers.IO, 43 ): Boolean { 44 val protectedContext = asProtectedContext(context) 45 return try { 46 withContext(dispatcher) { 47 var success: Boolean 48 protectedContext 49 .openFileOutput(fileName, Context.MODE_PRIVATE) 50 .use { 51 success = bitmap.compress( 52 Bitmap.CompressFormat.PNG, 53 /* quality = */ 100, 54 it, 55 ) 56 if (!success) { 57 Log.e(TAG, "Failed to write the bitmap to local storage") 58 } else { 59 Log.i(TAG, "Wrote bitmap to local storage. filename: $fileName") 60 } 61 } 62 success 63 } 64 } catch (e: Exception) { 65 Log.e(TAG, "Failed to export", e) 66 false 67 } 68 } 69 70 /** 71 * Imports the bitmap from an absolute path. This method may take several seconds to complete, 72 * so it should be called from a background [dispatcher]. 73 * 74 * @param absolutePath the absolute file path of the bitmap to be imported. 75 * @param dispatcher the dispatcher to run within. 76 * @return the imported wallpaper bitmap, or `null` if importing failed. 77 */ importBitmapFromAbsolutePathnull78 suspend fun importBitmapFromAbsolutePath( 79 absolutePath: String, 80 dispatcher: CoroutineDispatcher = Dispatchers.IO, 81 ): Bitmap? { 82 return try { 83 withContext(dispatcher) { 84 val bitmap = BitmapFactory.decodeFile(absolutePath) 85 if (bitmap == null) { 86 Log.e(TAG, "Failed to decode the bitmap") 87 } 88 bitmap 89 } 90 } catch (e: Exception) { 91 Log.e(TAG, "Failed to import the image", e) 92 null 93 } 94 } 95 96 /** 97 * Imports the bitmap from local storage. This method may take several seconds to complete, 98 * so it should be called from a background [dispatcher]. 99 * 100 * @param fileName name of the bitmap file in local storage. 101 * @param dispatcher the dispatcher to run within. 102 * @return the imported wallpaper bitmap, or `null` if importing failed. 103 */ importBitmapFromLocalStoragenull104 suspend fun importBitmapFromLocalStorage( 105 fileName: String, 106 context: Context, 107 dispatcher: CoroutineDispatcher = Dispatchers.IO, 108 ): Bitmap? { 109 return try { 110 withContext(dispatcher) { 111 val protectedContext = asProtectedContext(context) 112 val inputStream = protectedContext.openFileInput(fileName) 113 val bitmap = BitmapFactory.decodeStream(inputStream) 114 if (bitmap == null) { 115 Log.e(TAG, "Failed to decode the bitmap") 116 } 117 bitmap 118 } 119 } catch (e: Exception) { 120 Log.e(TAG, "Failed to import the image", e) 121 null 122 } 123 } 124 asProtectedContextnull125 private fun asProtectedContext(context: Context): Context { 126 return if (context.isDeviceProtectedStorage) { 127 context 128 } else { 129 context.createDeviceProtectedStorageContext() 130 } 131 } 132 133 private const val TAG = "WallpaperFileUtils" 134 const val FG_FILE_NAME = "fg_image" 135 const val BG_FILE_NAME = "bg_image" 136 } 137