/* * Copyright 2020-2022 Soren Stoutner . * * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Privacy Browser Android is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Privacy Browser Android. If not, see . */ package com.stoutner.privacybrowser.coroutines import android.app.Activity import android.graphics.Bitmap import android.graphics.Canvas import android.net.Uri import android.os.Build import android.provider.OpenableColumns import android.widget.LinearLayout import com.google.android.material.snackbar.Snackbar import com.stoutner.privacybrowser.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.lang.Exception // Declare the class constants. private const val SUCCESS = "Success" object SaveAboutVersionImageCoroutine { fun saveImage(activity: Activity, fileUri: Uri, aboutVersionLinearLayout: LinearLayout) { // Save the image using a coroutine. CoroutineScope(Dispatchers.Main).launch { // Create a saving image snackbar. val savingImageSnackbar: Snackbar // Process the image on the IO thread. withContext(Dispatchers.IO) { // Instantiate a file name string. val fileNameString: String // Query the exact file name if the API >= 26. if (Build.VERSION.SDK_INT >= 26) { // The API >= 26. // Get a cursor from the content resolver. val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null) // Get the file display name if the content resolve cursor is not null. if (contentResolverCursor != null) { // The content resolve cursor is not null. // Move to the first row. contentResolverCursor.moveToFirst() // Get the file name from the cursor. fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) // Close the cursor. contentResolverCursor.close() } else { // The content resolve cursor is null. // Use the URI last path segment as the file name string. fileNameString = fileUri.lastPathSegment.toString() } } else { // The API is < 26. // Use the URI last path segment as the file name string. fileNameString = fileUri.lastPathSegment.toString() } // Use the main thread to display a snackbar. withContext(Dispatchers.Main) { // Create a saving image snackbar. savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE) // Display the saving image snackbar. savingImageSnackbar.show() } // Create the about version bitmap. This can be replaced by PixelCopy once the minimum API >= 26. // 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. val aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.width, aboutVersionLinearLayout.height, Bitmap.Config.ARGB_8888) // Create a canvas. val aboutVersionCanvas = Canvas(aboutVersionBitmap) // Use the main thread to interact with the linear layout. withContext(Dispatchers.Main) { // Draw the current about version onto the bitmap. aboutVersionLinearLayout.draw(aboutVersionCanvas) } // Create an about version PNG byte array output stream. val aboutVersionByteArrayOutputStream = ByteArrayOutputStream() // Convert the bitmap to a PNG. `0` is for lossless compression (the only option for a PNG). This compression takes a long time. // Once the minimum API >= 30 this could be replaced with WEBP_LOSSLESS. aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream) // Create a file creation disposition string. var fileCreationDisposition = SUCCESS // Write the image inside a try block to capture any write exceptions. try { // Open an output stream. val outputStream = activity.contentResolver.openOutputStream(fileUri)!! // Write the webpage image to the image file. aboutVersionByteArrayOutputStream.writeTo(outputStream) // Close the output stream. outputStream.close() } catch (exception: Exception) { // Store the error in the file creation disposition string. fileCreationDisposition = exception.toString() } // Use the main thread to update the snackbars. withContext(Dispatchers.Main) { // Dismiss the saving image snackbar. savingImageSnackbar.dismiss() // Display a file creation disposition snackbar. if (fileCreationDisposition == SUCCESS) { // Create a file saved snackbar. Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show() } else { Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show() } } } } } }