]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveWebpageImage.java
f4b43d6c2d00d89cb1bef464e15b6bb3e502a1e4
[PrivacyBrowserAndroid.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.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;
30
31 import com.google.android.material.snackbar.Snackbar;
32
33 import com.stoutner.privacybrowser.R;
34 import com.stoutner.privacybrowser.views.NestedScrollWebView;
35
36 import java.io.ByteArrayOutputStream;
37 import java.io.OutputStream;
38 import java.lang.ref.WeakReference;
39
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;
44
45     // Declare the class constants.
46     private final String SUCCESS = "Success";
47
48     // Declare the class variables.
49     private Snackbar savingImageSnackbar;
50     private Bitmap webpageBitmap;
51     private final Uri fileUri;
52     private final String fileNameString;
53
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);
59
60         // Populate the class variables.
61         this.fileUri = fileUri;
62
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);
67
68             // Move to the first row.
69             contentResolverCursor.moveToFirst();
70
71             // Get the file name from the cursor.
72             fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
73
74             // Close the cursor.
75             contentResolverCursor.close();
76         } else {
77             // Use the file URI last path segment as the file name string.
78             fileNameString = fileUri.getLastPathSegment();
79         }
80     }
81
82     // `onPreExecute()` operates on the UI thread.
83     @Override
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();
88
89         // Abort if the activity or the nested scroll WebView is gone.
90         if ((activity == null) || activity.isFinishing() || nestedScrollWebView == null) {
91             return;
92         }
93
94         // Create a saving image snackbar.
95         savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image) + "  " + fileNameString, Snackbar.LENGTH_INDEFINITE);
96
97         // Display the saving image snackbar.
98         savingImageSnackbar.show();
99
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);
102
103         // Create a canvas.
104         Canvas webpageCanvas = new Canvas(webpageBitmap);
105
106         // Draw the current webpage onto the bitmap.  The nested scroll WebView commands must be run on the UI thread.
107         nestedScrollWebView.draw(webpageCanvas);
108     }
109
110     @Override
111     protected String doInBackground(Void... Void) {
112         // Get a handle for the activity.
113         Activity activity = activityWeakReference.get();
114
115         // Abort if the activity is gone.
116         if ((activity == null) || activity.isFinishing()) {
117             return "";
118         }
119
120         // Create a webpage PNG byte array output stream.
121         ByteArrayOutputStream webpageByteArrayOutputStream = new ByteArrayOutputStream();
122
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);
125
126         // Create a file creation disposition string.
127         String fileCreationDisposition = SUCCESS;
128
129         try {
130             // Create an image file output stream.
131             OutputStream imageFileOutputStream = activity.getContentResolver().openOutputStream(fileUri);
132
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();
138         }
139
140         // Return the file creation disposition string.
141         return fileCreationDisposition;
142     }
143
144     // `onPostExecute()` operates on the UI thread.
145     @Override
146     protected void onPostExecute(String fileCreationDisposition) {
147         // Get handles for the weak references.
148         Activity activity = activityWeakReference.get();
149         NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
150
151         // Abort if the activity is gone.
152         if ((activity == null) || activity.isFinishing()) {
153             return;
154         }
155
156         // Dismiss the saving image snackbar.
157         savingImageSnackbar.dismiss();
158
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();
163         } else {
164             // Display the file saving error.
165             Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + "  " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
166         }
167     }
168 }