27acbc5c6bcf015ebc447d6393d967c2cb7ba7e0
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / asynctasks / SaveWebpageImage.java
1 /*
2  * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.asynctasks;
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.AsyncTask;
27
28 import com.google.android.material.snackbar.Snackbar;
29
30 import com.stoutner.privacybrowser.R;
31 import com.stoutner.privacybrowser.views.NestedScrollWebView;
32
33 import java.io.ByteArrayOutputStream;
34 import java.io.OutputStream;
35 import java.lang.ref.WeakReference;
36
37 public class SaveWebpageImage extends AsyncTask<Void, Void, String> {
38     // Declare the weak references.
39     private final WeakReference<Activity> activityWeakReference;
40     private final WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
41
42     // Declare the class constants.
43     private final String SUCCESS = "Success";
44
45     // Declare the class variables.
46     private Snackbar savingImageSnackbar;
47     private Bitmap webpageBitmap;
48     private final String filePathString;
49
50     // The public constructor.
51     public SaveWebpageImage(Activity activity, String filePathString, NestedScrollWebView nestedScrollWebView) {
52         // Populate the weak references.
53         activityWeakReference = new WeakReference<>(activity);
54         nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
55
56         // Populate the class variables.
57         this.filePathString = filePathString;
58     }
59
60     // `onPreExecute()` operates on the UI thread.
61     @Override
62     protected void onPreExecute() {
63         // Get handles for the activity and the nested scroll WebView.
64         Activity activity = activityWeakReference.get();
65         NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
66
67         // Abort if the activity or the nested scroll WebView is gone.
68         if ((activity == null) || activity.isFinishing() || nestedScrollWebView == null) {
69             return;
70         }
71
72         // Create a saving image snackbar.
73         savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image) + "  " + filePathString, Snackbar.LENGTH_INDEFINITE);
74
75         // Display the saving image snackbar.
76         savingImageSnackbar.show();
77
78         // 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.
79         webpageBitmap = Bitmap.createBitmap(nestedScrollWebView.getHorizontalScrollRange(), nestedScrollWebView.getVerticalScrollRange(), Bitmap.Config.ARGB_8888);
80
81         // Create a canvas.
82         Canvas webpageCanvas = new Canvas(webpageBitmap);
83
84         // Draw the current webpage onto the bitmap.  The nested scroll WebView commands must be run on the UI thread.
85         nestedScrollWebView.draw(webpageCanvas);
86     }
87
88     @Override
89     protected String doInBackground(Void... Void) {
90         // Get a handle for the activity.
91         Activity activity = activityWeakReference.get();
92
93         // Abort if the activity is gone.
94         if ((activity == null) || activity.isFinishing()) {
95             return "";
96         }
97
98         // Create a webpage PNG byte array output stream.
99         ByteArrayOutputStream webpageByteArrayOutputStream = new ByteArrayOutputStream();
100
101         // 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.
102         webpageBitmap.compress(Bitmap.CompressFormat.PNG, 0, webpageByteArrayOutputStream);
103
104         // Create a file creation disposition string.
105         String fileCreationDisposition = SUCCESS;
106
107         try {
108             // Create an image file output stream.
109             OutputStream imageFileOutputStream = activity.getContentResolver().openOutputStream(Uri.parse(filePathString));
110
111             // Write the webpage image to the image file.
112             webpageByteArrayOutputStream.writeTo(imageFileOutputStream);
113         } catch (Exception exception) {
114             // Store the error in the file creation disposition string.
115             fileCreationDisposition = exception.toString();
116         }
117
118         // Return the file creation disposition string.
119         return fileCreationDisposition;
120     }
121
122     // `onPostExecute()` operates on the UI thread.
123     @Override
124     protected void onPostExecute(String fileCreationDisposition) {
125         // Get handles for the weak references.
126         Activity activity = activityWeakReference.get();
127         NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
128
129         // Abort if the activity is gone.
130         if ((activity == null) || activity.isFinishing()) {
131             return;
132         }
133
134         // Dismiss the saving image snackbar.
135         savingImageSnackbar.dismiss();
136
137         // Display a file creation disposition snackbar.
138         if (fileCreationDisposition.equals(SUCCESS)) {
139             // Display the file saved snackbar.
140             Snackbar.make(nestedScrollWebView, activity.getString(R.string.file_saved) + "  " + filePathString, Snackbar.LENGTH_SHORT).show();
141         } else {
142             // Display the file saving error.
143             Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + "  " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
144         }
145     }
146 }