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'
</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.
<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>
<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>
</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.
</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.
</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.
</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.
</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.
</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.
</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.
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;
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.
}
@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;
}
@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);
/*
- * 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>.
*
}
@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) {
// 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) {
}
}
- // Flush the output stream.
- outputStream.flush();
-
// Close the output stream.
outputStream.close();
} catch (Exception exception) {
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() {
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)
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"
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)
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() {
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)
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"
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)
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"
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)
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"
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)
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"
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)
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"
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)
import com.stoutner.privacybrowser.R
-// Declare the class constants.
+// Define the class constants.
private const val FONT_SIZE = "font_size"
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)
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"
// 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
// 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.
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"
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)
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)
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() {
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 ->
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() {
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() {
}
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
// 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.
}
// 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)
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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);
- }
- }
-}
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
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.
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>
}
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
• 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.
• 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.