]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Convert five AsyncTasks to Kotlin. https://redmine.stoutner.com/issues/931
authorSoren Stoutner <soren@stoutner.com>
Tue, 13 Dec 2022 20:57:50 +0000 (13:57 -0700)
committerSoren Stoutner <soren@stoutner.com>
Tue, 13 Dec 2022 20:57:50 +0000 (13:57 -0700)
30 files changed:
app/src/main/assets/de/about_licenses.html
app/src/main/assets/de/guide_interface.html
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.kt
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/PopulateBlocklists.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/coroutines/GetHostIpAddressesCoroutine.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateBlocklistsCoroutine.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/coroutines/PrepareSaveDialogCoroutine.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveAboutVersionImageCoroutine.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dataclasses/History.kt [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dataclasses/HistoryDataClass.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialog.kt [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialogDataClass.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.kt
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.kt
app/src/main/java/com/stoutner/privacybrowser/helpers/GetUrlSizeHelper.kt [new file with mode: 0644]
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-pt-rBR/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values/strings.xml

index 39e5a15fd0d64ecb9c6f41890c9b2f61a3187c4d..7c2165f42ff63b1069b892e7b9ed3d751bf2115c 100644 (file)
             <a href="https://material.io/icons/">Android Material icon set</a> and are released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
             Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
             The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
-        <p><svg class="left"><use href="../shared_images/create_folder.svg#icon"/></svg> is derived from <code>create_new_folder</code>,
-            which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
-            Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
-            The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
-        <p><svg class="left"><use href="../shared_images/clear_and_exit.svg#icon"/></svg> is derived from <code>exit_to_app</code>,
-            which is part of the <a href="https://material.io/icons/">Android Material icon set</a>
-            and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>. Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
-            The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
-        <p><svg class="left"><use href="../shared_images/night_mode.svg#icon"/></svg> is derived from <code>compare</code>,
-            which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
-            Modifications copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
-            The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
-        <p><img class="left" src="../shared_images/sort_selected.svg"/> is derived from <code>sort</code>, which is part of the <a href="https://material.io/icons/">Android Material icon set</a>
-            and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>. Modifications copyright 2019, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
-            The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
-        <p><img class="left" src="../shared_images/push_pin_filled_selected.svg"> is derived from <code>push_pin_selected</code>,
-            which is part of the <a href="https://material.io/icons/">Android Material icon set</a> and is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>.
-            Modifications copyright 2019-2020, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
-            The resulting image is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ license</a>.</p>
+        <p><svg class="left"><use href="../shared_images/create_folder.svg#icon"/></svg> ist abgeleitet von <code>create_new_folder</code>,
+            das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+            Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+            Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+        <p><svg class="left"><use href="../shared_images/clear_and_exit.svg#icon"/></svg> ist abgeleitet von <code>exit_to_app</code>,
+            das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+            Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+            Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+        <p><svg class="left"><use href="../shared_images/night_mode.svg#icon"/></svg> ist abgeleitet von <code>compare</code>,
+            das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+            Änderungen Copyright 2017, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+            Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+        <p><img class="left" src="../shared_images/sort_selected.svg"/> ist abgeleitet von <code>sort</code>, das Teil des <a href="https://material.io/icons/">Android Material icon set</a>
+            unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird. Änderungen Copyright 2019, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+            Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
+        <p><img class="left" src="../shared_images/push_pin_filled_selected.svg"> ist abgeleitet von <code>push_pin_selected</code>,
+            das Teil des <a href="https://material.io/icons/">Android Material icon set</a> unter der <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache Lizenz 2.0</a> veröffentlicht wird.
+            Änderungen Copyright 2019-2020, 2022 <a href="mailto:soren@stoutner.com">Soren Stoutner</a>.
+            Die resultierende Grafik wird unter der <a href="https://www.gnu.org/licenses/gpl-3.0.html">GPLv3+ Lizenz</a> veröffentlicht.</p>
         <p><svg class="left"><use href="../shared_images/cookie.svg#icon"/></svg> <code>cookie</code> was created by Google.
             It is released under the <a href ="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
             and can be downloaded from <a href="https://materialdesignicons.com/icon/cookie">Material Design Icons</a>. It is unchanged except for layout information like color and size.</p>
index 95d00dda4d821cb6edfb59733458348164c64ef1..736462787994ac46ee91e9b44d6ce2446ae76f2b 100644 (file)
@@ -36,7 +36,8 @@
             <a href="https://redmine.stoutner.com/boards/1/topics/243">bis das Menü angezeigt wird</a> und wischen dann, um es zu öffnen.
             Auf manchen Geräten mit abgerundeten Kanten kann das Ausklappen des Menüs schwierig sein. Daher kann das Lesezeichen-Menü auch über das Options-Menü geöffnet werden.</p>
 
-        <p>Tapping on a bookmark opens it in the current tab. Long-pressing on a bookmark opens it in a new tab and long-pressing on a folder opens all the bookmarks it directly contains in new tabs.
+        <p>Kurzes Antippen eines Lesezeichens öffnet dieses im aktuellen Tab,
+            langes Drücken öffnet es in einem neuen Tab und langes Drücken auf einen Ordner öffnet alle direkt darin enthaltenen Lesezeichen in neuen Tabs.
             Durch Antippen des obersten Symbols im Lesezeichen-Menü können abgespeicherte Lesezeichen bearbeitet und umgeordnet werden.</p>
     </body>
 </html>
index 637e9a52ae572e9ae4a3d0d40936aeb7af71c444..649cf6448889e46b7f4736b570563ffb10072297 100644 (file)
@@ -46,7 +46,6 @@ import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
@@ -129,12 +128,12 @@ import com.google.android.material.tabs.TabLayout;
 
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
-import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
-import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
-import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
-import com.stoutner.privacybrowser.dataclasses.PendingDialog;
+import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine;
+import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine;
+import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine;
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
@@ -186,12 +185,12 @@ import kotlin.Pair;
 
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener,
-        PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+        PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
 
     // Define the public static variables.
     public static final ExecutorService executorService = Executors.newFixedThreadPool(4);
     public static String orbotStatus = "unknown";
-    public static final ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+    public static final ArrayList<PendingDialogDataClass> pendingDialogsArrayList =  new ArrayList<>();
     public static String proxyMode = ProxyHelper.NONE;
 
     // Declare the public static variables.
@@ -223,10 +222,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private int savedTabPosition;
     private String savedProxyMode;
 
-    // Define the class variables.
-    @SuppressWarnings("rawtypes")
-    AsyncTask populateBlocklists;
-
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
@@ -664,8 +659,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Register the on back pressed callback.
         getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
 
+        // Instantiate the populate blocklists coroutine.
+        PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this);
+
         // Populate the blocklists.
-        populateBlocklists = new PopulateBlocklists(this, this).execute();
+        populateBlocklistsCoroutine.populateBlocklists(this);
     }
 
     @Override
@@ -858,10 +856,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Show any pending dialogs.
         for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
             // Get the pending dialog from the array list.
-            PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+            PendingDialogDataClass pendingDialogDataClass = pendingDialogsArrayList.get(i);
 
             // Show the pending dialog.
-            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+            pendingDialogDataClass.dialogFragment.show(getSupportFragmentManager(), pendingDialogDataClass.tag);
         }
 
         // Clear the pending dialogs array list.
@@ -959,11 +957,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksDatabaseHelper.close();
         }
 
-        // Stop populating the blocklists if the AsyncTask is running in the background.
-        if (populateBlocklists != null) {
-            populateBlocklists.cancel(true);
-        }
-
         // Run the default commands.
         super.onDestroy();
     }
@@ -1856,8 +1849,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
             } else {  // Handle the download inside of Privacy Browser.
                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+                PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptCookies());
             }
 
             // Consume the event.
@@ -2353,8 +2346,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(linkUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2425,8 +2417,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(imageUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2530,8 +2521,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(imageUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2557,8 +2547,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(linkUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -4098,7 +4087,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
                             } catch (Exception waitingForTorException) {
                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                                pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+                                pendingDialogsArrayList.add(new PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
                             }
                         }
                     }
@@ -4114,7 +4103,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
                         } catch (Exception orbotNotInstalledException) {
                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                            pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                            pendingDialogsArrayList.add(new PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
                         }
                     }
                 }
@@ -4150,7 +4139,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
                             } catch (Exception i2pNotInstalledException) {
                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                                pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                                pendingDialogsArrayList.add(new PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
                             }
                         }
                     }
@@ -5151,7 +5140,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Get the file name from the content disposition.
-                String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+                String fileNameString = PrepareSaveDialogCoroutine.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
                 // Instantiate the save dialog.
                 DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent,
@@ -5163,7 +5152,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
                 } catch (Exception exception) {  // The dialog could not be shown.
                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                    pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+                    pendingDialogsArrayList.add(new PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)));
                 }
             }
         });
@@ -5872,8 +5861,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get a URI for the current URL.
                 Uri currentUri = Uri.parse(url);
 
-                // Get the IP addresses for the host.
-                new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
+                // Get the current domain name.
+                String currentDomainName = currentUri.getHost();
+
+                if ((currentDomainName != null) && !currentDomainName.isEmpty()) {
+                    // Get the IP addresses for the current URI.
+                    GetHostIpAddressesCoroutine.getAddresses(currentDomainName, nestedScrollWebView, getSupportFragmentManager(), getString(R.string.pinned_mismatch));
+                }
 
                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
                 if (optionsMenu != null) {
@@ -6060,7 +6054,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
                     } catch (Exception exception) {
                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                        pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+                        pendingDialogsArrayList.add(new PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
                     }
                 }
             }
