]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveWebpageImage.java
Allow saving of `data:` URLs. https://redmine.stoutner.com/issues/596
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / asynctasks / SaveWebpageImage.java
1 /*
2  * Copyright © 2019-2020 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.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Bitmap;
27 import android.graphics.Canvas;
28 import android.net.Uri;
29 import android.os.AsyncTask;
30 import android.os.Build;
31 import android.view.View;
32
33 import androidx.core.content.FileProvider;
34
35 import com.google.android.material.snackbar.Snackbar;
36
37 import com.stoutner.privacybrowser.R;
38 import com.stoutner.privacybrowser.views.NestedScrollWebView;
39
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.lang.ref.WeakReference;
44
45 public class SaveWebpageImage extends AsyncTask<Void, Void, String> {
46     // Declare the weak references.
47     private WeakReference<Context> contextWeakReference;
48     private WeakReference<Activity> activityWeakReference;
49     private WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
50
51     // Declare the class constants.
52     private final String SUCCESS = "Success";
53
54     // Declare the class variables.
55     private Snackbar savingImageSnackbar;
56     private Bitmap webpageBitmap;
57     private String filePathString;
58
59     // The public constructor.
60     public SaveWebpageImage(Context context, Activity activity, String filePathString, NestedScrollWebView nestedScrollWebView) {
61         // Populate the weak references.
62         contextWeakReference = new WeakReference<>(context);
63         activityWeakReference = new WeakReference<>(activity);
64         nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
65
66         // Populate the class variables.
67         this.filePathString = filePathString;
68     }
69
70     // `onPreExecute()` operates on the UI thread.
71     @Override
72     protected void onPreExecute() {
73         // Get handles for the activity and the nested scroll WebView.
74         Activity activity = activityWeakReference.get();
75         NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
76
77         // Abort if the activity or the nested scroll WebView is gone.
78         if ((activity == null) || activity.isFinishing() || nestedScrollWebView == null) {
79             return;
80         }
81
82         // Create a saving image snackbar.
83         savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image) + "  " + filePathString, Snackbar.LENGTH_INDEFINITE);
84
85         // Display the saving image snackbar.
86         savingImageSnackbar.show();
87
88         // 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.
89         webpageBitmap = Bitmap.createBitmap(nestedScrollWebView.getHorizontalScrollRange(), nestedScrollWebView.getVerticalScrollRange(), Bitmap.Config.ARGB_8888);
90
91         // Create a canvas.
92         Canvas webpageCanvas = new Canvas(webpageBitmap);
93
94         // Draw the current webpage onto the bitmap.  The nested scroll WebView commands must be run on the UI thread.
95         nestedScrollWebView.draw(webpageCanvas);
96     }
97
98     @Override
99     protected String doInBackground(Void... Void) {
100         // Get a handle for the activity.
101         Activity activity = activityWeakReference.get();
102
103         // Abort if the activity is gone.
104         if ((activity == null) || activity.isFinishing()) {
105             return "";
106         }
107
108         // Create a webpage PNG byte array output stream.
109         ByteArrayOutputStream webpageByteArrayOutputStream = new ByteArrayOutputStream();
110
111         // 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.
112         webpageBitmap.compress(Bitmap.CompressFormat.PNG, 0, webpageByteArrayOutputStream);
113
114         // Get a file for the image.
115         File imageFile = new File(filePathString);
116
117         // Delete the current file if it exists.
118         if (imageFile.exists()) {
119             //noinspection ResultOfMethodCallIgnored
120             imageFile.delete();
121         }
122
123         // Create a file creation disposition string.
124         String fileCreationDisposition = SUCCESS;
125
126         try {
127             // Create an image file output stream.
128             FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
129
130             // Write the webpage image to the image file.
131             webpageByteArrayOutputStream.writeTo(imageFileOutputStream);
132
133             // Create a media scanner intent, which adds items like pictures to Android's recent file list.
134             Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
135
136             // Add the URI to the media scanner intent.
137             mediaScannerIntent.setData(Uri.fromFile(imageFile));
138
139             // Make it so.
140             activity.sendBroadcast(mediaScannerIntent);
141         } catch (Exception exception) {
142             // Store the error in the file creation disposition string.
143             fileCreationDisposition = exception.toString();
144         }
145
146         // Return the file creation disposition string.
147         return fileCreationDisposition;
148     }
149
150     // `onPostExecute()` operates on the UI thread.
151     @Override
152     protected void onPostExecute(String fileCreationDisposition) {
153         // Get handles for the weak references.
154         Context context = contextWeakReference.get();
155         Activity activity = activityWeakReference.get();
156         NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
157
158         // Abort if the activity is gone.
159         if ((activity == null) || activity.isFinishing()) {
160             return;
161         }
162
163         // Dismiss the saving image snackbar.
164         savingImageSnackbar.dismiss();
165
166         // Display a file creation disposition snackbar.
167         if (fileCreationDisposition.equals(SUCCESS)) {
168             // Create a file saved snackbar.
169             Snackbar imageSavedSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.file_saved) + "  " + filePathString, Snackbar.LENGTH_SHORT);
170
171             // Add an open action.
172             imageSavedSnackbar.setAction(R.string.open, (View view) -> {
173                 // Get a file for the file path string.
174                 File file = new File(filePathString);
175
176                 // Declare a file URI variable.
177                 Uri fileUri;
178
179                 // Get the URI for the file according to the Android version.
180                 if (Build.VERSION.SDK_INT >= 24) {  // Use a file provider.
181                     fileUri = FileProvider.getUriForFile(context, activity.getString(R.string.file_provider), file);
182                 } else {  // Get the raw file path URI.
183                     fileUri = Uri.fromFile(file);
184                 }
185
186                 // Get a handle for the content resolver.
187                 ContentResolver contentResolver = context.getContentResolver();
188
189                 // Create an open intent with `ACTION_VIEW`.
190                 Intent openIntent = new Intent(Intent.ACTION_VIEW);
191
192                 // Autodetect the MIME type.
193                 openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
194
195                 // Allow the app to read the file URI.
196                 openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
197
198                 // Show the chooser.
199                 activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
200             });
201
202             // Show the image saved snackbar.
203             imageSavedSnackbar.show();
204         } else {
205             // Display the file saving error.
206             Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + "  " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
207         }
208     }
209 }