2 * Copyright © 2019-2020 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.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;
33 import androidx.core.content.FileProvider;
35 import com.google.android.material.snackbar.Snackbar;
37 import com.stoutner.privacybrowser.R;
38 import com.stoutner.privacybrowser.views.NestedScrollWebView;
40 import java.io.ByteArrayOutputStream;
42 import java.io.FileOutputStream;
43 import java.lang.ref.WeakReference;
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;
51 // Declare the class constants.
52 private final String SUCCESS = "Success";
54 // Declare the class variables.
55 private Snackbar savingImageSnackbar;
56 private Bitmap webpageBitmap;
57 private String filePathString;
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);
66 // Populate the class variables.
67 this.filePathString = filePathString;
70 // `onPreExecute()` operates on the UI thread.
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();
77 // Abort if the activity or the nested scroll WebView is gone.
78 if ((activity == null) || activity.isFinishing() || nestedScrollWebView == null) {
82 // Create a saving image snackbar.
83 savingImageSnackbar = Snackbar.make(nestedScrollWebView, activity.getString(R.string.processing_image) + " " + filePathString, Snackbar.LENGTH_INDEFINITE);
85 // Display the saving image snackbar.
86 savingImageSnackbar.show();
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);
92 Canvas webpageCanvas = new Canvas(webpageBitmap);
94 // Draw the current webpage onto the bitmap. The nested scroll WebView commands must be run on the UI thread.
95 nestedScrollWebView.draw(webpageCanvas);
99 protected String doInBackground(Void... Void) {
100 // Get a handle for the activity.
101 Activity activity = activityWeakReference.get();
103 // Abort if the activity is gone.
104 if ((activity == null) || activity.isFinishing()) {
108 // Create a webpage PNG byte array output stream.
109 ByteArrayOutputStream webpageByteArrayOutputStream = new ByteArrayOutputStream();
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);
114 // Get a file for the image.
115 File imageFile = new File(filePathString);
117 // Delete the current file if it exists.
118 if (imageFile.exists()) {
119 //noinspection ResultOfMethodCallIgnored
123 // Create a file creation disposition string.
124 String fileCreationDisposition = SUCCESS;
127 // Create an image file output stream.
128 FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
130 // Write the webpage image to the image file.
131 webpageByteArrayOutputStream.writeTo(imageFileOutputStream);
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);
136 // Add the URI to the media scanner intent.
137 mediaScannerIntent.setData(Uri.fromFile(imageFile));
140 activity.sendBroadcast(mediaScannerIntent);
141 } catch (Exception exception) {
142 // Store the error in the file creation disposition string.
143 fileCreationDisposition = exception.toString();
146 // Return the file creation disposition string.
147 return fileCreationDisposition;
150 // `onPostExecute()` operates on the UI thread.
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();
158 // Abort if the activity is gone.
159 if ((activity == null) || activity.isFinishing()) {
163 // Dismiss the saving image snackbar.
164 savingImageSnackbar.dismiss();
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);
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);
176 // Declare a file URI variable.
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);
186 // Get a handle for the content resolver.
187 ContentResolver contentResolver = context.getContentResolver();
189 // Create an open intent with `ACTION_VIEW`.
190 Intent openIntent = new Intent(Intent.ACTION_VIEW);
192 // Autodetect the MIME type.
193 openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
195 // Allow the app to read the file URI.
196 openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
199 activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
202 // Show the image saved snackbar.
203 imageSavedSnackbar.show();
205 // Display the file saving error.
206 Snackbar.make(nestedScrollWebView, activity.getString(R.string.error_saving_file) + " " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();