index acfcc4227d6d9c56560dcdb2408ffedda5e9a4dd..3c7469579b2c82c8858a127293f9e599b9eb0bfe 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
@@ -29,11 +29,11 @@ import android.widget.ImageView
 import android.widget.TextView
 
 import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.dataclasses.History
+import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
 
 import java.util.ArrayList
 
-class HistoryArrayAdapter(context: Context, historyArrayList: ArrayList<History>, private val currentPageId: Int) : ArrayAdapter<History>(context, 0, historyArrayList) {
+class HistoryArrayAdapter(context: Context, historyDataClassArrayList: ArrayList<HistoryDataClass>, private val currentPageId: Int) : ArrayAdapter<HistoryDataClass>(context, 0, historyDataClassArrayList) {
     override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
         // Initialize a populated view from the convert view.
         var populatedView = convertView
@@ -64,4 +64,4 @@ class HistoryArrayAdapter(context: Context, historyArrayList: ArrayList<History>
         // Return the populated view.
         return populatedView
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java
deleted file mode 100644 (file)
index c3e30c6..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright © 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.os.AsyncTask;
-
-import androidx.fragment.app.FragmentManager;
-
-import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
-import com.stoutner.privacybrowser.views.NestedScrollWebView;
-
-import java.lang.ref.WeakReference;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-// This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
-public class GetHostIpAddresses extends AsyncTask<String, Void, String> {
-    // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
-    private final WeakReference<Activity> activityWeakReference;
-    private final WeakReference<FragmentManager> fragmentManagerWeakReference;
-    private final WeakReference<NestedScrollWebView> nestedScrollWebViewWeakReference;
-
-    public GetHostIpAddresses(Activity activity, FragmentManager fragmentManager, NestedScrollWebView nestedScrollWebView) {
-        // Populate the weak references.
-        activityWeakReference = new WeakReference<>(activity);
-        fragmentManagerWeakReference = new WeakReference<>(fragmentManager);
-        nestedScrollWebViewWeakReference = new WeakReference<>(nestedScrollWebView);
-    }
-
-    @Override
-    protected String doInBackground(String... domainName) {
-        // Get a handles for the weak references.
-        Activity activity = activityWeakReference.get();
-        FragmentManager fragmentManager = fragmentManagerWeakReference.get();
-        NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
-
-        // Abort if the activity or its components are gone.
-        if ((activity == null) || activity.isFinishing() || fragmentManager == null || nestedScrollWebView == null) {
-            // Return an empty spannable string builder.
-            return "";
-        }
-
-        // Initialize an IP address string builder.
-        StringBuilder ipAddresses = new StringBuilder();
-
-        // Get an array with the IP addresses for the host.
-        try {
-            // Get an array with all the IP addresses for the domain.
-            InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
-
-            // Add each IP address to the string builder.
-            for (InetAddress inetAddress : inetAddressesArray) {
-                // Add a line break to the string builder if this is not the first IP address.
-                if (ipAddresses.length() > 0) {
-                    ipAddresses.append("\n");
-                }
-
-                // Add the IP address to the string builder.
-                ipAddresses.append(inetAddress.getHostAddress());
-            }
-        } catch (UnknownHostException exception) {
-            // Do nothing.
-        }
-
-        // Return the string.
-        return ipAddresses.toString();
-    }
-
-    // `onPostExecute()` operates on the UI thread.
-    @Override
-    protected void onPostExecute(String ipAddresses) {
-        // Get a handle for the activity and the nested scroll WebView.
-        Activity activity = activityWeakReference.get();
-        FragmentManager fragmentManager = fragmentManagerWeakReference.get();
-        NestedScrollWebView nestedScrollWebView = nestedScrollWebViewWeakReference.get();
-
-        // Abort if the activity or its components are gone.
-        if ((activity == null) || activity.isFinishing() || fragmentManager == null || nestedScrollWebView == null) {
-            return;
-        }
-
-        // Store the IP addresses.
-        nestedScrollWebView.setCurrentIpAddresses(ipAddresses);
-
-        // Checked for pinned mismatches if there is pinned information and it is not ignored.
-        if ((nestedScrollWebView.hasPinnedSslCertificate() || !nestedScrollWebView.getPinnedIpAddresses().equals("")) && !nestedScrollWebView.getIgnorePinnedDomainInformation()) {
-            CheckPinnedMismatchHelper.checkPinnedMismatch(activity, fragmentManager, nestedScrollWebView);
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetUrlSize.java
deleted file mode 100644 (file)
index 2f645ba..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright © 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.webkit.CookieManager;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.URL;
-import java.text.NumberFormat;
-
-public class GetUrlSize extends AsyncTask<String, Void, String> {
-    // Define weak references for the calling context and alert dialog.
-    private final WeakReference<Context> contextWeakReference;
-    private final WeakReference<AlertDialog> alertDialogWeakReference;
-
-    // Define the class variables.
-    private final String userAgent;
-    private final boolean cookiesEnabled;
-
-    // The public constructor.
-    public GetUrlSize(Context context, AlertDialog alertDialog, String userAgent, boolean cookiesEnabled) {
-        // Populate the week references for the context and alert dialog.
-        contextWeakReference = new WeakReference<>(context);
-        alertDialogWeakReference = new WeakReference<>(alertDialog);
-
-        // Store the class variables.
-        this.userAgent = userAgent;
-        this.cookiesEnabled = cookiesEnabled;
-    }
-
-    @Override
-    protected String doInBackground(String... urlToSave) {
-        // Get a handle for the context and the fragment.
-        Context context = contextWeakReference.get();
-        AlertDialog alertDialog = alertDialogWeakReference.get();
-
-        // Abort if the fragment is gone.
-        if (alertDialog == null) {
-            return null;
-        }
-
-        // Initialize the formatted file size string.
-        String formattedFileSize = context.getString(R.string.unknown_size);
-
-        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
-        try {
-            // Get the URL from the calling fragment.
-            URL url = new URL(urlToSave[0]);
-
-            // Instantiate the proxy helper.
-            ProxyHelper proxyHelper = new ProxyHelper();
-
-            // Get the current proxy.
-            Proxy proxy = proxyHelper.getCurrentProxy(context);
-
-            // Open a connection to the URL.  No data is actually sent at this point.
-            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
-
-            // Add the user agent to the header property.
-            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
-
-            // Add the cookies if they are enabled.
-            if (cookiesEnabled) {
-                // Get the cookies for the current domain.
-                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
-
-                // Only add the cookies if they are not null.
-                if (cookiesString != null) {
-                    // Add the cookies to the header property.
-                    httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-                }
-            }
-
-            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
-            try {
-                // Exit if the task has been cancelled.
-                if (isCancelled()) {
-                    // Disconnect the HTTP URL connection.
-                    httpUrlConnection.disconnect();
-
-                    // Return the formatted file size string.
-                    return formattedFileSize;
-                }
-
-                // Get the status code.  This initiates a network connection.
-                int responseCode = httpUrlConnection.getResponseCode();
-
-                // Exit if the task has been cancelled.
-                if (isCancelled()) {
-                    // Disconnect the HTTP URL connection.
-                    httpUrlConnection.disconnect();
-
-                    // Return the formatted file size string.
-                    return formattedFileSize;
-                }
-
-                // Check the response code.
-                if (responseCode >= 400) {  // The response code is an error message.
-                    // Set the formatted file size to indicate a bad URL.
-                    formattedFileSize = context.getString(R.string.invalid_url);
-                } else {  // The response code is not an error message.
-                    // Get the content length header.
-                    String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
-
-                    // Only process the content length string if it isn't null.
-                    if (contentLengthString != null) {
-                        // Convert the content length string to a long.
-                        long fileSize = Long.parseLong(contentLengthString);
-
-                        // Format the file size.
-                        formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
-                    }
-                }
-            } finally {
-                // Disconnect the HTTP URL connection.
-                httpUrlConnection.disconnect();
-            }
-        } catch (Exception exception) {
-            // Set the formatted file size to indicate a bad URL.
-            formattedFileSize = context.getString(R.string.invalid_url);
-        }
-
-        // Return the formatted file size string.
-        return formattedFileSize;
-    }
-
-    // `onPostExecute()` operates on the UI thread.
-    @Override
-    protected void onPostExecute(String fileSize) {
-        // Get a handle for the alert dialog.
-        AlertDialog alertDialog = alertDialogWeakReference.get();
-
-        // Abort if the alert dialog is gone.
-        if (alertDialog == null) {
-            return;
-        }
-
-        // Get a handle for the file size text view.
-        TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview);
-
-        // Remove the incorrect warning below that the file size text view might be null.
-        assert fileSizeTextView != null;
-
-        // Update the file size.
-        fileSizeTextView.setText(fileSize);
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PopulateBlocklists.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PopulateBlocklists.java
deleted file mode 100644 (file)
index af2bea1..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright © 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.drawerlayout.widget.DrawerLayout;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.helpers.BlocklistHelper;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-public class PopulateBlocklists extends AsyncTask<Void, String, ArrayList<ArrayList<List<String[]>>>> {
-    // The public interface is used to send information back to the parent activity.
-    public interface PopulateBlocklistsListener {
-        void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists);
-    }
-
-    // Define a populate blocklists listener.
-    private final PopulateBlocklistsListener populateBlocklistsListener;
-
-    // Define weak references for the activity and context.
-    private final WeakReference<Context> contextWeakReference;
-    private final WeakReference<Activity> activityWeakReference;
-
-    // The public constructor.
-    public PopulateBlocklists(Context context, Activity activity) {
-        // Populate the weak reference to the context.
-        contextWeakReference = new WeakReference<>(context);
-
-        // Populate the weak reference to the activity.
-        activityWeakReference = new WeakReference<>(activity);
-
-        // Get a handle for the populate blocklists listener from the launching activity.
-        populateBlocklistsListener = (PopulateBlocklistsListener) context;
-    }
-
-    // `onPreExecute()` operates on the UI thread.
-    @Override
-    protected void onPreExecute() {
-        // Get a handle for the activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Get handles for the views.
-        RelativeLayout loadingBlocklistsRelativeLayout = activity.findViewById(R.id.loading_blocklists_relativelayout);
-
-        // Show the loading blocklists screen.
-        loadingBlocklistsRelativeLayout.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    protected ArrayList<ArrayList<List<String[]>>> doInBackground(Void... none) {
-        // Exit the AsyncTask if the app has been restarted.
-        if (isCancelled()) {
-            return null;
-        }
-
-        // Get a handle for the context.
-        Context context = contextWeakReference.get();
-
-        // Instantiate the blocklist helper.
-        BlocklistHelper blocklistHelper = new BlocklistHelper();
-
-        // Create a combined array list.
-        ArrayList<ArrayList<List<String[]>>> combinedBlocklists = new ArrayList<>();
-
-        // Load the blocklists if the context still exists.
-        if (context != null) {
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_easylist));
-
-            // Populate EasyList.
-            ArrayList<List<String[]>> easyList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/easylist.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_easyprivacy));
-
-            // Populate EasyPrivacy.
-            ArrayList<List<String[]>> easyPrivacy = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/easyprivacy.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_fanboys_annoyance_list));
-
-            // Populate Fanboy's Annoyance List.
-            ArrayList<List<String[]>> fanboysAnnoyanceList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/fanboy-annoyance.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_fanboys_social_blocking_list));
-
-            // Populate Fanboy's Social Blocking List.
-            ArrayList<List<String[]>> fanboysSocialList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/fanboy-social.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_ultralist));
-
-            // Populate UltraList.
-            ArrayList<List<String[]>> ultraList = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/ultralist.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-
-            // Update the progress.
-            publishProgress(context.getString(R.string.loading_ultraprivacy));
-
-            // Populate UltraPrivacy.
-            ArrayList<List<String[]>> ultraPrivacy = blocklistHelper.parseBlocklist(context.getAssets(), "blocklists/ultraprivacy.txt");
-
-            // Exit the AsyncTask if the app has been restarted.
-            if (isCancelled()) {
-                return null;
-            }
-
-
-
-            // Populate the combined array list.
-            combinedBlocklists.add(easyList);
-            combinedBlocklists.add(easyPrivacy);
-            combinedBlocklists.add(fanboysAnnoyanceList);
-            combinedBlocklists.add(fanboysSocialList);
-            combinedBlocklists.add(ultraList);
-            combinedBlocklists.add(ultraPrivacy);
-        }
-
-        // Return the combined array list.
-        return combinedBlocklists;
-    }
-
-    @Override
-    protected void onProgressUpdate(String... loadingBlocklist) {
-        // Get a handle for the activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Get a handle for the loading blocklist text view.
-        TextView loadingBlocklistTextView = activity.findViewById(R.id.loading_blocklist_textview);
-
-        // Update the status.
-        loadingBlocklistTextView.setText(loadingBlocklist[0]);
-    }
-
-    @Override
-    protected void onPostExecute(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
-        // Get a handle for the activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Get handles for the views.
-        DrawerLayout drawerLayout = activity.findViewById(R.id.drawerlayout);
-        RelativeLayout loadingBlocklistsRelativeLayout = activity.findViewById(R.id.loading_blocklists_relativelayout);
-
-        // Show the drawer layout.
-        drawerLayout.setVisibility(View.VISIBLE);
-
-        // Hide the loading blocklists screen.
-        loadingBlocklistsRelativeLayout.setVisibility(View.GONE);
-
-        // Enable the sliding drawers.
-        drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-        // Add the first tab.
-        populateBlocklistsListener.finishedPopulatingBlocklists(combinedBlocklists);
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java
deleted file mode 100644 (file)
index b869099..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright © 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.content.Context;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.webkit.CookieManager;
-import android.webkit.MimeTypeMap;
-
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.activities.MainWebViewActivity;
-import com.stoutner.privacybrowser.dataclasses.PendingDialog;
-import com.stoutner.privacybrowser.dialogs.SaveDialog;
-import com.stoutner.privacybrowser.helpers.ProxyHelper;
-
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.URL;
-import java.text.NumberFormat;
-
-public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
-    // Define weak references.
-    private final WeakReference<Activity> activityWeakReference;
-    private final WeakReference<Context> contextWeakReference;
-    private final WeakReference<FragmentManager> fragmentManagerWeakReference;
-
-    // Define the class variables.
-    private final String userAgent;
-    private final boolean cookiesEnabled;
-    private String urlString;
-
-    // The public constructor.
-    public PrepareSaveDialog(Activity activity, Context context, FragmentManager fragmentManager, String userAgent, boolean cookiesEnabled) {
-        // Populate the weak references.
-        activityWeakReference = new WeakReference<>(activity);
-        contextWeakReference = new WeakReference<>(context);
-        fragmentManagerWeakReference = new WeakReference<>(fragmentManager);
-
-        // Store the class variables.
-        this.userAgent = userAgent;
-        this.cookiesEnabled = cookiesEnabled;
-    }
-
-    @Override
-    protected String[] doInBackground(String... urlToSave) {
-        // Get a handle for the activity and context.
-        Activity activity = activityWeakReference.get();
-        Context context = contextWeakReference.get();
-
-        // Abort if the activity is gone.
-        if (activity == null || activity.isFinishing()) {
-            // Return a null string array.
-            return null;
-        }
-
-        // Get the URL string.
-        urlString = urlToSave[0];
-
-        // Define the strings.
-        String formattedFileSize;
-        String fileNameString;
-
-        // Populate the file size and name strings.
-        if (urlString.startsWith("data:")) {  // The URL contains the entire data of an image.
-            // Remove `data:` from the beginning of the URL.
-            String urlWithoutData = urlString.substring(5);
-
-            // Get the URL MIME type, which ends with a `;`.
-            String urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"));
-
-            // Get the Base64 data, which begins after a `,`.
-            String base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1);
-
-            // Calculate the file size of the data URL.  Each Base64 character represents 6 bits.
-            formattedFileSize = NumberFormat.getInstance().format(base64DataString.length() * 3L / 4) + " " + context.getString(R.string.bytes);
-
-            // Set the file name according to the MIME type.
-            fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType);
-        } else {  // The URL refers to the location of the data.
-            // Initialize the formatted file size string.
-            formattedFileSize = context.getString(R.string.unknown_size);
-
-            // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
-            try {
-                // Convert the URL string to a URL.
-                URL url = new URL(urlString);
-
-                // Instantiate the proxy helper.
-                ProxyHelper proxyHelper = new ProxyHelper();
-
-                // Get the current proxy.
-                Proxy proxy = proxyHelper.getCurrentProxy(context);
-
-                // Open a connection to the URL.  No data is actually sent at this point.
-                HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
-
-                // Add the user agent to the header property.
-                httpUrlConnection.setRequestProperty("User-Agent", userAgent);
-
-                // Add the cookies if they are enabled.
-                if (cookiesEnabled) {
-                    // Get the cookies for the current domain.
-                    String cookiesString = CookieManager.getInstance().getCookie(url.toString());
-
-                    // only add the cookies if they are not null.
-                    if (cookiesString != null) {
-                        // Add the cookies to the header property.
-                        httpUrlConnection.setRequestProperty("Cookie", cookiesString);
-                    }
-                }
-
-                // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
-                try {
-                    // Get the status code.  This initiates a network connection.
-                    int responseCode = httpUrlConnection.getResponseCode();
-
-                    // Check the response code.
-                    if (responseCode >= 400) {  // The response code is an error message.
-                        // Set the formatted file size to indicate a bad URL.
-                        formattedFileSize = context.getString(R.string.invalid_url);
-
-                        // Set the file name according to the URL.
-                        fileNameString = getFileNameFromUrl(context, urlString, null);
-                    } else {  // The response code is not an error message.
-                        // Get the headers.
-                        String contentLengthString = httpUrlConnection.getHeaderField("Content-Length");
-                        String contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition");
-                        String contentTypeString = httpUrlConnection.getContentType();
-
-                        // Remove anything after the MIME type in the content type string.
-                        if (contentTypeString.contains(";")) {
-                            // Remove everything beginning with the `;`.
-                            contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"));
-                        }
-
-                        // Only process the content length string if it isn't null.
-                        if (contentLengthString != null) {
-                            // Convert the content length string to a long.
-                            long fileSize = Long.parseLong(contentLengthString);
-
-                            // Format the file size.
-                            formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes);
-                        }
-
-                        // Get the file name string from the content disposition.
-                        fileNameString = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString);
-                    }
-                } finally {
-                    // Disconnect the HTTP URL connection.
-                    httpUrlConnection.disconnect();
-                }
-            } catch (Exception exception) {
-                // Set the formatted file size to indicate a bad URL.
-                formattedFileSize = context.getString(R.string.invalid_url);
-
-                // Set the file name according to the URL.
-                fileNameString = getFileNameFromUrl(context, urlString, null);
-            }
-        }
-
-        // Return the formatted file size and name as a string array.
-        return new String[] {formattedFileSize, fileNameString};
-    }
-
-    // `onPostExecute()` operates on the UI thread.
-    @Override
-    protected void onPostExecute(String[] fileStringArray) {
-        // Get a handle for the activity and the fragment manager.
-        Activity activity = activityWeakReference.get();
-        FragmentManager fragmentManager = fragmentManagerWeakReference.get();
-
-        // Abort if the activity is gone.
-        if (activity == null || activity.isFinishing()) {
-            // Exit.
-            return;
-        }
-
-        // Instantiate the save dialog.
-        DialogFragment saveDialogFragment = SaveDialog.saveUrl(urlString, fileStringArray[0], fileStringArray[1], userAgent, cookiesEnabled);
-
-        // Try to show the dialog.  Sometimes the window is not active.
-        try {
-            // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-            saveDialogFragment.show(fragmentManager, activity.getString(R.string.save_dialog));
-        } catch (Exception exception) {
-            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-            MainWebViewActivity.pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, activity.getString(R.string.save_dialog)));
-        }
-    }
-
-    // Content dispositions can contain other text besides the file name, and they can be in any order.
-    // Elements are separated by semicolons.  Sometimes the file names are contained in quotes.
-    public static String getFileNameFromHeaders(Context context, String contentDispositionString, String contentTypeString, String urlString) {
-        // Define a file name string.
-        String fileNameString;
-
-        // Only process the content disposition string if it isn't null.
-        if (contentDispositionString != null) {  // The content disposition is not null.
-            // Check to see if the content disposition contains a file name.
-            if (contentDispositionString.contains("filename=")) {  // The content disposition contains a filename.
-                // Get the part of the content disposition after `filename=`.
-                fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9);
-
-                // Remove any `;` and anything after it.  This removes any entries after the filename.
-                if (fileNameString.contains(";")) {
-                    // Remove the first `;` and everything after it.
-                    fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1);
-                }
-
-                // Remove any `"` at the beginning of the string.
-                if (fileNameString.startsWith("\"")) {
-                    // Remove the first character.
-                    fileNameString = fileNameString.substring(1);
-                }
-
-                // Remove any `"` at the end of the string.
-                if (fileNameString.endsWith("\"")) {
-                    // Remove the last character.
-                    fileNameString = fileNameString.substring(0, fileNameString.length() - 1);
-                }
-            } else {  // The headers contain no useful information.
-                // Get the file name string from the URL.
-                fileNameString = getFileNameFromUrl(context, urlString, contentTypeString);
-            }
-        } else {  // The content disposition is null.
-            // Get the file name string from the URL.
-            fileNameString = getFileNameFromUrl(context, urlString, contentTypeString);
-        }
-
-        // Return the file name string.
-        return fileNameString;
-    }
-
-    private static String getFileNameFromUrl(Context context, String urlString, String contentTypeString) {
-        // Convert the URL string to a URI.
-        Uri uri = Uri.parse(urlString);
-
-        // Get the last path segment.
-        String lastPathSegment = uri.getLastPathSegment();
-
-        // Use a default file name if the last path segment is null.
-        if (lastPathSegment == null) {
-            lastPathSegment = context.getString(R.string.file);
-
-            if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString)) {  // The content type contains a MIME type.
-                // Add the file extension that matches the MIME type.
-                lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString);
-            }
-        }
-
-        // Return the last path segment as the file name.
-        return lastPathSegment;
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java
deleted file mode 100644 (file)
index 49ae1d9..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.asynctasks;
-
-import android.app.Activity;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.provider.OpenableColumns;
-import android.widget.LinearLayout;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import com.stoutner.privacybrowser.R;
-
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.lang.ref.WeakReference;
-
-public class SaveAboutVersionImage extends AsyncTask<Void, Void, String> {
-    // Declare the class constants.
-    private final String SUCCESS = "Success";
-
-    // Declare the weak references.
-    private final WeakReference<Activity> activityWeakReference;
-    private final WeakReference<LinearLayout> aboutVersionLinearLayoutWeakReference;
-
-    // Declare the class variables.
-    private Snackbar savingImageSnackbar;
-    private Bitmap aboutVersionBitmap;
-    private final Uri fileUri;
-    private final String fileNameString;
-
-    // The public constructor.
-    public SaveAboutVersionImage(Activity activity, Uri fileUri, LinearLayout aboutVersionLinearLayout) {
-        // Populate the weak references.
-        activityWeakReference = new WeakReference<>(activity);
-        aboutVersionLinearLayoutWeakReference = new WeakReference<>(aboutVersionLinearLayout);
-
-        // Store the class variables.
-        this.fileUri = fileUri;
-
-        // Query the exact file name if the API >= 26.
-        if (Build.VERSION.SDK_INT >= 26) {
-            // Get a cursor from the content resolver.
-            Cursor contentResolverCursor = activity.getContentResolver().query(fileUri, null, null, null);
-
-            // Move to the first row.
-            contentResolverCursor.moveToFirst();
-
-            // Get the file name from the cursor.
-            fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
-
-            // Close the cursor.
-            contentResolverCursor.close();
-        } else {
-            // Use the URI last path segment as the file name string.
-            fileNameString = fileUri.getLastPathSegment();
-        }
-    }
-
-    // `onPreExecute()` operates on the UI thread.
-    @Override
-    protected void onPreExecute() {
-        // Get handles for the activity and the linear layout.
-        Activity activity = activityWeakReference.get();
-        LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
-
-        // Abort if the activity or the linear layout is gone.
-        if ((activity == null) || activity.isFinishing() || aboutVersionLinearLayout == null) {
-            return;
-        }
-
-        // Create a saving image snackbar.
-        savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image) + "  " + fileNameString, Snackbar.LENGTH_INDEFINITE);
-
-        // Display the saving image snackbar.
-        savingImageSnackbar.show();
-
-        // Create the about version bitmap.  This can be replaced by PixelCopy once the minimum API >= 26.
-        // 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.
-        aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.getWidth(), aboutVersionLinearLayout.getHeight(), Bitmap.Config.ARGB_8888);
-
-        // Create a canvas.
-        Canvas aboutVersionCanvas = new Canvas(aboutVersionBitmap);
-
-        // Draw the current about version onto the bitmap.  The linear layout commands must be run on the UI thread.
-        aboutVersionLinearLayout.draw(aboutVersionCanvas);
-    }
-
-    @Override
-    protected String doInBackground(Void... Void) {
-        // Get a handle for the activity.
-        Activity activity = activityWeakReference.get();
-
-        // Abort if the activity is gone.
-        if (((activity == null) || activity.isFinishing())) {
-            return "";
-        }
-
-        // Create an about version PNG byte array output stream.
-        ByteArrayOutputStream aboutVersionByteArrayOutputStream = new ByteArrayOutputStream();
-
-        // 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.
-        aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream);
-
-        // Create a file creation disposition string.
-        String fileCreationDisposition = SUCCESS;
-
-        try {
-            // Open an output stream.
-            OutputStream outputStream = activity.getContentResolver().openOutputStream(fileUri);
-
-            // Write the webpage image to the image file.
-            aboutVersionByteArrayOutputStream.writeTo(outputStream);
-
-            // Close the output stream.
-            outputStream.close();
-        } catch (Exception exception) {
-            // Store the error in the file creation disposition string.
-            fileCreationDisposition = exception.toString();
-        }
-
-        // return the file creation disposition string.
-        return fileCreationDisposition;
-    }
-
-    // `onPostExecute()` operates on the UI thread.
-    @Override
-    protected void onPostExecute(String fileCreationDisposition) {
-        // Get handles for the weak references.
-        Activity activity = activityWeakReference.get();
-        LinearLayout aboutVersionLinearLayout = aboutVersionLinearLayoutWeakReference.get();
-
-        // Abort if the activity is gone.
-        if ((activity == null) || activity.isFinishing()) {
-            return;
-        }
-
-        // Dismiss the saving image snackbar.
-        savingImageSnackbar.dismiss();
-
-        // Display a file creation disposition snackbar.
-        if (fileCreationDisposition.equals(SUCCESS)) {
-            // Create a file saved snackbar.
-            Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show();
-        } else {
-            Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show();
-        }
-    }
-}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/GetHostIpAddressesCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/GetHostIpAddressesCoroutine.kt
new file mode 100644 (file)
index 0000000..cc10176
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import androidx.fragment.app.FragmentManager
+import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper
+import com.stoutner.privacybrowser.views.NestedScrollWebView
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.lang.StringBuilder
+import java.net.InetAddress
+import java.net.UnknownHostException
+
+object GetHostIpAddressesCoroutine {
+    @JvmStatic
+    fun getAddresses(domainName: String, nestedScrollWebView: NestedScrollWebView, supportFragmentManager: FragmentManager, pinnedMismatchString: String) {
+        // Get the IP addresses using a coroutine.
+        CoroutineScope(Dispatchers.Main).launch {
+            // Get the IP addresses on the IO thread.
+            withContext(Dispatchers.IO) {
+                // Get an array with the IP addresses for the host.
+                try {
+                    // Initialize an IP address string builder.
+                    val ipAddresses = StringBuilder()
+
+                    // Get an array with all the IP addresses for the domain.
+                    val inetAddressesArray = InetAddress.getAllByName(domainName)
+
+                    // Add each IP address to the string builder.
+                    for (inetAddress in inetAddressesArray) {
+                        // Add a line break to the string builder if this is not the first IP address.
+                        if (ipAddresses.isNotEmpty()) {
+                            ipAddresses.append("\n")
+                        }
+
+                        // Add the IP address to the string builder.
+                        ipAddresses.append(inetAddress.hostAddress)
+                    }
+
+                    // Store the IP addresses.
+                    nestedScrollWebView.currentIpAddresses = ipAddresses.toString()
+                } catch (exception: UnknownHostException) {
+                    // Do nothing.
+                }
+            }
+        }
+
+        // Checked for pinned mismatches if there is pinned information and it is not ignored.
+        if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.pinnedIpAddresses != "") && !nestedScrollWebView.ignorePinnedDomainInformation) {
+            CheckPinnedMismatchHelper.checkPinnedMismatch(nestedScrollWebView, supportFragmentManager, pinnedMismatchString)
+        }
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateBlocklistsCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/PopulateBlocklistsCoroutine.kt
new file mode 100644 (file)
index 0000000..4311ef5
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.drawerlayout.widget.DrawerLayout
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.helpers.BlocklistHelper
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.util.ArrayList
+
+class PopulateBlocklistsCoroutine(context: Context) {
+    // The public interface is used to send information back to the parent activity.
+    interface PopulateBlocklistsListener {
+        fun finishedPopulatingBlocklists(combinedBlocklists: ArrayList<ArrayList<List<Array<String>>>>)
+    }
+
+    // Define a populate blocklists listener.
+    private val populateBlocklistsListener: PopulateBlocklistsListener
+
+    // Define the class variables.
+    private val context: Context
+
+    // The public constructor.
+    init {
+        // Get a handle for the populate blocklists listener from the launching activity.
+        populateBlocklistsListener = context as PopulateBlocklistsListener
+
+        // Store the context.
+        this.context = context
+    }
+
+    fun populateBlocklists(activity: Activity) {
+        // Use a coroutine to populate the blocklists.
+        CoroutineScope(Dispatchers.Main).launch {
+            // Get handles for the views.
+            val drawerLayout = activity.findViewById<DrawerLayout>(R.id.drawerlayout)
+            val loadingBlocklistsRelativeLayout = activity.findViewById<RelativeLayout>(R.id.loading_blocklists_relativelayout)
+            val loadingBlocklistTextView = activity.findViewById<TextView>(R.id.loading_blocklist_textview)
+
+            // Show the loading blocklists screen.
+            loadingBlocklistsRelativeLayout.visibility = View.VISIBLE
+
+            // Instantiate the blocklist helper.
+            val blocklistHelper = BlocklistHelper()
+
+            // Create a combined array list.
+            val combinedBlocklists = ArrayList<ArrayList<List<Array<String>>>>()
+
+            // Advertise the loading of EasyList.
+            loadingBlocklistTextView.text = context.getString(R.string.loading_easylist)
+
+            withContext(Dispatchers.IO) {
+                // Populate EasyList.
+                val easyList = blocklistHelper.parseBlocklist(context.assets, "blocklists/easylist.txt")
+
+                // Advertise the loading of EasyPrivacy.
+                withContext(Dispatchers.Main) {
+                    loadingBlocklistTextView.text = context.getString(R.string.loading_easyprivacy)
+                }
+
+                // Populate EasyPrivacy.
+                val easyPrivacy = blocklistHelper.parseBlocklist(context.assets, "blocklists/easyprivacy.txt")
+
+                // Advertise the loading of Fanboy's Annoyance List.
+                withContext(Dispatchers.Main) {
+                    loadingBlocklistTextView.text = context.getString(R.string.loading_fanboys_annoyance_list)
+                }
+
+                // Populate Fanboy's Annoyance List.
+                val fanboysAnnoyanceList = blocklistHelper.parseBlocklist(context.assets, "blocklists/fanboy-annoyance.txt")
+
+                // Advertise the loading of Fanboy's social blocking list.
+                withContext(Dispatchers.Main) {
+                    loadingBlocklistTextView.text = context.getString(R.string.loading_fanboys_social_blocking_list)
+                }
+
+                // Populate Fanboy's Social Blocking List.
+                val fanboysSocialList = blocklistHelper.parseBlocklist(context.assets, "blocklists/fanboy-social.txt")
+
+                // Advertise the loading of UltraList
+                withContext(Dispatchers.Main) {
+                    loadingBlocklistTextView.text = context.getString(R.string.loading_ultralist)
+                }
+
+                // Populate UltraList.
+                val ultraList = blocklistHelper.parseBlocklist(context.assets, "blocklists/ultralist.txt")
+
+                // Advertise the loading of UltraPrivacy.
+                withContext(Dispatchers.Main) {
+                    loadingBlocklistTextView.text = context.getString(R.string.loading_ultraprivacy)
+                }
+
+                // Populate UltraPrivacy.
+                val ultraPrivacy = blocklistHelper.parseBlocklist(context.assets, "blocklists/ultraprivacy.txt")
+
+                // Populate the combined array list.
+                combinedBlocklists.add(easyList)
+                combinedBlocklists.add(easyPrivacy)
+                combinedBlocklists.add(fanboysAnnoyanceList)
+                combinedBlocklists.add(fanboysSocialList)
+                combinedBlocklists.add(ultraList)
+                combinedBlocklists.add(ultraPrivacy)
+
+                // Update the UI.
+                withContext(Dispatchers.Main) {
+                    // Show the drawer layout.
+                    drawerLayout.visibility = View.VISIBLE
+
+                    // Hide the loading blocklists screen.
+                    loadingBlocklistsRelativeLayout.visibility = View.GONE
+
+                    // Enable the sliding drawers.
+                    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
+
+                    // Add the first tab.
+                    populateBlocklistsListener.finishedPopulatingBlocklists(combinedBlocklists)
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/PrepareSaveDialogCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/PrepareSaveDialogCoroutine.kt
new file mode 100644 (file)
index 0000000..1af0320
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.content.Context
+import android.net.Uri
+import android.webkit.CookieManager
+import android.webkit.MimeTypeMap
+
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass
+import com.stoutner.privacybrowser.dialogs.SaveDialog
+import com.stoutner.privacybrowser.helpers.ProxyHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.lang.Exception
+import java.net.HttpURLConnection
+import java.net.URL
+import java.text.NumberFormat
+
+object PrepareSaveDialogCoroutine {
+    @JvmStatic
+    fun prepareSaveDialog(context: Context, supportFragmentManager: FragmentManager, urlString: String, userAgent: String, cookiesEnabled: Boolean) {
+        // Use a coroutine to prepare the save dialog.
+        CoroutineScope(Dispatchers.Main).launch {
+            // Make the network requests on the IO thread.
+            withContext(Dispatchers.IO) {
+                // Define the strings.
+                var formattedFileSize: String
+                var fileNameString: String
+
+                // Populate the file size and name strings.
+                if (urlString.startsWith("data:")) {  // The URL contains the entire data of an image.
+                    // Remove `data:` from the beginning of the URL.
+                    val urlWithoutData = urlString.substring(5)
+
+                    // Get the URL MIME type, which ends with a `;`.
+                    val urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"))
+
+                    // Get the Base64 data, which begins after a `,`.
+                    val base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1)
+
+                    // Calculate the file size of the data URL.  Each Base64 character represents 6 bits.
+                    formattedFileSize = NumberFormat.getInstance().format(base64DataString.length * 3L / 4) + " " + context.getString(R.string.bytes)
+
+                    // Set the file name according to the MIME type.
+                    fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType)
+                } else {  // The URL refers to the location of the data.
+                    // Initialize the formatted file size string.
+                    formattedFileSize = context.getString(R.string.unknown_size)
+
+                    // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
+                    try {
+                        // Convert the URL string to a URL.
+                        val url = URL(urlString)
+
+                        // Instantiate the proxy helper.
+                        val proxyHelper = ProxyHelper()
+
+                        // Get the current proxy.
+                        val proxy = proxyHelper.getCurrentProxy(context)
+
+                        // Open a connection to the URL.  No data is actually sent at this point.
+                        val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
+
+                        // Add the user agent to the header property.
+                        httpUrlConnection.setRequestProperty("User-Agent", userAgent)
+
+                        // Add the cookies if they are enabled.
+                        if (cookiesEnabled) {
+                            // Get the cookies for the current domain.
+                            val cookiesString = CookieManager.getInstance().getCookie(url.toString())
+
+                            // Add the cookies if they are not null.
+                            if (cookiesString != null)
+                                httpUrlConnection.setRequestProperty("Cookie", cookiesString)
+                        }
+
+                        // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+                        try {
+                            // Get the status code.  This initiates a network connection.
+                            val responseCode = httpUrlConnection.responseCode
+
+                            // Check the response code.
+                            if (responseCode >= 400) {  // The response code is an error message.
+                                // Set the formatted file size to indicate a bad URL.
+                                formattedFileSize = context.getString(R.string.invalid_url)
+
+                                // Set the file name according to the URL.
+                                fileNameString = getFileNameFromUrl(context, urlString, null)
+                            } else {  // The response code is not an error message.
+                                // Get the headers.
+                                val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
+                                val contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition")
+                                var contentTypeString = httpUrlConnection.contentType
+
+                                // Remove anything after the MIME type in the content type string.
+                                if (contentTypeString.contains(";"))
+                                    contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"))
+
+                                // Only process the content length string if it isn't null.
+                                if (contentLengthString != null) {
+                                    // Convert the content length string to a long.
+                                    val fileSize = contentLengthString.toLong()
+
+                                    // Format the file size.
+                                    formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
+                                }
+
+                                // Get the file name string from the content disposition.
+                                fileNameString = getFileNameFromHeaders(context, contentDispositionString, contentTypeString, urlString)
+                            }
+                        } finally {
+                            // Disconnect the HTTP URL connection.
+                            httpUrlConnection.disconnect()
+                        }
+                    } catch (exception: Exception) {
+                        // Set the formatted file size to indicate a bad URL.
+                        formattedFileSize = context.getString(R.string.invalid_url)
+
+                        // Set the file name according to the URL.
+                        fileNameString = getFileNameFromUrl(context, urlString, null)
+                    }
+                }
+
+                // Display the dialog on the main thread.
+                withContext(Dispatchers.Main) {
+                    // Instantiate the save dialog.
+                    val saveDialogFragment: DialogFragment = SaveDialog.saveUrl(urlString, formattedFileSize, fileNameString, userAgent, cookiesEnabled)
+
+                    // Try to show the dialog.  Sometimes the window is not active.
+                    try {
+                        // Show the save dialog.
+                        saveDialogFragment.show(supportFragmentManager, context.getString(R.string.save_dialog))
+                    } catch (exception: Exception) {
+                        // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                        MainWebViewActivity.pendingDialogsArrayList.add(PendingDialogDataClass(saveDialogFragment, context.getString(R.string.save_dialog)))
+                    }
+                }
+            }
+        }
+    }
+
+    // Content dispositions can contain other text besides the file name, and they can be in any order.
+    // Elements are separated by semicolons.  Sometimes the file names are contained in quotes.
+    @JvmStatic
+    fun getFileNameFromHeaders(context: Context, contentDispositionString: String?, contentTypeString: String?, urlString: String): String {
+        // Define a file name string.
+        var fileNameString: String
+
+        // Only process the content disposition string if it isn't null.
+        if (contentDispositionString != null) {  // The content disposition is not null.
+            // Check to see if the content disposition contains a file name.
+            if (contentDispositionString.contains("filename=")) {  // The content disposition contains a filename.
+                // Get the part of the content disposition after `filename=`.
+                fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9)
+
+                // Remove any `;` and anything after it.  This removes any entries after the filename.
+                if (fileNameString.contains(";"))
+                    fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1)
+
+                // Remove any `"` at the beginning of the string.
+                if (fileNameString.startsWith("\""))
+                    fileNameString = fileNameString.substring(1)
+
+                // Remove any `"` at the end of the string.
+                if (fileNameString.endsWith("\""))
+                    fileNameString = fileNameString.substring(0, fileNameString.length - 1)
+            } else {  // The headers contain no useful information.
+                // Get the file name string from the URL.
+                fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
+            }
+        } else {  // The content disposition is null.
+            // Get the file name string from the URL.
+            fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
+        }
+
+        // Return the file name string.
+        return fileNameString
+    }
+
+    private fun getFileNameFromUrl(context: Context, urlString: String, contentTypeString: String?): String {
+        // Convert the URL string to a URI.
+        val uri = Uri.parse(urlString)
+
+        // Get the last path segment.
+        var lastPathSegment = uri.lastPathSegment
+
+        // Use a default file name if the last path segment is null.
+        if (lastPathSegment == null) {
+            // Set the last path segment to be the generic file name.
+            lastPathSegment = context.getString(R.string.file)
+
+            // Add a file extension if it can be detected.
+            if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString))
+                lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString)
+        }
+
+        // Return the last path segment as the file name.
+        return lastPathSegment
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveAboutVersionImageCoroutine.kt b/app/src/main/java/com/stoutner/privacybrowser/coroutines/SaveAboutVersionImageCoroutine.kt
new file mode 100644 (file)
index 0000000..8fdd9d0
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.coroutines
+
+import android.app.Activity
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.net.Uri
+import android.os.Build
+import android.provider.OpenableColumns
+import android.widget.LinearLayout
+
+import com.google.android.material.snackbar.Snackbar
+
+import com.stoutner.privacybrowser.R
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.io.ByteArrayOutputStream
+import java.lang.Exception
+
+// Declare the class constants.
+private const val SUCCESS = "Success"
+
+object SaveAboutVersionImageCoroutine {
+    fun saveImage(activity: Activity, fileUri: Uri, aboutVersionLinearLayout: LinearLayout) {
+        // Save the image using a coroutine.
+        CoroutineScope(Dispatchers.Main).launch {
+            // Create a saving image snackbar.
+            val savingImageSnackbar: Snackbar
+
+            // Process the image on the IO thread.
+            withContext(Dispatchers.IO) {
+                // Instantiate a file name string.
+                val fileNameString: String
+
+                // Query the exact file name if the API >= 26.
+                if (Build.VERSION.SDK_INT >= 26) {  // The API >= 26.
+                    // Get a cursor from the content resolver.
+                    val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)
+
+                    // Get the file display name if the content resolve cursor is not null.
+                    if (contentResolverCursor != null) {  // The content resolve cursor is not null.
+                        // Move to the first row.
+                        contentResolverCursor.moveToFirst()
+
+                        // Get the file name from the cursor.
+                        fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
+
+                        // Close the cursor.
+                        contentResolverCursor.close()
+                    } else {  // The content resolve cursor is null.
+                        // Use the URI last path segment as the file name string.
+                        fileNameString = fileUri.lastPathSegment.toString()
+                    }
+                } else {  // The API is < 26.
+                    // Use the URI last path segment as the file name string.
+                    fileNameString = fileUri.lastPathSegment.toString()
+                }
+
+                // Use the main thread to display a snackbar.
+                withContext(Dispatchers.Main) {
+                    // Create a saving image snackbar.
+                    savingImageSnackbar = Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.processing_image, fileNameString), Snackbar.LENGTH_INDEFINITE)
+
+                    // Display the saving image snackbar.
+                    savingImageSnackbar.show()
+                }
+
+                // Create the about version bitmap.  This can be replaced by PixelCopy once the minimum API >= 26.
+                // 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.
+                val aboutVersionBitmap = Bitmap.createBitmap(aboutVersionLinearLayout.width, aboutVersionLinearLayout.height, Bitmap.Config.ARGB_8888)
+
+                // Create a canvas.
+                val aboutVersionCanvas = Canvas(aboutVersionBitmap)
+
+                // Use the main thread to interact with the linear layout.
+                withContext(Dispatchers.Main) {
+                    // Draw the current about version onto the bitmap.
+                    aboutVersionLinearLayout.draw(aboutVersionCanvas)
+                }
+
+                // Create an about version PNG byte array output stream.
+                val aboutVersionByteArrayOutputStream = ByteArrayOutputStream()
+
+                // 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.
+                aboutVersionBitmap.compress(Bitmap.CompressFormat.PNG, 0, aboutVersionByteArrayOutputStream)
+
+                // Create a file creation disposition string.
+                var fileCreationDisposition = SUCCESS
+
+                // Write the image inside a try block to capture any write exceptions.
+                try {
+                    // Open an output stream.
+                    val outputStream = activity.contentResolver.openOutputStream(fileUri)!!
+
+                    // Write the webpage image to the image file.
+                    aboutVersionByteArrayOutputStream.writeTo(outputStream)
+
+                    // Close the output stream.
+                    outputStream.close()
+                } catch (exception: Exception) {
+                    // Store the error in the file creation disposition string.
+                    fileCreationDisposition = exception.toString()
+                }
+
+                // Use the main thread to update the snackbars.
+                withContext(Dispatchers.Main) {
+                    // Dismiss the saving image snackbar.
+                    savingImageSnackbar.dismiss()
+
+                    // Display a file creation disposition snackbar.
+                    if (fileCreationDisposition == SUCCESS) {
+                        // Create a file saved snackbar.
+                        Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
+                    } else {
+                        Snackbar.make(aboutVersionLinearLayout, activity.getString(R.string.error_saving_file, fileNameString, fileCreationDisposition), Snackbar.LENGTH_INDEFINITE).show()
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dataclasses/History.kt b/app/src/main/java/com/stoutner/privacybrowser/dataclasses/History.kt
deleted file mode 100644 (file)
index a293c93..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright © 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dataclasses
-
-import android.graphics.Bitmap
-
-// Define the history data class.  The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
-data class History(@JvmField val favoriteIcon: Bitmap, @JvmField val url: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dataclasses/HistoryDataClass.kt b/app/src/main/java/com/stoutner/privacybrowser/dataclasses/HistoryDataClass.kt
new file mode 100644 (file)
index 0000000..c678202
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2017,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dataclasses
+
+import android.graphics.Bitmap
+
+// Define the history data class.  The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
+data class HistoryDataClass(@JvmField val favoriteIcon: Bitmap, @JvmField val url: String)
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialog.kt
deleted file mode 100644 (file)
index 916f4c4..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright © 2021-2022 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.dataclasses
-
-import androidx.fragment.app.DialogFragment
-
-// Define the pending dialogs data class.  The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
-data class PendingDialog (@JvmField val dialogFragment: DialogFragment, @JvmField val tag: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialogDataClass.kt b/app/src/main/java/com/stoutner/privacybrowser/dataclasses/PendingDialogDataClass.kt
new file mode 100644 (file)
index 0000000..87d5972
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dataclasses
+
+import androidx.fragment.app.DialogFragment
+
+// Define the pending dialogs data class.  The `@JvmField` notation can be remove once all the code has migrated to Kotlin.
+data class PendingDialogDataClass (@JvmField val dialogFragment: DialogFragment, @JvmField val tag: String)
index 77cd1c1acb7b16434b6c7614b58e71842450cb72..63a6eaa301c0d8f76a41e0ab3b589b2c97d0719e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2019-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
@@ -22,7 +22,6 @@ package com.stoutner.privacybrowser.dialogs
 import android.app.Dialog
 import android.content.Context
 import android.content.DialogInterface
-import android.os.AsyncTask
 import android.os.Bundle
 import android.text.Editable
 import android.text.InputType
@@ -36,7 +35,14 @@ import androidx.fragment.app.DialogFragment
 import androidx.preference.PreferenceManager
 
 import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.asynctasks.GetUrlSize
+import com.stoutner.privacybrowser.helpers.GetUrlSizeHelper
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import java.net.URL
 
 // Define the class constants.
 private const val URL_STRING = "url_string"
@@ -49,9 +55,6 @@ class SaveDialog : DialogFragment() {
     // Declare the class variables.
     private lateinit var saveListener: SaveListener
 
-    // Define the class variables.
-    private var getUrlSize: AsyncTask<*, *, *>? = null
-
     // The public interface is used to send information back to the parent activity.
     interface SaveListener {
         fun onSaveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment)
@@ -170,26 +173,29 @@ class SaveDialog : DialogFragment() {
             }
 
             override fun afterTextChanged(editable: Editable) {
-                // Cancel the get URL size AsyncTask if it is running.
-                if (getUrlSize != null) {
-                    getUrlSize!!.cancel(true)
-                }
-
                 // Get the current URL to save.
                 val urlToSave = urlEditText.text.toString()
 
-                // Wipe the file size text view.
-                fileSizeTextView.text = ""
-
-                // Get the file size for the current URL.
-                getUrlSize = GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave)
-
                 // Enable the save button if the URL is populated.
                 saveButton.isEnabled = urlToSave.isNotEmpty()
+
+                CoroutineScope(Dispatchers.Main).launch {
+                    // Create a URL size string.
+                    var urlSize: String
+
+                    // Get the URL size on the IO thread.
+                    withContext(Dispatchers.IO) {
+                        // Get the URL size.
+                        urlSize = GetUrlSizeHelper.getUrl(requireContext(), URL(urlToSave), userAgentString, cookiesEnabled)
+                    }
+
+                    // Display the updated URL.
+                    fileSizeTextView.text = urlSize
+                }
             }
         })
 
         // Return the alert dialog.
         return alertDialog
     }
-}
\ No newline at end of file
+}
index e9de8b31eda4fc20223234fa35fec70c4d8ca0fd..23cdd5f20acdd373cd2e40e7ac2f0152e3ba5b23 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
@@ -39,7 +39,7 @@ import androidx.preference.PreferenceManager
 import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.activities.MainWebViewActivity
 import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter
