2 * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser 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.
11 * Privacy Browser 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.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.asynctasks;
22 import android.app.Activity;
23 import android.database.Cursor;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.net.Uri;
27 import android.os.AsyncTask;
28 import android.os.Build;
29 import android.provider.OpenableColumns;
31 import com.google.android.material.snackbar.Snackbar;
33 import com.stoutner.privacybrowser.R;
34 import com.stoutner.privacybrowser.views.NestedScrollWebView;
36 import java.io.ByteArrayOutputStream;
37 import java.io.OutputStream;
38 import java.lang.ref.WeakReference;
40 public class SaveWebpageImage extends AsyncTask<Void, Void, String> {
41 // Declare the weak references.
42 private final WeakReference<Activity> activityWeakReference;
43 private final WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
45 // Declare the class constants.
46 private final String SUCCESS = "Success";
48 // Declare the class variables.
49 private Snackbar savingImageSnackbar;
50 private Bitmap webpageBitmap;
51 private final Uri fileUri;
52 private final String fileNameString;
54 // The public constructor.
55 public SaveWebpageImage(Activity activity, Uri fileUri, NestedScrollWebView nestedScrollWebView) {
56 // Populate the weak references.
57 activityWeakReference = new WeakReference<>(activity);
58 nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
60 // Populate the class variables.
61 this.fileUri = fileUri;
63 // Query the exact file name if the API >= 26.
64 if (Build.VERSION.SDK_INT >= 26) {
65 // Get a cursor from the content resolver.
66 Cursor contentResolverCursor = activity.getContentResolver().query(fileUri, null, null, null);
68 // Move to the first row.
69 contentResolverCursor.moveToFirst();
71 // Get the file name from the cursor.
72 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
75 contentResolverCursor.close();
77 // Use the file URI last path segment as the file name string.
78 fileNameString = fileUri.getLastPathSegment();
82 // `onPreExecute()` operates on the UI thread.
84 protected void onPreExecute() {
85 // Get handles for the activity and the nested scroll WebView.
86 Activity activity = activityWeakReference.get();
87 NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
89 // Abort if the activity or the nested scroll WebView is gone.
90 if ((activity == null) || activity.isFinishing() || nestedScrollWebView == null) {
94 // Create a saving image snackbar.
95 savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image) + " " + fileNameString, Snackbar.LENGTH_INDEFINITE);
97 // Display the saving image snackbar.
98 savingImageSnackbar.show();
100 // Create a webpage bitmap. Once the Minimum API >= 26 Bitmap.Config.RBGA_F16 can be used instead of ARGB_8888. The nested scroll WebView commands must be run on the UI thread.
101 webpageBitmap = Bitmap.createBitmap(nestedScrollWebView.getHorizontalScrollRange(), nestedScrollWebView.getVerticalScrollRange(), Bitmap.Config.ARGB_8888);
104 Canvas webpageCanvas = new Canvas(webpageBitmap);
106 // Draw the current webpage onto the bitmap. The nested scroll WebView commands must be run on the UI thread.
107 nestedScrollWebView.draw(webpageCanvas);
111 protected String doInBackground(Void... Void) {
112 // Get a handle for the activity.
113 Activity activity = activityWeakReference.get();
115 // Abort if the activity is gone.
116 if ((activity == null) || activity.isFinishing()) {
120 // Create a webpage PNG byte array output stream.
121 ByteArrayOutputStream webpageByteArrayOutputStream = new ByteArrayOutputStream();
123 // 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.
124 webpageBitmap.compress(Bitmap.CompressFormat.PNG, 0, webpageByteArrayOutputStream);
126 // Create a file creation disposition string.
127 String fileCreationDisposition = SUCCESS;
130 // Create an image file output stream.
131 OutputStream imageFileOutputStream = activity.getContentResolver().openOutputStream(fileUri);
133 // Write the webpage image to the image file.
134 webpageByteArrayOutputStream.writeTo(imageFileOutputStream);
135 } catch (Exception exception) {
136 // Store the error in the file creation disposition string.
137 fileCreationDisposition = exception.toString();
140 // Return the file creation disposition string.
141 return fileCreationDisposition;
144 // `onPostExecute()` operates on the UI thread.
146 protected void onPostExecute(String fileCreationDisposition) {
147 // Get handles for the weak references.
148 Activity activity = activityWeakReference.get();
149 NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
151 // Abort if the activity is gone.
152 if ((activity == null) || activity.isFinishing()) {
156 // Dismiss the saving image snackbar.
157 savingImageSnackbar.dismiss();
159 // Display a file creation disposition snackbar.
160 if (fileCreationDisposition.equals(SUCCESS)) {
161 // Display the image saved snackbar.
162 Snackbar.make(nestedScrollWebView, activity.getString(R.string.image_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
164 // Display the file saving error.
165 Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + " " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();