Add share, copy, and save options to About > Version. https://redmine.stoutner.com...
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / asynctasks / SaveAboutVersionImage.java
1 /*
2  * Copyright © 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 import android.widget.LinearLayout;
33
34 import androidx.core.content.FileProvider;
35
36 import com.google.android.material.snackbar.Snackbar;
37
38 import com.stoutner.privacybrowser.R;
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 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;
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 aboutVersionBitmap;
57     private String filePathString;
58
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);
65
66         // Store 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 linear layout.
74         Activity activity = activityWeakReference.get();
75         LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
76
77         // Abort if the activity or the linear layout is gone.
78         if ((activity == null) || activity.isFinishing() || aboutVersionLinearLayout == null) {
79             return;
80         }
81
82         // Create a saving image snackbar.
83         savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + "  " + filePathString, Snackbar.LENGTH_INDEFINITE);
84
85         // Display the saving image snackbar.
86         savingImageSnackbar.show();
87
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);
91
92         // Create a canvas.
93         Canvas aboutVersionCanvas = new Canvas(aboutVersionBitmap);
94
95         // Draw the current about version onto the bitmap.  The linear layout commands must be run on the UI thread.
96         aboutVersionLinearLayout.draw(aboutVersionCanvas);
97     }
98
99     @Override
100     protected String doInBackground(Void... Void) {
101         // Get a handle for the activity.
102         Activity activity = activityWeakReference.get();
103
104         // Abort if the activity is gone.
105         if (((activity == null) || activity.isFinishing())) {
106             return "";
107         }
108
109         // Create an about version PNG byte array output stream.
110         ByteArrayOutputStream aboutVersionByteArrayOutputStream = new ByteArrayOutputStream();
111
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);
114
115         // Get a file for the image.
116         File imageFile = new File(filePathString);
117
118         // Delete the current file if it exists.
119         if (imageFile.exists()) {
120             //noinspection ResultOfMethodCallIgnored
121             imageFile.delete();
122         }
123
124         // Create a file creation disposition string.
125         String fileCreationDisposition = SUCCESS;
126
127         try {
128             // Create an image file output stream.
129             FileOutputStream imageFileOutputStream = new FileOutputStream(imageFile);
130
131             // Write the webpage image to the image file.
132             aboutVersionByteArrayOutputStream.writeTo(imageFileOutputStream);
133
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);
136
137             // Add the URI to the media scanner intent.
138             mediaScannerIntent.setData(Uri.fromFile(imageFile));
139
140             // Make it so.
141             activity.sendBroadcast(mediaScannerIntent);
142         } catch (Exception exception) {
143             // Store the error in the file creation disposition string.
144             fileCreationDisposition = exception.toString();
145         }
146
147         // return the file creation disposition string.
148         return fileCreationDisposition;
149     }
150
151     // `onPostExecute()` operates on the UI thread.
152     @Override
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();
158
159         // Abort if the activity is gone.
160         if ((activity == null) || activity.isFinishing()) {
161             return;
162         }
163
164         // Dismiss the saving image snackbar.
165         savingImageSnackbar.dismiss();
166
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);
171
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);
176
177                 // Declare a file URI variable.
178                 Uri fileUri;
179
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);
185                 }
186
187                 // Get a handle for the content resolver.
188                 ContentResolver contentResolver = context.getContentResolver();
189
190                 // Create an open intent with `ACTION_VIEW`.
191                 Intent openIntent = new Intent(Intent.ACTION_VIEW);
192
193                 // Autodetect the MIME type.
194                 openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
195
196                 // Allow the app to read the file URI.
197                 openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
198
199                 // Show the chooser.
200                 activity.startActivity(Intent.createChooser(openIntent, context.getString(R.string.open)));
201             });
202
203             // Show the image saved snackbar.
204             imageSavedSnackbar.show();
205         } else {
206             Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file) + "  " + fileCreationDisposition, Snackbar.LENGTH_INDEFINITE).show();
207         }
208     }
209 }