]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveAboutVersionImageCoroutine.kt
Bump the minimum API to 26. https://redmine.stoutner.com/issues/1163
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / coroutines / SaveAboutVersionImageCoroutine.kt
1 /*
2  * Copyright 2020-2022, 2024 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.provider.OpenableColumns
27 import android.widget.LinearLayout
28
29 import com.google.android.material.snackbar.Snackbar
30
31 import com.stoutner.privacybrowser.R
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.Dispatchers
34 import kotlinx.coroutines.launch
35 import kotlinx.coroutines.withContext
36
37 import java.io.ByteArrayOutputStream
38 import java.lang.Exception
39
40 // Declare the class constants.
41 private const val SUCCESS = "Success"
42
43 object SaveAboutVersionImageCoroutine {
44     fun saveImage(activity: Activity, fileUri: Uri, aboutVersionLinearLayout: LinearLayout) {
45         // Save the image using a coroutine.
46         CoroutineScope(Dispatchers.Main).launch {
47             // Create a saving image snackbar.
48             val savingImageSnackbar: Snackbar
49
50             // Process the image on the IO thread.
51             withContext(Dispatchers.IO) {
52                 // Instantiate a file name string.
53                 val fileNameString: String
54
55                 // Get a cursor from the content resolver.
56                 val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)
57
58                 // Get the file display name if the content resolve cursor is not null.
59                 if (contentResolverCursor != null) {  // The content resolve cursor is not null.
60                     // Move to the first row.
61                     contentResolverCursor.moveToFirst()
62
63                     // Get the file name from the cursor.
64                     fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
65
66                     // Close the cursor.
67                     contentResolverCursor.close()
68                 } else {  // The content resolve cursor is null.
69                     // Use the URI last path segment as the file name string.
70                     fileNameString = fileUri.lastPathSegment.toString()
71                 }
72
73                 // Use the main thread to display a snackbar.
74                 withContext(Dispatchers.Main) {
75                     // Create a saving image snackbar.
76                     savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE)
77
78                     // Display the saving image snackbar.
79                     savingImageSnackbar.show()
80                 }
81
82                 // Create an empty bitmap with the dimensions of the linear layout.  Once the minimum API >= 33 Bitmap.Config.RGBA_1010102 can be used instead of RBGA_F16.
83                 val aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.width, aboutVersionLinearLayout.height, Bitmap.Config.RGBA_F16)
84
85                 // Create a canvas.
86                 val aboutVersionCanvas = Canvas(aboutVersionBitmap)
87
88                 // Use the main thread to interact with the linear layout.
89                 withContext(Dispatchers.Main) {
90                     // Draw the current about version onto the bitmap.  It might be possible to do this with PixelCopy, but I am not sure that would be any better.
91                     aboutVersionLinearLayout.draw(aboutVersionCanvas)
92                 }
93
94                 // Create an about version PNG byte array output stream.
95                 val aboutVersionByteArrayOutputStream = ByteArrayOutputStream()
96
97                 // Convert the bitmap to a PNG.  `0` is for lossless compression (the only option for a PNG).  This compression takes a long time.
98                 // Once the minimum API >= 30 this can be replaced with WEBP_LOSSLESS.
99                 aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream)
100
101                 // Create a file creation disposition string.
102                 var fileCreationDisposition = SUCCESS
103
104                 // Write the image inside a try block to capture any write exceptions.
105                 try {
106                     // Open an output stream.
107                     val outputStream = activity.contentResolver.openOutputStream(fileUri)!!
108
109                     // Write the webpage image to the image file.
110                     aboutVersionByteArrayOutputStream.writeTo(outputStream)
111
112                     // Close the output stream.
113                     outputStream.close()
114                 } catch (exception: Exception) {
115                     // Store the error in the file creation disposition string.
116                     fileCreationDisposition = exception.toString()
117                 }
118
119                 // Use the main thread to update the snackbars.
120                 withContext(Dispatchers.Main) {
121                     // Dismiss the saving image snackbar.
122                     savingImageSnackbar.dismiss()
123
124                     // Display a file creation disposition snackbar.
125                     if (fileCreationDisposition == SUCCESS) {
126                         // Create a file saved snackbar.
127                         Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
128                     } else {
129                         Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show()
130                     }
131                 }
132             }
133         }
134     }
135 }