2 * Copyright © 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;
32 import android.widget.LinearLayout;
34 import androidx.core.content.FileProvider;
36 import com.google.android.material.snackbar.Snackbar;
38 import com.stoutner.privacybrowser.R;
40 import java.io.ByteArrayOutputStream;
42 import java.io.FileOutputStream;
43 import java.lang.ref.WeakReference;
45 public class SaveAboutVersionImage extends AsyncTask<Void, Void, String> {
46 // Declare the weak references.
47 private WeakReference<Context> contextWeakReference;
48 private WeakReference<Activity> activityWeakReference;
49 private WeakReference<LinearLayout> aboutVersionLinearLayoutWeakReference;
51 // Declare the class constants.
52 private final String SUCCESS = "Success";
54 // Declare the class variables.
55 private Snackbar savingImageSnackbar;
56 private Bitmap aboutVersionBitmap;
57 private String filePathString;
59 // The public constructor.
60 public SaveAboutVersionImage(Context context, Activity activity, String filePathString, LinearLayout aboutVersionLinearLayout) {
61 // Populate the weak references.
62 contextWeakReference = new WeakReference<>(context);
63 activityWeakReference = new WeakReference<>(activity);
64 aboutVersionLinearLayoutWeakReference = new WeakReference<>(aboutVersionLinearLayout);
66 // Store 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 linear layout.
74 Activity activity = activityWeakReference.get();
75 LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
77 // Abort if the activity or the linear layout is gone.
78 if ((activity == null) || activity.isFinishing() || aboutVersionLinearLayout == null) {
82 // Create a saving image snackbar.
83 savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + " " + filePathString, Snackbar.LENGTH_INDEFINITE);
85 // Display the saving image snackbar.
86 savingImageSnackbar.show();
88 // Create the about version bitmap. This can be replaced by PixelCopy once the minimum API >= 26.
89 // Once the Minimum API >= 26 Bitmap.Config.RBGA_F16 can be used instead of ARGB_8888. The linear layout commands must be run on the UI thread.
90 aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.getWidth(), aboutVersionLinearLayout.getHeight(), Bitmap.Config.ARGB_8888);
93 Canvas aboutVersionCanvas = new Canvas(aboutVersionBitmap);
95 // Draw the current about version onto the bitmap. The linear layout commands must be run on the UI thread.
96 aboutVersionLinearLayout.draw(aboutVersionCanvas);
100 protected String doInBackground(Void... Void) {
101 // Get a handle for the activity.
102 Activity activity = activityWeakReference.get();
104 // Abort if the activity is gone.
105 if (((activity == null) || activity.isFinishing())) {
109 // Create an about version PNG byte array output stream.
110 ByteArrayOutputStream aboutVersionByteArrayOutputStream = new ByteArrayOutputStream();
112 // 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.
113 aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream);
115 // Get a file for the image.
116 File imageFile = new File(filePathString);
118 // Delete the current file if it exists.
119 if (imageFile.exists()) {
120 //noinspection ResultOfMethodCallIgnored
124 // Create a file creation disposition string.
125 String fileCreationDisposition = SUCCESS;
128 // Create an image file output stream.
129 FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
131 // Write the webpage image to the image file.
132 aboutVersionByteArrayOutputStream.writeTo(imageFileOutputStream);
134 // Create a media scanner intent, which adds items like pictures to Android's recent file list.
135 Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
137 // Add the URI to the media scanner intent.
138 mediaScannerIntent.setData(Uri.fromFile(imageFile));
141 activity.sendBroadcast(mediaScannerIntent);
142 } catch (Exception exception) {
143 // Store the error in the file creation disposition string.
144 fileCreationDisposition = exception.toString();
147 // return the file creation disposition string.
148 return fileCreationDisposition;
151 // `onPostExecute()` operates on the UI thread.
153 protected void onPostExecute(String fileCreationDisposition) {
154 // Get handles for the weak references.
155 Context context = contextWeakReference.get();
156 Activity activity = activityWeakReference.get();
157 LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
159 // Abort if the activity is gone.
160 if ((activity == null) || activity.isFinishing()) {
164 // Dismiss the saving image snackbar.
165 savingImageSnackbar.dismiss();
167 // Display a file creation disposition snackbar.
168 if (fileCreationDisposition.equals(SUCCESS)) {
169 // Create a file saved snackbar.
170 Snackbar imageSavedSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.file_saved) + " " + filePathString, Snackbar.LENGTH_SHORT);
172 // Add an open action.
173 imageSavedSnackbar.setAction(R.string.open, (View view) -> {
174 // Get a file for the file path string.
175 File file = new File(filePathString);
177 // Declare a file URI variable.
180 // Get the URI for the file according to the Android version.
181 if (Build.VERSION.SDK_INT >= 24) { // Use a file provider.
182 fileUri = FileProvider.getUriForFile(context, activity.getString(R.string.file_provider), file);
183 } else { // Get the raw file path URI.
184 fileUri = Uri.fromFile(file);
187 // Get a handle for the content resolver.
188 ContentResolver contentResolver = context.getContentResolver();
190 // Create an open intent with `ACTION_VIEW`.
191 Intent openIntent = new Intent(Intent.ACTION_VIEW);
193 // Autodetect the MIME type.
194 openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
196 // Allow the app to read the file URI.
197 openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
200 activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
203 // Show the image saved snackbar.
204 imageSavedSnackbar.show();
206 Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file) + " " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();