-import com.stoutner.privacybrowser.dataclasses.History
+import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
 import com.stoutner.privacybrowser.views.NestedScrollWebView
 
 // Define the class constants.
@@ -115,7 +115,7 @@ class UrlHistoryDialog : DialogFragment() {
         val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
 
         // Create a history array list.
-        val historyArrayList = ArrayList<History>()
+        val historyDataClassArrayList = ArrayList<HistoryDataClass>()
 
         // Populate the history array list, descending from the end of the list so that the newest entries are at the top.  `-1` is needed because the history array list is zero-based.
         for (i in webBackForwardList.size - 1 downTo 0) {
@@ -128,10 +128,10 @@ class UrlHistoryDialog : DialogFragment() {
             }
 
             // Store the favorite icon and the URL in history entry.
-            val historyEntry = History(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
+            val historyDataClassEntry = HistoryDataClass(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
 
             // Add this history entry to the history array list.
-            historyArrayList.add(historyEntry)
+            historyDataClassArrayList.add(historyDataClassEntry)
         }
 
         // Subtract the original current page ID from the array size because the order of the array is reversed so that the newest entries are at the top.  `-1` is needed because the array is zero-based.
@@ -174,7 +174,7 @@ class UrlHistoryDialog : DialogFragment() {
         alertDialog.show()
 
         // Instantiate a history array adapter.
-        val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyArrayList, currentPageId)
+        val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyDataClassArrayList, currentPageId)
 
         // Get a handle for the list view.
         val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
@@ -206,4 +206,4 @@ class UrlHistoryDialog : DialogFragment() {
         // Return the alert dialog.
         return alertDialog
     }
-}
\ No newline at end of file
+}
index 6e5c066869b5390d2e401a1c26b1fa7e55e101b0..1d129078461dab04428d09f0007a6a24e0c66434 100644 (file)
@@ -53,7 +53,7 @@ import com.google.android.material.snackbar.Snackbar
 
 import com.stoutner.privacybrowser.BuildConfig
 import com.stoutner.privacybrowser.R
