From 031def95c6d9bfc14113fe86b4a5690233d93ce2 Mon Sep 17 00:00:00 2001 From: Soren Stoutner <soren@stoutner.com> Date: Wed, 31 Mar 2021 11:13:09 -0700 Subject: [PATCH] Migrate the rest of the dialogs to Kotlin. https://redmine.stoutner.com/issues/683 --- app/build.gradle | 2 +- app/src/main/assets/de/about_changelog.html | 8 +- app/src/main/assets/en/about_changelog.html | 4 +- app/src/main/assets/es/about_changelog.html | 4 +- app/src/main/assets/fr/about_changelog.html | 4 +- app/src/main/assets/it/about_changelog.html | 4 +- .../main/assets/pt-rBR/about_changelog.html | 4 +- app/src/main/assets/ru/about_changelog.html | 4 +- app/src/main/assets/tr/about_changelog.html | 4 +- .../activities/MainWebViewActivity.java | 53 +- .../activities/RequestsActivity.java | 10 +- .../asynctasks/SaveAboutVersionImage.java | 3 - .../privacybrowser/asynctasks/SaveUrl.java | 3 - .../privacybrowser/dialogs/AddDomainDialog.kt | 4 +- .../dialogs/CreateBookmarkDialog.kt | 4 +- .../dialogs/CreateBookmarkFolderDialog.kt | 4 +- .../dialogs/CreateHomeScreenShortcutDialog.kt | 4 +- .../dialogs/EditBookmarkDatabaseViewDialog.kt | 4 +- .../dialogs/EditBookmarkDialog.kt | 4 +- .../EditBookmarkFolderDatabaseViewDialog.kt | 4 +- .../dialogs/EditBookmarkFolderDialog.kt | 4 +- .../privacybrowser/dialogs/FontSizeDialog.kt | 4 +- .../dialogs/HttpAuthenticationDialog.kt | 9 +- .../dialogs/MoveToFolderDialog.kt | 4 +- .../privacybrowser/dialogs/OpenDialog.kt | 2 +- .../dialogs/PinnedMismatchDialog.kt | 4 +- .../dialogs/ProxyNotInstalledDialog.kt | 2 +- .../privacybrowser/dialogs/SaveDialog.kt | 10 +- .../dialogs/SaveWebpageDialog.java | 334 ------------- .../dialogs/SaveWebpageDialog.kt | 289 +++++++++++ .../dialogs/SslCertificateErrorDialog.java | 467 ------------------ .../dialogs/SslCertificateErrorDialog.kt | 441 +++++++++++++++++ .../dialogs/UrlHistoryDialog.java | 245 --------- .../dialogs/UrlHistoryDialog.kt | 212 ++++++++ .../dialogs/ViewRequestDialog.java | 349 ------------- .../dialogs/ViewRequestDialog.kt | 269 ++++++++++ .../dialogs/ViewSslCertificateDialog.java | 335 ------------- .../dialogs/ViewSslCertificateDialog.kt | 297 +++++++++++ .../dialogs/WaitingForProxyDialog.java | 79 --- .../dialogs/WaitingForProxyDialog.kt | 61 +++ .../fragments/AboutWebViewFragment.kt | 4 +- .../main/res/menu/webview_options_menu.xml | 9 +- build.gradle | 2 +- .../metadata/android/de-DE/changelogs/52.txt | 2 +- .../metadata/android/de-DE/changelogs/54.txt | 2 +- 45 files changed, 1676 insertions(+), 1895 deletions(-) delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt delete mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.kt diff --git a/app/build.gradle b/app/build.gradle index 2dff0f45..4eb4f1ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,7 +88,7 @@ dependencies { implementation 'androidx.webkit:webkit:1.4.0' // Include the Kotlin standard libraries. This should be the same version number listed in project build.gradle. - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.31' + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32' // Include the Google material library. implementation 'com.google.android.material:material:1.3.0' diff --git a/app/src/main/assets/de/about_changelog.html b/app/src/main/assets/de/about_changelog.html index 1fafac45..a1d639b8 100644 --- a/app/src/main/assets/de/about_changelog.html +++ b/app/src/main/assets/de/about_changelog.html @@ -33,8 +33,8 @@ </head> <body> - <h3>3.7 (version code 54)</h3> - <p>29. März 2021 - Mindest-API 19, Ziel-API 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (version code 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29. März 2021</a> - Mindest-API 19, Ziel-API 30</p> <ul> <li>Datei-Zugriff geändert, um <a href="https://redmine.stoutner.com/issues/546">"Scoped Storage" und das "Storage Access Framework"</a> nutzen zu können. Dies erlaubt, die Ziel-API auf 30 zu erhöhen und ermöglicht, die gefährlichen Berechtigungen READ_EXTERNAL_STORAGE und WRITE_EXTERNAL_STORAGE zu vermeiden. @@ -51,7 +51,7 @@ <li>Fehler behoben, durch den das <a href="https://redmine.stoutner.com/issues/616">Hamburger-Menü zu einem Pfeil</a> wurde, wenn das Navigations-Menü beim Neustart der App geöffnet war.</li> <li><a href="https://redmine.stoutner.com/issues/650">Ãffnen des Options-Menüs</a> beschleunigt.</li> <li>Aktualisierte deutsche Ãbersetzung von Bernhard G. Keller.</li> - <li>Aktualisierte Brasilianisch Portugiesische Ãbersetzung von <a href="mailto:mochileiro2006-trilhas@yahoo.com.br">Thiago Nazareno Conceição Silva de Jesus</a>.</li> + <li>Aktualisierte brasilianisch-portugiesische Ãbersetzung von <a href="mailto:mochileiro2006-trilhas@yahoo.com.br">Thiago Nazareno Conceição Silva de Jesus</a>.</li> <li>Aktualisierte französische Ãbersetzung von <a href="mailto:kevinliste@framalistes.org">Kévin L</a>.</li> <li>Aktualisierte italienische Ãbersetzung von Francesco Buratti.</li> <li>Aktualisierte russische Ãbersetzung.</li> @@ -89,7 +89,7 @@ <a href="https://redmine.stoutner.com/issues/359">für</a> <a href="https://redmine.stoutner.com/issues/551">das</a> <a href="https://redmine.stoutner.com/issues/610">Nutzer</a> <a href="https://redmine.stoutner.com/issues/618">Erlebnis</a> <a href="https://redmine.stoutner.com/issues/609">und</a> <a href="https://redmine.stoutner.com/issues/592">der grafischen</a> <a href="https://redmine.stoutner.com/issues/567">Oberfläche</a> <a href="https://redmine.stoutner.com/issues/554">umgesetzt</a>.</li> - <li>Teilweise Ãbersetzung in brasilianisches Portugiesisch von <a href="mailto:mochileiro2006-trilhas@yahoo.com.br">Thiago Nazareno Conceição Silva de Jesus</a>.</li> + <li>Teilweise Ãbersetzung in brasilianisch-portugiesische von <a href="mailto:mochileiro2006-trilhas@yahoo.com.br">Thiago Nazareno Conceição Silva de Jesus</a>.</li> <li>Aktualisierte deutsche Ãbersetzung von Bernhard G. Keller.</li> <li>Aktualisierte französische Ãbersetzung von <a href="mailto:kevinliste@framalistes.org">Kévin L</a>.</li> <li>Aktualisierte italienische Ãbersetzung von Francesco Buratti.</li> diff --git a/app/src/main/assets/en/about_changelog.html b/app/src/main/assets/en/about_changelog.html index 3d2b3e31..c9049d05 100644 --- a/app/src/main/assets/en/about_changelog.html +++ b/app/src/main/assets/en/about_changelog.html @@ -27,8 +27,8 @@ </head> <body> - <h3>3.7 (version code 54)</h3> - <p>29 March 2021 - minimum API 19, target API 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (version code 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 March 2021</a> - minimum API 19, target API 30</p> <ul> <li>Redesign file access to work with <a href="https://redmine.stoutner.com/issues/546">scoped storage and the Storage Access Framework</a>. This allows the target API to be bumped to 30 and removes the need for the dangerous READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions. diff --git a/app/src/main/assets/es/about_changelog.html b/app/src/main/assets/es/about_changelog.html index d0c8cd6e..364b7bbe 100644 --- a/app/src/main/assets/es/about_changelog.html +++ b/app/src/main/assets/es/about_changelog.html @@ -29,8 +29,8 @@ </head> <body> - <h3>3.7 (código de versión 54)</h3> - <p>29 de marzo de 2021 - API mÃnimo 19, API dirigido 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (código de versión 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 de marzo de 2021</a> - API mÃnimo 19, API dirigido 30</p> <ul> <li>Rediseñar el acceso a los archivos para que funcione con <a href="https://redmine.stoutner.com/issues/546">el almacenamiento de alcance y el marco de acceso al almacenamiento</a>. Esto permite que la API de destino se eleve a 30 y elimina la necesidad de los peligrosos permisos READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE. diff --git a/app/src/main/assets/fr/about_changelog.html b/app/src/main/assets/fr/about_changelog.html index d6c292c0..9a963879 100644 --- a/app/src/main/assets/fr/about_changelog.html +++ b/app/src/main/assets/fr/about_changelog.html @@ -29,8 +29,8 @@ </head> <body> - <h3>3.7 (version du code 54)</h3> - <p>29 Mars 2021 - API minimale : 19, API optimale : 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (version du code 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 Mars 2021</a> - API minimale : 19, API optimale : 30</p> <ul> <li>Redesign file access to work with <a href="https://redmine.stoutner.com/issues/546">scoped storage and the Storage Access Framework</a>. This allows the target API to be bumped to 30 and removes the need for the dangerous READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions. diff --git a/app/src/main/assets/it/about_changelog.html b/app/src/main/assets/it/about_changelog.html index 4f3a9a19..7f950afe 100644 --- a/app/src/main/assets/it/about_changelog.html +++ b/app/src/main/assets/it/about_changelog.html @@ -29,8 +29,8 @@ </head> <body> - <h3>3.7 (versione codice 54)</h3> - <p>29 Marzo 2021 - minima API 19, target API 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (versione codice 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 Marzo 2021</a> - minima API 19, target API 30</p> <ul> <li>Re-design dell'accesso ai file per funzionare con la <a href="https://redmine.stoutner.com/issues/546">scoped storage e lo Storage Access Framework</a>. Questo permette alla target API di essere aggiornata alla 30 ed elimina la necessità di avere le autorizzazioni molto pericolose READ_EXTERNAL_STORAGE e WRITE_EXTERNAL_STORAGE. diff --git a/app/src/main/assets/pt-rBR/about_changelog.html b/app/src/main/assets/pt-rBR/about_changelog.html index 11b056b3..f5a610f2 100644 --- a/app/src/main/assets/pt-rBR/about_changelog.html +++ b/app/src/main/assets/pt-rBR/about_changelog.html @@ -29,8 +29,8 @@ </head> <body> - <h3>3.7 (código da versão 54)</h3> - <p>29 March 2021 - minimum API 19, target API 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (código da versão 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 March 2021</a> - minimum API 19, target API 30</p> <ul> <li>Redesign file access to work with <a href="https://redmine.stoutner.com/issues/546">scoped storage and the Storage Access Framework</a>. This allows the target API to be bumped to 30 and removes the need for the dangerous READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions. diff --git a/app/src/main/assets/ru/about_changelog.html b/app/src/main/assets/ru/about_changelog.html index 2686e98a..229a592d 100644 --- a/app/src/main/assets/ru/about_changelog.html +++ b/app/src/main/assets/ru/about_changelog.html @@ -27,8 +27,8 @@ </head> <body> - <h3>3.7 (код веÑÑии 54)</h3> - <p>29 маÑÑа 2021 года - минималÑнÑй API 19, Ñелевой API 29</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (код веÑÑии 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 маÑÑа 2021 года</a> - минималÑнÑй API 19, Ñелевой API 29</p> <ul> <li>ÐÑполнен Ñедизайн доÑÑÑпа к Ñайлам Ð´Ð»Ñ ÑабоÑÑ Ñ <a href="https://redmine.stoutner.com/issues/546">огÑаниÑеннÑми по маÑÑÑÐ°Ð±Ñ Ñ ÑанилиÑем и ÑÑеймвоÑком доÑÑÑпа к Ñ ÑанилиÑÑ</a>. ÐÑо позволило ÑвелиÑиÑÑ Ñелевой API до 30 и иÑклÑÑиÑÑ Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ÑÑÑ Ð² опаÑнÑÑ ÑазÑеÑениÑÑ READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE. diff --git a/app/src/main/assets/tr/about_changelog.html b/app/src/main/assets/tr/about_changelog.html index e42b3f06..37db0435 100644 --- a/app/src/main/assets/tr/about_changelog.html +++ b/app/src/main/assets/tr/about_changelog.html @@ -27,8 +27,8 @@ </head> <body> - <h3>3.7 (version code 54)</h3> - <p>29 Mart 2021 - minimum API 19, target API 30</p> + <h3><a href="https://www.stoutner.com/privacy-browser-3-7/">3.7</a> (version code 54)</h3> + <p><a href="https://gitweb.stoutner.com/?p=PrivacyBrowser.git;a=commitdiff;h=f3b9172adedd74f705ddc0beac80798ae84f2920">29 Mart 2021</a> - minimum API 19, target API 30</p> <ul> <li>Redesign file access to work with <a href="https://redmine.stoutner.com/issues/546">scoped storage and the Storage Access Framework</a>. This allows the target API to be bumped to 30 and removes the need for the dangerous READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 88d41867..6b2d7eb0 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -94,6 +94,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; @@ -1764,6 +1765,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog)); // Consume the event. + return true; + } else if (menuItemId == R.id.save_archive) { + /* TODO. + try { + // Create an MHT file. + File mhtFile = File.createTempFile("mht_file", ".mht", getCacheDir()); + + // Populate the MHT file. + currentWebView.saveWebArchive(mhtFile.toString()); + + // Check the file length. + Log.i("MHT", "MHT file size: " + mhtFile.length()); + } catch (Exception exception){ + Log.i("MHT", "MHT exception: " + exception.toString()); + } + + */ + return true; } else if (menuItemId == R.id.add_to_homescreen) { // Add to homescreen. // Instantiate the create home screen shortcut dialog. @@ -3008,35 +3027,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment) { + public void onSaveWebpage(int saveType, @Nullable String originalUrlString, DialogFragment dialogFragment) { // Get the dialog. Dialog dialog = dialogFragment.getDialog(); // Remove the incorrect lint warning below that the dialog might be null. assert dialog != null; - // Get a handle for the edit texts. - EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext); + // Get a handle for the file name edit text. EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); - // Define the save webpage URL. - String saveWebpageUrl; - - // Store the URL. - if ((originalUrlString != null) && originalUrlString.startsWith("data:")) { - // Save the original URL. - saveWebpageUrl = originalUrlString; - } else { - // Get the URL from the edit text, which may have been modified. - saveWebpageUrl = dialogUrlEditText.getText().toString(); - } - // Get the file path from the edit text. String saveWebpageFilePath = fileNameEditText.getText().toString(); //Save the webpage according to the save type. switch (saveType) { case SaveWebpageDialog.SAVE_URL: + // Get a handle for the dialog URL edit text. + EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext); + + // Define the save webpage URL. + String saveWebpageUrl; + + // Store the URL. + if ((originalUrlString != null) && originalUrlString.startsWith("data:")) { + // Save the original URL. + saveWebpageUrl = originalUrlString; + } else { + // Get the URL from the edit text, which may have been modified. + saveWebpageUrl = dialogUrlEditText.getText().toString(); + } + // Save the URL. new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); break; @@ -3607,7 +3628,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void navigateHistory(String url, int steps) { + public void navigateHistory(@NonNull String url, int steps) { // Apply the domain settings. applyDomainSettings(currentWebView, url, false, false, false); diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java index 0b9be790..dcb9d66c 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.java @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>. + * Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>. * * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. * @@ -263,15 +263,15 @@ public class RequestsActivity extends AppCompatActivity implements ViewRequestDi } @Override - public void onPrevious(int id) { + public void onPrevious(int currentId) { // Show the previous dialog. - launchViewRequestDialog(id -1); + launchViewRequestDialog(currentId -1); } @Override - public void onNext(int id) { + public void onNext(int currentId) { // Show the next dialog. - launchViewRequestDialog(id + 1); + launchViewRequestDialog(currentId + 1); } private void launchViewRequestDialog(int id) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java index ed762a14..377ce7be 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java @@ -112,9 +112,6 @@ public class SaveAboutVersionImage extends AsyncTask<Void, Void, String> { // Write the webpage image to the image file. aboutVersionByteArrayOutputStream.writeTo(outputStream); - // Flush the output stream. - outputStream.flush(); - // Close the output stream. outputStream.close(); } catch (Exception exception) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java index 7d981c0f..03ab3746 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java @@ -198,9 +198,6 @@ public class SaveUrl extends AsyncTask<String, Long, String> { } } - // Flush the output stream. - outputStream.flush(); - // Close the output stream. outputStream.close(); } catch (Exception exception) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt index 464a24ab..8a602ef5 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.kt @@ -40,7 +40,7 @@ import androidx.preference.PreferenceManager import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper -// Declare the class constants. +// Define the class constants. private const val URL_STRING = "url_string" class AddDomainDialog : DialogFragment() { @@ -100,7 +100,7 @@ class AddDomainDialog : DialogFragment() { dialogBuilder.setTitle(R.string.add_domain) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.add_domain_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.add_domain_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt index 02832137..e787e1e3 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.kt @@ -41,7 +41,7 @@ import com.stoutner.privacybrowser.R import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val URL_STRING = "url_string" private const val TITLE = "title" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -122,7 +122,7 @@ class CreateBookmarkDialog : DialogFragment() { dialogBuilder.setIcon(favoriteIconDrawable) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.create_bookmark_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.create_bookmark_dialog, null)) // Set a listener on the cancel button. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt index c2b4f5f6..42f9a167 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.kt @@ -45,7 +45,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" class CreateBookmarkFolderDialog : DialogFragment() { @@ -114,7 +114,7 @@ class CreateBookmarkFolderDialog : DialogFragment() { dialogBuilder.setTitle(R.string.create_folder) // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.create_bookmark_folder_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.create_bookmark_folder_dialog, null)) // Set a listener on the cancel button. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt index b0df47fa..32c4e51a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt @@ -50,7 +50,7 @@ import com.stoutner.privacybrowser.R import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val SHORTCUT_NAME = "shortcut_name" private const val URL_STRING = "url_string" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -118,7 +118,7 @@ class CreateHomeScreenShortcutDialog : DialogFragment() { dialogBuilder.setIcon(favoriteIconDrawable) // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)) // Set a listener on the close button. Using null closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt index 95d25660..542c4632 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDatabaseViewDialog.kt @@ -56,7 +56,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val DATABASE_ID = "database_id" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -145,7 +145,7 @@ class EditBookmarkDatabaseViewDialog : DialogFragment() { dialogBuilder.setTitle(R.string.edit_bookmark) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.edit_bookmark_databaseview_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.edit_bookmark_databaseview_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt index 8fb3568a..752f5b1c 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.kt @@ -46,7 +46,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val DATABASE_ID = "database_id" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -133,7 +133,7 @@ class EditBookmarkDialog : DialogFragment() { dialogBuilder.setTitle(R.string.edit_bookmark) // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.edit_bookmark_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.edit_bookmark_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt index 63ba8a3c..e15e29ef 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDatabaseViewDialog.kt @@ -50,7 +50,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val DATABASE_ID = "database_id" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -138,7 +138,7 @@ class EditBookmarkFolderDatabaseViewDialog : DialogFragment() { dialogBuilder.setTitle(R.string.edit_folder) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.edit_bookmark_folder_databaseview_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.edit_bookmark_folder_databaseview_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt index 46082b89..1c70b82d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.kt @@ -46,7 +46,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream -// Declare the class constants. +// Define the class constants. private const val DATABASE_ID = "database_id" private const val FAVORITE_ICON_BYTE_ARRAY = "favorite_icon_byte_array" @@ -134,7 +134,7 @@ class EditBookmarkFolderDialog : DialogFragment() { dialogBuilder.setTitle(R.string.edit_folder) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.edit_bookmark_folder_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.edit_bookmark_folder_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/FontSizeDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/FontSizeDialog.kt index 80593fd9..b1c45673 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/FontSizeDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/FontSizeDialog.kt @@ -36,7 +36,7 @@ import androidx.preference.PreferenceManager import com.stoutner.privacybrowser.R -// Declare the class constants. +// Define the class constants. private const val FONT_SIZE = "font_size" class FontSizeDialog : DialogFragment() { @@ -93,7 +93,7 @@ class FontSizeDialog : DialogFragment() { dialogBuilder.setTitle(R.string.font_size) // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.font_size_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.font_size_dialog, null)) // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.close, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.kt index b3cf7074..42e43d91 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/HttpAuthenticationDialog.kt @@ -42,7 +42,7 @@ import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.activities.MainWebViewActivity import com.stoutner.privacybrowser.views.NestedScrollWebView -// Declare the class constants. +// Define the class constants. private const val HOST = "host" private const val REALM = "realm" private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" @@ -51,7 +51,7 @@ class HttpAuthenticationDialog : DialogFragment() { // Define the class variables. private var dismissDialog: Boolean = false - // Define the class views. + // Declare the class views. private lateinit var usernameEditText: EditText private lateinit var passwordEditText: EditText @@ -115,10 +115,7 @@ class HttpAuthenticationDialog : DialogFragment() { // Set the title. dialogBuilder.setTitle(R.string.http_authentication) - // Get the activity's layout inflater. - val layoutInflater = requireActivity().layoutInflater - - // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. + // Set the view. The parent view is `null` because it will be assigned by the alert dialog. dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null)) // Set the close button listener. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt index 84bb7478..c7e7494e 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.kt @@ -53,7 +53,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream import java.lang.StringBuilder -// Declare the class constants. +// Define the class constants. private const val CURRENT_FOLDER = "current_folder" private const val SELECTED_BOOKMARKS_LONG_ARRAY = "selected_bookmarks_long_array" @@ -125,7 +125,7 @@ class MoveToFolderDialog : DialogFragment() { dialogBuilder.setTitle(R.string.move_to_folder) // Set the view. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.move_to_folder_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.move_to_folder_dialog, null)) // Set the listener for the cancel button. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt index 5bdf65ac..e62b6fa6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/OpenDialog.kt @@ -76,7 +76,7 @@ class OpenDialog : DialogFragment() { dialogBuilder.setTitle(R.string.open) // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.open_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.open_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt index 0dd2ce57..c3a5c7fe 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt @@ -43,7 +43,7 @@ import com.stoutner.privacybrowser.adapters.PinnedMismatchPagerAdapter import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper import com.stoutner.privacybrowser.views.NestedScrollWebView -// Declare the class constants. +// Define the class constants. private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" class PinnedMismatchDialog : DialogFragment() { @@ -140,7 +140,7 @@ class PinnedMismatchDialog : DialogFragment() { dialogBuilder.setTitle(R.string.pinned_mismatch) // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.pinned_mismatch_linearlayout, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_mismatch_linearlayout, null)) // Set the update button listener. dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int -> diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt index d8ed9f19..ee22df72 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ProxyNotInstalledDialog.kt @@ -31,7 +31,7 @@ import androidx.preference.PreferenceManager import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.helpers.ProxyHelper -// Declare the class constants. +// Define the class constants. private const val PROXY_MODE = "proxy_mode" class ProxyNotInstalledDialog : DialogFragment() { diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt index 35eb2e0d..312ebd05 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt @@ -31,12 +31,14 @@ import android.text.TextWatcher import android.view.WindowManager import android.widget.Button import android.widget.EditText + import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager + import com.stoutner.privacybrowser.R -// Declare the class constants. +// Define the class constants. private const val SAVE_TYPE = "save_type" class SaveDialog : DialogFragment() { @@ -58,7 +60,7 @@ class SaveDialog : DialogFragment() { } companion object { - // Declare the companion object constants. These can be moved to class constants once all of the code has transitioned to Kotlin. + // Define the companion object constants. These can be moved to class constants once all of the code has transitioned to Kotlin. const val SAVE_LOGCAT = 0 const val SAVE_ABOUT_VERSION_TEXT = 1 const val SAVE_ABOUT_VERSION_IMAGE = 2 @@ -75,7 +77,7 @@ class SaveDialog : DialogFragment() { // Create a new instance of the save dialog. val saveDialog = SaveDialog() - // Add the arguments bundle to the dialog. + // Add the arguments bundle to the new dialog. saveDialog.arguments = argumentsBundle // Return the new dialog. @@ -135,7 +137,7 @@ class SaveDialog : DialogFragment() { } // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.save_dialog, null)) + dialogBuilder.setView(layoutInflater.inflate(R.layout.save_dialog, null)) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java deleted file mode 100644 index 40fdbda6..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.preference.PreferenceManager; - -import com.google.android.material.textfield.TextInputLayout; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.MainWebViewActivity; -import com.stoutner.privacybrowser.asynctasks.GetUrlSize; - -public class SaveWebpageDialog extends DialogFragment { - public static final int SAVE_URL = 0; - public static final int SAVE_IMAGE = 1; - - // Define the save webpage listener. - private SaveWebpageListener saveWebpageListener; - - // The public interface is used to send information back to the parent activity. - public interface SaveWebpageListener { - void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment); - } - - // Define the get URL size AsyncTask. This allows previous instances of the task to be cancelled if a new one is run. - @SuppressWarnings("rawtypes") - private AsyncTask getUrlSize; - - @Override - public void onAttach(@NonNull Context context) { - // Run the default commands. - super.onAttach(context); - - // Get a handle for the save webpage listener from the launching context. - saveWebpageListener = (SaveWebpageListener) context; - } - - public static SaveWebpageDialog saveWebpage(int saveType, String urlString, String fileSizeString, String contentDispositionFileNameString, String userAgentString, boolean cookiesEnabled) { - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the arguments in the bundle. - argumentsBundle.putInt("save_type", saveType); - argumentsBundle.putString("url_string", urlString); - argumentsBundle.putString("file_size_string", fileSizeString); - argumentsBundle.putString("content_disposition_file_name_string", contentDispositionFileNameString); - argumentsBundle.putString("user_agent_string", userAgentString); - argumentsBundle.putBoolean("cookies_enabled", cookiesEnabled); - - // Create a new instance of the save webpage dialog. - SaveWebpageDialog saveWebpageDialog = new SaveWebpageDialog(); - - // Add the arguments bundle to the new dialog. - saveWebpageDialog.setArguments(argumentsBundle); - - // Return the new dialog. - return saveWebpageDialog; - } - - // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a handle for the arguments. - Bundle arguments = getArguments(); - - // Remove the incorrect lint warning that the arguments might be null. - assert arguments != null; - - // Get the arguments from the bundle. - int saveType = arguments.getInt("save_type"); - String originalUrlString = arguments.getString("url_string"); - String fileSizeString = arguments.getString("file_size_string"); - String contentDispositionFileNameString = arguments.getString("content_disposition_file_name_string"); - String userAgentString = arguments.getString("user_agent_string"); - boolean cookiesEnabled = arguments.getBoolean("cookies_enabled"); - - // Get handles for the context and the activity. - Context context = requireContext(); - Activity activity = requireActivity(); - - // Use an alert dialog builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the title and icon according to the save type. - switch (saveType) { - case SAVE_URL: - // Set the title. - dialogBuilder.setTitle(R.string.save_url); - - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - dialogBuilder.setIcon(R.drawable.copy_enabled_day); - } else { - dialogBuilder.setIcon(R.drawable.copy_enabled_night); - } - break; - - case SAVE_IMAGE: - // Set the title. - dialogBuilder.setTitle(R.string.save_image); - - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - dialogBuilder.setIcon(R.drawable.images_enabled_day); - } else { - - dialogBuilder.setIcon(R.drawable.images_enabled_night); - } - break; - } - - // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(activity.getLayoutInflater().inflate(R.layout.save_webpage_dialog, null)); - - // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. - dialogBuilder.setNegativeButton(R.string.cancel, null); - - // Set the save button listener. - dialogBuilder.setPositiveButton(R.string.save, (DialogInterface dialog, int which) -> { - // Return the dialog fragment to the parent activity. - saveWebpageListener.onSaveWebpage(saveType, originalUrlString, this); - }); - - // Create an alert dialog from the builder. - AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the incorrect lint warning below that the window might be null. - assert alertDialog.getWindow() != null; - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // The alert dialog must be shown before items in the layout can be modified. - alertDialog.show(); - - // Get handles for the layout items. - TextInputLayout urlTextInputLayout = alertDialog.findViewById(R.id.url_textinputlayout); - EditText urlEditText = alertDialog.findViewById(R.id.url_edittext); - EditText fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext); - Button browseButton = alertDialog.findViewById(R.id.browse_button); - TextView fileSizeTextView = alertDialog.findViewById(R.id.file_size_textview); - Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - - // Remove the incorrect warnings that the views might be null. - assert urlTextInputLayout != null; - assert urlEditText != null; - assert fileNameEditText != null; - assert browseButton != null; - assert fileSizeTextView != null; - - // Set the file size text view. - fileSizeTextView.setText(fileSizeString); - - // Modify the layout based on the save type. - if (saveType == SAVE_URL) { // A URL is being saved. - // Remove the incorrect lint error below that the URL string might be null. - assert originalUrlString != null; - - // Populate the URL edit text according to the type. This must be done before the text change listener is created below so that the file size isn't requested again. - if (originalUrlString.startsWith("data:")) { // The URL contains the entire data of an image. - // Get a substring of the data URL with the first 100 characters. Otherwise, the user interface will freeze while trying to layout the edit text. - String urlSubstring = originalUrlString.substring(0, 100) + "â¦"; - - // Populate the URL edit text with the truncated URL. - urlEditText.setText(urlSubstring); - - // Disable the editing of the URL edit text. - urlEditText.setInputType(InputType.TYPE_NULL); - } else { // The URL contains a reference to the location of the data. - // Populate the URL edit text with the full URL. - urlEditText.setText(originalUrlString); - } - - // Update the file size and the status of the save button when the URL changes. - urlEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - // Do nothing. - } - - @Override - public void 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. - String urlToSave = urlEditText.getText().toString(); - - // Wipe the file size text view. - fileSizeTextView.setText(""); - - // Get the file size for the current URL. - getUrlSize = new GetUrlSize(context, alertDialog, userAgentString, cookiesEnabled).execute(urlToSave); - - // Enable the save button if the URL and file name are populated. - saveButton.setEnabled(!urlToSave.isEmpty() && !fileNameEditText.getText().toString().isEmpty()); - } - }); - } else { // An archive or an image is being saved. - // Hide the URL edit text and the file size text view. - urlTextInputLayout.setVisibility(View.GONE); - fileSizeTextView.setVisibility(View.GONE); - } - - // Initially disable the save button. - saveButton.setEnabled(false); - - // Update the status of the save button when the file name changes. - fileNameEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing. - } - - @Override - public void afterTextChanged(Editable s) { - // Get the current file name. - String fileNameString = fileNameEditText.getText().toString(); - - // Enable the save button based on the save type. - if (saveType == SAVE_URL) { // A URL is being saved. - // Enable the save button if the file name and the URL is populated. - saveButton.setEnabled(!fileNameString.isEmpty() && !urlEditText.getText().toString().isEmpty()); - } else { // An archive or an image is being saved. - // Enable the save button if the file name is populated. - saveButton.setEnabled(!fileNameString.isEmpty()); - } - } - }); - - // Create a file name string. - String fileName = ""; - - // Set the file name according to the type. - switch (saveType) { - case SAVE_URL: - // Use the file name from the content disposition. - fileName = contentDispositionFileNameString; - break; - - case SAVE_IMAGE: - // Use a file name ending in `.png`. - fileName = getString(R.string.webpage_png); - break; - } - - // Save the file name as the default file name. This must be final to be used in the lambda below. - final String defaultFileName = fileName; - - // Handle clicks on the browse button. - browseButton.setOnClickListener((View view) -> { - // Create the file picker intent. - Intent browseIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - - // Set the intent MIME type to include all files so that everything is visible. - browseIntent.setType("*/*"); - - // Set the initial file name according to the type. - browseIntent.putExtra(Intent.EXTRA_TITLE, defaultFileName); - - // Request a file that can be opened. - browseIntent.addCategory(Intent.CATEGORY_OPENABLE); - - // Start the file picker. This must be started under `activity` so that the request code is returned correctly. - activity.startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE); - }); - - // Return the alert dialog. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.kt new file mode 100644 index 00000000..b27e1703 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveWebpageDialog.kt @@ -0,0 +1,289 @@ +/* + * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.res.Configuration +import android.os.AsyncTask +import android.os.Bundle +import android.text.Editable +import android.text.InputType +import android.text.TextWatcher +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.EditText +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.google.android.material.textfield.TextInputLayout + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.activities.MainWebViewActivity +import com.stoutner.privacybrowser.asynctasks.GetUrlSize + +// Define the class constants. +private const val SAVE_TYPE = "save_type" +private const val URL_STRING = "url_string" +private const val FILE_SIZE_STRING = "file_size_string" +private const val FILE_NAME_STRING = "file_name_string" +private const val USER_AGENT_STRING = "user_agent_string" +private const val COOKIES_ENABLED = "cookies_enabled" + +class SaveWebpageDialog : DialogFragment() { + // Declare the class variables. + private lateinit var saveWebpageListener: SaveWebpageListener + + // Define the class variables. + private var getUrlSize: AsyncTask<*, *, *>? = null + + // The public interface is used to send information back to the parent activity. + interface SaveWebpageListener { + fun onSaveWebpage(saveType: Int, originalUrlString: String?, dialogFragment: DialogFragment) + } + + override fun onAttach(context: Context) { + // Run the default commands. + super.onAttach(context) + + // Get a handle for the save webpage listener from the launching context. + saveWebpageListener = context as SaveWebpageListener + } + + companion object { + // Define the companion object constants. These can be moved to class constants once all of the code has transitioned to Kotlin. + const val SAVE_URL = 0 + const val SAVE_IMAGE = 1 + + // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. + @JvmStatic + fun saveWebpage(saveType: Int, urlString: String?, fileSizeString: String?, fileNameString: String, userAgentString: String?, cookiesEnabled: Boolean): SaveWebpageDialog { + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the arguments in the bundle. + argumentsBundle.putInt(SAVE_TYPE, saveType) + argumentsBundle.putString(URL_STRING, urlString) + argumentsBundle.putString(FILE_SIZE_STRING, fileSizeString) + argumentsBundle.putString(FILE_NAME_STRING, fileNameString) + argumentsBundle.putString(USER_AGENT_STRING, userAgentString) + argumentsBundle.putBoolean(COOKIES_ENABLED, cookiesEnabled) + + // Create a new instance of the save webpage dialog. + val saveWebpageDialog = SaveWebpageDialog() + + // Add the arguments bundle to the new dialog. + saveWebpageDialog.arguments = argumentsBundle + + // Return the new dialog. + return saveWebpageDialog + } + } + + // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the arguments from the bundle. + val saveType = requireArguments().getInt(SAVE_TYPE) + val originalUrlString = requireArguments().getString(URL_STRING) + val fileSizeString = requireArguments().getString(FILE_SIZE_STRING) + val fileNameString = requireArguments().getString(FILE_NAME_STRING)!! + val userAgentString = requireArguments().getString(USER_AGENT_STRING) + val cookiesEnabled = requireArguments().getBoolean(COOKIES_ENABLED) + + // Use an alert dialog builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Set the title and icon according to the save type. + when (saveType) { + SAVE_URL -> { + // Set the title. + dialogBuilder.setTitle(R.string.save_url) + + // Set the icon according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + dialogBuilder.setIcon(R.drawable.copy_enabled_day) + } else { + dialogBuilder.setIcon(R.drawable.copy_enabled_night) + } + } + + SAVE_IMAGE -> { + // Set the title. + dialogBuilder.setTitle(R.string.save_image) + + // Set the icon according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + dialogBuilder.setIcon(R.drawable.images_enabled_day) + } else { + dialogBuilder.setIcon(R.drawable.images_enabled_night) + } + } + } + + // Set the view. The parent view is null because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.save_webpage_dialog, null)) + + // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. + dialogBuilder.setNegativeButton(R.string.cancel, null) + + // Set the save button listener. + dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int -> + // Return the dialog fragment to the parent activity. + saveWebpageListener.onSaveWebpage(saveType, originalUrlString, this) + } + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // The alert dialog must be shown before items in the layout can be modified. + alertDialog.show() + + // Get handles for the layout items. + val urlTextInputLayout = alertDialog.findViewById<TextInputLayout>(R.id.url_textinputlayout)!! + val urlEditText = alertDialog.findViewById<EditText>(R.id.url_edittext)!! + val fileNameEditText = alertDialog.findViewById<EditText>(R.id.file_name_edittext)!! + val browseButton = alertDialog.findViewById<Button>(R.id.browse_button)!! + val fileSizeTextView = alertDialog.findViewById<TextView>(R.id.file_size_textview)!! + val saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + + // Set the file size text view. + fileSizeTextView.text = fileSizeString + + // Modify the layout based on the save type. + if (saveType == SAVE_URL) { // A URL is being saved. + // Populate the URL edit text according to the type. This must be done before the text change listener is created below so that the file size isn't requested again. + if (originalUrlString!!.startsWith("data:")) { // The URL contains the entire data of an image. + // Get a substring of the data URL with the first 100 characters. Otherwise, the user interface will freeze while trying to layout the edit text. + val urlSubstring = originalUrlString.substring(0, 100) + "â¦" + + // Populate the URL edit text with the truncated URL. + urlEditText.setText(urlSubstring) + + // Disable the editing of the URL edit text. + urlEditText.inputType = InputType.TYPE_NULL + } else { // The URL contains a reference to the location of the data. + // Populate the URL edit text with the full URL. + urlEditText.setText(originalUrlString) + } + + // Update the file size and the status of the save button when the URL changes. + urlEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + // Do nothing. + } + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + // Do nothing. + } + + 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 and file name are populated. + saveButton.isEnabled = urlToSave.isNotEmpty() && fileNameEditText.text.toString().isNotEmpty() + } + }) + } else { // An archive or an image is being saved. + // Hide the URL edit text and the file size text view. + urlTextInputLayout.visibility = View.GONE + fileSizeTextView.visibility = View.GONE + } + + // Initially disable the save button. + saveButton.isEnabled = false + + // Update the status of the save button when the file name changes. + fileNameEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // Do nothing. + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + // Do nothing. + } + + override fun afterTextChanged(s: Editable) { + // Enable the save button based on the save type. + if (saveType == SAVE_URL) { // A URL is being saved. + // Enable the save button if the file name and the URL are populated. + saveButton.isEnabled = fileNameEditText.text.toString().isNotEmpty() && urlEditText.text.toString().isNotEmpty() + } else { // An archive or an image is being saved. + // Enable the save button if the file name is populated. + saveButton.isEnabled = fileNameEditText.text.toString().isNotEmpty() + } + } + }) + + // Handle clicks on the browse button. + browseButton.setOnClickListener { + // Create the file picker intent. + val browseIntent = Intent(Intent.ACTION_CREATE_DOCUMENT) + + // Set the intent MIME type to include all files so that everything is visible. + browseIntent.type = "*/*" + + // Set the initial file name according to the type. + browseIntent.putExtra(Intent.EXTRA_TITLE, fileNameString) + + // Request a file that can be opened. + browseIntent.addCategory(Intent.CATEGORY_OPENABLE) + + // Start the file picker. This must be started under `activity` so that the request code is returned correctly. + requireActivity().startActivityForResult(browseIntent, MainWebViewActivity.BROWSE_SAVE_WEBPAGE_REQUEST_CODE) + } + + // Return the alert dialog. + return alertDialog + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java deleted file mode 100644 index c34faaad..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.net.Uri; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.webkit.SslErrorHandler; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.MainWebViewActivity; -import com.stoutner.privacybrowser.fragments.WebViewTabFragment; -import com.stoutner.privacybrowser.views.NestedScrollWebView; - -import java.lang.ref.WeakReference; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.text.DateFormat; -import java.util.Date; - -public class SslCertificateErrorDialog extends DialogFragment { - public static SslCertificateErrorDialog displayDialog(SslError error, long webViewFragmentId) { - // Get the various components of the SSL error message. - int primaryErrorIntForBundle = error.getPrimaryError(); - String urlWithErrorForBundle = error.getUrl(); - SslCertificate sslCertificate = error.getCertificate(); - String issuedToCNameForBundle = sslCertificate.getIssuedTo().getCName(); - String issuedToONameForBundle = sslCertificate.getIssuedTo().getOName(); - String issuedToUNameForBundle = sslCertificate.getIssuedTo().getUName(); - String issuedByCNameForBundle = sslCertificate.getIssuedBy().getCName(); - String issuedByONameForBundle = sslCertificate.getIssuedBy().getOName(); - String issuedByUNameForBundle = sslCertificate.getIssuedBy().getUName(); - Date startDateForBundle = sslCertificate.getValidNotBeforeDate(); - Date endDateForBundle = sslCertificate.getValidNotAfterDate(); - - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the SSL error message components in a `Bundle`. - argumentsBundle.putInt("primary_error_int", primaryErrorIntForBundle); - argumentsBundle.putString("url_with_error", urlWithErrorForBundle); - argumentsBundle.putString("issued_to_cname", issuedToCNameForBundle); - argumentsBundle.putString("issued_to_oname", issuedToONameForBundle); - argumentsBundle.putString("issued_to_uname", issuedToUNameForBundle); - argumentsBundle.putString("issued_by_cname", issuedByCNameForBundle); - argumentsBundle.putString("issued_by_oname", issuedByONameForBundle); - argumentsBundle.putString("issued_by_uname", issuedByUNameForBundle); - argumentsBundle.putString("start_date", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDateForBundle)); - argumentsBundle.putString("end_date", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDateForBundle)); - argumentsBundle.putLong("webview_fragment_id", webViewFragmentId); - - // Create a new instance of the SSL certificate error dialog. - SslCertificateErrorDialog thisSslCertificateErrorDialog = new SslCertificateErrorDialog(); - - // Add the arguments bundle to the new dialog. - thisSslCertificateErrorDialog.setArguments(argumentsBundle); - - // Return the new dialog. - return thisSslCertificateErrorDialog; - } - - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a handle for the arguments. - Bundle arguments = getArguments(); - - // Remove the incorrect lint warning that the arguments might be null. - assert arguments != null; - - // Get the variables from the bundle. - int primaryErrorInt = arguments.getInt("primary_error_int"); - String urlWithErrors = arguments.getString("url_with_error"); - String issuedToCName = arguments.getString("issued_to_cname"); - String issuedToOName = arguments.getString("issued_to_oname"); - String issuedToUName = arguments.getString("issued_to_uname"); - String issuedByCName = arguments.getString("issued_by_cname"); - String issuedByOName = arguments.getString("issued_by_oname"); - String issuedByUName = arguments.getString("issued_by_uname"); - String startDate = arguments.getString("start_date"); - String endDate = arguments.getString("end_date"); - long webViewFragmentId = arguments.getLong("webview_fragment_id"); - - // Get the current position of this WebView fragment. - int webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId); - - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Remove the incorrect lint warning below that the fragment view might be null. - assert fragmentView != null; - - // Get a handle for the current WebView. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Get a handle for the SSL error handler. - SslErrorHandler sslErrorHandler = nestedScrollWebView.getSslErrorHandler(); - - // Get the activity's layout inflater. - LayoutInflater layoutInflater = requireActivity().getLayoutInflater(); - - // Use an alert dialog builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_night); - } else { - dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_day); - } - - // Set the title. - dialogBuilder.setTitle(R.string.ssl_certificate_error); - - // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); - - // Set a listener on the cancel button. - dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { - // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. - if (sslErrorHandler != null) { - // Cancel the request. - sslErrorHandler.cancel(); - - // Reset the SSL error handler. - nestedScrollWebView.resetSslErrorHandler(); - } - }); - - // Set a listener on the proceed button. - dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> { - // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. - if (sslErrorHandler != null) { - // Cancel the request. - sslErrorHandler.proceed(); - - // Reset the SSL error handler. - nestedScrollWebView.resetSslErrorHandler(); - } - }); - - - // Create an alert dialog from the alert dialog builder. - AlertDialog alertDialog = dialogBuilder.create(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // Get a URI for the URL with errors. - Uri uriWithErrors = Uri.parse(urlWithErrors); - - // Get the IP addresses for the URI. - new GetIpAddresses(getActivity(), alertDialog).execute(uriWithErrors.getHost()); - - // The alert dialog must be shown before the contents can be modified. - alertDialog.show(); - - // Get handles for the `TextViews` - TextView primaryErrorTextView = alertDialog.findViewById(R.id.primary_error); - TextView urlTextView = alertDialog.findViewById(R.id.url); - TextView issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname); - TextView issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname); - TextView issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname); - TextView issuedByTextView = alertDialog.findViewById(R.id.issued_by_textview); - TextView issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname); - TextView issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname); - TextView issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname); - TextView validDatesTextView = alertDialog.findViewById(R.id.valid_dates_textview); - TextView startDateTextView = alertDialog.findViewById(R.id.start_date); - TextView endDateTextView = alertDialog.findViewById(R.id.end_date); - - // Remove the incorrect lint warnings below that the views might be null. - assert primaryErrorTextView != null; - assert urlTextView != null; - assert issuedToCNameTextView != null; - assert issuedToONameTextView != null; - assert issuedToUNameTextView != null; - assert issuedByTextView != null; - assert issuedByCNameTextView != null; - assert issuedByONameTextView != null; - assert issuedByUNameTextView != null; - assert validDatesTextView != null; - assert startDateTextView != null; - assert endDateTextView != null; - - // Setup the common strings. - String urlLabel = getString(R.string.url_label) + " "; - String cNameLabel = getString(R.string.common_name) + " "; - String oNameLabel = getString(R.string.organization) + " "; - String uNameLabel = getString(R.string.organizational_unit) + " "; - String startDateLabel = getString(R.string.start_date) + " "; - String endDateLabel = getString(R.string.end_date) + " "; - - // Create a spannable string builder for each text view that needs multiple colors of text. - SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithErrors); - SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); - SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); - SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); - SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCName); - SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByOName); - SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUName); - SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); - SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); - - // Define the color spans. - ForegroundColorSpan blueColorSpan; - ForegroundColorSpan redColorSpan; - - // Set the color spans according to the theme. The deprecated `getResources()` must be used until the minimum API >= 23. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); - } else { - blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_700)); - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900)); - } - - // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - urlStringBuilder.setSpan(blueColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Initialize `primaryErrorString`. - String primaryErrorString = ""; - - // Highlight the primary error in red and store the primary error string in `primaryErrorString`. - switch (primaryErrorInt) { - case SslError.SSL_IDMISMATCH: - // Change the URL span colors to red. - urlStringBuilder.setSpan(redColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Store the primary error string. - primaryErrorString = getString(R.string.cn_mismatch); - break; - - case SslError.SSL_UNTRUSTED: - // Change the issued by text view text to red. The deprecated `getResources().getColor` must be used until the minimum API >= 23. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - issuedByTextView.setTextColor(getResources().getColor(R.color.red_900)); - } else { - issuedByTextView.setTextColor(getResources().getColor(R.color.red_a700)); - } - - // Change the issued by span color to red. - issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Store the primary error string. - primaryErrorString = getString(R.string.untrusted); - break; - - case SslError.SSL_DATE_INVALID: - // Change the valid dates text view text to red. The deprecated `getResources().getColor` must be used until the minimum API >= 23. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - validDatesTextView.setTextColor(getResources().getColor(R.color.red_900)); - } else { - validDatesTextView.setTextColor(getResources().getColor(R.color.red_a700)); - } - - // Change the date span colors to red. - startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Store the primary error string. - primaryErrorString = getString(R.string.invalid_date); - break; - - case SslError.SSL_NOTYETVALID: - // Change the start date span color to red. - startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Store the primary error string. - primaryErrorString = getString(R.string.future_certificate); - break; - - case SslError.SSL_EXPIRED: - // Change the end date span color to red. - endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Store the primary error string. - primaryErrorString = getString(R.string.expired_certificate); - break; - - case SslError.SSL_INVALID: - // Store the primary error string. - primaryErrorString = getString(R.string.invalid_certificate); - break; - } - - - // Display the strings. - primaryErrorTextView.setText(primaryErrorString); - urlTextView.setText(urlStringBuilder); - issuedToCNameTextView.setText(issuedToCNameStringBuilder); - issuedToONameTextView.setText(issuedToONameStringBuilder); - issuedToUNameTextView.setText(issuedToUNameStringBuilder); - issuedByCNameTextView.setText(issuedByCNameStringBuilder); - issuedByONameTextView.setText(issuedByONameStringBuilder); - issuedByUNameTextView.setText(issuedByUNameStringBuilder); - startDateTextView.setText(startDateStringBuilder); - endDateTextView.setText(endDateStringBuilder); - - // `onCreateDialog` requires the return of an alert dialog. - return alertDialog; - } - - - // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder` contains the results. - private static class GetIpAddresses extends AsyncTask<String, Void, SpannableStringBuilder> { - // The weak references are used to determine if the activity or the alert dialog have disappeared while the AsyncTask is running. - private final WeakReference<Activity> activityWeakReference; - private final WeakReference<AlertDialog> alertDialogWeakReference; - - GetIpAddresses(Activity activity, AlertDialog alertDialog) { - // Populate the weak references. - activityWeakReference = new WeakReference<>(activity); - alertDialogWeakReference = new WeakReference<>(alertDialog); - } - - @Override - protected SpannableStringBuilder doInBackground(String... domainName) { - // Get handles for the activity and the alert dialog. - Activity activity = activityWeakReference.get(); - AlertDialog alertDialog = alertDialogWeakReference.get(); - - // Abort if the activity or the dialog is gone. - if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) { - return new SpannableStringBuilder(); - } - - // 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) { - // Check to see if this is not the first IP address. - if (ipAddresses.length() > 0) { - // Add a line break to the string builder first. - ipAddresses.append("\n"); - } - - // Add the IP Address to the string builder. - ipAddresses.append(inetAddress.getHostAddress()); - } - } catch (UnknownHostException exception) { - // Do nothing. - } - - // Set the label. - String ipAddressesLabel = activity.getString(R.string.ip_addresses) + " "; - - // Create a spannable string builder. - SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + ipAddresses); - - // Create a blue foreground color span. - ForegroundColorSpan blueColorSpan; - - // Get the current theme status. - int currentThemeStatus = activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.violet_500)); - } else { - blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_700)); - } - - // Set the string builder to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Return the formatted string. - return ipAddressesStringBuilder; - } - - // `onPostExecute()` operates on the UI thread. - @Override - protected void onPostExecute(SpannableStringBuilder ipAddresses) { - // Get handles for the activity and the alert dialog. - Activity activity = activityWeakReference.get(); - AlertDialog alertDialog = alertDialogWeakReference.get(); - - // Abort if the activity or the alert dialog is gone. - if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) { - return; - } - - // Get a handle for the IP addresses text view. - TextView ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses); - - // Remove the incorrect lint warning below that the view might be null. - assert ipAddressesTextView != null; - - // Populate the IP addresses text view. - ipAddressesTextView.setText(ipAddresses); - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt new file mode 100644 index 00000000..139e6576 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt @@ -0,0 +1,441 @@ +/* + * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Dialog +import android.content.DialogInterface +import android.content.res.Configuration +import android.net.Uri +import android.net.http.SslError +import android.os.AsyncTask +import android.os.Bundle +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import android.view.WindowManager +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.activities.MainWebViewActivity +import com.stoutner.privacybrowser.views.NestedScrollWebView + +import java.lang.ref.WeakReference +import java.net.InetAddress +import java.net.UnknownHostException +import java.text.DateFormat + +// Define the class constants. +private const val PRIMARY_ERROR_INT = "primary_error_int" +private const val URL_WITH_ERRORS = "url_with_errors" +private const val ISSUED_TO_CNAME = "issued_to_cname" +private const val ISSUED_TO_ONAME = "issued_to_oname" +private const val ISSUED_TO_UNAME = "issued_to_uname" +private const val ISSUED_BY_CNAME = "issued_by_cname" +private const val ISSUED_BY_ONAME = "issued_by_oname" +private const val ISSUED_BY_UNAME = "issued_by_uname" +private const val START_DATE = "start_date" +private const val END_DATE = "end_date" +private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" + +class SslCertificateErrorDialog : DialogFragment() { + companion object { + // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. + @JvmStatic + fun displayDialog(sslError: SslError, webViewFragmentId: Long): SslCertificateErrorDialog { + // Get the various components of the SSL error message. + val primaryErrorInt = sslError.primaryError + val urlWithErrors = sslError.url + val sslCertificate = sslError.certificate + val issuedToCName = sslCertificate.issuedTo.cName + val issuedToOName = sslCertificate.issuedTo.oName + val issuedToUName = sslCertificate.issuedTo.uName + val issuedByCName = sslCertificate.issuedBy.cName + val issuedByOName = sslCertificate.issuedBy.oName + val issuedByUName = sslCertificate.issuedBy.uName + val startDate = sslCertificate.validNotBeforeDate + val endDate = sslCertificate.validNotAfterDate + + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the SSL error message components in the bundle. + argumentsBundle.putInt(PRIMARY_ERROR_INT, primaryErrorInt) + argumentsBundle.putString(URL_WITH_ERRORS, urlWithErrors) + argumentsBundle.putString(ISSUED_TO_CNAME, issuedToCName) + argumentsBundle.putString(ISSUED_TO_ONAME, issuedToOName) + argumentsBundle.putString(ISSUED_TO_UNAME, issuedToUName) + argumentsBundle.putString(ISSUED_BY_CNAME, issuedByCName) + argumentsBundle.putString(ISSUED_BY_ONAME, issuedByOName) + argumentsBundle.putString(ISSUED_BY_UNAME, issuedByUName) + argumentsBundle.putString(START_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)) + argumentsBundle.putString(END_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)) + argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId) + + // Create a new instance of the SSL certificate error dialog. + val thisSslCertificateErrorDialog = SslCertificateErrorDialog() + + // Add the arguments bundle to the new dialog. + thisSslCertificateErrorDialog.arguments = argumentsBundle + + // Return the new dialog. + return thisSslCertificateErrorDialog + } + } + + // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the variables from the bundle. + val primaryErrorInt = requireArguments().getInt(PRIMARY_ERROR_INT) + val urlWithErrors = requireArguments().getString(URL_WITH_ERRORS) + val issuedToCName = requireArguments().getString(ISSUED_TO_CNAME) + val issuedToOName = requireArguments().getString(ISSUED_TO_ONAME) + val issuedToUName = requireArguments().getString(ISSUED_TO_UNAME) + val issuedByCName = requireArguments().getString(ISSUED_BY_CNAME) + val issuedByOName = requireArguments().getString(ISSUED_BY_ONAME) + val issuedByUName = requireArguments().getString(ISSUED_BY_UNAME) + val startDate = requireArguments().getString(START_DATE) + val endDate = requireArguments().getString(END_DATE) + val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID) + + // Get the current position of this WebView fragment. + val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId) + + // Get the WebView tab fragment. + val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) + + // Get the fragment view. + val fragmentView = webViewTabFragment.requireView() + + // Get a handle for the current WebView. + val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) + + // Get a handle for the SSL error handler. + val sslErrorHandler = nestedScrollWebView.sslErrorHandler + + // Use an alert dialog builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Set the icon according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_day) + } else { + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_night) + } + + // Set the title. + dialogBuilder.setTitle(R.string.ssl_certificate_error) + + // Set the view. The parent view is null because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)) + + // Set a listener on the cancel button. + dialogBuilder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> + // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. + if (sslErrorHandler != null) { + // Cancel the request. + sslErrorHandler.cancel() + + // Reset the SSL error handler. + nestedScrollWebView.resetSslErrorHandler() + } + } + + // Set a listener on the proceed button. + dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int -> + // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. + if (sslErrorHandler != null) { + // Proceed to the website. + sslErrorHandler.proceed() + + // Reset the SSL error handler. + nestedScrollWebView.resetSslErrorHandler() + } + } + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Get a URI for the URL with errors. + val uriWithErrors = Uri.parse(urlWithErrors) + + // Get the IP addresses for the URI. + GetIpAddresses(requireActivity(), alertDialog).execute(uriWithErrors.host) + + // The alert dialog must be shown before the contents can be modified. + alertDialog.show() + + // Get handles for the views. + val primaryErrorTextView = alertDialog.findViewById<TextView>(R.id.primary_error)!! + val urlTextView = alertDialog.findViewById<TextView>(R.id.url)!! + val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!! + val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!! + val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!! + val issuedByTextView = alertDialog.findViewById<TextView>(R.id.issued_by_textview)!! + val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!! + val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!! + val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!! + val validDatesTextView = alertDialog.findViewById<TextView>(R.id.valid_dates_textview)!! + val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!! + val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!! + + // Setup the common strings. + val urlLabel = getString(R.string.url_label) + " " + val cNameLabel = getString(R.string.common_name) + " " + val oNameLabel = getString(R.string.organization) + " " + val uNameLabel = getString(R.string.organizational_unit) + " " + val startDateLabel = getString(R.string.start_date) + " " + val endDateLabel = getString(R.string.end_date) + " " + + // Create a spannable string builder for each text view that needs multiple colors of text. + val urlStringBuilder = SpannableStringBuilder(urlLabel + urlWithErrors) + val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName) + val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName) + val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName) + val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName) + val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName) + val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName) + val startDateStringBuilder = SpannableStringBuilder(startDateLabel + startDate) + val endDateStringBuilder = SpannableStringBuilder(endDateLabel + endDate) + + // Define the color spans. + val blueColorSpan: ForegroundColorSpan + val redColorSpan: ForegroundColorSpan + + // Set the color spans according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.blue_700)) + @Suppress("DEPRECATION") + redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_a700)) + } else { + @Suppress("DEPRECATION") + blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.violet_700)) + @Suppress("DEPRECATION") + redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_900)) + } + + // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + urlStringBuilder.setSpan(blueColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Define the primary error string. + var primaryErrorString = "" + + // Highlight the primary error in red and store it in the primary error string. + when (primaryErrorInt) { + SslError.SSL_IDMISMATCH -> { + // Change the URL span colors to red. + urlStringBuilder.setSpan(redColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Store the primary error string. + primaryErrorString = getString(R.string.cn_mismatch) + } + + SslError.SSL_UNTRUSTED -> { + // Change the issued by text view text to red. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + issuedByTextView.setTextColor(resources.getColor(R.color.red_a700)) + } else { + @Suppress("DEPRECATION") + issuedByTextView.setTextColor(resources.getColor(R.color.red_900)) + } + + // Change the issued by span color to red. + issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Store the primary error string. + primaryErrorString = getString(R.string.untrusted) + } + + SslError.SSL_DATE_INVALID -> { + // Change the valid dates text view text to red. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + validDatesTextView.setTextColor(resources.getColor(R.color.red_a700)) + } else { + @Suppress("DEPRECATION") + validDatesTextView.setTextColor(resources.getColor(R.color.red_900)) + } + + // Change the date span colors to red. + startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Store the primary error string. + primaryErrorString = getString(R.string.invalid_date) + } + + SslError.SSL_NOTYETVALID -> { + // Change the start date span color to red. + startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Store the primary error string. + primaryErrorString = getString(R.string.future_certificate) + } + + SslError.SSL_EXPIRED -> { + // Change the end date span color to red. + endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Store the primary error string. + primaryErrorString = getString(R.string.expired_certificate) + } + + SslError.SSL_INVALID -> + // Store the primary error string. + primaryErrorString = getString(R.string.invalid_certificate) + } + + + // Display the strings. + primaryErrorTextView.text = primaryErrorString + urlTextView.text = urlStringBuilder + issuedToCNameTextView.text = issuedToCNameStringBuilder + issuedToONameTextView.text = issuedToONameStringBuilder + issuedToUNameTextView.text = issuedToUNameStringBuilder + issuedByCNameTextView.text = issuedByCNameStringBuilder + issuedByONameTextView.text = issuedByONameStringBuilder + issuedByUNameTextView.text = issuedByUNameStringBuilder + startDateTextView.text = startDateStringBuilder + endDateTextView.text = endDateStringBuilder + + // Return the alert dialog. + return alertDialog + } + + // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder` contains the results. + private class GetIpAddresses constructor(activity: Activity, alertDialog: AlertDialog) : AsyncTask<String, Void?, SpannableStringBuilder>() { + // Define the weak references. + private val activityWeakReference: WeakReference<Activity> = WeakReference(activity) + private val alertDialogWeakReference: WeakReference<AlertDialog> = WeakReference(alertDialog) + + override fun doInBackground(vararg domainName: String): SpannableStringBuilder { + // Get handles for the activity and the alert dialog. + val activity = activityWeakReference.get() + val alertDialog = alertDialogWeakReference.get() + + // Abort if the activity or the dialog is gone. + if (activity == null || activity.isFinishing || alertDialog == null) { + return SpannableStringBuilder() + } + + // Initialize an IP address string builder. + val ipAddresses = StringBuilder() + + // Get an array with the IP addresses for the host. + try { + // Get an array with all the IP addresses for the domain. + val inetAddressesArray = InetAddress.getAllByName(domainName[0]) + + // Add each IP address to the string builder. + for (inetAddress in inetAddressesArray) { + // Check to see if this is not the first IP address. + if (ipAddresses.isNotEmpty()) { + // Add a line break to the string builder first. + ipAddresses.append("\n") + } + + // Add the IP Address to the string builder. + ipAddresses.append(inetAddress.hostAddress) + } + } catch (exception: UnknownHostException) { + // Do nothing. + } + + // Set the label. + val ipAddressesLabel = activity.getString(R.string.ip_addresses) + " " + + // Create a spannable string builder. + val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + ipAddresses) + + // Create a blue foreground color span. + val blueColorSpan: ForegroundColorSpan + + // Get the current theme status. + val currentThemeStatus = activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + ForegroundColorSpan(activity.resources.getColor(R.color.blue_700)) + } else { + @Suppress("DEPRECATION") + ForegroundColorSpan(activity.resources.getColor(R.color.violet_500)) + } + + // Set the string builder to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Return the formatted string. + return ipAddressesStringBuilder + } + + // `onPostExecute()` operates on the UI thread. + override fun onPostExecute(ipAddresses: SpannableStringBuilder) { + // Get handles for the activity and the alert dialog. + val activity = activityWeakReference.get() + val alertDialog = alertDialogWeakReference.get() + + // Abort if the activity or the alert dialog is gone. + if (activity == null || activity.isFinishing || alertDialog == null) { + return + } + + // Get a handle for the IP addresses text view. + val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!! + + // Populate the IP addresses text view. + ipAddressesTextView.text = ipAddresses + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java deleted file mode 100644 index 3f8035e0..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.webkit.WebBackForwardList; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.DialogFragment; // The AndroidX dialog fragment must be used or an error is produced on API <=22. - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.MainWebViewActivity; -import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter; -import com.stoutner.privacybrowser.definitions.History; -import com.stoutner.privacybrowser.fragments.WebViewTabFragment; -import com.stoutner.privacybrowser.views.NestedScrollWebView; - -import java.util.ArrayList; - -public class UrlHistoryDialog extends DialogFragment{ - // The public interface is used to send information back to the parent activity. - public interface NavigateHistoryListener { - void navigateHistory(String url, int steps); - } - - // The navigate history listener is used in `onAttach()` and `onCreateDialog()`. - private NavigateHistoryListener navigateHistoryListener; - - @Override - public void onAttach(@NonNull Context context) { - // Run the default commands. - super.onAttach(context); - - // Get a handle for the listener from the launching context. - navigateHistoryListener = (NavigateHistoryListener) context; - } - - public static UrlHistoryDialog loadBackForwardList(long webViewFragmentId) { - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the WebView fragment ID in the bundle. - argumentsBundle.putLong("webview_fragment_id", webViewFragmentId); - - // Create a new instance of the URL history dialog. - UrlHistoryDialog urlHistoryDialog = new UrlHistoryDialog(); - - // Add the arguments bundle to this instance. - urlHistoryDialog.setArguments(argumentsBundle); - - // Return the new URL history dialog. - return urlHistoryDialog; - } - - @Override - @NonNull - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = requireActivity().getLayoutInflater(); - - // Get the arguments. - Bundle arguments = getArguments(); - - // Remove the incorrect lint error that arguments might be null. - assert arguments != null; - - // Get the WebView fragment ID from the arguments. - long webViewFragmentId = arguments.getLong("webview_fragment_id"); - - // Get the current position of this WebView fragment. - int webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId); - - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Remove the incorrect lint warning below that the fragment view might be null. - assert fragmentView != null; - - // Get a handle for the current WebView. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - // Get the web back forward list from the WebView. - WebBackForwardList webBackForwardList = nestedScrollWebView.copyBackForwardList(); - - // Store the current page index. - int currentPageIndex = webBackForwardList.getCurrentIndex(); - - // Remove the lint warning below that `getContext()` might be null. - assert getContext() != null; - - // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21. - Drawable defaultFavoriteIconDrawable = ContextCompat.getDrawable(getContext(), R.drawable.world); - - // Convert the default favorite icon drawable to a `BitmapDrawable`. - BitmapDrawable defaultFavoriteIconBitmapDrawable = (BitmapDrawable) defaultFavoriteIconDrawable; - - // Remove the incorrect lint error that `getBitmap()` might be null. - assert defaultFavoriteIconBitmapDrawable != null; - - // Extract a bitmap from the default favorite icon bitmap drawable. - Bitmap defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.getBitmap(); - - // Create a history array list. - ArrayList<History> historyArrayList = new ArrayList<>(); - - // 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 (int i=webBackForwardList.getSize() -1; i >= 0; i--) { - // Create a variable to store the favorite icon bitmap. - Bitmap favoriteIconBitmap; - - // Determine the favorite icon bitmap - if (webBackForwardList.getItemAtIndex(i).getFavicon() == null) { - // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon. - favoriteIconBitmap = defaultFavoriteIcon; - } else { // Use the icon from the web back forward list. - favoriteIconBitmap = webBackForwardList.getItemAtIndex(i).getFavicon(); - } - - // Store the favorite icon and the URL in history entry. - History historyEntry = new History(favoriteIconBitmap, webBackForwardList.getItemAtIndex(i).getUrl()); - - // Add this history entry to the history array list. - historyArrayList.add(historyEntry); - } - - // 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. - int currentPageId = webBackForwardList.getSize() - 1 - currentPageIndex; - - // Use an alert dialog builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog); - - // Set the title. - dialogBuilder.setTitle(R.string.history); - - // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.url_history_dialog, null)); - - // Setup the clear history button. - dialogBuilder.setNegativeButton(R.string.clear_history, (DialogInterface dialog, int which) -> { - // Clear the history. - nestedScrollWebView.clearHistory(); - }); - - // Set an `onClick()` listener on the positive button. - dialogBuilder.setPositiveButton(R.string.close, (DialogInterface dialog, int which) -> { - // Do nothing if `Close` is clicked. The `Dialog` will automatically close. - }); - - // Create an alert dialog from the alert dialog builder. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - //The alert dialog must be shown before the contents can be modified. - alertDialog.show(); - - // Instantiate a history array adapter. - HistoryArrayAdapter historyArrayAdapter = new HistoryArrayAdapter(getContext(), historyArrayList, currentPageId); - - // Get a handle for the list view. - ListView listView = alertDialog.findViewById(R.id.history_listview); - - // Remove the incorrect lint warning below that the view might be null. - assert listView != null; - - // Set the list view adapter. - listView.setAdapter(historyArrayAdapter); - - // Listen for clicks on entries in the list view. - listView.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id) -> { - // Convert the long ID to an int. - int itemId = (int) id; - - // Only consume the click if it is not on the `currentPageId`. - if (itemId != currentPageId) { - // Get a handle for the URL text view. - TextView urlTextView = view.findViewById(R.id.history_url_textview); - - // Get the URL. - String url = urlTextView.getText().toString(); - - // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`. - navigateHistoryListener.navigateHistory(url, currentPageId - itemId); - - // Dismiss the alert dialog. - alertDialog.dismiss(); - } - }); - - // Return the alert dialog. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.kt new file mode 100644 index 00000000..9778954e --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.kt @@ -0,0 +1,212 @@ +/* + * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.graphics.drawable.BitmapDrawable +import android.os.Bundle +import android.view.View +import android.view.WindowManager +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.ListView +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment +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.definitions.History +import com.stoutner.privacybrowser.views.NestedScrollWebView + +// Define the class constants. +private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" + +class UrlHistoryDialog : DialogFragment() { + // Declare the class variables. + private lateinit var navigateHistoryListener: NavigateHistoryListener + + // The public interface is used to send information back to the parent activity. + interface NavigateHistoryListener { + fun navigateHistory(url: String, steps: Int) + } + + override fun onAttach(context: Context) { + // Run the default commands. + super.onAttach(context) + + // Get a handle for the listener from the launching context. + navigateHistoryListener = context as NavigateHistoryListener + } + + companion object { + // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. + @JvmStatic + fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog { + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the WebView fragment ID in the bundle. + argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId) + + // Create a new instance of the URL history dialog. + val urlHistoryDialog = UrlHistoryDialog() + + // Add the arguments bundle to the new dialog. + urlHistoryDialog.arguments = argumentsBundle + + // Return the new dialog. + return urlHistoryDialog + } + } + + // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the WebView fragment ID from the arguments. + val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID) + + // Get the current position of this WebView fragment. + val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId) + + // Get the WebView tab fragment. + val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) + + // Get the fragment view. + val fragmentView = webViewTabFragment.requireView() + + // Get a handle for the current nested scroll WebView. + val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) + + // Get the web back forward list from the nested scroll WebView. + val webBackForwardList = nestedScrollWebView.copyBackForwardList() + + // Store the current page index. + val currentPageIndex = webBackForwardList.currentIndex + + // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21. + val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world) + + // Convert the default favorite icon drawable to a bitmap drawable. + val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable) + + // Extract a bitmap from the default favorite icon bitmap drawable. + val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap + + // Create a history array list. + val historyArrayList = ArrayList<History>() + + // 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) { + // Store the favorite icon bitmap. + val favoriteIconBitmap = if (webBackForwardList.getItemAtIndex(i).favicon == null) { + // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon. + defaultFavoriteIcon + } else { // Use the icon from the web back forward list. + webBackForwardList.getItemAtIndex(i).favicon + } + + // Store the favorite icon and the URL in history entry. + val historyEntry = History(favoriteIconBitmap, webBackForwardList.getItemAtIndex(i).url) + + // Add this history entry to the history array list. + historyArrayList.add(historyEntry) + } + + // 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. + val currentPageId = webBackForwardList.size - 1 - currentPageIndex + + // Use an alert dialog builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Set the title. + dialogBuilder.setTitle(R.string.history) + + // Set the view. The parent view is `null` because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.url_history_dialog, null)) + + // Setup the clear history button listener. + dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int -> + // Clear the history. + nestedScrollWebView.clearHistory() + } + + // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. + dialogBuilder.setPositiveButton(R.string.close, null) + + // Create an alert dialog from the alert dialog builder. + val alertDialog = dialogBuilder.create() + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + //The alert dialog must be shown before the contents can be modified. + alertDialog.show() + + // Instantiate a history array adapter. + val historyArrayAdapter = HistoryArrayAdapter(context, historyArrayList, currentPageId) + + // Get a handle for the list view. + val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!! + + // Set the list view adapter. + listView.adapter = historyArrayAdapter + + // Listen for clicks on entries in the list view. + listView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, id: Long -> + // Convert the long ID to an int. + val itemId = id.toInt() + + // Only consume the click if it is not on the current page ID. + if (itemId != currentPageId) { + // Get a handle for the URL text view. + val urlTextView = view.findViewById<TextView>(R.id.history_url_textview) + + // Get the URL. + val url = urlTextView.text.toString() + + // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`. + navigateHistoryListener.navigateHistory(url, currentPageId - itemId) + + // Dismiss the alert dialog. + alertDialog.dismiss() + } + } + + // Return the alert dialog. + return alertDialog + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.java deleted file mode 100644 index c862159e..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright © 2018-2020 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.helpers.BlocklistHelper; - -public class ViewRequestDialog extends DialogFragment { - // The public interface is used to send information back to the parent activity. - public interface ViewRequestListener { - void onPrevious(int id); - - void onNext(int id); - } - - // The view request listener is used in `onAttach()` and `onCreateDialog()`. - private ViewRequestListener viewRequestListener; - - public void onAttach(@NonNull Context context) { - // Run the default commands. - super.onAttach(context); - - // Get a handle for the listener from the launching context. - viewRequestListener = (ViewRequestListener) context; - } - - public static ViewRequestDialog request(int id, boolean isLastRequest, String[] requestDetails) { - // Create a bundle. - Bundle bundle = new Bundle(); - - // Store the request details. - bundle.putInt("id", id); - bundle.putBoolean("is_last_request", isLastRequest); - bundle.putStringArray("request_details", requestDetails); - - // Add the bundle to the dialog. - ViewRequestDialog viewRequestDialog = new ViewRequestDialog(); - viewRequestDialog.setArguments(bundle); - - // Return the new dialog. - return viewRequestDialog; - } - - @Override - @NonNull - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Remove the incorrect lint warning that `getInt()` might be null. - assert getArguments() != null; - - // Get the info from the bundle. - int id = getArguments().getInt("id"); - boolean isLastRequest = getArguments().getBoolean("is_last_request"); - String[] requestDetails = getArguments().getStringArray("request_details"); - - // Use an alert dialog builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog); - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the icon according to the theme. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - dialogBuilder.setIcon(R.drawable.block_ads_enabled_night); - } else { - dialogBuilder.setIcon(R.drawable.block_ads_enabled_day); - } - - // Create the dialog title. - String title = getResources().getString(R.string.request_details) + " - " + id; - - // Set the title. - dialogBuilder.setTitle(title); - - // Remove the incorrect lint warnings about items being null. - assert requestDetails != null; - assert getActivity() != null; - - // Set the view. The parent view is null because it will be assigned by the alert dialog. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.view_request_dialog, null)); - - // Set the close button. - dialogBuilder.setNeutralButton(R.string.close, (DialogInterface dialog, int which) -> { - // Do nothing. The dialog will close automatically. - }); - - // Set the previous button. - dialogBuilder.setNegativeButton(R.string.previous, (DialogInterface dialog, int which) -> { - // Load the previous request. - viewRequestListener.onPrevious(id); - }); - - // Set the next button. - dialogBuilder.setPositiveButton(R.string.next, (DialogInterface dialog, int which) -> { - // Load the next request. - viewRequestListener.onNext(id); - }); - - // Create an alert dialog from the alert dialog builder. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - //The alert dialog must be shown before the contents can be modified. - alertDialog.show(); - - // Get handles for the dialog views. - TextView requestDisposition = alertDialog.findViewById(R.id.request_disposition); - TextView requestUrl = alertDialog.findViewById(R.id.request_url); - TextView requestBlockListLabel = alertDialog.findViewById(R.id.request_blocklist_label); - TextView requestBlockList = alertDialog.findViewById(R.id.request_blocklist); - TextView requestSubListLabel = alertDialog.findViewById(R.id.request_sublist_label); - TextView requestSubList = alertDialog.findViewById(R.id.request_sublist); - TextView requestBlockListEntriesLabel = alertDialog.findViewById(R.id.request_blocklist_entries_label); - TextView requestBlockListEntries = alertDialog.findViewById(R.id.request_blocklist_entries); - TextView requestBlockListOriginalEntryLabel = alertDialog.findViewById(R.id.request_blocklist_original_entry_label); - TextView requestBlockListOriginalEntry = alertDialog.findViewById(R.id.request_blocklist_original_entry); - Button previousButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE); - Button nextButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); - - // Remove the incorrect lint warnings below that the views might be null. - assert requestDisposition != null; - assert requestUrl != null; - assert requestBlockListLabel != null; - assert requestBlockList != null; - assert requestSubListLabel != null; - assert requestSubList != null; - assert requestBlockListEntriesLabel != null; - assert requestBlockListEntries != null; - assert requestBlockListOriginalEntryLabel != null; - assert requestBlockListOriginalEntry != null; - - // Disable the previous button if the first resource request is displayed. - previousButton.setEnabled(!(id == 1)); - - // Disable the next button if the last resource request is displayed. - nextButton.setEnabled(!isLastRequest); - - // Set the request action text. - switch (requestDetails[BlocklistHelper.REQUEST_DISPOSITION]) { - case BlocklistHelper.REQUEST_DEFAULT: - // Set the text. - requestDisposition.setText(R.string.default_allowed); - - // Set the background color. - requestDisposition.setBackgroundColor(getResources().getColor(R.color.transparent)); - break; - - case BlocklistHelper.REQUEST_ALLOWED: - // Set the text. - requestDisposition.setText(R.string.allowed); - - // Set the background color. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.blue_700_50)); - } else { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.blue_100)); - } - break; - - case BlocklistHelper.REQUEST_THIRD_PARTY: - // Set the text. - requestDisposition.setText(R.string.third_party_blocked); - - // Set the background color. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.yellow_700_50)); - } else { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.yellow_100)); - } - break; - - case BlocklistHelper.REQUEST_BLOCKED: - // Set the text. - requestDisposition.setText(R.string.blocked); - - // Set the background color. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.red_700_40)); - } else { - requestDisposition.setBackgroundColor(getResources().getColor(R.color.red_100)); - } - break; - } - - // Display the request URL. - requestUrl.setText(requestDetails[BlocklistHelper.REQUEST_URL]); - - // Modify the dialog based on the request action. - if (requestDetails.length == 2) { // A default request. - // Hide the unused views. - requestBlockListLabel.setVisibility(View.GONE); - requestBlockList.setVisibility(View.GONE); - requestSubListLabel.setVisibility(View.GONE); - requestSubList.setVisibility(View.GONE); - requestBlockListEntriesLabel.setVisibility(View.GONE); - requestBlockListEntries.setVisibility(View.GONE); - requestBlockListOriginalEntryLabel.setVisibility(View.GONE); - requestBlockListOriginalEntry.setVisibility(View.GONE); - } else { // A blocked or allowed request. - // Set the text on the text views. - requestBlockList.setText(requestDetails[BlocklistHelper.REQUEST_BLOCKLIST]); - requestBlockListEntries.setText(requestDetails[BlocklistHelper.REQUEST_BLOCKLIST_ENTRIES]); - requestBlockListOriginalEntry.setText(requestDetails[BlocklistHelper.REQUEST_BLOCKLIST_ORIGINAL_ENTRY]); - - // Set the sublist text. - switch (requestDetails[BlocklistHelper.REQUEST_SUBLIST]) { - case BlocklistHelper.MAIN_WHITELIST: - requestSubList.setText(R.string.main_whitelist); - break; - - case BlocklistHelper.FINAL_WHITELIST: - requestSubList.setText(R.string.final_whitelist); - break; - - case BlocklistHelper.DOMAIN_WHITELIST: - requestSubList.setText(R.string.domain_whitelist); - break; - - case BlocklistHelper.DOMAIN_INITIAL_WHITELIST: - requestSubList.setText(R.string.domain_initial_whitelist); - break; - - case BlocklistHelper.DOMAIN_FINAL_WHITELIST: - requestSubList.setText(R.string.domain_final_whitelist); - break; - - case BlocklistHelper.THIRD_PARTY_WHITELIST: - requestSubList.setText(R.string.third_party_whitelist); - break; - - case BlocklistHelper.THIRD_PARTY_DOMAIN_WHITELIST: - requestSubList.setText(R.string.third_party_domain_whitelist); - break; - - case BlocklistHelper.THIRD_PARTY_DOMAIN_INITIAL_WHITELIST: - requestSubList.setText(R.string.third_party_domain_initial_whitelist); - break; - - case BlocklistHelper.MAIN_BLACKLIST: - requestSubList.setText(R.string.main_blacklist); - break; - - case BlocklistHelper.INITIAL_BLACKLIST: - requestSubList.setText(R.string.initial_blacklist); - break; - - case BlocklistHelper.FINAL_BLACKLIST: - requestSubList.setText(R.string.final_blacklist); - break; - - case BlocklistHelper.DOMAIN_BLACKLIST: - requestSubList.setText(R.string.domain_blacklist); - break; - - case BlocklistHelper.DOMAIN_INITIAL_BLACKLIST: - requestSubList.setText(R.string.domain_initial_blacklist); - break; - - case BlocklistHelper.DOMAIN_FINAL_BLACKLIST: - requestSubList.setText(R.string.domain_final_blacklist); - break; - - case BlocklistHelper.DOMAIN_REGULAR_EXPRESSION_BLACKLIST: - requestSubList.setText(R.string.domain_regular_expression_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_BLACKLIST: - requestSubList.setText(R.string.third_party_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_INITIAL_BLACKLIST: - requestSubList.setText(R.string.third_party_initial_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_DOMAIN_BLACKLIST: - requestSubList.setText(R.string.third_party_domain_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST: - requestSubList.setText(R.string.third_party_domain_initial_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST: - requestSubList.setText(R.string.third_party_regular_expression_blacklist); - break; - - case BlocklistHelper.THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST: - requestSubList.setText(R.string.third_party_domain_regular_expression_blacklist); - break; - - case BlocklistHelper.REGULAR_EXPRESSION_BLACKLIST: - requestSubList.setText(R.string.regular_expression_blacklist); - break; - } - } - - // `onCreateDialog` requires the return of an alert dialog. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.kt new file mode 100644 index 00000000..2b7e345b --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewRequestDialog.kt @@ -0,0 +1,269 @@ +/* + * Copyright © 2018-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.content.res.Configuration +import android.os.Bundle +import android.view.View +import android.view.WindowManager +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.helpers.BlocklistHelper + +// Define the class constants. +private const val ID = "id" +private const val IS_LAST_REQUEST = "is_last_request" +private const val REQUEST_DETAILS = "request_details" + +class ViewRequestDialog : DialogFragment() { + // Define the class variables. + private lateinit var viewRequestListener: ViewRequestListener + + // The public interface is used to send information back to the parent activity. + interface ViewRequestListener { + // Show the previous request. + fun onPrevious(currentId: Int) + + // Show the next request. + fun onNext(currentId: Int) + } + + override fun onAttach(context: Context) { + // Run the default commands. + super.onAttach(context) + + // Get a handle for the listener from the launching context. + viewRequestListener = context as ViewRequestListener + } + + companion object { + // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. + @JvmStatic + fun request(id: Int, isLastRequest: Boolean, requestDetails: Array<String>): ViewRequestDialog { + // Create a bundle. + val bundle = Bundle() + + // Store the request details. + bundle.putInt(ID, id) + bundle.putBoolean(IS_LAST_REQUEST, isLastRequest) + bundle.putStringArray(REQUEST_DETAILS, requestDetails) + + // Create a new instance of the view request dialog. + val viewRequestDialog = ViewRequestDialog() + + // Add the arguments to the new dialog. + viewRequestDialog.arguments = bundle + + // Return the new dialog. + return viewRequestDialog + } + } + + // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the arguments from the bundle. + val id = requireArguments().getInt(ID) + val isLastRequest = requireArguments().getBoolean(IS_LAST_REQUEST) + val requestDetails = requireArguments().getStringArray(REQUEST_DETAILS)!! + + // Use an alert dialog builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Set the icon according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + dialogBuilder.setIcon(R.drawable.block_ads_enabled_day) + } else { + dialogBuilder.setIcon(R.drawable.block_ads_enabled_night) + } + + // Set the title. + dialogBuilder.setTitle(resources.getString(R.string.request_details) + " - " + id) + + // Set the view. The parent view is null because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.view_request_dialog, null)) + + // Set the close button. Using `null` as the listener closes the dialog without doing anything else. + dialogBuilder.setNeutralButton(R.string.close, null) + + // Set the previous button. + dialogBuilder.setNegativeButton(R.string.previous) { _: DialogInterface?, _: Int -> + // Load the previous request. + viewRequestListener.onPrevious(id) + } + + // Set the next button. + dialogBuilder.setPositiveButton(R.string.next) { _: DialogInterface?, _: Int -> + // Load the next request. + viewRequestListener.onNext(id) + } + + // Create an alert dialog from the alert dialog builder. + val alertDialog = dialogBuilder.create() + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + //The alert dialog must be shown before the contents can be modified. + alertDialog.show() + + // Get handles for the dialog views. + val requestDisposition = alertDialog.findViewById<TextView>(R.id.request_disposition)!! + val requestUrl = alertDialog.findViewById<TextView>(R.id.request_url)!! + val requestBlockListLabel = alertDialog.findViewById<TextView>(R.id.request_blocklist_label)!! + val requestBlockList = alertDialog.findViewById<TextView>(R.id.request_blocklist)!! + val requestSubListLabel = alertDialog.findViewById<TextView>(R.id.request_sublist_label)!! + val requestSubList = alertDialog.findViewById<TextView>(R.id.request_sublist)!! + val requestBlockListEntriesLabel = alertDialog.findViewById<TextView>(R.id.request_blocklist_entries_label)!! + val requestBlockListEntries = alertDialog.findViewById<TextView>(R.id.request_blocklist_entries)!! + val requestBlockListOriginalEntryLabel = alertDialog.findViewById<TextView>(R.id.request_blocklist_original_entry_label)!! + val requestBlockListOriginalEntry = alertDialog.findViewById<TextView>(R.id.request_blocklist_original_entry)!! + val previousButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) + val nextButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE) + + // Disable the previous button if the first resource request is displayed. + previousButton.isEnabled = (id != 1) + + // Disable the next button if the last resource request is displayed. + nextButton.isEnabled = !isLastRequest + + // Set the request action text. + when (requestDetails[BlocklistHelper.REQUEST_DISPOSITION]) { + BlocklistHelper.REQUEST_DEFAULT -> { + // Set the text. + requestDisposition.setText(R.string.default_allowed) + + // Set the background color. The deprecated `getColor()` must be used until the minimum API >= 23. + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.transparent)) + } + + BlocklistHelper.REQUEST_ALLOWED -> { + // Set the text. + requestDisposition.setText(R.string.allowed) + + // Set the background color according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.blue_100)) + } else { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.blue_700_50)) + } + } + + BlocklistHelper.REQUEST_THIRD_PARTY -> { + // Set the text. + requestDisposition.setText(R.string.third_party_blocked) + + // Set the background color according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.yellow_100)) + } else { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.yellow_700_50)) + } + } + BlocklistHelper.REQUEST_BLOCKED -> { + // Set the text. + requestDisposition.setText(R.string.blocked) + + // Set the background color according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.red_100)) + } else { + @Suppress("DEPRECATION") + requestDisposition.setBackgroundColor(resources.getColor(R.color.red_700_40)) + } + } + } + + // Display the request URL. + requestUrl.text = requestDetails[BlocklistHelper.REQUEST_URL] + + // Modify the dialog based on the request action. + if (requestDetails.size == 2) { // A default request. + // Hide the unused views. + requestBlockListLabel.visibility = View.GONE + requestBlockList.visibility = View.GONE + requestSubListLabel.visibility = View.GONE + requestSubList.visibility = View.GONE + requestBlockListEntriesLabel.visibility = View.GONE + requestBlockListEntries.visibility = View.GONE + requestBlockListOriginalEntryLabel.visibility = View.GONE + requestBlockListOriginalEntry.visibility = View.GONE + } else { // A blocked or allowed request. + // Set the text on the text views. + requestBlockList.text = requestDetails[BlocklistHelper.REQUEST_BLOCKLIST] + requestBlockListEntries.text = requestDetails[BlocklistHelper.REQUEST_BLOCKLIST_ENTRIES] + requestBlockListOriginalEntry.text = requestDetails[BlocklistHelper.REQUEST_BLOCKLIST_ORIGINAL_ENTRY] + when (requestDetails[BlocklistHelper.REQUEST_SUBLIST]) { + BlocklistHelper.MAIN_WHITELIST -> requestSubList.setText(R.string.main_whitelist) + BlocklistHelper.FINAL_WHITELIST -> requestSubList.setText(R.string.final_whitelist) + BlocklistHelper.DOMAIN_WHITELIST -> requestSubList.setText(R.string.domain_whitelist) + BlocklistHelper.DOMAIN_INITIAL_WHITELIST -> requestSubList.setText(R.string.domain_initial_whitelist) + BlocklistHelper.DOMAIN_FINAL_WHITELIST -> requestSubList.setText(R.string.domain_final_whitelist) + BlocklistHelper.THIRD_PARTY_WHITELIST -> requestSubList.setText(R.string.third_party_whitelist) + BlocklistHelper.THIRD_PARTY_DOMAIN_WHITELIST -> requestSubList.setText(R.string.third_party_domain_whitelist) + BlocklistHelper.THIRD_PARTY_DOMAIN_INITIAL_WHITELIST -> requestSubList.setText(R.string.third_party_domain_initial_whitelist) + BlocklistHelper.MAIN_BLACKLIST -> requestSubList.setText(R.string.main_blacklist) + BlocklistHelper.INITIAL_BLACKLIST -> requestSubList.setText(R.string.initial_blacklist) + BlocklistHelper.FINAL_BLACKLIST -> requestSubList.setText(R.string.final_blacklist) + BlocklistHelper.DOMAIN_BLACKLIST -> requestSubList.setText(R.string.domain_blacklist) + BlocklistHelper.DOMAIN_INITIAL_BLACKLIST -> requestSubList.setText(R.string.domain_initial_blacklist) + BlocklistHelper.DOMAIN_FINAL_BLACKLIST -> requestSubList.setText(R.string.domain_final_blacklist) + BlocklistHelper.DOMAIN_REGULAR_EXPRESSION_BLACKLIST -> requestSubList.setText(R.string.domain_regular_expression_blacklist) + BlocklistHelper.THIRD_PARTY_BLACKLIST -> requestSubList.setText(R.string.third_party_blacklist) + BlocklistHelper.THIRD_PARTY_INITIAL_BLACKLIST -> requestSubList.setText(R.string.third_party_initial_blacklist) + BlocklistHelper.THIRD_PARTY_DOMAIN_BLACKLIST -> requestSubList.setText(R.string.third_party_domain_blacklist) + BlocklistHelper.THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST -> requestSubList.setText(R.string.third_party_domain_initial_blacklist) + BlocklistHelper.THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST -> requestSubList.setText(R.string.third_party_regular_expression_blacklist) + BlocklistHelper.THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST -> requestSubList.setText(R.string.third_party_domain_regular_expression_blacklist) + BlocklistHelper.REGULAR_EXPRESSION_BLACKLIST -> requestSubList.setText(R.string.regular_expression_blacklist) + } + } + + // Return the alert dialog. + return alertDialog + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java deleted file mode 100644 index 84848c6b..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.net.http.SslCertificate; -import android.os.Bundle; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.preference.PreferenceManager; - -import com.stoutner.privacybrowser.activities.MainWebViewActivity; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.fragments.WebViewTabFragment; -import com.stoutner.privacybrowser.views.NestedScrollWebView; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; - -public class ViewSslCertificateDialog extends DialogFragment { - public static ViewSslCertificateDialog displayDialog(long webViewFragmentId) { - // Create an arguments bundle. - Bundle argumentsBundle = new Bundle(); - - // Store the WebView fragment ID in the bundle. - argumentsBundle.putLong("webview_fragment_id", webViewFragmentId); - - // Create a new instance of the dialog. - ViewSslCertificateDialog viewSslCertificateDialog = new ViewSslCertificateDialog(); - - // Add the bundle to the dialog. - viewSslCertificateDialog.setArguments(argumentsBundle); - - // Return the new dialog. - return viewSslCertificateDialog; - } - - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a handle for the activity and the context. - Activity activity = requireActivity(); - Context context = requireContext(); - - // Get the activity's layout inflater. - LayoutInflater layoutInflater = activity.getLayoutInflater(); - - // Get the arguments. - Bundle arguments = getArguments(); - - // Remove the incorrect lint warning below that the arguments might be null. - assert arguments != null; - - // Get the current position of this WebView fragment. - int webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(arguments.getLong("webview_fragment_id")); - - // Get the WebView tab fragment. - WebViewTabFragment webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition); - - // Get the fragment view. - View fragmentView = webViewTabFragment.getView(); - - // Remove the incorrect lint warning below that the fragment view might be null. - assert fragmentView != null; - - // Get a handle for the current WebView. - NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); - - - // Use a builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog); - - // Create a drawable version of the favorite icon. - Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), nestedScrollWebView.getFavoriteOrDefaultIcon()); - - // Set the icon. - dialogBuilder.setIcon(favoriteIconDrawable); - - // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. - dialogBuilder.setNegativeButton(R.string.close, null); - - // Get the SSL certificate. - SslCertificate sslCertificate = nestedScrollWebView.getCertificate(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); - - // Check to see if the website is encrypted. - if (sslCertificate == null) { // The website is not encrypted. - // Set the title. - dialogBuilder.setTitle(R.string.unencrypted_website); - - // Set the Layout. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null)); - - // Create an alert dialog from the alert dialog builder. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - } else { // Display the SSL certificate information - // Set the title. - dialogBuilder.setTitle(R.string.ssl_certificate); - - // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate_dialog, null)); - - // Create an alert dialog from the builder. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // The alert dialog must be shown before items in the layout can be modified. - alertDialog.show(); - - // Get handles for the text views. - TextView domainTextView = alertDialog.findViewById(R.id.domain); - TextView ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses); - TextView issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname); - TextView issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname); - TextView issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname); - TextView issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname); - TextView issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname); - TextView issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname); - TextView startDateTextView = alertDialog.findViewById(R.id.start_date); - TextView endDateTextView = alertDialog.findViewById(R.id.end_date); - - // Remove the incorrect warning that the views might be null. - assert domainTextView != null; - assert ipAddressesTextView != null; - assert issuedToCNameTextView != null; - assert issuedToONameTextView != null; - assert issuedToUNameTextView != null; - assert issuedByCNameTextView != null; - assert issuedByONameTextView != null; - assert issuedByUNameTextView != null; - assert startDateTextView != null; - assert endDateTextView != null; - - // Setup the labels. - String domainLabel = getString(R.string.domain_label) + " "; - String ipAddressesLabel = getString(R.string.ip_addresses) + " "; - String cNameLabel = getString(R.string.common_name) + " "; - String oNameLabel = getString(R.string.organization) + " "; - String uNameLabel = getString(R.string.organizational_unit) + " "; - String startDateLabel = getString(R.string.start_date) + " "; - String endDateLabel = getString(R.string.end_date) + " "; - - // Convert the formatted URL string to a URI. - Uri uri = Uri.parse(nestedScrollWebView.getUrl()); - - // Extract the domain name from the URI. - String domainString = uri.getHost(); - - // Get the strings from the SSL certificate. - String issuedToCName = sslCertificate.getIssuedTo().getCName(); - String issuedToOName = sslCertificate.getIssuedTo().getOName(); - String issuedToUName = sslCertificate.getIssuedTo().getUName(); - String issuedByCName = sslCertificate.getIssuedBy().getCName(); - String issuedByOName = sslCertificate.getIssuedBy().getOName(); - String issuedByUName = sslCertificate.getIssuedBy().getUName(); - Date startDate = sslCertificate.getValidNotBeforeDate(); - Date endDate = sslCertificate.getValidNotAfterDate(); - - // Create spannable string builders for each text view that needs multiple colors of text. - SpannableStringBuilder domainStringBuilder = new SpannableStringBuilder(domainLabel + domainString); - SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.getCurrentIpAddresses()); - SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); - SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); - SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); - SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCName); - SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByOName); - SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUName); - SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)); - SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)); - - // Define the color spans. - ForegroundColorSpan blueColorSpan; - ForegroundColorSpan redColorSpan; - - // Get the current theme status. - int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - // Set the color spans according to the theme. The deprecated `getResources()` must be used until the minimum API >= 23. - if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { - blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); - } else { - blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.violet_700)); - redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900)); - } - - // Remove the incorrect lint error that `.equals` might produce a NullPointerException. - assert domainString != null; - - // Formet the domain string and issued to CName colors. - if (domainString.equals(issuedToCName)) { // `domainString` and `issuedToCName` match. - // Set the strings to be blue. - domainStringBuilder.setSpan(blueColorSpan, domainLabel.length(), domainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else if(issuedToCName.startsWith("*.")){ // `issuedToCName` begins with a wildcard. - // Remove the initial `*.`. - String baseCertificateDomain = issuedToCName.substring(2); - - // Setup a copy of `domainString` to test subdomains. - String domainStringSubdomain = domainString; - - // Initialize `domainNamesMatch`. - boolean domainNamesMatch = false; - - // Check all the subdomains in `domainStringSubdomain` against `baseCertificateDomain`. - while (!domainNamesMatch && domainStringSubdomain.contains(".")) { // Stop checking if we know that `domainNamesMatch` is `true` or if we run out of `.`. - // Test the `domainStringSubdomain` against `baseCertificateDomain`. - if (domainStringSubdomain.equals(baseCertificateDomain)) { - domainNamesMatch = true; - } - - // Strip out the lowest subdomain of `certificateCommonNameSubdomain`. - domainStringSubdomain = domainStringSubdomain.substring(domainStringSubdomain.indexOf(".") + 1); - } - - // Format the domain and issued to Common Name according to `domainNamesMatch`. - if (domainNamesMatch) { // `domainString` is a subdomain of the wildcard `issuedToCNameString`. - // Set the strings to be blue. - domainStringBuilder.setSpan(blueColorSpan, domainLabel.length(), domainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // `domainString` is not a subdomain of the wildcard `issuedToCNameString`. - // Set the string to be red. - domainStringBuilder.setSpan(redColorSpan, domainLabel.length(), domainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } else { // The strings do not match and `issuedToCNameString` does not begin with a wildcard. - // Set the strings to be red. - domainStringBuilder.setSpan(redColorSpan, domainLabel.length(), domainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Set the IP addresses, issued to, and issued by spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Get the current date. - Date currentDate = Calendar.getInstance().getTime(); - - // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (startDate.after(currentDate)) { // The certificate start date is in the future. - startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate start date is in the past. - startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - if (endDate.before(currentDate)) { // The certificate end date is in the past. - endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // The certificate end date is in the future. - endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - // Display the strings. - domainTextView.setText(domainStringBuilder); - ipAddressesTextView.setText(ipAddressesStringBuilder); - issuedToCNameTextView.setText(issuedToCNameStringBuilder); - issuedToONameTextView.setText(issuedToONameStringBuilder); - issuedToUNameTextView.setText(issuedToUNameStringBuilder); - issuedByCNameTextView.setText(issuedByCNameStringBuilder); - issuedByONameTextView.setText(issuedByONameStringBuilder); - issuedByUNameTextView.setText(issuedByUNameStringBuilder); - startDateTextView.setText(startDateStringBuilder); - endDateTextView.setText(endDateStringBuilder); - - // `onCreateDialog` requires the return of an alert dialog. - return alertDialog; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt new file mode 100644 index 00000000..2bebf905 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt @@ -0,0 +1,297 @@ +/* + * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.res.Configuration +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Bundle +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import android.view.WindowManager +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.activities.MainWebViewActivity +import com.stoutner.privacybrowser.views.NestedScrollWebView + +import java.text.DateFormat +import java.util.Calendar + +// Define the class constants. +private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" + +class ViewSslCertificateDialog : DialogFragment() { + companion object { + // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. + @JvmStatic + fun displayDialog(webViewFragmentId: Long): ViewSslCertificateDialog { + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the WebView fragment ID in the bundle. + argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId) + + // Create a new instance of the view SSL certificate dialog. + val viewSslCertificateDialog = ViewSslCertificateDialog() + + // Add the bundle to the new dialog. + viewSslCertificateDialog.arguments = argumentsBundle + + // Return the new dialog. + return viewSslCertificateDialog + } + } + + // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Get the current position of this WebView fragment. + val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(requireArguments().getLong(WEBVIEW_FRAGMENT_ID)) + + // Get the WebView tab fragment. + val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) + + // Get the fragment view. + val fragmentView = webViewTabFragment.requireView() + + // Get a handle for the current nested scroll WebView. + val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) + + // Use a builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Create a drawable version of the favorite icon. + val favoriteIconDrawable: Drawable = BitmapDrawable(resources, nestedScrollWebView.favoriteOrDefaultIcon) + + // Set the icon. + dialogBuilder.setIcon(favoriteIconDrawable) + + // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. + dialogBuilder.setNegativeButton(R.string.close, null) + + // Get the SSL certificate. + val sslCertificate = nestedScrollWebView.certificate + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Check to see if the website is encrypted. + if (sslCertificate == null) { // The website is not encrypted. + // Set the title. + dialogBuilder.setTitle(R.string.unencrypted_website) + + // Set the Layout. The parent view is `null` because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null)) + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Return the alert dialog. + return alertDialog + } else { // The website is encrypted. + // Set the title. + dialogBuilder.setTitle(R.string.ssl_certificate) + + // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate_dialog, null)) + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // The alert dialog must be shown before items in the layout can be modified. + alertDialog.show() + + // Get handles for the text views. + val domainTextView = alertDialog.findViewById<TextView>(R.id.domain)!! + val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!! + val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!! + val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!! + val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!! + val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!! + val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!! + val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!! + val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!! + val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!! + + // Setup the labels. + val domainLabel = getString(R.string.domain_label) + " " + val ipAddressesLabel = getString(R.string.ip_addresses) + " " + val cNameLabel = getString(R.string.common_name) + " " + val oNameLabel = getString(R.string.organization) + " " + val uNameLabel = getString(R.string.organizational_unit) + " " + val startDateLabel = getString(R.string.start_date) + " " + val endDateLabel = getString(R.string.end_date) + " " + + // Convert the URL to a URI. + val uri = Uri.parse(nestedScrollWebView.url) + + // Extract the domain name from the URI. + val domainString = uri.host + + // Get the strings from the SSL certificate. + val issuedToCName = sslCertificate.issuedTo.cName + val issuedToOName = sslCertificate.issuedTo.oName + val issuedToUName = sslCertificate.issuedTo.uName + val issuedByCName = sslCertificate.issuedBy.cName + val issuedByOName = sslCertificate.issuedBy.oName + val issuedByUName = sslCertificate.issuedBy.uName + val startDate = sslCertificate.validNotBeforeDate + val endDate = sslCertificate.validNotAfterDate + + // Create spannable string builders for each text view that needs multiple colors of text. + val domainStringBuilder = SpannableStringBuilder(domainLabel + domainString) + val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses) + val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName) + val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName) + val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName) + val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName) + val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName) + val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName) + val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)) + val endDateStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)) + + // Define the color spans. + val blueColorSpan: ForegroundColorSpan + val redColorSpan: ForegroundColorSpan + + // Get the current theme status. + val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + + // Set the color spans according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { + @Suppress("DEPRECATION") + blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.blue_700)) + @Suppress("DEPRECATION") + redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_a700)) + } else { + @Suppress("DEPRECATION") + blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.violet_700)) + @Suppress("DEPRECATION") + redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_900)) + } + + // Format the domain string and issued to CName colors. + if (domainString == issuedToCName) { // The domain and issued to CName match. + // Set the strings to be blue. + domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else if (issuedToCName.startsWith("*.")) { // The issued to CName begins with a wildcard. + // Remove the initial `*.`. + val baseCertificateDomain = issuedToCName.substring(2) + + // Setup a copy of the domain string to test subdomains. + var domainStringSubdomain = domainString!! + + // Define a domain names match variable. + var domainNamesMatch = false + + // Check all the subdomains against the base certificate domain. + while (!domainNamesMatch && domainStringSubdomain.contains(".")) { // Stop checking if we know that the domain names match or if we run out of subdomains. + // Test the subdomain against the base certificate domain. + if (domainStringSubdomain == baseCertificateDomain) { + domainNamesMatch = true + } + + // Strip out the lowest subdomain. + domainStringSubdomain = domainStringSubdomain.substring(domainStringSubdomain.indexOf(".") + 1) + } + + // Format the domain and issued to CName. + if (domainNamesMatch) { // The domain is a subdomain of the wildcard certificate. + // Set the strings to be blue. + domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else { // The domain is not a subdomain of the wildcard certificate. + // Set the string to be red. + domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + } else { // The strings do not match and issued to CName does not begin with a wildcard. + // Set the strings to be red. + domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + // Set the IP addresses, issued to, and issued by spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + + // Get the current date. + val currentDate = Calendar.getInstance().time + + // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (startDate.after(currentDate)) { // The certificate start date is in the future. + startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else { // The certificate start date is in the past. + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (endDate.before(currentDate)) { // The certificate end date is in the past. + endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } else { // The certificate end date is in the future. + endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + // Display the strings. + domainTextView.text = domainStringBuilder + ipAddressesTextView.text = ipAddressesStringBuilder + issuedToCNameTextView.text = issuedToCNameStringBuilder + issuedToONameTextView.text = issuedToONameStringBuilder + issuedToUNameTextView.text = issuedToUNameStringBuilder + issuedByCNameTextView.text = issuedByCNameStringBuilder + issuedByONameTextView.text = issuedByONameStringBuilder + issuedByUNameTextView.text = issuedByUNameStringBuilder + startDateTextView.text = startDateStringBuilder + endDateTextView.text = endDateStringBuilder + + // Return the alert dialog. + return alertDialog + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.java deleted file mode 100644 index d87385db..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright © 2019-2020 Soren Stoutner <soren@stoutner.com>. - * - * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. - * - * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.preference.PreferenceManager; - -import com.stoutner.privacybrowser.R; - -public class WaitingForProxyDialog extends DialogFragment { - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a handle for the context. - Context context = requireContext(); - - // Get the activity's layout inflater. - LayoutInflater layoutInflater = requireActivity().getLayoutInflater(); - - // Use a builder to create the alert dialog. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialog); - - // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(layoutInflater.inflate(R.layout.waiting_for_proxy_dialog, null)); - - // Create an alert dialog from the alert dialog builder. - AlertDialog alertDialog = dialogBuilder.create(); - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - - // Get the screenshot preference. - boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Remove the warning below that `getWindow()` might be null. - assert alertDialog.getWindow() != null; - - // Disable screenshots. - alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } - - // The alert dialog must be shown before items in the layout can be modified. - alertDialog.show(); - - // Return the alert dialog. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.kt new file mode 100644 index 00000000..145580be --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/WaitingForProxyDialog.kt @@ -0,0 +1,61 @@ +/* + * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>. + * + * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>. + * + * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.stoutner.privacybrowser.dialogs + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.WindowManager + +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager + +import com.stoutner.privacybrowser.R + +class WaitingForProxyDialog : DialogFragment() { + // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. + @SuppressLint("InflateParams") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Use a builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.waiting_for_proxy_dialog, null)) + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Return the alert dialog. + return alertDialog + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt index 0a93fb0c..054d200e 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutWebViewFragment.kt @@ -38,11 +38,11 @@ import androidx.webkit.WebViewFeature import com.stoutner.privacybrowser.R -// Declare the class constants. +// Define the class constants. private const val TAB_NUMBER = "tab_number" class AboutWebViewFragment : Fragment() { - // Declare the class variables. + // Define the class variables. private var tabNumber = 0 // Declare the class views. diff --git a/app/src/main/res/menu/webview_options_menu.xml b/app/src/main/res/menu/webview_options_menu.xml index 08946609..861ab8a0 100644 --- a/app/src/main/res/menu/webview_options_menu.xml +++ b/app/src/main/res/menu/webview_options_menu.xml @@ -349,10 +349,17 @@ android:orderInCategory="1201" app:showAsAction="never" /> + <!-- TODO. --> + <item + android:id="@+id/save_archive" + android:title="Save Archive" + android:orderInCategory="1202" + app:showAsAction="never" /> + <item android:id="@+id/save_image" android:title="@string/save_image" - android:orderInCategory="1202" + android:orderInCategory="1203" app:showAsAction="never" /> </menu> </item> diff --git a/build.gradle b/build.gradle index 509939b6..b07a40b2 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.1.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/fastlane/metadata/android/de-DE/changelogs/52.txt b/fastlane/metadata/android/de-DE/changelogs/52.txt index 8f0fb1e1..0d649411 100644 --- a/fastlane/metadata/android/de-DE/changelogs/52.txt +++ b/fastlane/metadata/android/de-DE/changelogs/52.txt @@ -15,7 +15,7 @@ ⢠Fix eines Bugs, durch den gepinnte SSL-Zertifikate manchmal gegen das Zertifikat der vorhergehenden Website geprüft wurden. ⢠Mastodon-Eintrag zu "Ãber Privacy Browser" > Links hinzugefügt. ⢠Diverse kleinere Verbesserungen für das Nutzer Erlebnis und der grafischen Oberfläche umgesetzt. -⢠Teilweise Ãbersetzung in brasilianisches Portugiesisch von Thiago Nazareno Conceição Silva de Jesus. +⢠Teilweise Ãbersetzung in brasilianisch-portugiesische von Thiago Nazareno Conceição Silva de Jesus. ⢠Aktualisierte deutsche Ãbersetzung von Bernhard G. Keller. ⢠Aktualisierte französische Ãbersetzung von Kévin L. ⢠Aktualisierte italienische Ãbersetzung von Francesco Buratti. diff --git a/fastlane/metadata/android/de-DE/changelogs/54.txt b/fastlane/metadata/android/de-DE/changelogs/54.txt index efd21a17..81a807a4 100644 --- a/fastlane/metadata/android/de-DE/changelogs/54.txt +++ b/fastlane/metadata/android/de-DE/changelogs/54.txt @@ -10,7 +10,7 @@ ⢠Fehler behoben, durch den das Hamburger-Menü zu einem Pfeil wurde, wenn das Navigations-Menü beim Neustart der App geöffnet war. ⢠Ãffnen des Options-Menüs beschleunigt. ⢠Aktualisierte deutsche Ãbersetzung von Bernhard G. Keller. -⢠Aktualisierte Brasilianisch Portugiesische Ãbersetzung von Thiago Nazareno Conceição Silva de Jesus. +⢠Aktualisierte brasilianisch-portugiesische Ãbersetzung von Thiago Nazareno Conceição Silva de Jesus. ⢠Aktualisierte französische Ãbersetzung von Kévin L. ⢠Aktualisierte italienische Ãbersetzung von Francesco Buratti. ⢠Aktualisierte russische Ãbersetzung. -- 2.47.2