]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveAboutVersionImageCoroutine.kt
Convert five AsyncTasks to Kotlin. https://redmine.stoutner.com/issues/931
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / coroutines / SaveAboutVersionImageCoroutine.kt
1 /*
2  * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.coroutines
21
22 import android.app.Activity
23 import android.graphics.Bitmap
24 import android.graphics.Canvas
25 import android.net.Uri
26 import android.os.Build
27 import android.provider.OpenableColumns
28 import android.widget.LinearLayout
29
30 import com.google.android.material.snackbar.Snackbar
31
32 import com.stoutner.privacybrowser.R
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.Dispatchers
35 import kotlinx.coroutines.launch
36 import kotlinx.coroutines.withContext
37
38 import java.io.ByteArrayOutputStream
39 import java.lang.Exception
40
41 // Declare the class constants.
42 private const val SUCCESS = "Success"
43
44 object SaveAboutVersionImageCoroutine {
45     fun saveImage(activity: Activity, fileUri: Uri, aboutVersionLinearLayout: LinearLayout) {
46         // Save the image using a coroutine.
47         CoroutineScope(Dispatchers.Main).launch {
48             // Create a saving image snackbar.
49             val savingImageSnackbar: Snackbar
50
51             // Process the image on the IO thread.
52             withContext(Dispatchers.IO) {
53                 // Instantiate a file name string.
54                 val fileNameString: String
55
56                 // Query the exact file name if the API >= 26.
57                 if (Build.VERSION.SDK_INT >= 26) {  // The API >= 26.
58                     // Get a cursor from the content resolver.
59                     val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)
60
61                     // Get the file display name if the content resolve cursor is not null.
62                     if (contentResolverCursor != null) {  // The content resolve cursor is not null.
63                         // Move to the first row.
64                         contentResolverCursor.moveToFirst()
65
66                         // Get the file name from the cursor.
67                         fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
68
69                         // Close the cursor.
70                         contentResolverCursor.close()
71                     } else {  // The content resolve cursor is null.
72                         // Use the URI last path segment as the file name string.
73                         fileNameString = fileUri.lastPathSegment.toString()
74                     }
75                 } else {  // The API is < 26.
76                     // Use the URI last path segment as the file name string.
77                     fileNameString = fileUri.lastPathSegment.toString()
78                 }
79
80                 // Use the main thread to display a snackbar.
81                 withContext(Dispatchers.Main) {
82                     // Create a saving image snackbar.
83                     savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE)
84
85                     // Display the saving image snackbar.
86                     savingImageSnackbar.show()
87                 }
88
89                 // Create the about version bitmap.  This can be replaced by PixelCopy once the minimum API >= 26.
90                 // Once the Minimum API >= 26 Bitmap.Config.RBGA_F16 can be used instead of ARGB_8888.  The linear layout commands must be run on the UI thread.
91                 val aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.width, aboutVersionLinearLayout.height, Bitmap.Config.ARGB_8888)
92
93                 // Create a canvas.
94                 val aboutVersionCanvas = Canvas(aboutVersionBitmap)
95
96                 // Use the main thread to interact with the linear layout.
97                 withContext(Dispatchers.Main) {
98                     // Draw the current about version onto the bitmap.
99                     aboutVersionLinearLayout.draw(aboutVersionCanvas)
100                 }
101
102                 // Create an about version PNG byte array output stream.
103                 val aboutVersionByteArrayOutputStream = ByteArrayOutputStream()
104
105                 // Convert the bitmap to a PNG.  `0` is for lossless compression (the only option for a PNG).  This compression takes a long time.
106                 // Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS.
107                 aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream)
108
109                 // Create a file creation disposition string.
110                 var fileCreationDisposition = SUCCESS
111
112                 // Write the image inside a try block to capture any write exceptions.
113                 try {
114                     // Open an output stream.
115                     val outputStream = activity.contentResolver.openOutputStream(fileUri)!!
116
117                     // Write the webpage image to the image file.
118                     aboutVersionByteArrayOutputStream.writeTo(outputStream)
119
120                     // Close the output stream.
121                     outputStream.close()
122                 } catch (exception: Exception) {
123                     // Store the error in the file creation disposition string.
124                     fileCreationDisposition = exception.toString()
125                 }
126
127                 // Use the main thread to update the snackbars.
128                 withContext(Dispatchers.Main) {
129                     // Dismiss the saving image snackbar.
130                     savingImageSnackbar.dismiss()
131
132                     // Display a file creation disposition snackbar.
133                     if (fileCreationDisposition == SUCCESS) {
134                         // Create a file saved snackbar.
135                         Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
136                     } else {
137                         Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show()
138                     }
139                 }
140             }
141         }
142     }
143 }