-import com.stoutner.privacybrowser.asynctasks.SaveAboutVersionImage
+import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
 
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -210,11 +210,9 @@ class AboutVersionFragment : Fragment() {
 
     // Define the save about version image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
     private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri: Uri? ->
-        // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
-        if (fileUri != null) {
-            // Save the about version image.
-            SaveAboutVersionImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout)).execute()
-        }
+        // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
+        if (fileUri != null)
+            SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
index b67a7f3f46e4506468b713b195d1712e34b70d1a..8926f289c94c5e5df1aeb3c86e9951a03366430d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2018-2019,2021-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
 
 package com.stoutner.privacybrowser.helpers
 
-import android.app.Activity
-
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.FragmentManager
 
-import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.activities.MainWebViewActivity
-import com.stoutner.privacybrowser.dataclasses.PendingDialog
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass
 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog.Companion.displayDialog
 import com.stoutner.privacybrowser.views.NestedScrollWebView
 
@@ -36,7 +33,7 @@ import java.util.Date
 
 object CheckPinnedMismatchHelper {
     @JvmStatic
-    fun checkPinnedMismatch(activity: Activity, fragmentManager: FragmentManager, nestedScrollWebView: NestedScrollWebView) {
+    fun checkPinnedMismatch(nestedScrollWebView: NestedScrollWebView, supportFragmentManager: FragmentManager, pinnedMismatchString: String) {
         // Initialize the current SSL certificate variables.
         var currentWebsiteIssuedToCName = ""
         var currentWebsiteIssuedToOName = ""
@@ -127,11 +124,11 @@ object CheckPinnedMismatchHelper {
             // Try to show the dialog.  Sometimes the window is not active.
             try {
                 // Show the pinned mismatch alert dialog.
-                pinnedMismatchDialogFragment.show(fragmentManager, activity.getString(R.string.pinned_mismatch))
+                pinnedMismatchDialogFragment.show(supportFragmentManager, pinnedMismatchString)
             } catch (exception: Exception) {
                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                MainWebViewActivity.pendingDialogsArrayList.add(PendingDialog(pinnedMismatchDialogFragment, activity.getString(R.string.pinned_mismatch)))
+                MainWebViewActivity.pendingDialogsArrayList.add(PendingDialogDataClass(pinnedMismatchDialogFragment, pinnedMismatchString))
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/GetUrlSizeHelper.kt b/app/src/main/java/com/stoutner/privacybrowser/helpers/GetUrlSizeHelper.kt
new file mode 100644 (file)
index 0000000..6c3b3f8
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser Android is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.helpers
+
+import android.content.Context
+import android.webkit.CookieManager
+
+import com.stoutner.privacybrowser.R
+
+import java.lang.Exception
+import java.net.HttpURLConnection
+import java.net.URL
+import java.text.NumberFormat
+
+object GetUrlSizeHelper {
+    fun getUrl(context: Context, url: URL, userAgent: String, cookiesEnabled: Boolean): String {
+        // Initialize the formatted file size string.
+        var formattedFileSize = context.getString(R.string.unknown_size)
+
+        // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
+        try {
+            // Instantiate the proxy helper.
+            val proxyHelper = ProxyHelper()
+
+            // Get the current proxy.
+            val proxy = proxyHelper.getCurrentProxy(context)
+
+            // Open a connection to the URL.  No data is actually sent at this point.
+            val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
+
+            // Add the user agent to the header property.
+            httpUrlConnection.setRequestProperty("User-Agent", userAgent)
+
+            // Add the cookies if they are enabled.
+            if (cookiesEnabled) {
+                // Get the cookies for the current domain.
+                val cookiesString = CookieManager.getInstance().getCookie(url.toString())
+
+                // Only add the cookies if they are not null.
+                if (cookiesString != null) {
+                    // Add the cookies to the header property.
+                    httpUrlConnection.setRequestProperty("Cookie", cookiesString)
+                }
+            }
+
+            // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
+            try {
+                // Get the status code.  This initiates a network connection.
+                val responseCode = httpUrlConnection.responseCode
+
+                // Check the response code.
+                if (responseCode >= 400) {  // The response code is an error message.
+                    // Set the formatted file size to indicate a bad URL.
+                    formattedFileSize = context.getString(R.string.invalid_url)
+                } else {  // The response code is not an error message.
+                    // Get the content length header.
+                    val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
+
+                    // Only process the content length string if it isn't null.
+                    if (contentLengthString != null) {
+                        // Convert the content length string to a long.
+                        val fileSize = contentLengthString.toLong()
+
+                        // Format the file size.
+                        formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
+                    }
+                }
+            } finally {
+                // Disconnect the HTTP URL connection.
+                httpUrlConnection.disconnect()
+            }
+        } catch (exception: Exception) {
+            // Set the formatted file size to indicate a bad URL.
+            formattedFileSize = context.getString(R.string.invalid_url)
+        }
+
+        // Return the formatted file size.
+        return formattedFileSize
+    }
+}
index 20c459830f7d34fbfb8112ce72dd4784dbb6d939..0040f4f278473e589c960b3f5cb61932ed23969b 100644 (file)
     <string name="unknown_size">Unbekannte Größe</string>
     <string name="invalid_url">Ungültige URL</string>
     <string name="saving_file">Speichere Datei:</string>
-    <string name="processing_image">Bild wird bearbeitet… :</string>
+    <string name="processing_image">Bild wird bearbeitet: \u0020 %1$s</string>
     <string name="error_saving_file">Fehler beim Speichern der Datei %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Unbekannter Fehler</string>
 
         <string name="saved_ip_addresses">Gespeicherte IP-Adressen</string>
         <string name="current_ip_addresses">aktuelle IP-Adressen</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Verschlüsselung</string>
     <string-array name="encryption_type">
         <item>keine</item>
     <string name="import_button">importieren</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">entschlüsseln</string>
     <string name="export_successful">Export erfolgreich.</string>
-    <string name="export_failed">Export fehlgeschlagen:  %1$s</string>
-    <string name="import_failed">Import fehlgeschlagen:  %1$s</string>
+    <string name="export_failed">Export fehlgeschlagen: \u0020 %1$s</string>
+    <string name="import_failed">Import fehlgeschlagen: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 16cac6a0b7b0d1cac555d09e910951ca891161ec..6684252ed7def5f10f1ee318ec16278ac863df6a 100644 (file)
     <string name="unknown_size">Tamaño desconocido</string>
     <string name="invalid_url">URL inválida</string>
     <string name="saving_file">Guardando archivo:</string>
-    <string name="processing_image">Procesando imagen… :</string>
+    <string name="processing_image">Procesando imagen: \u0020 %1$s</string>
     <string name="error_saving_file">Error al guardar %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Error desconocido</string>
 
         <string name="saved_ip_addresses">Direcciones IP guardadas</string>
         <string name="current_ip_addresses">Direcciones IP actuales</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Cifrado</string>
     <string-array name="encryption_type">
         <item>Ninguno</item>
     <string name="import_button">Importar</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">Descifrar</string>
     <string name="export_successful">Exportación exitosa.</string>
-    <string name="export_failed">Exportación fallida:  %1$s</string>
-    <string name="import_failed">Importación fallida:  %1$s</string>
+    <string name="export_failed">Exportación fallida: \u0020 %1$s</string>
+    <string name="import_failed">Importación fallida: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 08c53c985ab8f67ee13dcdd7be35211a1600a28b..476012b14aaa2cf7182d06f0301c1f183fa83a08 100644 (file)
     <string name="unknown_size">taille inconnue</string>
     <string name="invalid_url">URL invalide</string>
     <string name="saving_file">Enregistrement du fichier :</string>
-    <string name="processing_image">Traitement de l\'image… :</string>
+    <string name="processing_image">Traitement de l\'image: \u0020 %1$s</string>
     <string name="error_saving_file">Erreur lors de l\'enregistrement de %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Erreur inconnue</string>
 
         <string name="saved_ip_addresses">Adresse(s) IP sauvegardée(s)</string>
         <string name="current_ip_addresses">Adresse(s) IP courante(s)</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Chiffrement</string>
     <string-array name="encryption_type">
         <item>Aucun</item>
index f02a29a6191d2b2e9aaf40a7d26699c20d179dbc..3e9021cfdacab5c52c4c9608a136680f7bc717de 100644 (file)
     <string name="unknown_size">Dimensione sconosciuta</string>
     <string name="invalid_url">URL non valida</string>
     <string name="saving_file">Salvataggio file:</string>
-    <string name="processing_image">Creazione immagine… :</string>
+    <string name="processing_image">Creazione immagine: \u0020 %1$s</string>
     <string name="error_saving_file">Error di salvataggio di %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Errore sconosciuto</string>
 
         <string name="saved_ip_addresses">Indirizzi IP salvati</string>
         <string name="current_ip_addresses">Indirizzi IP attuali</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Cifratura</string>
     <string-array name="encryption_type">
         <item>Nessuna</item>
     <string name="import_button">Importa</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">Decripta</string>
     <string name="export_successful">Esportazione riuscita</string>
-    <string name="export_failed">Esportazione fallita:  %1$s</string>
-    <string name="import_failed">Importazione fallita:  %1$s</string>
+    <string name="export_failed">Esportazione fallita: \u0020 %1$s</string>
+    <string name="import_failed">Importazione fallita: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 3ab6a0527ab9d0be80ee7807c17acca05297cb27..687c0305adebd1078699b24c747a139ee8549788 100644 (file)
     <string name="unknown_size">tamanho desconhecido</string>
     <string name="invalid_url">URL inválida</string>
     <string name="saving_file">Salvando file:</string>
-    <string name="processing_image">Processando imagem… :</string>
+    <string name="processing_image">Processando imagem: \u0020 %1$s</string>
     <string name="error_saving_file">Erro ao salvar %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Erro desconhecido</string>
 
         <string name="saved_ip_addresses">Endereços IP salvos</string>
         <string name="current_ip_addresses">Endereços IP atuais</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Encriptação</string>
     <string-array name="encryption_type">
         <item>Nenhum</item>
     <string name="import_button">Importar</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">Descriptografar</string>
     <string name="export_successful">Exportação bem sucedida.</string>
-    <string name="export_failed">A exportação falhou:  %1$s</string>
-    <string name="import_failed">A importação falhou:  %1$s</string>
+    <string name="export_failed">A exportação falhou: \u0020 %1$s</string>
+    <string name="import_failed">A importação falhou: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index f38e108e0c3a2e48abffe31aa9c5ac64d0608615..7557214b2bbc7924997f0bc7d311bdc14fc13d12 100644 (file)
     <string name="unknown_size">неизвестный размер</string>
     <string name="invalid_url">неправильный URL</string>
     <string name="saving_file">Сохранение файла:</string>
-    <string name="processing_image">Обработка изображения… :</string>
+    <string name="processing_image">Обработка изображения: \u0020 %1$s</string>
     <string name="error_saving_file">Ошибка сохранения %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Неизвестная ошибка</string>
 
         <string name="saved_ip_addresses">Сохраненные IP-адреса</string>
         <string name="current_ip_addresses">Текущие IP-адреса</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Шифрование</string>
     <string-array name="encryption_type">
         <item>Нет</item>
     <string name="import_button">Импорт</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">Расшифровать</string>
     <string name="export_successful">Экспорт выполнен.</string>
-    <string name="export_failed">Сбой при экспорте:  %1$s</string>
-    <string name="import_failed">Сбой при импорте:  %1$s</string>
+    <string name="export_failed">Сбой при экспорте: \u0020 %1$s</string>
+    <string name="import_failed">Сбой при импорте: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 08b905f4f9f429c1a9214ffc2b09f8543c0eb197..bef8d5a00728e36e56fbf31fae8e7098b6dcec69 100644 (file)
         <string name="saved_ip_addresses">Kayıtlı IP adresleri</string>
         <string name="current_ip_addresses">Geçerli IP adresleri</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Şifreleme</string>
     <string-array name="encryption_type">
         <item>Hiçbiri</item>
     <string name="import_button">İçeri aktar</string>  <!-- `import` is a reserved word and cannot be used as the name. -->
     <string name="decrypt">Şifre çöz (Decrypt)</string>
     <string name="export_successful">Dışa aktarım başarılı</string>
-    <string name="export_failed">Dışa aktarım başarısız:  %1$s</string>
-    <string name="import_failed">İçe aktarım başarısız:  %1$s</string>
+    <string name="export_failed">Dışa aktarım başarısız: \u0020 %1$s</string>
+    <string name="import_failed">İçe aktarım başarısız: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
index 824d3d62210140f896fe7e9c1a05def532a9f604..22f64b94a6d4cae3d84f96989308b422ff933a8d 100644 (file)
     <string name="invalid_url">invalid URL</string>
     <string name="saving_file">Saving file:</string>
     <string name="saved">%1$s saved.</string>
-    <string name="processing_image">Processing image… :</string>
+    <string name="processing_image">Processing image: \u0020 %1$s</string>
     <string name="error_saving_file">Error saving %1$s: \u0020 %2$s</string>
     <string name="unknown_error">Unknown error</string>
 
         <string name="saved_ip_addresses">Saved IP addresses</string>
         <string name="current_ip_addresses">Current IP addresses</string>
 
-    <!-- Import/Export.  The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
+    <!-- Import/Export.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
+      The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->
     <string name="encryption">Encryption</string>
     <string-array name="encryption_type">
         <item>None</item>
     <string name="privacy_browser_settings_pbs">Privacy Browser Settings %1$s.pbs</string>
     <string name="privacy_browser_settings_pbs_aes">Privacy Browser Settings %1$s.pbs.aes</string>
     <string name="export_successful">Export successful.</string>
-    <string name="export_failed">Export failed:  %1$s</string>
-    <string name="import_failed">Import failed:  %1$s</string>
+    <string name="export_failed">Export failed: \u0020 %1$s</string>
+    <string name="import_failed">Import failed: \u0020 %1$s</string>
 
     <!-- Logcat.  Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.
         The `%1$s` code inserts variables into the displayed text and should be preserved in translation.  <https://developer.android.com/reference/kotlin/java/util/Formatter> -->