]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java
Release 3.7.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SaveWebpageDialog.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.dialogs;
21
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.res.Configuration;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.text.Editable;
33 import android.text.InputType;
34 import android.text.TextWatcher;
35 import android.view.View;
36 import android.view.WindowManager;
37 import android.widget.Button;
38 import android.widget.EditText;
39 import android.widget.TextView;
40
41 import androidx.annotation.NonNull;
42 import androidx.appcompat.app.AlertDialog;
43 import androidx.fragment.app.DialogFragment;
44 import androidx.preference.PreferenceManager;
45
46 import com.google.android.material.textfield.TextInputLayout;
47 import com.stoutner.privacybrowser.R;
48 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
49 import com.stoutner.privacybrowser.asynctasks.GetUrlSize;
50
51 public class SaveWebpageDialog extends DialogFragment {
52     public static final int SAVE_URL = 0;
53     public static final int SAVE_IMAGE = 1;
54
55     // Define the save webpage listener.
56     private SaveWebpageListener saveWebpageListener;
57
58     // The public interface is used to send information back to the parent activity.
59     public interface SaveWebpageListener {
60         void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment);
61     }
62
63     // Define the get URL size AsyncTask.  This allows previous instances of the task to be cancelled if a new one is run.
64     @SuppressWarnings("rawtypes")
65     private AsyncTask getUrlSize;
66
67     @Override
68     public void onAttach(@NonNull Context context) {
69         // Run the default commands.
70         super.onAttach(context);
71
72         // Get a handle for the save webpage listener from the launching context.
73         saveWebpageListener = (SaveWebpageListener) context;
74     }
75
76     public static SaveWebpageDialog saveWebpage(int saveType, String urlString, String fileSizeString, String contentDispositionFileNameString, String userAgentString, boolean cookiesEnabled) {
77         // Create an arguments bundle.
78         Bundle argumentsBundle = new Bundle();
79
80         // Store the arguments in the bundle.
81         argumentsBundle.putInt("save_type", saveType);
82         argumentsBundle.putString("url_string", urlString);
83         argumentsBundle.putString("file_size_string", fileSizeString);
84         argumentsBundle.putString("content_disposition_file_name_string", contentDispositionFileNameString);
85         argumentsBundle.putString("user_agent_string", userAgentString);
86         argumentsBundle.putBoolean("cookies_enabled", cookiesEnabled);
87
88         // Create a new instance of the save webpage dialog.
89         SaveWebpageDialog saveWebpageDialog = new SaveWebpageDialog();
90
91         // Add the arguments bundle to the new dialog.
92         saveWebpageDialog.setArguments(argumentsBundle);
93
94         // Return the new dialog.
95         return saveWebpageDialog;
96     }
97
98     // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
99     @SuppressLint("InflateParams")
100     @Override
101     @NonNull
102     public Dialog onCreateDialog(Bundle savedInstanceState) {
103         // Get a handle for the arguments.
104         Bundle arguments = getArguments();
105
106         // Remove the incorrect lint warning that the arguments might be null.
107         assert arguments != null;
108
109         // Get the arguments from the bundle.
110         int saveType = arguments.getInt("save_type");
111         String originalUrlString = arguments.getString("url_string");
112         String fileSizeString = arguments.getString("file_size_string");
113         String contentDispositionFileNameString = arguments.getString("content_disposition_file_name_string");
114         String userAgentString = arguments.getString("user_agent_string");
115         boolean cookiesEnabled = arguments.getBoolean("cookies_enabled");
116
117         // Get handles for the context and the activity.
118         Context context = requireContext();
119         Activity activity = requireActivity();
120
121         // Use an alert dialog builder to create the alert dialog.
122         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog);
123
124         // Get the current theme status.
125         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
126
127         // Set the title and icon according to the save type.
128         switch (saveType) {
129             case SAVE_URL:
130                 // Set the title.
131                 dialogBuilder.setTitle(R.string.save_url);
132
133                 // Set the icon according to the theme.
134                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
135                     dialogBuilder.setIcon(R.drawable.copy_enabled_day);
136                 } else {
137                     dialogBuilder.setIcon(R.drawable.copy_enabled_night);
138                 }
139                 break;
140
141             case SAVE_IMAGE:
142                 // Set the title.
143                 dialogBuilder.setTitle(R.string.save_image);
144
145                 // Set the icon according to the theme.
146                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
147                     dialogBuilder.setIcon(R.drawable.images_enabled_day);
148                 } else {
149
150                     dialogBuilder.setIcon(R.drawable.images_enabled_night);
151                 }
152                 break;
153         }
154
155         // Set the view.  The parent view is null because it will be assigned by the alert dialog.
156         dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_webpage_dialog, null));
157
158         // Set the cancel button listener.  Using `null` as the listener closes the dialog without doing anything else.
159         dialogBuilder.setNegativeButton(R.string.cancel, null);
160
161         // Set the save button listener.
162         dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> {
163             // Return the dialog fragment to the parent activity.
164             saveWebpageListener.onSaveWebpage(saveType, originalUrlString, this);
165         });
166
167         // Create an alert dialog from the builder.
168         AlertDialog alertDialog = dialogBuilder.create();
169
170         // Remove the incorrect lint warning below that the window might be null.
171         assert alertDialog.getWindow() != null;
172
173         // Get a handle for the shared preferences.
174         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
175
176         // Get the screenshot preference.
177         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
178
179         // Disable screenshots if not allowed.
180         if (!allowScreenshots) {
181             alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
182         }
183
184         // The alert dialog must be shown before items in the layout can be modified.
185         alertDialog.show();
186
187         // Get handles for the layout items.
188         TextInputLayout urlTextInputLayout = alertDialog.findViewById(R.id.url_textinputlayout);
189         EditText urlEditText = alertDialog.findViewById(R.id.url_edittext);
190         EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext);
191         Button browseButton = alertDialog.findViewById(R.id.browse_button);
192         TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview);
193         Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
194
195         // Remove the incorrect warnings that the views might be null.
196         assert urlTextInputLayout != null;
197         assert urlEditText != null;
198         assert fileNameEditText != null;
199         assert browseButton != null;
200         assert fileSizeTextView != null;
201
202         // Set the file size text view.
203         fileSizeTextView.setText(fileSizeString);
204
205         // Modify the layout based on the save type.
206         if (saveType == SAVE_URL) {  // A URL is being saved.
207             // Remove the incorrect lint error below that the URL string might be null.
208             assert originalUrlString != null;
209
210             // Populate the URL edit text according to the type.  This must be done before the text change listener is created below so that the file size isn't requested again.
211             if (originalUrlString.startsWith("data:")) {  // The URL contains the entire data of an image.
212                 // Get a substring of the data URL with the first 100 characters.  Otherwise, the user interface will freeze while trying to layout the edit text.
213                 String urlSubstring = originalUrlString.substring(0, 100) + "…";
214
215                 // Populate the URL edit text with the truncated URL.
216                 urlEditText.setText(urlSubstring);
217
218                 // Disable the editing of the URL edit text.
219                 urlEditText.setInputType(InputType.TYPE_NULL);
220             } else {  // The URL contains a reference to the location of the data.
221                 // Populate the URL edit text with the full URL.
222                 urlEditText.setText(originalUrlString);
223             }
224
225             // Update the file size and the status of the save button when the URL changes.
226             urlEditText.addTextChangedListener(new TextWatcher() {
227                 @Override
228                 public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
229                     // Do nothing.
230                 }
231
232                 @Override
233                 public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
234                     // Do nothing.
235                 }
236
237                 @Override
238                 public void afterTextChanged(Editable editable) {
239                     // Cancel the get URL size AsyncTask if it is running.
240                     if ((getUrlSize != null)) {
241                         getUrlSize.cancel(true);
242                     }
243
244                     // Get the current URL to save.
245                     String urlToSave = urlEditText.getText().toString();
246
247                     // Wipe the file size text view.
248                     fileSizeTextView.setText("");
249
250                     // Get the file size for the current URL.
251                     getUrlSize = new GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave);
252
253                     // Enable the save button if the URL and file name are populated.
254                     saveButton.setEnabled(!urlToSave.isEmpty() && !fileNameEditText.getText().toString().isEmpty());
255                 }
256             });
257         } else {  // An archive or an image is being saved.
258             // Hide the URL edit text and the file size text view.
259             urlTextInputLayout.setVisibility(View.GONE);
260             fileSizeTextView.setVisibility(View.GONE);
261         }
262
263         // Initially disable the save button.
264         saveButton.setEnabled(false);
265
266         // Update the status of the save button when the file name changes.
267         fileNameEditText.addTextChangedListener(new TextWatcher() {
268             @Override
269             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
270                 // Do nothing.
271             }
272
273             @Override
274             public void onTextChanged(CharSequence s, int start, int before, int count) {
275                 // Do nothing.
276             }
277
278             @Override
279             public void afterTextChanged(Editable s) {
280                 // Get the current file name.
281                 String fileNameString = fileNameEditText.getText().toString();
282
283                 // Enable the save button based on the save type.
284                 if (saveType == SAVE_URL) {  // A URL is being saved.
285                     // Enable the save button if the file name and the URL is populated.
286                     saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty());
287                 } else {  // An archive or an image is being saved.
288                     // Enable the save button if the file name is populated.
289                     saveButton.setEnabled(!fileNameString.isEmpty());
290                 }
291             }
292         });
293
294         // Create a file name string.
295         String fileName = "";
296
297         // Set the file name according to the type.
298         switch (saveType) {
299             case SAVE_URL:
300                 // Use the file name from the content disposition.
301                 fileName = contentDispositionFileNameString;
302                 break;
303
304             case SAVE_IMAGE:
305                 // Use a file name ending in `.png`.
306                 fileName = getString(R.string.webpage_png);
307                 break;
308         }
309
310         // Save the file name as the default file name.  This must be final to be used in the lambda below.
311         final String defaultFileName = fileName;
312
313         // Handle clicks on the browse button.
314         browseButton.setOnClickListener((View view) -> {
315             // Create the file picker intent.
316             Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
317
318             // Set the intent MIME type to include all files so that everything is visible.
319             browseIntent.setType("*/*");
320
321             // Set the initial file name according to the type.
322             browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName);
323
324             // Request a file that can be opened.
325             browseIntent.addCategory(Intent.CATEGORY_OPENABLE);
326
327             // Start the file picker.  This must be started under `activity` so that the request code is returned correctly.
328             activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE);
329         });
330
331         // Return the alert dialog.
332         return alertDialog;
333     }
334 }